diff --git a/.jscodeshift/repo/bsky-agent.js b/.jscodeshift/repo/bsky-agent.js deleted file mode 100644 index 57e501959d..0000000000 --- a/.jscodeshift/repo/bsky-agent.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Codemod to replace BskyAgent with AtpAgent - * - * Before: - * import {BskyAgent} from '@atproto/api` - * BskyAgent.appLabelers.includes(labeler) - * - * After: - * import {AtpAgent} from '@atproto/api` - * AtpAgent.appLabelers.includes(labeler) - * - * Handles import specifiers, type annotations, static member access - * (BskyAgent.configure), `extends BskyAgent`, and `new BskyAgent()`. Whole - * identifiers only, so names like `OpaqueBskyAgent` are left untouched. - * - * Usage: jscodeshift -t .jscodeshift/repo/bsky-agent.js - * Example: jscodeshift -t .jscodeshift/repo/bsky-agent.js src/lib/moderation.ts - */ - -/* eslint-disable */ - -export const parser = 'tsx' - -export default function transformer(file, api) { - const j = api.jscodeshift - const root = j(file.source) - - // Replace every standalone `BskyAgent` identifier with `AtpAgent`. This - // covers imports, type references, member expressions, `extends`, and `new`. - root - .find(j.Identifier, {name: 'BskyAgent'}) - .replaceWith(() => j.identifier('AtpAgent')) - - // Renaming can leave a duplicate `AtpAgent` specifier on the @atproto/api - // import if the file already imported it. Dedupe by imported name, keeping - // the type-only modifier only if every duplicate was type-only. - root - .find(j.ImportDeclaration, {source: {value: '@atproto/api'}}) - .forEach(path => { - const seen = new Map() - for (const spec of path.value.specifiers) { - if (spec.type !== 'ImportSpecifier') { - seen.set(Symbol(), spec) - continue - } - const name = spec.imported.name - const existing = seen.get(name) - if (!existing) { - seen.set(name, spec) - } else if ( - existing.importKind === 'type' && - spec.importKind !== 'type' - ) { - // Prefer the value (non-type) import if either usage needs it. - seen.set(name, spec) - } - } - path.value.specifiers = Array.from(seen.values()) - }) - - return root.toSource() -} diff --git a/__tests__/lib/string.test.ts b/__tests__/lib/string.test.ts index 8a56423796..da34bd0dc6 100644 --- a/__tests__/lib/string.test.ts +++ b/__tests__/lib/string.test.ts @@ -1,4 +1,4 @@ -import {RichText} from '@atproto/api' +import {RichText} from '@bsky.app/sdk/richtext' import {i18n} from '@lingui/core' import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player' diff --git a/lexicons/app/bsky/unspecced/defs.json b/lexicons/app/bsky/unspecced/defs.json index 609de81f8f..3dd830a6d2 100644 --- a/lexicons/app/bsky/unspecced/defs.json +++ b/lexicons/app/bsky/unspecced/defs.json @@ -6,48 +6,31 @@ "type": "object", "required": ["uri"], "properties": { - "uri": { - "type": "string", - "format": "at-uri" - } + "uri": { "type": "string", "format": "at-uri" } } }, "skeletonSearchActor": { "type": "object", "required": ["did"], "properties": { - "did": { - "type": "string", - "format": "did" - } + "did": { "type": "string", "format": "did" } } }, "skeletonSearchStarterPack": { "type": "object", "required": ["uri"], "properties": { - "uri": { - "type": "string", - "format": "at-uri" - } + "uri": { "type": "string", "format": "at-uri" } } }, "trendingTopic": { "type": "object", "required": ["topic", "link"], "properties": { - "topic": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "description": { - "type": "string" - }, - "link": { - "type": "string" - } + "topic": { "type": "string" }, + "displayName": { "type": "string" }, + "description": { "type": "string" }, + "link": { "type": "string" } } }, "skeletonTrend": { @@ -61,32 +44,14 @@ "dids" ], "properties": { - "topic": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "description": { - "type": "string" - }, - "link": { - "type": "string" - }, - "startedAt": { - "type": "string", - "format": "datetime" - }, - "postCount": { - "type": "integer" - }, - "status": { - "type": "string", - "knownValues": ["hot"] - }, - "category": { - "type": "string" - }, + "topic": { "type": "string" }, + "displayName": { "type": "string" }, + "description": { "type": "string" }, + "link": { "type": "string" }, + "startedAt": { "type": "string", "format": "datetime" }, + "postCount": { "type": "integer" }, + "status": { "type": "string", "knownValues": ["hot"] }, + "category": { "type": "string" }, "dids": { "type": "array", "items": { @@ -107,32 +72,14 @@ "actors" ], "properties": { - "topic": { - "type": "string" - }, - "displayName": { - "type": "string" - }, - "description": { - "type": "string" - }, - "link": { - "type": "string" - }, - "startedAt": { - "type": "string", - "format": "datetime" - }, - "postCount": { - "type": "integer" - }, - "status": { - "type": "string", - "knownValues": ["hot"] - }, - "category": { - "type": "string" - }, + "topic": { "type": "string" }, + "displayName": { "type": "string" }, + "description": { "type": "string" }, + "link": { "type": "string" }, + "startedAt": { "type": "string", "format": "datetime" }, + "postCount": { "type": "integer" }, + "status": { "type": "string", "knownValues": ["hot"] }, + "category": { "type": "string" }, "actors": { "type": "array", "items": { @@ -153,10 +100,7 @@ "mutedByViewer" ], "properties": { - "post": { - "type": "ref", - "ref": "app.bsky.feed.defs#postView" - }, + "post": { "type": "ref", "ref": "app.bsky.feed.defs#postView" }, "moreParents": { "type": "boolean", "description": "This post has more parents that were not present in the response. This is just a boolean, without the number of parents." @@ -167,7 +111,15 @@ }, "opThread": { "type": "boolean", - "description": "This post is part of a contiguous thread by the OP from the thread root. Many different OP threads can happen in the same thread." + "description": "This post is part of a contiguous thread by the OP from the thread root. Sub-threads by OP deeper in the tree are not considered an OP thread." + }, + "opThreadPostIndex": { + "type": "integer", + "description": "The 1-indexed position of this post within the contiguous OP thread. Only present when this post is part of the OP thread (see `opThread`)." + }, + "opThreadPostCount": { + "type": "integer", + "description": "The total number of posts in the contiguous OP thread that this post belongs to. Only present when this post is part of the OP thread (see `opThread`)." }, "hiddenByThreadgate": { "type": "boolean", @@ -191,10 +143,7 @@ "type": "object", "required": ["author"], "properties": { - "author": { - "type": "ref", - "ref": "app.bsky.feed.defs#blockedAuthor" - } + "author": { "type": "ref", "ref": "app.bsky.feed.defs#blockedAuthor" } } }, "ageAssuranceState": { diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index 1d22db2747..1cb148b151 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -1645,7 +1645,7 @@ "count": 8 }, "typescript/no-unsafe-member-access": { - "count": 9 + "count": 8 } }, "src/view/com/util/EmptyState.tsx": { diff --git a/package.json b/package.json index 5d92bc833f..4415b0fa58 100644 --- a/package.json +++ b/package.json @@ -100,7 +100,6 @@ "prettier": "prettier --check ." }, "dependencies": { - "@atproto/api": "0.20.39", "@atproto/common-web": "0.5.9", "@atproto/lex": "0.3.6", "@atproto/lex-password-session": "0.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3b8bcb5f36..92a4d68c35 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -242,9 +242,6 @@ importers: .: dependencies: - '@atproto/api': - specifier: 0.20.39 - version: 0.20.39 '@atproto/common-web': specifier: 0.5.9 version: 0.5.9 @@ -908,10 +905,6 @@ packages: resolution: {integrity: sha512-vfvoDhu6ds6BT3Pqe+d2/LBU1WpDRtt69y6zltlfMCoucPm82m1j50/Wb6xTvv+oZ+2j0JyZVXsmXQ12SWYQJg==} engines: {node: '>=22'} - '@atproto/api@0.20.39': - resolution: {integrity: sha512-FRjwAmYe0kKlWsAe8IvfbmAAKyjpXnl2NV67vWEEjux0FwIEIG6JbOo6YkD5tjK2xjL0Daa429R0E8phmxRrrQ==} - engines: {node: '>=22'} - '@atproto/common-web@0.5.9': resolution: {integrity: sha512-2c5C6YV8352JJz9Z5fNfcsstllaKGAb3cQW9aO7unMAGh/FzTGFq+xOwNMW2lTeagO6z530uxYm8AiSRM6O+DQ==} engines: {node: '>=22'} @@ -973,10 +966,6 @@ packages: engines: {node: '>=22'} hasBin: true - '@atproto/lexicon@0.7.11': - resolution: {integrity: sha512-ukCZMCWmiu7fAlssBbLs+IMaf2K2R9mADaTKz2jEeC/xeZ3jT77Ka0S2G+ELBB+Dc9ijBACU0iPMQLhquOkuzQ==} - engines: {node: '>=22'} - '@atproto/repo@0.10.11': resolution: {integrity: sha512-QqDzEv8d6NUuqZ0xvWG+n8DfWd4Vuka04nyuD9oglKsj5avB1zbEsWx3Oit1oYtZFUf9JSVWMDqMXhu3UbB+lA==} engines: {node: '>=22'} @@ -985,10 +974,6 @@ packages: resolution: {integrity: sha512-EHsEHtasH/DGPljBYecVDjweGMQ5eTu6Ns0GZ5z0qdqpOI8ipBvldWnMIxWkA+5HfZ9Dsu4V1MX0WP7wgL8cuA==} engines: {node: '>=22'} - '@atproto/xrpc@0.8.10': - resolution: {integrity: sha512-++FNpTVF61fF8+3Kk1RYtdTO7xyUTdIc/Cmfk2CMn29Qw9tZD8bmW+M5db1Z0okYWFWK8Wqu7bc9VmF8tUB8Jg==} - engines: {node: '>=22'} - '@babel/code-frame@7.10.4': resolution: {integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==} @@ -4320,9 +4305,6 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - await-lock@3.0.0: - resolution: {integrity: sha512-eO6fLiSnrJrMdjWMNK8zbVRXPs2TKJg78iKZd9wDpN3na5tcoV6EoeiOlMgk2QaAQ1gIrK1YuMsJHXWqz89tSA==} - babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -9754,17 +9736,6 @@ snapshots: '@atproto-labs/simple-store@0.5.1': {} - '@atproto/api@0.20.39': - dependencies: - '@atproto/common-web': 0.5.9 - '@atproto/lexicon': 0.7.11 - '@atproto/syntax': 0.7.4 - '@atproto/xrpc': 0.8.10 - await-lock: 3.0.0 - multiformats: 13.4.2 - tlds: 1.261.0 - zod: 3.25.76 - '@atproto/common-web@0.5.9': dependencies: '@atproto/lex-data': 0.1.7 @@ -9875,13 +9846,6 @@ snapshots: tslib: 2.8.1 yargs: 18.1.0 - '@atproto/lexicon@0.7.11': - dependencies: - '@atproto/common-web': 0.5.9 - '@atproto/syntax': 0.7.4 - multiformats: 13.4.2 - zod: 3.25.76 - '@atproto/repo@0.10.11': dependencies: '@atproto/common': 0.8.0 @@ -9898,11 +9862,6 @@ snapshots: iso-datestring-validator: 2.2.2 tslib: 2.8.1 - '@atproto/xrpc@0.8.10': - dependencies: - '@atproto/lexicon': 0.7.11 - zod: 3.25.76 - '@babel/code-frame@7.10.4': dependencies: '@babel/highlight': 7.25.9 @@ -13788,8 +13747,6 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - await-lock@3.0.0: {} - babel-jest@29.7.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1): dependencies: '@babel/core': 7.29.0(supports-color@8.1.1) diff --git a/src/ageAssurance/const.ts b/src/ageAssurance/const.ts index 326552681c..9d20a64ea6 100644 --- a/src/ageAssurance/const.ts +++ b/src/ageAssurance/const.ts @@ -1,8 +1,3 @@ -import { - ageAssuranceRuleIDs as ids, - type AppBskyAgeassuranceDefs, -} from '@atproto/api' - import {AgeAssuranceAccess} from '#/ageAssurance/types' import { ANDROID_API_LEVEL, @@ -11,6 +6,7 @@ import { IS_IOS, IS_WEB, } from '#/env' +import {app} from '#/lexicons' /** * Minimum age required to access the app at all. @@ -44,19 +40,17 @@ export const AGE_ASSURANCE_PLATFORM: 'web' | 'ios' | 'android' = IS_WEB export const DEVICE_SIGNALS_SUPPORTED: boolean = (IS_IOS && IOS_MAJOR_VERSION >= 26) || (IS_ANDROID && ANDROID_API_LEVEL >= 23) -export const FALLBACK_REGION_CONFIG: AppBskyAgeassuranceDefs.ConfigRegion = { +export const FALLBACK_REGION_CONFIG: app.bsky.ageassurance.defs.ConfigRegion = { countryCode: '*', regionCode: undefined, minAccessAge: MIN_ACCESS_AGE, rules: [ - { - $type: ids.IfDeclaredOverAge, + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({ age: MIN_ACCESS_AGE, access: AgeAssuranceAccess.Full, - }, - { - $type: ids.Default, + }), + app.bsky.ageassurance.defs.configRegionRuleDefault.build({ access: AgeAssuranceAccess.None, - }, + }), ], } diff --git a/src/ageAssurance/data.tsx b/src/ageAssurance/data.tsx index 2540c4b1f9..0eed2bc43f 100644 --- a/src/ageAssurance/data.tsx +++ b/src/ageAssurance/data.tsx @@ -1,11 +1,5 @@ import {createContext, useCallback, useContext, useEffect, useMemo} from 'react' import * as AgeRange from 'expo-age-range' -import { - type AppBskyAgeassuranceDefs, - type AppBskyAgeassuranceGetConfig, - type AppBskyAgeassuranceGetState, - type ChatBskyActorDeclaration, -} from '@atproto/api' import {type Client} from '@atproto/lex' import {getPreferences} from '@bsky.app/sdk' import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister' @@ -38,7 +32,7 @@ import { } from '#/ageAssurance/util' import {IS_DEV} from '#/env' import {useGeolocation} from '#/geolocation' -import {app} from '#/lexicons' +import {app, type chat} from '#/lexicons' import {device} from '#/storage' /** @@ -102,14 +96,12 @@ export async function getConfig() { * before there is any session (and while logged out), so it goes through the * process-wide public client rather than a bundle one. */ - return (await getPublicAppviewClient().call( - app.bsky.ageassurance.getConfig, - )) as AppBskyAgeassuranceGetConfig.OutputSchema + return await getPublicAppviewClient().call(app.bsky.ageassurance.getConfig) } export function getConfigFromCache(): - | AppBskyAgeassuranceGetConfig.OutputSchema + | app.bsky.ageassurance.getConfig.$OutputBody | undefined { - return qc.getQueryData( + return qc.getQueryData( configQueryKey, ) } @@ -130,7 +122,7 @@ export function prefetchConfig() { try { logger.debug(`prefetchAgeAssuranceConfig: resolving...`) const res = await networkRetry(3, () => getConfig()) - qc.setQueryData( + qc.setQueryData( configQueryKey, res, ) @@ -146,7 +138,7 @@ export function prefetchConfig() { export async function refetchConfig() { logger.debug(`refetchConfig: fetching...`) const res = await getConfig() - qc.setQueryData( + qc.setQueryData( configQueryKey, res, ) @@ -216,8 +208,8 @@ export function getServerStateFromCache({ did, }: { did: string -}): AppBskyAgeassuranceGetState.OutputSchema | undefined { - return qc.getQueryData( +}): app.bsky.ageassurance.getState.$OutputBody | undefined { + return qc.getQueryData( createServerStateQueryKey({did}), ) } @@ -243,7 +235,7 @@ export async function prefetchServerState({ logger.debug(`prefetchServerState: resolving...`) const res = await networkRetry(3, () => getServerState({appviewClient})) if (res) { - qc.setQueryData(qk, res) + qc.setQueryData(qk, res) } } catch (err) { const e = err as Error @@ -262,7 +254,7 @@ export async function refetchServerState({ logger.debug(`refetchServerState: fetching...`) const res = await networkRetry(3, () => getServerState({appviewClient})) if (res) { - qc.setQueryData( + qc.setQueryData( createServerStateQueryKey({did}), res, ) @@ -272,16 +264,16 @@ export async function refetchServerState({ export function usePatchServerState() { const {currentAccount} = useSession() return useCallback( - (next: AppBskyAgeassuranceDefs.State) => { + (next: app.bsky.ageassurance.defs.State) => { if (!currentAccount) return const did = currentAccount.did const prev = getServerStateFromCache({did}) - const merged: AppBskyAgeassuranceGetState.OutputSchema = { + const merged: app.bsky.ageassurance.getState.$OutputBody = { metadata: {}, ...(prev || {}), state: next, } - qc.setQueryData( + qc.setQueryData( createServerStateQueryKey({did}), merged, ) @@ -347,7 +339,7 @@ export function useServerStateQuery() { export type OtherRequiredData = { birthdate: string | undefined - actorDeclaration?: ChatBskyActorDeclaration.Main + actorDeclaration?: chat.bsky.actor.declaration.Main } export function createOtherRequiredDataQueryKey({did}: {did: string}) { return ['otherRequiredData', did] @@ -422,7 +414,7 @@ export function setOtherRequiredDataActorDeclarationCache({ actorDeclaration, }: { did: string - actorDeclaration: ChatBskyActorDeclaration.Main + actorDeclaration: chat.bsky.actor.declaration.Main }) { const prev = getOtherRequiredDataFromCache({did}) const next: OtherRequiredData = { @@ -564,7 +556,7 @@ export function getDeviceSignalsFromCacheForRegion({ region, }: { did: string - region: AppBskyAgeassuranceDefs.ConfigRegion + region: app.bsky.ageassurance.defs.ConfigRegion }): AgeRange.AgeRangeResponse | undefined { const regionKey = createRegionKey(region) return getDeviceSignalsMapFromCache({did})?.[regionKey] @@ -718,11 +710,11 @@ export type AgeAssuranceServerData = { /** * The raw config from the appview. */ - config: AppBskyAgeassuranceDefs.Config | undefined + config: app.bsky.ageassurance.defs.Config | undefined /** * The raw state from the appview. Must be further processed before being useful. */ - state: AppBskyAgeassuranceDefs.State | undefined + state: app.bsky.ageassurance.defs.State | undefined metadata: AgeAssuranceMetadata | undefined /** * The native on-device age signals for the region the user is currently in, diff --git a/src/ageAssurance/debug.ts b/src/ageAssurance/debug.ts index 74121976ca..b640c4a2de 100644 --- a/src/ageAssurance/debug.ts +++ b/src/ageAssurance/debug.ts @@ -1,13 +1,10 @@ import type * as AgeRange from 'expo-age-range' -import { - ageAssuranceRuleIDs as ids, - type AppBskyAgeassuranceDefs, - type AppBskyAgeassuranceGetState, -} from '@atproto/api' +import {toDatetimeString} from '@atproto/syntax' import {type OtherRequiredData} from '#/ageAssurance/data' import {IS_DEV, IS_E2E} from '#/env' import {type Geolocation} from '#/geolocation' +import {app} from '#/lexicons' export const enabled = (IS_DEV && false) || IS_E2E @@ -31,31 +28,31 @@ export const otherRequiredData: OtherRequiredData = { } const serverStateEnabled = false || IS_E2E -export const serverState: AppBskyAgeassuranceGetState.OutputSchema | undefined = - serverStateEnabled - ? { - state: { - lastInitiatedAt: undefined, // new Date(2025, 1, 1).toISOString(), - status: 'unknown', - access: 'unknown', - }, - metadata: { - accountCreatedAt: new Date(2023, 1, 1).toISOString(), - }, - } - : undefined +export const serverState: + | app.bsky.ageassurance.getState.$OutputBody + | undefined = serverStateEnabled + ? { + state: { + lastInitiatedAt: undefined, // new Date(2025, 1, 1).toISOString(), + status: 'unknown', + access: 'unknown', + }, + metadata: { + accountCreatedAt: toDatetimeString(new Date(2023, 1, 1)), + }, + } + : undefined -export const config: AppBskyAgeassuranceDefs.Config = { +export const config: app.bsky.ageassurance.defs.Config = { regions: [ { countryCode: 'AA', regionCode: undefined, minAccessAge: 13, rules: [ - { - $type: ids.Default, + app.bsky.ageassurance.defs.configRegionRuleDefault.build({ access: 'full', - }, + }), ], }, { @@ -68,65 +65,55 @@ export const config: AppBskyAgeassuranceDefs.Config = { minAccessAge: 18, additionalVerificationMethods: ['device'], rules: [ - { + app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({ age: 18, access: 'full', - $type: ids.IfAssuredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleDefault.build({ access: 'none', - $type: ids.Default, - }, + }), ], }, { countryCode: 'GB', minAccessAge: 13, rules: [ - { + app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({ age: 18, access: 'full', - $type: ids.IfAssuredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({ age: 13, access: 'safe', - $type: ids.IfDeclaredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleDefault.build({ access: 'none', - $type: ids.Default, - }, + }), ], }, { countryCode: 'AU', minAccessAge: 16, rules: [ - { + app.bsky.ageassurance.defs.configRegionRuleIfAccountNewerThan.build({ date: '2025-12-10T00:00:00Z', access: 'none', - $type: ids.IfAccountNewerThan, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({ age: 18, access: 'full', - $type: ids.IfAssuredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({ age: 16, access: 'safe', - $type: ids.IfAssuredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({ age: 16, access: 'safe', - $type: ids.IfDeclaredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleDefault.build({ access: 'none', - $type: ids.Default, - }, + }), ], }, { @@ -134,20 +121,17 @@ export const config: AppBskyAgeassuranceDefs.Config = { regionCode: 'SD', minAccessAge: 13, rules: [ - { + app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({ age: 18, access: 'full', - $type: ids.IfAssuredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({ age: 13, access: 'safe', - $type: ids.IfDeclaredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleDefault.build({ access: 'none', - $type: ids.Default, - }, + }), ], }, { @@ -155,20 +139,17 @@ export const config: AppBskyAgeassuranceDefs.Config = { regionCode: 'WY', minAccessAge: 13, rules: [ - { + app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({ age: 18, access: 'full', - $type: ids.IfAssuredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({ age: 13, access: 'safe', - $type: ids.IfDeclaredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleDefault.build({ access: 'none', - $type: ids.Default, - }, + }), ], }, { @@ -176,20 +157,17 @@ export const config: AppBskyAgeassuranceDefs.Config = { regionCode: 'OH', minAccessAge: 13, rules: [ - { + app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({ age: 18, access: 'full', - $type: ids.IfAssuredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({ age: 13, access: 'safe', - $type: ids.IfDeclaredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleDefault.build({ access: 'none', - $type: ids.Default, - }, + }), ], }, { @@ -197,15 +175,13 @@ export const config: AppBskyAgeassuranceDefs.Config = { regionCode: 'MS', minAccessAge: 18, rules: [ - { + app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({ age: 18, access: 'full', - $type: ids.IfAssuredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleDefault.build({ access: 'none', - $type: ids.Default, - }, + }), ], }, { @@ -213,20 +189,17 @@ export const config: AppBskyAgeassuranceDefs.Config = { regionCode: 'VA', minAccessAge: 16, rules: [ - { + app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({ age: 16, access: 'full', - $type: ids.IfAssuredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({ age: 16, access: 'full', - $type: ids.IfDeclaredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleDefault.build({ access: 'none', - $type: ids.Default, - }, + }), ], }, { @@ -234,45 +207,38 @@ export const config: AppBskyAgeassuranceDefs.Config = { regionCode: 'TN', minAccessAge: 18, rules: [ - { + app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({ age: 18, access: 'full', - $type: ids.IfAssuredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({ age: 18, access: 'full', - $type: ids.IfDeclaredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleDefault.build({ access: 'none', - $type: ids.Default, - }, + }), ], }, { countryCode: 'BR', minAccessAge: 13, rules: [ - { + app.bsky.ageassurance.defs.configRegionRuleIfAssuredOverAge.build({ age: 18, access: 'full', - $type: ids.IfAssuredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({ age: 18, access: 'full', - $type: ids.IfDeclaredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge.build({ age: 13, access: 'safe', - $type: ids.IfDeclaredOverAge, - }, - { + }), + app.bsky.ageassurance.defs.configRegionRuleDefault.build({ access: 'none', - $type: ids.Default, - }, + }), ], }, ], diff --git a/src/ageAssurance/state.ts b/src/ageAssurance/state.ts index 2a0b76446b..8ede9d13fa 100644 --- a/src/ageAssurance/state.ts +++ b/src/ageAssurance/state.ts @@ -1,9 +1,6 @@ import {useEffect, useMemo, useState} from 'react' import type * as AgeRange from 'expo-age-range' -import { - type AppBskyAgeassuranceDefs, - computeAgeAssuranceRegionAccess, -} from '@atproto/api' +import {computeAgeAssuranceRegionAccess} from '@bsky.app/sdk/utils' import {getAge} from '#/lib/strings/time' import {useSession} from '#/state/session' @@ -30,6 +27,7 @@ import { getAgeAssuranceRegionConfigWithFallback, } from '#/ageAssurance/util' import {type Geolocation, useGeolocation} from '#/geolocation' +import {type app} from '#/lexicons' import {device} from '#/storage' /** @@ -47,8 +45,8 @@ function computeAgeAssuranceState({ }: { hasSession: boolean geolocation: Geolocation - config?: AppBskyAgeassuranceDefs.Config - state?: AppBskyAgeassuranceDefs.State + config?: app.bsky.ageassurance.defs.Config + state?: app.bsky.ageassurance.defs.State metadata?: AgeAssuranceMetadata deviceSignals?: AgeRange.AgeRangeResponse }) { diff --git a/src/ageAssurance/types.ts b/src/ageAssurance/types.ts index 5dcabeb268..79fd5f1820 100644 --- a/src/ageAssurance/types.ts +++ b/src/ageAssurance/types.ts @@ -1,5 +1,5 @@ import type * as AgeRange from 'expo-age-range' -import {type computeAgeAssuranceRegionAccess} from '@atproto/api' +import {type computeAgeAssuranceRegionAccess} from '@bsky.app/sdk/utils' import {logger} from '#/ageAssurance/logger' diff --git a/src/ageAssurance/useComputeAgeAssuranceRegionAccess.ts b/src/ageAssurance/useComputeAgeAssuranceRegionAccess.ts index 5ba9e1ba6d..8f88398ce6 100644 --- a/src/ageAssurance/useComputeAgeAssuranceRegionAccess.ts +++ b/src/ageAssurance/useComputeAgeAssuranceRegionAccess.ts @@ -1,5 +1,5 @@ import {useCallback} from 'react' -import {computeAgeAssuranceRegionAccess} from '@atproto/api' +import {computeAgeAssuranceRegionAccess} from '@bsky.app/sdk/utils' import {useAgeAssuranceServerDataContext} from '#/ageAssurance/data' import {logger} from '#/ageAssurance/logger' diff --git a/src/ageAssurance/util.test.ts b/src/ageAssurance/util.test.ts index 1da131b920..59e46e785f 100644 --- a/src/ageAssurance/util.test.ts +++ b/src/ageAssurance/util.test.ts @@ -1,31 +1,47 @@ -import {getAgeAssuranceRegionConfig} from '@atproto/api' - import {getAgeAssuranceRegionConfigForGeolocation} from '#/ageAssurance/util' +import {type app} from '#/lexicons' jest.mock('#/ageAssurance/data') -jest.mock('@atproto/api', () => ({ - ...jest.requireActual('@atproto/api'), - getAgeAssuranceRegionConfig: jest.fn(), -})) /* - * Platform-based region filtering itself is implemented and tested in - * `@atproto/api` (see `getAgeAssuranceRegionConfig`). What we own - and test - * here - is that region resolution passes the current platform through. The - * jest preset is `jest-expo/ios`, so `AGE_ASSURANCE_PLATFORM` resolves to - * `ios` in these tests. + * Platform scoping is applied locally in `util.ts` (the SDK region matcher + * takes no platform filter). The jest preset is `jest-expo/ios`, so + * `AGE_ASSURANCE_PLATFORM` resolves to `ios` in these tests. */ describe('getAgeAssuranceRegionConfigForGeolocation', () => { - it('passes the current platform to the SDK region matcher', () => { - const config = {regions: []} - getAgeAssuranceRegionConfigForGeolocation(config, { - countryCode: 'US', - regionCode: 'TX', - }) - expect(getAgeAssuranceRegionConfig).toHaveBeenCalledWith(config, { - countryCode: 'US', - regionCode: 'TX', - platform: 'ios', - }) + const region = ( + countryCode: string, + regionCode?: string, + platforms?: app.bsky.ageassurance.defs.ConfigRegion['platforms'], + ): app.bsky.ageassurance.defs.ConfigRegion => ({ + countryCode, + regionCode, + platforms, + minAccessAge: 13, + rules: [], + }) + + it('skips regions for other platforms and continues matching', () => { + const web = region('US', undefined, ['web']) + const ios = region('US', undefined, ['ios']) + + expect( + getAgeAssuranceRegionConfigForGeolocation( + {regions: [web, ios]}, + {countryCode: 'US', regionCode: undefined}, + ), + ).toBe(ios) + }) + + it('matches a region-specific config before a later country config', () => { + const texas = region('US', 'TX') + const us = region('US') + + expect( + getAgeAssuranceRegionConfigForGeolocation( + {regions: [texas, us]}, + {countryCode: 'US', regionCode: 'TX'}, + ), + ).toBe(texas) }) }) diff --git a/src/ageAssurance/util.ts b/src/ageAssurance/util.ts index fcda8056f1..39bb14ab0c 100644 --- a/src/ageAssurance/util.ts +++ b/src/ageAssurance/util.ts @@ -1,11 +1,10 @@ import {useMemo} from 'react' import type * as AgeRange from 'expo-age-range' +import {type ModerationPrefs} from '@bsky.app/sdk/moderation' import { - AppBskyAgeassuranceDefs, computeAgeAssuranceRegionAccess, getAgeAssuranceRegionConfig, -} from '@atproto/api' -import {type ModerationPrefs} from '@bsky.app/sdk/moderation' +} from '@bsky.app/sdk/utils' import {getAge} from '#/lib/strings/time' import {regionName} from '#/locale/helpers' @@ -25,6 +24,8 @@ import { } from '#/ageAssurance/types' import {type Geolocation, useGeolocation} from '#/geolocation' import {USRegionNameToRegionCode} from '#/geolocation/util' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' /** * Resolves a geolocation to its matched age assurance region config, or @@ -41,13 +42,31 @@ import {USRegionNameToRegionCode} from '#/geolocation/util' * risk desyncing the write and read keys and silently losing grants. */ export function getAgeAssuranceRegionConfigForGeolocation( - config: AppBskyAgeassuranceDefs.Config, + config: app.bsky.ageassurance.defs.Config, geolocation: Geolocation, -): AppBskyAgeassuranceDefs.ConfigRegion | undefined { - return getAgeAssuranceRegionConfig(config, { +): app.bsky.ageassurance.defs.ConfigRegion | undefined { + /* + * The SDK's region matcher takes no `platform` filter, so platform scoping + * is applied here: regions restricted to other platforms are dropped before + * matching. Once the SDK accepts a platform, this whole function body + * collapses to: + * + * return getAgeAssuranceRegionConfig(config, { + * countryCode: geolocation.countryCode ?? '', + * regionCode: geolocation.regionCode, + * platform: AGE_ASSURANCE_PLATFORM, + * }) + */ + const scoped: app.bsky.ageassurance.defs.Config = { + ...config, + regions: config.regions.filter( + region => + !region.platforms || region.platforms.includes(AGE_ASSURANCE_PLATFORM), + ), + } + return getAgeAssuranceRegionConfig(scoped, { countryCode: geolocation.countryCode ?? '', regionCode: geolocation.regionCode, - platform: AGE_ASSURANCE_PLATFORM, }) } @@ -59,9 +78,9 @@ export function getAgeAssuranceRegionConfigForGeolocation( * which can return undefined if the geolocation does not match any AA region. */ export function getAgeAssuranceRegionConfigWithFallback( - config: AppBskyAgeassuranceDefs.Config, + config: app.bsky.ageassurance.defs.Config, geolocation: Geolocation, -): AppBskyAgeassuranceDefs.ConfigRegion { +): app.bsky.ageassurance.defs.ConfigRegion { return ( getAgeAssuranceRegionConfigForGeolocation(config, geolocation) || FALLBACK_REGION_CONFIG @@ -74,9 +93,9 @@ export function getAgeAssuranceRegionConfigWithFallback( * historical KWS-only behavior). */ export function getRegionAdditionalVerificationMethods( - region: AppBskyAgeassuranceDefs.ConfigRegion, + region: app.bsky.ageassurance.defs.ConfigRegion, ): NonNullable< - AppBskyAgeassuranceDefs.ConfigRegion['additionalVerificationMethods'] + app.bsky.ageassurance.defs.ConfigRegion['additionalVerificationMethods'] > { return region.additionalVerificationMethods ?? [] } @@ -86,7 +105,7 @@ export function getRegionAdditionalVerificationMethods( * age APIs (Apple Declared Age Range / Google Play Age Signals). */ export function regionAllowsDeviceVerification( - region: AppBskyAgeassuranceDefs.ConfigRegion, + region: app.bsky.ageassurance.defs.ConfigRegion, ): boolean { return getRegionAdditionalVerificationMethods(region).includes('device') } @@ -122,7 +141,7 @@ export function createRegionKey(region: { * usable data. */ export function getAgeAssuranceDataFromDeviceSignals( - region: AppBskyAgeassuranceDefs.ConfigRegion, + region: app.bsky.ageassurance.defs.ConfigRegion, deviceSignals: AgeRange.AgeRangeResponse | undefined, ): { assuredAge?: number @@ -183,7 +202,7 @@ export function canBirthdateUpdateIncreaseAccess({ metadata, deviceSignals, }: { - region: AppBskyAgeassuranceDefs.ConfigRegion + region: app.bsky.ageassurance.defs.ConfigRegion metadata?: AgeAssuranceMetadata deviceSignals?: AgeRange.AgeRangeResponse }): boolean { @@ -212,8 +231,14 @@ export function canBirthdateUpdateIncreaseAccess({ const thresholds = new Set([region.minAccessAge]) for (const rule of region.rules) { if ( - AppBskyAgeassuranceDefs.isConfigRegionRuleIfDeclaredOverAge(rule) || - AppBskyAgeassuranceDefs.isConfigRegionRuleIfDeclaredUnderAge(rule) + bsky.isType( + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredOverAge, + rule, + ) || + bsky.isType( + app.bsky.ageassurance.defs.configRegionRuleIfDeclaredUnderAge, + rule, + ) ) { thresholds.add(rule.age) } @@ -303,7 +328,7 @@ export function computeAgeAssuranceFlags({ deviceSignals, }: { state: AgeAssuranceState - regionConfig: AppBskyAgeassuranceDefs.ConfigRegion + regionConfig: app.bsky.ageassurance.defs.ConfigRegion metadata?: AgeAssuranceMetadata deviceSignals?: AgeRange.AgeRangeResponse }): AgeAssuranceFlags { diff --git a/src/components/AccountList.tsx b/src/components/AccountList.tsx index a65eeb0722..156ba951bb 100644 --- a/src/components/AccountList.tsx +++ b/src/components/AccountList.tsx @@ -1,6 +1,5 @@ import {Fragment, useCallback} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -19,6 +18,7 @@ import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/ import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' import {useActorStatus} from '#/features/liveNow' +import {type app} from '#/lexicons' export function AccountList({ onSelectAccount, @@ -107,7 +107,7 @@ function AccountItem({ isCurrentAccount, isPendingAccount, }: { - profile?: AppBskyActorDefs.ProfileViewDetailed + profile?: app.bsky.actor.defs.ProfileViewDetailed account: SessionAccount onSelect: (account: SessionAccount) => void isCurrentAccount: boolean diff --git a/src/components/Autocomplete/useAutocomplete/index.ts b/src/components/Autocomplete/useAutocomplete/index.ts index b310876b84..de5009a600 100644 --- a/src/components/Autocomplete/useAutocomplete/index.ts +++ b/src/components/Autocomplete/useAutocomplete/index.ts @@ -1,9 +1,8 @@ import {useCallback, useMemo} from 'react' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, 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 9b59a2555f..6189141934 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 '@bsky.app/sdk/moderation' -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 f98148f52c..dc7a8395c3 100644 --- a/src/components/AvatarStack.tsx +++ b/src/components/AvatarStack.tsx @@ -1,6 +1,6 @@ import {View} from 'react-native' +import {moderateProfile} from '@bsky.app/sdk/moderation' -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/BotBadge.tsx b/src/components/BotBadge.tsx index b5e6f867b6..eb19e83351 100644 --- a/src/components/BotBadge.tsx +++ b/src/components/BotBadge.tsx @@ -1,5 +1,4 @@ import {type Insets, View} from 'react-native' -import {type ComAtprotoLabelDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {atoms as a, useTheme} from '#/alf' @@ -8,11 +7,12 @@ import {Button} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' import {Bot_Filled as RobotIcon} from '#/components/icons/Bot' import {useAnalytics} from '#/analytics' +import {type com} from '#/lexicons' import type * as bsky from '#/types/bsky' export function isBotAccount(profile: { did: string - labels?: ComAtprotoLabelDefs.Label[] + labels?: com.atproto.label.defs.Label[] }): boolean { return ( profile.labels?.some(l => l.val === 'bot' && l.src === profile.did) ?? false diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx index 2e7b070fb1..b8e60ff286 100644 --- a/src/components/FeedCard.tsx +++ b/src/components/FeedCard.tsx @@ -1,6 +1,6 @@ import {useCallback, useEffect, useMemo} from 'react' import {type GestureResponderEvent, View} from 'react-native' -import {type AppBskyFeedDefs, type AppBskyGraphDefs, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {RichText as RichTextApi} from '@bsky.app/sdk/richtext' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' @@ -31,11 +31,12 @@ import {RichText, type RichTextProps} from '#/components/RichText' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useActiveLiveEventFeedUris} from '#/features/liveEvents/context' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from './icons/Trash' type Props = { - view: AppBskyFeedDefs.GeneratorView + view: app.bsky.feed.defs.GeneratorView onPress?: () => void } @@ -254,7 +255,7 @@ export function SaveButton({ pin, ...props }: { - view: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView + view: app.bsky.feed.defs.GeneratorView | app.bsky.graph.defs.ListView pin?: boolean text?: boolean } & Partial) { @@ -269,7 +270,7 @@ function SaveButtonInner({ text = true, ...buttonProps }: { - view: AppBskyFeedDefs.GeneratorView | AppBskyGraphDefs.ListView + view: app.bsky.feed.defs.GeneratorView | app.bsky.graph.defs.ListView pin?: boolean text?: boolean } & Partial) { @@ -379,7 +380,7 @@ function SaveButtonInner({ export function createProfileFeedHref({ feed, }: { - feed: AppBskyFeedDefs.GeneratorView + feed: app.bsky.feed.defs.GeneratorView }) { const urip = new AtUri(feed.uri) const handleOrDid = feed.creator.handle || feed.creator.did diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx index fec0447b61..aa77a7b36b 100644 --- a/src/components/FeedInterstitials.tsx +++ b/src/components/FeedInterstitials.tsx @@ -7,7 +7,6 @@ import Animated, { LayoutAnimationConfig, LinearTransition, } from 'react-native-reanimated' -import {type AppBskyFeedDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -40,6 +39,7 @@ import {ProgressGuideList} from '#/components/ProgressGuide/List' import {Text} from '#/components/Typography' import {type Metrics, useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' import {FollowDialogWithoutGuide} from './ProgressGuide/FollowDialog' @@ -569,7 +569,7 @@ export function SuggestedFeeds() { const {gtMobile} = useBreakpoints() const feeds = useMemo(() => { - const items: AppBskyFeedDefs.GeneratorView[] = [] + const items: app.bsky.feed.defs.GeneratorView[] = [] if (!data) return items diff --git a/src/components/KnownFollowers.tsx b/src/components/KnownFollowers.tsx index bda482c1c0..b6f90fc401 100644 --- a/src/components/KnownFollowers.tsx +++ b/src/components/KnownFollowers.tsx @@ -1,16 +1,15 @@ import {useRef} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, 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' import {atoms as a, useTheme} from '#/alf' import {Link, type LinkProps} from '#/components/Link' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' const AVI_SIZE = 30 @@ -24,7 +23,7 @@ const AVI_BORDER = 1 * `count` includes blocked users and `followers` does not. */ export function shouldShowKnownFollowers( - knownFollowers?: AppBskyActorDefs.KnownFollowers, + knownFollowers?: app.bsky.actor.defs.KnownFollowers, ) { return knownFollowers && knownFollowers.followers.length > 0 } @@ -42,7 +41,9 @@ export function KnownFollowers({ minimal?: boolean showIfEmpty?: boolean }) { - const cache = useRef>(new Map()) + const cache = useRef>( + new Map(), + ) /* * Results for `knownFollowers` are not sorted consistently, so when @@ -83,7 +84,7 @@ function KnownFollowersInner({ }: { profile: bsky.profile.AnyProfileView moderationOpts: ModerationOpts - cachedKnownFollowers: AppBskyActorDefs.KnownFollowers + cachedKnownFollowers: app.bsky.actor.defs.KnownFollowers onLinkPress?: LinkProps['onPress'] minimal?: boolean showIfEmpty?: boolean diff --git a/src/components/LikedByList.tsx b/src/components/LikedByList.tsx index fcaa50bbcc..28718f3d88 100644 --- a/src/components/LikedByList.tsx +++ b/src/components/LikedByList.tsx @@ -1,5 +1,4 @@ import {useCallback, useMemo, useState} from 'react' -import {type AppBskyFeedGetLikes as GetLikes} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -11,8 +10,15 @@ import {useResolveUriQuery} from '#/state/queries/resolve-uri' import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' import {List} from '#/view/com/util/List' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {type app} from '#/lexicons' -function renderItem({item, index}: {item: GetLikes.Like; index: number}) { +function renderItem({ + item, + index, +}: { + item: app.bsky.feed.getLikes.Like + index: number +}) { return ( peekable?: boolean }) { @@ -59,9 +55,9 @@ export function Embed({ const tiles: React.ReactNode[] = [] for (const item of e.view.items) { if (tiles.length >= 4) break - if (!AppBskyEmbedGallery.isViewImage(item)) continue + if (!bsky.isType(app.bsky.embed.gallery.viewImage, item)) continue if (peekable) { - const image: AppBskyEmbedImages.ViewImage = { + const image: app.bsky.embed.images.ViewImage = { thumb: item.thumbnail, fullsize: item.fullsize, alt: item.alt, @@ -107,7 +103,17 @@ export function Embed({ // ignore any unknowns e.media.view !== null ) { - return + /* + * Every media arm's view is a `$Typed<...>` of a def that `PostView.embed` + * also admits, but TS will not narrow the parsed union back into the raw + * embed union, so the arm is re-asserted here. + */ + return ( + + ) } return null @@ -202,7 +208,7 @@ export function VideoItem({ ) } -function PeekableImageItem({image}: {image: AppBskyEmbedImages.ViewImage}) { +function PeekableImageItem({image}: {image: app.bsky.embed.images.ViewImage}) { const {t: l} = useLingui() const saveImage = useSaveImageToMediaLibrary() diff --git a/src/components/NewskieDialog.tsx b/src/components/NewskieDialog.tsx index 7df67d80dc..43025aca94 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} from '@atproto/api' +import {moderateProfile} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -8,7 +8,6 @@ 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' @@ -20,12 +19,13 @@ import {Newskie} from '#/components/icons/Newskie' import * as StarterPackCard from '#/components/StarterPack/StarterPackCard' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' export function NewskieDialog({ profile, disabled, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed disabled?: boolean }) { const t = useTheme() @@ -75,7 +75,7 @@ function DialogInner({ createdAt, now, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed createdAt: string now: number }) { diff --git a/src/components/Pills.tsx b/src/components/Pills.tsx index 70b7c98a2c..809b197029 100644 --- a/src/components/Pills.tsx +++ b/src/components/Pills.tsx @@ -1,6 +1,6 @@ import {useMemo} from 'react' import {View} from 'react-native' -import {BSKY_LABELER_DID} from '@atproto/api' +import {api} from '@bsky.app/sdk' import {type ModerationCause} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' @@ -67,7 +67,7 @@ export function Label({ const desc = useModerationCauseDescription(cause) const isLabeler = Boolean(desc.sourceType && desc.sourceDid) const isBlueskyLabel = - desc.sourceType === 'labeler' && desc.sourceDid === BSKY_LABELER_DID + desc.sourceType === 'labeler' && desc.sourceDid === api.moderation.did const avi = size === 'lg' ? 16 : 12 return ( diff --git a/src/components/Post/Embed/ChatInviteEmbed.tsx b/src/components/Post/Embed/ChatInviteEmbed.tsx index d5f69f49e9..f6a9a0e4d9 100644 --- a/src/components/Post/Embed/ChatInviteEmbed.tsx +++ b/src/components/Post/Embed/ChatInviteEmbed.tsx @@ -1,10 +1,10 @@ import {type StyleProp, type ViewStyle} from 'react-native' -import {type AppBskyEmbedExternal} from '@atproto/api' import {atoms as a} from '#/alf' import * as ChatInvite from '#/components/dms/ChatInvite' import {ExternalEmbed} from '#/components/Post/Embed/ExternalEmbed' import {JoinRequestEmbedBody} from '#/components/Post/Embed/JoinRequestEmbed' +import {type app} from '#/lexicons' /** * Renders a chat invite link found in an `app.bsky.embed.external` embed (e.g. @@ -18,7 +18,7 @@ export function ChatInviteEmbed({ style, }: { code: string - link: AppBskyEmbedExternal.ViewExternal + link: app.bsky.embed.external.ViewExternal onOpen?: () => void style?: StyleProp }) { @@ -34,7 +34,7 @@ function ChatInviteEmbedBody({ onOpen, style, }: { - link: AppBskyEmbedExternal.ViewExternal + link: app.bsky.embed.external.ViewExternal onOpen?: () => void style?: StyleProp }) { diff --git a/src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx b/src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx index 529952f08e..9334581c5b 100644 --- a/src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx +++ b/src/components/Post/Embed/ExternalEmbed/ExternalGif.tsx @@ -5,7 +5,6 @@ import { Pressable, } from 'react-native' import {Image} from 'expo-image' -import {type AppBskyEmbedExternal} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -17,12 +16,13 @@ import {EmbedConsentDialog} from '#/components/dialogs/EmbedConsent' import {Fill} from '#/components/Fill' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {IS_IOS, IS_NATIVE, IS_WEB} from '#/env' +import {type app} from '#/lexicons' export function ExternalGif({ link, params, }: { - link: AppBskyEmbedExternal.ViewExternal + link: app.bsky.embed.external.ViewExternal params: EmbedPlayerParams }) { const t = useTheme() diff --git a/src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx b/src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx index 9decdea95b..63c4236aa1 100644 --- a/src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx +++ b/src/components/Post/Embed/ExternalEmbed/ExternalPlayer.tsx @@ -15,7 +15,6 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context' import {WebView} from 'react-native-webview' import {scheduleOnRN} from 'react-native-worklets' import {Image} from 'expo-image' -import {type AppBskyEmbedExternal} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' @@ -34,6 +33,7 @@ import {Fill} from '#/components/Fill' import {KeepAwake} from '#/components/KeepAwake' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' interface ShouldStartLoadRequest { url: string @@ -122,7 +122,7 @@ export function ExternalPlayer({ link, params, }: { - link: AppBskyEmbedExternal.ViewExternal + link: app.bsky.embed.external.ViewExternal params: EmbedPlayerParams }) { const t = useTheme() diff --git a/src/components/Post/Embed/ExternalEmbed/index.tsx b/src/components/Post/Embed/ExternalEmbed/index.tsx index 4432eb6632..a496780524 100644 --- a/src/components/Post/Embed/ExternalEmbed/index.tsx +++ b/src/components/Post/Embed/ExternalEmbed/index.tsx @@ -1,7 +1,6 @@ import {useMemo} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' import {Image} from 'expo-image' -import {type AppBskyEmbedExternal} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -20,6 +19,7 @@ import {Earth_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' import {Link} from '#/components/Link' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' import {ExternalGif} from './ExternalGif' import {ExternalPlayer} from './ExternalPlayer' import {GifEmbed} from './Gif' @@ -30,7 +30,7 @@ export const ExternalEmbed = ({ style, hideAlt, }: { - link: AppBskyEmbedExternal.ViewExternal + link: app.bsky.embed.external.ViewExternal onOpen?: () => void style?: StyleProp hideAlt?: boolean diff --git a/src/components/Post/Embed/FeedEmbed.tsx b/src/components/Post/Embed/FeedEmbed.tsx index 8871a63b59..9e1e3e022d 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 '@bsky.app/sdk/moderation' -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/ImageEmbed.tsx b/src/components/Post/Embed/ImageEmbed.tsx index 073c47fbb1..cc3ce6c443 100644 --- a/src/components/Post/Embed/ImageEmbed.tsx +++ b/src/components/Post/Embed/ImageEmbed.tsx @@ -2,7 +2,6 @@ import {useRef} from 'react' import {InteractionManager, View} from 'react-native' import {type AnimatedRef} from 'react-native-reanimated' import {Image} from 'expo-image' -import {AppBskyEmbedGallery, type AppBskyEmbedImages} from '@atproto/api' import {atoms as a, tokens} from '#/alf' import {AutoSizedImage} from '#/components/images/AutoSizedImage' @@ -16,6 +15,8 @@ import {type Dimensions} from '#/components/Lightbox/types' import {ImageContextMenu} from '#/components/Post/Embed/ImageContextMenu' import {PostEmbedViewContext} from '#/components/Post/Embed/types' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {type EmbedType} from '#/types/bsky/post' import {type CommonProps} from './types' @@ -29,14 +30,16 @@ export function ImageEmbed({ }) { const ax = useAnalytics() const {openLightbox} = useLightboxControls() - const images: AppBskyEmbedImages.ViewImage[] = + const images: app.bsky.embed.images.ViewImage[] = embed.type === 'gallery' - ? embed.view.items.filter(AppBskyEmbedGallery.isViewImage).map(item => ({ - thumb: item.thumbnail, - fullsize: item.fullsize, - alt: item.alt, - aspectRatio: item.aspectRatio, - })) + ? embed.view.items + .filter(item => bsky.isType(app.bsky.embed.gallery.viewImage, item)) + .map(item => ({ + thumb: item.thumbnail, + fullsize: item.fullsize, + alt: item.alt, + aspectRatio: item.aspectRatio, + })) : embed.view.images const useExpandedLayout = embed.type === 'gallery' diff --git a/src/components/Post/Embed/ListEmbed.tsx b/src/components/Post/Embed/ListEmbed.tsx index d6daca265c..11ed196597 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 '@bsky.app/sdk/moderation' -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/StandardSiteEmbed/StandardSiteMetaRow.tsx b/src/components/Post/Embed/StandardSiteEmbed/StandardSiteMetaRow.tsx index 837f1b4a05..d2ee4cf545 100644 --- a/src/components/Post/Embed/StandardSiteEmbed/StandardSiteMetaRow.tsx +++ b/src/components/Post/Embed/StandardSiteEmbed/StandardSiteMetaRow.tsx @@ -1,6 +1,6 @@ import {Fragment, type ReactNode} from 'react' import {View} from 'react-native' -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {Trans} from '@lingui/react/macro' import {toNiceDomain} from '#/lib/strings/url-helpers' diff --git a/src/components/Post/Embed/StandardSiteEmbed/StandardSiteThemeProvider.tsx b/src/components/Post/Embed/StandardSiteEmbed/StandardSiteThemeProvider.tsx index 00c15c4401..ff08bdd2c0 100644 --- a/src/components/Post/Embed/StandardSiteEmbed/StandardSiteThemeProvider.tsx +++ b/src/components/Post/Embed/StandardSiteEmbed/StandardSiteThemeProvider.tsx @@ -1,6 +1,5 @@ -import {type AppBskyEmbedExternal} from '@atproto/api' - import {Context, useAlf, utils} from '#/alf' +import {type app} from '#/lexicons' /** * Overrides only the values needed for `secondary_inverted` buttons atm. @@ -12,7 +11,7 @@ export function StandardSiteThemeProvider({ view, children, }: { - view: AppBskyEmbedExternal.ViewExternal + view: app.bsky.embed.external.ViewExternal children: React.ReactNode }) { const alf = useAlf() diff --git a/src/components/Post/Embed/StandardSiteEmbed/index.tsx b/src/components/Post/Embed/StandardSiteEmbed/index.tsx index bb4c030fcc..0c50723a15 100644 --- a/src/components/Post/Embed/StandardSiteEmbed/index.tsx +++ b/src/components/Post/Embed/StandardSiteEmbed/index.tsx @@ -1,6 +1,6 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' import {Image} from 'expo-image' -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' diff --git a/src/components/Post/Embed/StandardSiteEmbed/publishers.ts b/src/components/Post/Embed/StandardSiteEmbed/publishers.ts index aba3e28cc2..d85cea820b 100644 --- a/src/components/Post/Embed/StandardSiteEmbed/publishers.ts +++ b/src/components/Post/Embed/StandardSiteEmbed/publishers.ts @@ -1,8 +1,7 @@ -import {type AppBskyEmbedExternal} from '@atproto/api' - import {Leaflet} from '#/components/icons/community/Leaflet' import {Offprint} from '#/components/icons/community/Offprint' import {Pckt} from '#/components/icons/community/Pckt' +import {type app} from '#/lexicons' export type StandardSitePublisher = { host: string @@ -25,7 +24,7 @@ function hostFromUri(uri: string | undefined): string | null { } export function getStandardSitePublisherHost( - view: AppBskyEmbedExternal.ViewExternal, + view: app.bsky.embed.external.ViewExternal, ): string | null { return hostFromUri(view.source?.uri) } @@ -40,7 +39,7 @@ function matchByHost(host: string | null): StandardSitePublisher | null { } export function matchStandardSitePublisher( - view: AppBskyEmbedExternal.ViewExternal, + view: app.bsky.embed.external.ViewExternal, ): StandardSitePublisher | null { return matchByHost(getStandardSitePublisherHost(view)) } diff --git a/src/components/Post/Embed/StandardSiteEmbed/types.ts b/src/components/Post/Embed/StandardSiteEmbed/types.ts index 208f01b7a6..129559b994 100644 --- a/src/components/Post/Embed/StandardSiteEmbed/types.ts +++ b/src/components/Post/Embed/StandardSiteEmbed/types.ts @@ -1,7 +1,7 @@ -import {type AppBskyEmbedExternal} from '@atproto/api' +import {type app} from '#/lexicons' export type CommonProps = { - view: AppBskyEmbedExternal.ViewExternal + view: app.bsky.embed.external.ViewExternal } export type PreviewProps = { diff --git a/src/components/Post/Embed/StandardSiteEmbed/utils.test.ts b/src/components/Post/Embed/StandardSiteEmbed/utils.test.ts index 5f5dd23d60..cd31849516 100644 --- a/src/components/Post/Embed/StandardSiteEmbed/utils.test.ts +++ b/src/components/Post/Embed/StandardSiteEmbed/utils.test.ts @@ -1,10 +1,9 @@ -import {type AppBskyEmbedExternal} from '@atproto/api' - +import {type app} from '#/lexicons' import {isStandardSiteEmbed, isStandardSitePublicationEmbed} from './utils' function makeView( partial: Record, -): AppBskyEmbedExternal.ViewExternal { +): app.bsky.embed.external.ViewExternal { return { uri: 'https://example.com/post', title: 'title', diff --git a/src/components/Post/Embed/StandardSiteEmbed/utils.ts b/src/components/Post/Embed/StandardSiteEmbed/utils.ts index 2338ca59a3..e43ee0fd99 100644 --- a/src/components/Post/Embed/StandardSiteEmbed/utils.ts +++ b/src/components/Post/Embed/StandardSiteEmbed/utils.ts @@ -1,29 +1,31 @@ -import { - type AppBskyEmbedExternal, - AtUri, - type ComAtprotoRepoStrongRef, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' -export function isStandardSiteDocumentUri(ref: ComAtprotoRepoStrongRef.Main) { +import {type app, type com} from '#/lexicons' + +export function isStandardSiteDocumentUri( + ref: com.atproto.repo.strongRef.Main, +) { return new AtUri(ref.uri).collection.startsWith('site.standard.document') } export function isStandardSitePublicationUri( - ref: ComAtprotoRepoStrongRef.Main, + ref: com.atproto.repo.strongRef.Main, ) { return new AtUri(ref.uri).collection.startsWith('site.standard.publication') } -export function isStandardSiteUri(ref: ComAtprotoRepoStrongRef.Main) { +export function isStandardSiteUri(ref: com.atproto.repo.strongRef.Main) { return new AtUri(ref.uri).collection.startsWith('site.standard.') } -export function isStandardSiteEmbed(view: AppBskyEmbedExternal.ViewExternal) { +export function isStandardSiteEmbed( + view: app.bsky.embed.external.ViewExternal, +) { return view.associatedRefs?.some(ref => isStandardSiteUri(ref)) } export function isStandardSitePublicationEmbed( - view: AppBskyEmbedExternal.ViewExternal, + view: app.bsky.embed.external.ViewExternal, ) { return ( view.associatedRefs?.some( diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx index bcc33ba50b..a8ea65a43b 100644 --- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx +++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx @@ -1,6 +1,5 @@ import {useImperativeHandle, useRef, useState} from 'react' import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native' -import {type AppBskyEmbedVideo} from '@atproto/api' import {BlueskyVideoView} from '@bsky.app/video' import {useLingui} from '@lingui/react/macro' @@ -17,6 +16,7 @@ import {KeepAwake} from '#/components/KeepAwake' import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {useReportDialogMetadataContext} from '#/components/moderation/ReportDialog/ReportDialogMetadataContext' import {useVideoMuteState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' +import {type app} from '#/lexicons' import {GifPresentationControls} from '../GifPresentationControls' import {TimeIndicator} from './TimeIndicator' @@ -29,7 +29,7 @@ export function VideoEmbedInnerNative({ onError, }: { ref: React.Ref<{togglePlayback: () => void}> - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View setStatus: (status: 'playing' | 'paused') => void setIsLoading: (isLoading: boolean) => void setIsActive: (isActive: boolean) => void diff --git a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.shared.ts b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.shared.ts index 9184ccf37c..8c4ef57d20 100644 --- a/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.shared.ts +++ b/src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.shared.ts @@ -1,7 +1,7 @@ -import {type AppBskyEmbedVideo} from '@atproto/api' +import {type app} from '#/lexicons' export type VideoEmbedInnerWebProps = { - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View active: boolean setActive: () => void onScreen: boolean diff --git a/src/components/Post/Embed/VideoEmbed/index.tsx b/src/components/Post/Embed/VideoEmbed/index.tsx index d78aad4985..dc394b8a6e 100644 --- a/src/components/Post/Embed/VideoEmbed/index.tsx +++ b/src/components/Post/Embed/VideoEmbed/index.tsx @@ -1,7 +1,6 @@ import {useCallback, useEffect, useRef, useState} from 'react' import {ActivityIndicator, View} from 'react-native' import {ImageBackground} from 'expo-image' -import {type AppBskyEmbedVideo} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -17,12 +16,13 @@ import {useThrottledValue} from '#/components/hooks/useThrottledValue' import {ConstrainedImage} from '#/components/images/AutoSizedImage' import {PlayButtonIcon} from '#/components/video/PlayButtonIcon' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import {GifPresentationControls} from './GifPresentationControls' import {VideoEmbedInnerNative} from './VideoEmbedInner/VideoEmbedInnerNative' import * as VideoFallback from './VideoEmbedInner/VideoFallback' interface Props { - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View } export function VideoEmbed({embed}: Props) { diff --git a/src/components/Post/Embed/VideoEmbed/index.web.tsx b/src/components/Post/Embed/VideoEmbed/index.web.tsx index 0eb622bad7..a02fff613a 100644 --- a/src/components/Post/Embed/VideoEmbed/index.web.tsx +++ b/src/components/Post/Embed/VideoEmbed/index.web.tsx @@ -7,7 +7,6 @@ import { useState, } from 'react' import {View} from 'react-native' -import {type AppBskyEmbedVideo} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -25,6 +24,7 @@ import { } from '#/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb' import {useAnalytics} from '#/analytics' import {IS_WEB_FIREFOX} from '#/env' +import {type app} from '#/lexicons' import {useActiveVideoWeb} from './ActiveVideoWebContext' import * as VideoFallback from './VideoEmbedInner/VideoFallback' @@ -37,7 +37,7 @@ const noop = () => {} */ const MIN_CARD_WIDTH = 280 -export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) { +export function VideoEmbed({embed}: {embed: app.bsky.embed.video.View}) { const t = useTheme() const ref = useRef(null) const { @@ -297,7 +297,7 @@ function VideoError({ error, retry, }: { - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View error: unknown retry: () => void }) { diff --git a/src/components/Post/Embed/index.tsx b/src/components/Post/Embed/index.tsx index 3c31bd72a4..d6fde617c9 100644 --- a/src/components/Post/Embed/index.tsx +++ b/src/components/Post/Embed/index.tsx @@ -1,18 +1,13 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' -import { - type $Typed, - type AppBskyFeedDefs, - AppBskyFeedPost, - AtUri, -} from '@atproto/api' +import {type $Typed} from '@atproto/lex' +import {AtUri} from '@atproto/syntax' +import {moderatePost} from '@bsky.app/sdk/moderation' 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' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {unstableCacheProfileView} from '#/state/queries/profile' @@ -30,6 +25,7 @@ import {isStandardSiteEmbed} from '#/components/Post/Embed/StandardSiteEmbed/uti import {RichText} from '#/components/RichText' import {Embed as StarterPackCard} from '#/components/StarterPack/StarterPackCard' import {SubtleHover} from '#/components/SubtleHover' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' import { type Embed as TEmbed, @@ -267,7 +263,7 @@ export function QuoteEmbed({ linkDisabled?: boolean }) { const moderationOpts = useModerationOpts() - const quote = useMemo<$Typed>( + const quote = useMemo<$Typed>( () => ({ ...embed.view, $type: 'app.bsky.feed.defs#postView', @@ -287,16 +283,10 @@ export function QuoteEmbed({ const itemTitle = `Post by ${quote.author.handle}` const richText = useMemo(() => { - if ( - !bsky.dangerousIsType( - quote.record, - AppBskyFeedPost.isRecord, - ) - ) - return undefined + if (!bsky.isType(app.bsky.feed.post, quote.record)) return undefined const {text, facets} = quote.record return text.trim() - ? new RichTextAPI({text: text, facets: asSdkFacets(facets)}) + ? new RichTextAPI({text: text, facets: facets}) : undefined }, [quote.record]) diff --git a/src/components/Post/Embed/types.ts b/src/components/Post/Embed/types.ts index 41031fdf05..eb478347fd 100644 --- a/src/components/Post/Embed/types.ts +++ b/src/components/Post/Embed/types.ts @@ -1,7 +1,8 @@ import {type StyleProp, type ViewStyle} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' +import {type app} from '#/lexicons' + export enum PostEmbedViewContext { ThreadHighlighted = 'ThreadHighlighted', Feed = 'Feed', @@ -20,10 +21,10 @@ export type CommonProps = { * events (post:photoEmbed:*). When the embed has no owning post (e.g. * composer previews), leave this undefined and no events will be emitted. */ - post?: AppBskyFeedDefs.PostView + post?: app.bsky.feed.defs.PostView feedDescriptor?: string } export type EmbedProps = CommonProps & { - embed?: AppBskyFeedDefs.PostView['embed'] + embed?: app.bsky.feed.defs.PostView['embed'] } diff --git a/src/components/Post/Translated/index.tsx b/src/components/Post/Translated/index.tsx index 94e0ea697a..849222df03 100644 --- a/src/components/Post/Translated/index.tsx +++ b/src/components/Post/Translated/index.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo} from 'react' import {Platform, type StyleProp, type TextStyle, View} from 'react-native' -import {type AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {HITSLOP_30} from '#/lib/constants' @@ -28,6 +27,7 @@ import * as Select from '#/components/Select' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' const X_ICON_OFFSET = 16 @@ -38,7 +38,7 @@ export function TranslatedPost({ postTextStyle = a.text_md, }: { hideTranslateLink?: boolean - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView postTextStyle?: StyleProp }) { const langPrefs = useLanguagePrefs() @@ -46,11 +46,8 @@ export function TranslatedPost({ key: post.uri, }) - const record = useMemo(() => { - return bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) + const record = useMemo(() => { + return bsky.isType(app.bsky.feed.post, post.record) ? post.record : undefined }, [post]) diff --git a/src/components/PostControls/BookmarkButton.tsx b/src/components/PostControls/BookmarkButton.tsx index b2ed043365..8d43947823 100644 --- a/src/components/PostControls/BookmarkButton.tsx +++ b/src/components/PostControls/BookmarkButton.tsx @@ -1,6 +1,5 @@ import {memo} from 'react' import {type Insets} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -15,6 +14,7 @@ import {Bookmark, BookmarkFilled} from '#/components/icons/Bookmark' import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' import * as toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import {PostControlButton, PostControlButtonIcon} from './PostControlButton' export const BookmarkButton = memo(function BookmarkButton({ @@ -23,7 +23,7 @@ export const BookmarkButton = memo(function BookmarkButton({ logContext, hitSlop, }: { - post: Shadow + post: Shadow big?: boolean logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' hitSlop?: Insets diff --git a/src/components/PostControls/PostMenu/PostMenuItems.tsx b/src/components/PostControls/PostMenu/PostMenuItems.tsx index 43fdaaf1cd..8c31ad4ce4 100644 --- a/src/components/PostControls/PostMenu/PostMenuItems.tsx +++ b/src/components/PostControls/PostMenu/PostMenuItems.tsx @@ -6,12 +6,7 @@ import { type ViewStyle, } from 'react-native' import * as Clipboard from 'expo-clipboard' -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, - AtUri, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' @@ -97,6 +92,7 @@ import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' import {IS_INTERNAL} from '#/env' +import {type app} from '#/lexicons' let PostMenuItems = ({ post, @@ -110,17 +106,17 @@ let PostMenuItems = ({ forceGoogleTranslate, }: { testID: string - post: Shadow + post: Shadow postFeedContext: string | undefined postReqId: string | undefined - record: AppBskyFeedPost.Record + record: app.bsky.feed.post.Main richText: RichTextAPI style?: StyleProp hitSlop?: PressableProps['hitSlop'] size?: 'lg' | 'md' | 'sm' timestamp: string - threadgateRecord?: AppBskyFeedThreadgate.Record - onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void + threadgateRecord?: app.bsky.feed.threadgate.Main + onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' forceGoogleTranslate: boolean }): React.ReactNode => { diff --git a/src/components/PostControls/PostMenu/index.tsx b/src/components/PostControls/PostMenu/index.tsx index c0826683b6..1c7e412065 100644 --- a/src/components/PostControls/PostMenu/index.tsx +++ b/src/components/PostControls/PostMenu/index.tsx @@ -1,10 +1,5 @@ import {memo, useMemo, useState} from 'react' import {type Insets} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, -} from '@atproto/api' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {useLingui} from '@lingui/react/macro' @@ -13,6 +8,7 @@ import {EventStopper} from '#/view/com/util/EventStopper' import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' import * as Menu from '#/components/Menu' import {useMenuControl} from '#/components/Menu' +import {type app} from '#/lexicons' import {PostControlButton, PostControlButtonIcon} from '../PostControlButton' import {PostMenuItems} from './PostMenuItems' @@ -32,15 +28,15 @@ let PostMenuButton = ({ forceGoogleTranslate, }: { testID: string - post: Shadow + post: Shadow postFeedContext: string | undefined postReqId: string | undefined big?: boolean - record: AppBskyFeedPost.Record + record: app.bsky.feed.post.Main richText: RichTextAPI timestamp: string - threadgateRecord?: AppBskyFeedThreadgate.Record - onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void + threadgateRecord?: app.bsky.feed.threadgate.Main + onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void hitSlop?: Insets logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' forceGoogleTranslate: boolean diff --git a/src/components/PostControls/ShareMenu/RecentChats.tsx b/src/components/PostControls/ShareMenu/RecentChats.tsx index 94086c420d..33441352fc 100644 --- a/src/components/PostControls/ShareMenu/RecentChats.tsx +++ b/src/components/PostControls/ShareMenu/RecentChats.tsx @@ -1,6 +1,5 @@ import {ScrollView, View} from 'react-native' -import {type ChatBskyActorDefs} from '@atproto/api' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -8,7 +7,6 @@ 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' @@ -23,6 +21,7 @@ import {type ConvoWithDetails, parseConvoView} from '#/components/dms/util' import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {type chat} from '#/lexicons' export function RecentChats({ postUri, @@ -114,7 +113,7 @@ function RecentChatItem({ onPress: () => void moderationOpts: ModerationOpts convo: ConvoWithDetails - primaryMember: ChatBskyActorDefs.ProfileViewBasic + primaryMember: chat.bsky.actor.defs.ProfileViewBasic }) { const {_} = useLingui() const t = useTheme() diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.tsx index dc66340fd3..716e1ec851 100644 --- a/src/components/PostControls/ShareMenu/ShareMenuItems.tsx +++ b/src/components/PostControls/ShareMenu/ShareMenuItems.tsx @@ -1,6 +1,6 @@ import {memo, useMemo} from 'react' import * as ExpoClipboard from 'expo-clipboard' -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx index ef93865513..c1ca0f3728 100644 --- a/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx +++ b/src/components/PostControls/ShareMenu/ShareMenuItems.types.tsx @@ -1,22 +1,18 @@ import {type PressableProps, type StyleProp, type ViewStyle} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, -} from '@atproto/api' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {type Shadow} from '#/state/cache/post-shadow' +import {type app} from '#/lexicons' export interface ShareMenuItemsProps { testID: string - post: Shadow - record: AppBskyFeedPost.Record + post: Shadow + record: app.bsky.feed.post.Main richText: RichTextAPI style?: StyleProp hitSlop?: PressableProps['hitSlop'] size?: 'lg' | 'md' | 'sm' timestamp: string - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main onShare: () => void } diff --git a/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx b/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx index 1950be0c4b..fb2fd20988 100644 --- a/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx +++ b/src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx @@ -1,5 +1,5 @@ import {memo, useMemo} from 'react' -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' diff --git a/src/components/PostControls/ShareMenu/index.tsx b/src/components/PostControls/ShareMenu/index.tsx index 121cd2a779..e47a13bc95 100644 --- a/src/components/PostControls/ShareMenu/index.tsx +++ b/src/components/PostControls/ShareMenu/index.tsx @@ -1,11 +1,6 @@ import {memo, useMemo, useState} from 'react' import {type Insets} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, - AtUri, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -21,6 +16,7 @@ import {ArrowShareRight_Stroke2_Corner2_Rounded as ArrowShareRightIcon} from '#/ import * as Menu from '#/components/Menu' import {useMenuControl} from '#/components/Menu' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import {PostControlButton, PostControlButtonIcon} from '../PostControlButton' import {ShareMenuItems} from './ShareMenuItems' @@ -37,12 +33,12 @@ let ShareMenuButton = ({ logContext, }: { testID: string - post: Shadow + post: Shadow big?: boolean - record: AppBskyFeedPost.Record + record: app.bsky.feed.post.Main richText: RichTextAPI timestamp: string - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main onShare: () => void hitSlop?: Insets logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' diff --git a/src/components/PostControls/index.tsx b/src/components/PostControls/index.tsx index 88f30b0561..66dd50cb84 100644 --- a/src/components/PostControls/index.tsx +++ b/src/components/PostControls/index.tsx @@ -1,10 +1,5 @@ import {memo, useMemo, useState} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, -} from '@atproto/api' import {type RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' @@ -29,6 +24,7 @@ import {useFormatPostStatCount} from '#/components/PostControls/util' import * as Skele from '#/components/Skeleton' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import {BookmarkButton} from './BookmarkButton' import { PostControlButton, @@ -57,8 +53,8 @@ let PostControls = ({ forceGoogleTranslate = false, }: { big?: boolean - post: Shadow - record: AppBskyFeedPost.Record + post: Shadow + record: app.bsky.feed.post.Main richText: RichTextAPI feedContext?: string | undefined reqId?: string | undefined @@ -66,8 +62,8 @@ let PostControls = ({ onPressReply: () => void onPostReply?: (postUri: string | undefined) => void logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' - threadgateRecord?: AppBskyFeedThreadgate.Record - onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void + threadgateRecord?: app.bsky.feed.threadgate.Main + onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void viaRepost?: {uri: string; cid: string} variant?: 'compact' | 'normal' | 'large' forceGoogleTranslate?: boolean diff --git a/src/components/ProfileCard.tsx b/src/components/ProfileCard.tsx index cdc8d194a1..9db85d2749 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -6,12 +6,11 @@ import { View, type ViewStyle, } from 'react-native' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, 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 74dbb33f28..4f05f8e7ae 100644 --- a/src/components/ProfileHoverCard/index.web.tsx +++ b/src/components/ProfileHoverCard/index.web.tsx @@ -1,14 +1,12 @@ import {memo, useCallback, useEffect, useMemo, useReducer, useRef} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, 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' @@ -40,6 +38,7 @@ import {Text} from '#/components/Typography' import {IS_WEB_TOUCH_DEVICE} from '#/env' import {useActorStatus} from '#/features/liveNow' import {LiveStatus} from '#/features/liveNow/components/LiveStatusDialog' +import {type app} from '#/lexicons' import {type ProfileHoverCardProps} from './types' const floatingMiddlewares = [ @@ -416,7 +415,7 @@ function Inner({ moderationOpts, hide, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed moderationOpts: ModerationOpts hide: () => void }) { diff --git a/src/components/StarterPack/Main/FeedsList.tsx b/src/components/StarterPack/Main/FeedsList.tsx index 43ceacc629..2d0c8f1bad 100644 --- a/src/components/StarterPack/Main/FeedsList.tsx +++ b/src/components/StarterPack/Main/FeedsList.tsx @@ -1,6 +1,5 @@ import {forwardRef, useCallback, useImperativeHandle, useState} from 'react' import {type ListRenderItemInfo, View} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' import {List, type ListRef} from '#/view/com/util/List' @@ -8,13 +7,14 @@ import {type SectionRef} from '#/screens/Profile/Sections/types' import {atoms as a, useTheme} from '#/alf' import * as FeedCard from '#/components/FeedCard' import {IS_NATIVE, IS_WEB} from '#/env' +import {type app} from '#/lexicons' -function keyExtractor(item: AppBskyFeedDefs.GeneratorView) { +function keyExtractor(item: app.bsky.feed.defs.GeneratorView) { return item.uri } interface ProfilesListProps { - feeds: AppBskyFeedDefs.GeneratorView[] + feeds: app.bsky.feed.defs.GeneratorView[] headerHeight: number scrollElRef: ListRef } @@ -39,7 +39,7 @@ export const FeedsList = forwardRef( const renderItem = ({ item, index, - }: ListRenderItemInfo) => { + }: ListRenderItemInfo) => { return ( + InfiniteData > moderationOpts: ModerationOpts headerHeight: number @@ -87,7 +84,7 @@ export const ProfilesList = forwardRef( const renderItem = ({ item, index, - }: ListRenderItemInfo) => { + }: ListRenderItemInfo) => { return ( void @@ -57,7 +57,7 @@ interface ProfileFeedgensProps { emptyStateIcon?: React.ComponentType | React.ReactElement } -function keyExtractor(item: AppBskyGraphDefs.StarterPackViewBasic) { +function keyExtractor(item: app.bsky.graph.defs.StarterPackViewBasic) { return item.uri } @@ -147,7 +147,7 @@ export function ProfileStarterPacks({ ({ item, index, - }: ListRenderItemInfo) => { + }: ListRenderItemInfo) => { return ( }) { const {record} = starterPack - if ( - !bsky.dangerousIsType( - record, - AppBskyGraphStarterpack.isRecord, - ) - ) { + if (!bsky.isType(app.bsky.graph.starterpack, record)) { return null } diff --git a/src/components/StarterPack/QrCodeDialog.tsx b/src/components/StarterPack/QrCodeDialog.tsx index e239eee9a4..483224f9bc 100644 --- a/src/components/StarterPack/QrCodeDialog.tsx +++ b/src/components/StarterPack/QrCodeDialog.tsx @@ -3,7 +3,6 @@ import {View} from 'react-native' import type ViewShot from 'react-native-view-shot' import {requestPermissionsAsync, saveToLibraryAsync} from 'expo-media-library' import * as Sharing from 'expo-sharing' -import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -21,6 +20,7 @@ import {QrCode} from '#/components/StarterPack/QrCode' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' import {IS_NATIVE, IS_WEB} from '#/env' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' export function QrCodeDialog({ @@ -28,7 +28,7 @@ export function QrCodeDialog({ link, control, }: { - starterPack: AppBskyGraphDefs.StarterPackView + starterPack: app.bsky.graph.defs.StarterPackView link?: string control: DialogControlProps }) { @@ -87,12 +87,7 @@ export function QrCodeDialog({ } else { setIsSaveProcessing(true) - if ( - !bsky.validate( - starterPack.record, - AppBskyGraphStarterpack.validateRecord, - ) - ) { + if (!bsky.matches(app.bsky.graph.starterpack, starterPack.record)) { return } diff --git a/src/components/StarterPack/ShareDialog.tsx b/src/components/StarterPack/ShareDialog.tsx index 0dd640d3c0..17c794fc1d 100644 --- a/src/components/StarterPack/ShareDialog.tsx +++ b/src/components/StarterPack/ShareDialog.tsx @@ -1,6 +1,5 @@ import {View} from 'react-native' import {Image} from 'expo-image' -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -19,9 +18,10 @@ import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_NATIVE, IS_WEB} from '#/env' +import {type app} from '#/lexicons' interface Props { - starterPack: AppBskyGraphDefs.StarterPackView + starterPack: app.bsky.graph.defs.StarterPackView link?: string imageLoaded?: boolean qrDialogControl: DialogControlProps diff --git a/src/components/StarterPack/StarterPackCard.tsx b/src/components/StarterPack/StarterPackCard.tsx index 97c5c6fe82..2f99ab0651 100644 --- a/src/components/StarterPack/StarterPackCard.tsx +++ b/src/components/StarterPack/StarterPackCard.tsx @@ -1,7 +1,7 @@ import {useMemo} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' -import {AppBskyGraphStarterpack, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' @@ -19,6 +19,7 @@ import { type LinkProps as BaseLinkProps, } from '#/components/Link' import {Text} from '#/components/Typography' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' export function Default({ @@ -62,12 +63,7 @@ export function Card({ const t = useTheme() const {currentAccount} = useSession() - if ( - !bsky.dangerousIsType( - record, - AppBskyGraphStarterpack.isRecord, - ) - ) { + if (!bsky.isType(app.bsky.graph.starterpack, record)) { return null } @@ -127,10 +123,7 @@ export function useStarterPackLink({ return { to: `/starter-pack/${handleOrDid}/${rkey}`, - label: bsky.dangerousIsType( - view.record, - AppBskyGraphStarterpack.isRecord, - ) + label: bsky.isType(app.bsky.graph.starterpack, view.record) ? _(msg`Navigate to ${view.record.name}`) : _(msg`Navigate to starter pack`), precache, @@ -154,12 +147,7 @@ export function Link({ return {rkey, handleOrDid: creator.handle || creator.did} }, [starterPack]) - if ( - !bsky.dangerousIsType( - record, - AppBskyGraphStarterpack.isRecord, - ) - ) { + if (!bsky.isType(app.bsky.graph.starterpack, record)) { return null } diff --git a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx index 498300491d..04b1ae3df0 100644 --- a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx +++ b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx @@ -1,7 +1,6 @@ import {useRef} from 'react' import {type ListRenderItemInfo} from 'react-native' import {View} from 'react-native' -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' @@ -22,9 +21,10 @@ import { } from '#/components/StarterPack/Wizard/WizardListCard' import {Text} from '#/components/Typography' import {IS_WEB} from '#/env' +import {type app} from '#/lexicons' function keyExtractor( - item: AppBskyActorDefs.ProfileViewBasic | AppBskyFeedDefs.GeneratorView, + item: app.bsky.actor.defs.ProfileViewBasic | app.bsky.feed.defs.GeneratorView, index: number, ) { return `${item.did}-${index}` @@ -41,7 +41,7 @@ export function WizardEditListDialog({ state: WizardState dispatch: (action: WizardAction) => void moderationOpts: ModerationOpts - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed }) { const {_} = useLingui() const t = useTheme() diff --git a/src/components/StarterPack/Wizard/WizardListCard.tsx b/src/components/StarterPack/Wizard/WizardListCard.tsx index 353fd77b13..1d33b27e40 100644 --- a/src/components/StarterPack/Wizard/WizardListCard.tsx +++ b/src/components/StarterPack/Wizard/WizardListCard.tsx @@ -1,12 +1,15 @@ import {Keyboard, View} from 'react-native' -import {type AppBskyActorDefs, type AppBskyFeedDefs} from '@atproto/api' -import {type ModerationOpts, type ModerationUI} from '@bsky.app/sdk/moderation' +import { + moderateFeedGenerator, + moderateProfile, + 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' @@ -21,6 +24,7 @@ import * as Toggle from '#/components/forms/Toggle' import {Checkbox} from '#/components/forms/Toggle' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' function WizardListCard({ @@ -36,8 +40,8 @@ function WizardListCard({ }: { type: 'user' | 'algo' btnType: 'checkbox' | 'remove' - profile?: AppBskyActorDefs.ProfileViewBasic - feed?: AppBskyFeedDefs.GeneratorView + profile?: app.bsky.actor.defs.ProfileViewBasic + feed?: app.bsky.feed.defs.GeneratorView displayName: string subtitle: string onPress: () => void @@ -178,7 +182,7 @@ export function WizardFeedCard({ moderationOpts, }: { btnType: 'checkbox' | 'remove' - generator: AppBskyFeedDefs.GeneratorView + generator: app.bsky.feed.defs.GeneratorView state: WizardState dispatch: (action: WizardAction) => void moderationOpts: ModerationOpts diff --git a/src/components/TrendingTopics.tsx b/src/components/TrendingTopics.tsx index 56fe08ccc9..a067545369 100644 --- a/src/components/TrendingTopics.tsx +++ b/src/components/TrendingTopics.tsx @@ -1,5 +1,5 @@ import {useEffect, useMemo} from 'react' -import {type AppBskyUnspeccedDefs, type AtUri} from '@atproto/api' +import {type AtUri} from '@atproto/syntax' import {useLingui} from '@lingui/react/macro' import {PressableScale} from '#/lib/custom-animations/PressableScale' @@ -9,6 +9,7 @@ import {useCallOnce} from '#/lib/once' import {native} from '#/alf' import {Link as InternalLink, type LinkProps} from '#/components/Link' import {type Metrics, useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' export function TrendingTopicLink({ topic: raw, @@ -18,7 +19,7 @@ export function TrendingTopicLink({ children, ...rest }: { - topic: AppBskyUnspeccedDefs.TrendView + topic: app.bsky.unspecced.defs.TrendView metricContext: Metrics['trendingTopic:seen']['context'] rank: number recId?: string @@ -75,7 +76,7 @@ type ParsedTrendingTopic = } export function useTopic( - raw: AppBskyUnspeccedDefs.TrendView, + raw: app.bsky.unspecced.defs.TrendView, ): ParsedTrendingTopic { const {t: l} = useLingui() return useMemo(() => { diff --git a/src/components/VideoPostCard.tsx b/src/components/VideoPostCard.tsx index bb79fa6a78..ee487e0520 100644 --- a/src/components/VideoPostCard.tsx +++ b/src/components/VideoPostCard.tsx @@ -2,12 +2,6 @@ import {useMemo} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' import {LinearGradient} from 'expo-linear-gradient' -import { - type AppBskyActorDefs, - AppBskyEmbedVideo, - type AppBskyFeedDefs, - AppBskyFeedPost, -} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {useLingui} from '@lingui/react/macro' @@ -26,6 +20,7 @@ import {Link} from '#/components/Link' import {MediaInsetBorder} from '#/components/MediaInsetBorder' import * as Hider from '#/components/moderation/Hider' import {Text} from '#/components/Typography' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' function getBlackColor(t: ReturnType) { @@ -42,7 +37,7 @@ export function VideoPostCard({ moderation, onInteract, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView sourceContext: VideoFeedSourceContext moderation: ModerationDecision /** @@ -75,13 +70,10 @@ export function VideoPostCard({ * Filtering should be done at a higher level, such as `PostFeed` or * `PostFeedVideoGridRow`, but we need to protect here as well. */ - if (!AppBskyEmbedVideo.isView(embed)) return null + if (!bsky.isType(app.bsky.embed.video.view, embed)) return null const author = post.author - const text = bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) + const text = bsky.isType(app.bsky.feed.post, post.record) ? post.record?.text : '' const likeCount = post?.likeCount ?? 0 @@ -275,7 +267,7 @@ export function VideoPostCardPlaceholder() { export function VideoPostCardTextPlaceholder({ author, }: { - author?: AppBskyActorDefs.ProfileViewBasic + author?: app.bsky.actor.defs.ProfileViewBasic }) { const t = useTheme() @@ -355,7 +347,7 @@ export function CompactVideoPostCard({ moderation, onInteract, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView sourceContext: VideoFeedSourceContext moderation: ModerationDecision /** @@ -386,7 +378,7 @@ export function CompactVideoPostCard({ * Filtering should be done at a higher level, such as `PostFeed` or * `PostFeedVideoGridRow`, but we need to protect here as well. */ - if (!AppBskyEmbedVideo.isView(embed)) return null + if (!bsky.isType(app.bsky.embed.video.view, embed)) return null const likeCount = post?.likeCount ?? 0 const showLikeCount = false diff --git a/src/components/WhoCanReply.tsx b/src/components/WhoCanReply.tsx index be7636ff4b..2202ed8e33 100644 --- a/src/components/WhoCanReply.tsx +++ b/src/components/WhoCanReply.tsx @@ -6,12 +6,7 @@ import { View, type ViewStyle, } from 'react-native' -import { - type AppBskyFeedDefs, - AppBskyFeedPost, - type AppBskyGraphDefs, - AtUri, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -38,10 +33,11 @@ import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' interface WhoCanReplyProps { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView isThreadAuthor: boolean style?: StyleProp } @@ -58,10 +54,7 @@ export function WhoCanReply({post, isThreadAuthor, style}: WhoCanReplyProps) { * unexpectedly, we should check to make sure it's for sure the root URI. */ const rootUri = - bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) && post.record.reply?.root + bsky.isType(app.bsky.feed.post, post.record) && post.record.reply?.root ? post.record.reply.root.uri : post.uri const settings = useMemo(() => { @@ -209,7 +202,7 @@ function WhoCanReplyDialog({ embeddingDisabled, }: { control: Dialog.DialogControlProps - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView settings: ThreadgateAllowUISetting[] embeddingDisabled: boolean }) { @@ -255,7 +248,7 @@ function Rules({ settings, embeddingDisabled, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView settings: ThreadgateAllowUISetting[] embeddingDisabled: boolean }) { @@ -313,8 +306,8 @@ function Rule({ lists, }: { rule: ThreadgateAllowUISetting - post: AppBskyFeedDefs.PostView - lists: AppBskyGraphDefs.ListViewBasic[] | undefined + post: app.bsky.feed.defs.PostView + lists: app.bsky.graph.defs.ListViewBasic[] | undefined }) { if (rule.type === 'mention') { return mentioned users diff --git a/src/components/activity-notifications/SubscribeProfileDialog.tsx b/src/components/activity-notifications/SubscribeProfileDialog.tsx index 51d6622407..bbb635c390 100644 --- a/src/components/activity-notifications/SubscribeProfileDialog.tsx +++ b/src/components/activity-notifications/SubscribeProfileDialog.tsx @@ -1,7 +1,6 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyNotificationDefs, type Un$Typed} from '@atproto/api' -import {type DidString} from '@atproto/syntax' +import {type Un$Typed} from '@atproto/lex' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -118,11 +117,10 @@ function DialogInner({ error, } = useMutation({ mutationFn: async ( - activitySubscription: Un$Typed, + activitySubscription: Un$Typed, ) => { await client.call(app.bsky.notification.putActivitySubscription, { - // the profile view carries an already-resolved did - subject: profile.did as DidString, + subject: profile.did, activitySubscription, }) }, @@ -307,8 +305,8 @@ function DialogInner({ } function parseActivitySubscription( - sub?: AppBskyNotificationDefs.ActivitySubscription, -): Un$Typed { + sub?: app.bsky.notification.defs.ActivitySubscription, +): Un$Typed { if (!sub) return {post: false, reply: false} const {post, reply} = sub return {post, reply} diff --git a/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx b/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx index 59df413324..e3a9dd41a7 100644 --- a/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx @@ -1,6 +1,5 @@ import {useState} from 'react' import {View} from 'react-native' -import {ToolsOzoneReportDefs} from '@atproto/api' import {type DidString} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -18,7 +17,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {logger} from '#/ageAssurance' import {useAnalytics} from '#/analytics' -import {com} from '#/lexicons' +import {com, tools} from '#/lexicons' export function AgeAssuranceAppealDialog({ control, @@ -60,7 +59,7 @@ function Inner({control}: {control: Dialog.DialogControlProps}) { await client.call( com.atproto.moderation.createReport, { - reasonType: ToolsOzoneReportDefs.REASONAPPEAL, + reasonType: tools.ozone.report.defs.reasonAppeal.value, subject: { $type: 'com.atproto.admin.defs#repoRef', // the persisted account did is already resolved diff --git a/src/components/contacts/contacts.ts b/src/components/contacts/contacts.ts index 205c2ccb49..e30736a8ea 100644 --- a/src/components/contacts/contacts.ts +++ b/src/components/contacts/contacts.ts @@ -1,6 +1,5 @@ -import {type AppBskyContactDefs} from '@atproto/api' - import {type CountryCode} from '#/lib/international-telephone-codes' +import {type app} from '#/lexicons' import {normalizePhoneNumber} from './phone-number' import {type Contact, type Match} from './state' @@ -70,7 +69,7 @@ export function normalizeContactBook( export function filterMatchedNumbers( contacts: Contact[], - results: AppBskyContactDefs.MatchAndContactIndex[], + results: app.bsky.contact.defs.MatchAndContactIndex[], mapping: Map, ) { const filteredIds = new Set() @@ -87,7 +86,7 @@ export function filterMatchedNumbers( export function getMatchedContacts( contacts: Contact[], - results: AppBskyContactDefs.MatchAndContactIndex[], + results: app.bsky.contact.defs.MatchAndContactIndex[], mapping: Map, ): Array { const contactsById = new Map(contacts.map(c => [c.id, c])) diff --git a/src/components/dialogs/Embed.tsx b/src/components/dialogs/Embed.tsx index 105895ce3d..caecc9a2a9 100644 --- a/src/components/dialogs/Embed.tsx +++ b/src/components/dialogs/Embed.tsx @@ -1,6 +1,6 @@ import {memo, useEffect, useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, type AppBskyFeedPost, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -20,15 +20,16 @@ import { } from '#/components/icons/Chevron' import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBracketsIcon} from '#/components/icons/CodeBrackets' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' export type ColorModeValues = 'system' | 'light' | 'dark' type EmbedDialogProps = { control: Dialog.DialogControlProps - postAuthor: AppBskyActorDefs.ProfileViewBasic + postAuthor: app.bsky.actor.defs.ProfileViewBasic postCid: string postUri: string - record: AppBskyFeedPost.Record + record: app.bsky.feed.post.Main timestamp: string } diff --git a/src/components/dialogs/PostInteractionSettingsDialog.tsx b/src/components/dialogs/PostInteractionSettingsDialog.tsx index 63684f4831..4b04c9fefa 100644 --- a/src/components/dialogs/PostInteractionSettingsDialog.tsx +++ b/src/components/dialogs/PostInteractionSettingsDialog.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {LayoutAnimation, Text as NestedText, View} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -133,10 +132,10 @@ export type PostInteractionSettingsDialogProps = { */ rootPostUri: string /** - * Optional initial {@link AppBskyFeedDefs.ThreadgateView} to use if we + * Optional initial {@link app.bsky.feed.defs.ThreadgateView} to use if we * happen to have one before opening the settings dialog. */ - initialThreadgateView?: AppBskyFeedDefs.ThreadgateView + initialThreadgateView?: app.bsky.feed.defs.ThreadgateView } /** diff --git a/src/components/dialogs/SearchablePeopleList.tsx b/src/components/dialogs/SearchablePeopleList.tsx index 221beb9350..3813a3c745 100644 --- a/src/components/dialogs/SearchablePeopleList.tsx +++ b/src/components/dialogs/SearchablePeopleList.tsx @@ -7,11 +7,10 @@ import { useState, } from 'react' import {TextInput, View} from 'react-native' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, 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/StarterPackDialog.tsx b/src/components/dialogs/StarterPackDialog.tsx index ac1cb6aaef..6f9888da8c 100644 --- a/src/components/dialogs/StarterPackDialog.tsx +++ b/src/components/dialogs/StarterPackDialog.tsx @@ -1,9 +1,5 @@ import {useCallback} from 'react' import {View} from 'react-native' -import { - type AppBskyGraphGetStarterPacksWithMembership, - AppBskyGraphStarterpack, -} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' @@ -33,10 +29,11 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' type StarterPackWithMembership = - AppBskyGraphGetStarterPacksWithMembership.StarterPackWithMembership + app.bsky.graph.getStarterPacksWithMembership.StarterPackWithMembership export type StarterPackDialogProps = { control: Dialog.DialogControlProps @@ -324,12 +321,7 @@ function StarterPackItem({ const {record} = starterPack - if ( - !bsky.dangerousIsType( - record, - AppBskyGraphStarterpack.isRecord, - ) - ) { + if (!bsky.isType(app.bsky.graph.starterpack, record)) { return null } diff --git a/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx b/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx index e6d14b2405..b9979554cd 100644 --- a/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx +++ b/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyGraphDefs, type AppBskyGraphStarterpack} from '@atproto/api' import {TID} from '@atproto/common-web' import {type $Typed} from '@atproto/lex' import { @@ -37,7 +36,7 @@ export function CreateListFromStarterPackDialog({ starterPack, }: { control: Dialog.DialogControlProps - starterPack: AppBskyGraphDefs.StarterPackView + starterPack: app.bsky.graph.defs.StarterPackView }) { const {_} = useLingui() const t = useTheme() @@ -50,7 +49,7 @@ export function CreateListFromStarterPackDialog({ const createDialogControl = Dialog.useDialogControl() const loadingDialogControl = Dialog.useDialogControl() - const record = starterPack.record as AppBskyGraphStarterpack.Record + const record = starterPack.record as app.bsky.graph.starterpack.Main const onPressCreate = () => { control.close(() => createDialogControl.open()) diff --git a/src/components/dialogs/lists/CreateOrEditListDialog.tsx b/src/components/dialogs/lists/CreateOrEditListDialog.tsx index 1f3ec8b7c9..5b3a38be2b 100644 --- a/src/components/dialogs/lists/CreateOrEditListDialog.tsx +++ b/src/components/dialogs/lists/CreateOrEditListDialog.tsx @@ -1,6 +1,5 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyGraphDefs} from '@atproto/api' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -8,7 +7,7 @@ import {Plural, Trans} from '@lingui/react/macro' import {cleanError} from '#/lib/strings/errors' import {isOverMaxGraphemeCount} from '#/lib/strings/helpers' -import {asSdkFacets, richTextToString} from '#/lib/strings/rich-text-helpers' +import {richTextToString} from '#/lib/strings/rich-text-helpers' import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' import {logger} from '#/logger' import {type ImageMeta} from '#/state/gallery' @@ -28,6 +27,7 @@ import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {IS_WEB} from '#/env' +import {type app} from '#/lexicons' const DISPLAY_NAME_MAX_GRAPHEMES = 64 const DESCRIPTION_MAX_GRAPHEMES = 300 @@ -46,8 +46,8 @@ export function CreateOrEditListDialog({ initialValues, }: { control: Dialog.DialogControlProps - list?: AppBskyGraphDefs.ListView - purpose?: AppBskyGraphDefs.ListPurpose + list?: app.bsky.graph.defs.ListView + purpose?: app.bsky.graph.defs.ListPurpose onSave?: (uri: string) => void initialValues?: InitialListValues }) { @@ -114,8 +114,8 @@ function DialogInner({ onPressCancel, initialValues, }: { - list?: AppBskyGraphDefs.ListView - purpose?: AppBskyGraphDefs.ListPurpose + list?: app.bsky.graph.defs.ListView + purpose?: app.bsky.graph.defs.ListPurpose onSave?: (uri: string) => void setDirty: (dirty: boolean) => void onPressCancel: () => void @@ -169,7 +169,7 @@ function DialogInner({ // We want to be working with a blank state here, so let's get the // serialized version and turn it back into a RichText const serialized = richTextToString( - new RichTextAPI({text, facets: asSdkFacets(facets)}), + new RichTextAPI({text, facets: facets}), false, ) diff --git a/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx b/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx index a8f04e2857..ab63ea5677 100644 --- a/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx +++ b/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' -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' @@ -23,6 +22,7 @@ import { import {Loader} from '#/components/Loader' import * as ProfileCard from '#/components/ProfileCard' import * as Toast from '#/components/Toast' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' export function ListAddRemoveUsersDialog({ @@ -31,7 +31,7 @@ export function ListAddRemoveUsersDialog({ onChange, }: { control: Dialog.DialogControlProps - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView onChange?: ( type: 'add' | 'remove', profile: bsky.profile.AnyProfileView, @@ -52,7 +52,7 @@ function DialogInner({ list, onChange, }: { - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView onChange?: ( type: 'add' | 'remove', profile: bsky.profile.AnyProfileView, @@ -89,7 +89,7 @@ function DialogInner({ * Returns undefined for pending, false for not a member, and string for a member (the URI of the membership record) */ function getMembership( - listMembers: AppBskyGraphDefs.ListItemView[] | undefined, + listMembers: app.bsky.graph.defs.ListItemView[] | undefined, actorDid: string, ): string | false | undefined { if (!listMembers) { @@ -107,8 +107,8 @@ function UserResult({ moderationOpts, }: { profile: bsky.profile.AnyProfileView - list: AppBskyGraphDefs.ListView - listMembers: AppBskyGraphDefs.ListItemView[] | undefined + list: app.bsky.graph.defs.ListView + listMembers: app.bsky.graph.defs.ListItemView[] | undefined onChange?: ( type: 'add' | 'remove', profile: bsky.profile.AnyProfileView, diff --git a/src/components/dialogs/nuxs/index.tsx b/src/components/dialogs/nuxs/index.tsx index 2fa8780d95..bbd40feb52 100644 --- a/src/components/dialogs/nuxs/index.tsx +++ b/src/components/dialogs/nuxs/index.tsx @@ -6,7 +6,6 @@ import { useMemo, useState, } from 'react' -import {type AppBskyActorDefs} from '@atproto/api' import {logger} from '#/logger' import {STALE} from '#/state/queries' @@ -30,6 +29,7 @@ import {isSnoozed, snooze, unsnooze} from '#/components/dialogs/nuxs/snoozing' import {type EnabledCheckProps} from '#/components/dialogs/nuxs/utils' import {useAnalytics} from '#/analytics' import {useGeolocation} from '#/geolocation' +import {type app} from '#/lexicons' type Context = { activeNux: Nux | undefined @@ -93,7 +93,7 @@ function Inner({ preferences, }: { currentAccount: SessionAccount - currentProfile: AppBskyActorDefs.ProfileViewDetailed + currentProfile: app.bsky.actor.defs.ProfileViewDetailed preferences: UsePreferencesQueryResponse }) { const ax = useAnalytics() diff --git a/src/components/dialogs/nuxs/utils.ts b/src/components/dialogs/nuxs/utils.ts index 315908cc85..812f28eb1c 100644 --- a/src/components/dialogs/nuxs/utils.ts +++ b/src/components/dialogs/nuxs/utils.ts @@ -1,14 +1,13 @@ -import {type AppBskyActorDefs} from '@atproto/api' - import {type UsePreferencesQueryResponse} from '#/state/queries/preferences' import {type SessionAccount} from '#/state/session' import {type AnalyticsContextType} from '#/analytics' import {type Geolocation} from '#/geolocation' +import {type app} from '#/lexicons' export type EnabledCheckProps = { features: AnalyticsContextType['features'] currentAccount: SessionAccount - currentProfile: AppBskyActorDefs.ProfileViewDetailed + currentProfile: app.bsky.actor.defs.ProfileViewDetailed preferences: UsePreferencesQueryResponse geolocation: Geolocation } diff --git a/src/components/dms/ActionsWrapper.tsx b/src/components/dms/ActionsWrapper.tsx index b9782949b6..837214b74c 100644 --- a/src/components/dms/ActionsWrapper.tsx +++ b/src/components/dms/ActionsWrapper.tsx @@ -1,11 +1,11 @@ import {View} from 'react-native' -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' import {useMessageReplies} from '#/components/dms/MessageReplies' import {SwipeToReply} from '#/components/dms/SwipeToReply' +import {type chat} from '#/lexicons' import type * as bsky from '#/types/bsky' export function ActionsWrapper({ @@ -15,7 +15,7 @@ export function ActionsWrapper({ moderationOpts, children, }: { - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView isFromSelf: boolean senderProfile?: bsky.profile.AnyProfileView moderationOpts: ModerationOpts | undefined diff --git a/src/components/dms/ActionsWrapper.web.tsx b/src/components/dms/ActionsWrapper.web.tsx index 0ef5503370..469c339bf8 100644 --- a/src/components/dms/ActionsWrapper.web.tsx +++ b/src/components/dms/ActionsWrapper.web.tsx @@ -1,6 +1,5 @@ import {useCallback, useRef, useState} from 'react' import {Pressable, View} from 'react-native' -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' @@ -14,6 +13,7 @@ import {MessageContextMenu} from '#/components/dms/MessageContextMenu' import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsHorizontalIcon} from '#/components/icons/DotGrid' import {EmojiSmile_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji' import * as Toast from '#/components/Toast' +import {type chat} from '#/lexicons' import type * as bsky from '#/types/bsky' import {EmojiReactionPicker} from './EmojiReactionPicker' import { @@ -29,7 +29,7 @@ export function ActionsWrapper({ moderationOpts, children, }: { - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView isFromSelf: boolean senderProfile?: bsky.profile.AnyProfileView moderationOpts: ModerationOpts | undefined diff --git a/src/components/dms/AfterReportConversationDialog.tsx b/src/components/dms/AfterReportConversationDialog.tsx index 8e484156f4..98973ea533 100644 --- a/src/components/dms/AfterReportConversationDialog.tsx +++ b/src/components/dms/AfterReportConversationDialog.tsx @@ -1,6 +1,5 @@ import {memo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {StackActions, useNavigation} from '@react-navigation/native' @@ -19,6 +18,7 @@ import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' type ReportDialogParams = { convoId: string @@ -113,7 +113,7 @@ function DoneStep({ }: { convoId: string currentScreen: 'list' | 'conversation' - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed }) { const {t: l} = useLingui() const navigation = useNavigation() diff --git a/src/components/dms/AfterReportDialog.tsx b/src/components/dms/AfterReportDialog.tsx index 060ea1fcfb..c60f1ac0b6 100644 --- a/src/components/dms/AfterReportDialog.tsx +++ b/src/components/dms/AfterReportDialog.tsx @@ -1,6 +1,5 @@ import {memo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {StackActions, useNavigation} from '@react-navigation/native' @@ -19,6 +18,7 @@ import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' type ReportDialogParams = { convoId: string @@ -116,7 +116,7 @@ function DoneStep({ }: { convoId: string currentScreen: 'list' | 'conversation' - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed }) { const {t: l} = useLingui() const navigation = useNavigation() diff --git a/src/components/dms/ChatInvite/Card.tsx b/src/components/dms/ChatInvite/Card.tsx index 92e0e96814..177eb5d5ae 100644 --- a/src/components/dms/ChatInvite/Card.tsx +++ b/src/components/dms/ChatInvite/Card.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {ChatBskyGroupDefs} from '@atproto/api' import {Plural, Trans} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' @@ -10,6 +9,8 @@ import {AvatarBubbles} from '#/components/AvatarBubbles' import {InlineLinkText} from '#/components/Link' import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {useChatInvite} from './Context' /** @@ -21,7 +22,8 @@ export function Card({size}: {size: 'large' | 'small'}) { const t = useTheme() const {preview, hasFixedHeight} = useChatInvite() - if (!ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) return null + if (!bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview)) + return null const ownerDisplayName = createSanitizedDisplayName(preview.owner) const ownerHandle = sanitizeHandle(preview.owner.handle, '@') diff --git a/src/components/dms/ChatInvite/Root.tsx b/src/components/dms/ChatInvite/Root.tsx index 16f5bdfae4..83796e1e61 100644 --- a/src/components/dms/ChatInvite/Root.tsx +++ b/src/components/dms/ChatInvite/Root.tsx @@ -1,5 +1,4 @@ import {setStringAsync} from 'expo-clipboard' -import {ChatBskyGroupDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -18,6 +17,8 @@ import {type Props as SVGIconProps} from '#/components/icons/common' import {RaisingHand4Finger_Stroke2_Corner2_Rounded as HandIcon} from '#/components/icons/RaisingHand' import {useIntentDialogs} from '#/components/intents/IntentDialogs' import * as Toast from '#/components/Toast' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import { type ChatInviteAction, ChatInviteProvider, @@ -71,7 +72,7 @@ export function Root({ status = 'loading' } else if (error) { status = 'error' - } else if (ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) { + } else if (bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview)) { status = 'available' } else { // Resolved to a disabled/invalid/unrecognized preview - nothing to join. @@ -79,7 +80,7 @@ export function Root({ } let action: ChatInviteAction | undefined - if (ChatBskyGroupDefs.isJoinLinkPreviewView(preview)) { + if (bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview)) { const convoId = preview.convo?.id const isFollowing = preview.owner.viewer?.followedBy ?? false const hasRequested = !convoId && preview.viewer?.requestedAt != null diff --git a/src/components/dms/ChatProfileTabs.tsx b/src/components/dms/ChatProfileTabs.tsx index 341d94392d..5a0e7a8fb3 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 '@bsky.app/sdk/moderation' 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/EmojiReactionPicker.tsx b/src/components/dms/EmojiReactionPicker.tsx index de2b818b96..b54881117e 100644 --- a/src/components/dms/EmojiReactionPicker.tsx +++ b/src/components/dms/EmojiReactionPicker.tsx @@ -1,6 +1,5 @@ import {useMemo, useState} from 'react' import {useWindowDimensions, View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -14,6 +13,7 @@ import { import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' import {type TriggerProps} from '#/components/Menu/types' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' import {EmojiPopup} from './EmojiPopup' import {hasAlreadyReacted, hasReachedReactionLimit} from './util' @@ -21,7 +21,7 @@ export function EmojiReactionPicker({ message, onEmojiSelect, }: { - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView children?: TriggerProps['children'] onEmojiSelect: (emoji: string) => void }) { diff --git a/src/components/dms/EmojiReactionPicker.web.tsx b/src/components/dms/EmojiReactionPicker.web.tsx index 8740330575..c84151f367 100644 --- a/src/components/dms/EmojiReactionPicker.web.tsx +++ b/src/components/dms/EmojiReactionPicker.web.tsx @@ -1,6 +1,5 @@ import {useState} from 'react' import {Pressable, View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {DropdownMenu} from 'radix-ui' @@ -10,6 +9,7 @@ import * as EmojiPicker from '#/components/EmojiPicker' import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' import * as Menu from '#/components/Menu' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' import {hasAlreadyReacted, hasReachedReactionLimit} from './util' export function EmojiReactionPicker({ @@ -17,7 +17,7 @@ export function EmojiReactionPicker({ children, onEmojiSelect, }: { - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView children?: EmojiPicker.TriggerProps['children'] onEmojiSelect: (emoji: string) => void }) { @@ -40,7 +40,7 @@ function MenuInner({ message, onEmojiSelect, }: { - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView onEmojiSelect: (emoji: string) => void }) { const t = useTheme() diff --git a/src/components/dms/InitiateChatFlow.tsx b/src/components/dms/InitiateChatFlow.tsx index 3a93b966b0..271016a5d5 100644 --- a/src/components/dms/InitiateChatFlow.tsx +++ b/src/components/dms/InitiateChatFlow.tsx @@ -7,12 +7,11 @@ import { useState, } from 'react' import {LayoutAnimation, type TextInput, View} from 'react-native' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, 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 0bfa156eed..1807f2888d 100644 --- a/src/components/dms/MessageContextMenu.tsx +++ b/src/components/dms/MessageContextMenu.tsx @@ -2,7 +2,6 @@ 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} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {RichText} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' @@ -10,7 +9,7 @@ import {useLingui} from '@lingui/react/macro' import {EMOJI_REACTION_LIMIT} from '#/lib/constants' import {useGoogleTranslate} from '#/lib/hooks/useGoogleTranslate' -import {asSdkFacets, richTextToString} from '#/lib/strings/rich-text-helpers' +import {richTextToString} from '#/lib/strings/rich-text-helpers' import {useMaybeProfileShadow} from '#/state/cache/profile-shadow' import {useConvoActive} from '#/state/messages/convo' import {useLanguagePrefs} from '#/state/preferences' @@ -28,6 +27,7 @@ import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Tra import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' +import {type chat} from '#/lexicons' import type * as bsky from '#/types/bsky' import {EmojiReactionPicker} from './EmojiReactionPicker' import {canReact, hasReachedReactionLimit} from './util' @@ -39,7 +39,7 @@ export let MessageContextMenu = ({ children, swipeGesture, }: { - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView senderProfile?: bsky.profile.AnyProfileView moderationOpts: ModerationOpts | undefined children: TriggerProps['children'] @@ -86,7 +86,7 @@ export let MessageContextMenu = ({ const str = richTextToString( new RichText({ text: message.text, - facets: asSdkFacets(message.facets), + facets: message.facets, }), true, ) diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx index a835230db2..2a3d895544 100644 --- a/src/components/dms/MessageItem.tsx +++ b/src/components/dms/MessageItem.tsx @@ -22,12 +22,7 @@ import Animated, { ZoomIn, ZoomOut, } from 'react-native-reanimated' -import { - AppBskyEmbedRecord, - type ChatBskyActorDefs, - ChatBskyConvoDefs, - ChatBskyEmbedJoinLink, -} from '@atproto/api' +import {moderateProfile} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro' @@ -35,9 +30,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' import {type Shadow} from '#/state/cache/types' import {type ConvoItem} from '#/state/messages/convo/types' @@ -59,6 +52,8 @@ import * as ProfileCard from '#/components/ProfileCard' import * as Prompt from '#/components/Prompt' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' +import {app, chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {DateDivider} from './DateDivider' import {MessageItemEmbed} from './MessageItemEmbed' import {MessageItemInviteEmbed} from './MessageItemInviteEmbed' @@ -77,16 +72,19 @@ const SQUARED_BORDER_RADIUS = 4 const DISPLAY_NAME_INSET = 20 export type MessageItemNeighbor = - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView + | chat.bsky.convo.defs.MessageView + | chat.bsky.convo.defs.DeletedMessageView | null function messageIsReply(message: MessageItemNeighbor): boolean { return ( - ChatBskyConvoDefs.isMessageView(message) && - (ChatBskyConvoDefs.isMessageView(message.replyTo) || - ChatBskyConvoDefs.isDeletedMessageView(message.replyTo) || - ChatBskyConvoDefs.isMessageBeforeUserJoinedGroupView(message.replyTo)) + bsky.isType(chat.bsky.convo.defs.messageView, message) && + (bsky.isType(chat.bsky.convo.defs.messageView, message.replyTo) || + bsky.isType(chat.bsky.convo.defs.deletedMessageView, message.replyTo) || + bsky.isType( + chat.bsky.convo.defs.messageBeforeUserJoinedGroupView, + message.replyTo, + )) ) } @@ -98,7 +96,7 @@ function isWithinClusterBoundary({ direction, }: { isPending: boolean - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView adjacentMessage: MessageItemNeighbor isFromSameSender: boolean direction: 'prev' | 'next' @@ -110,7 +108,7 @@ function isWithinClusterBoundary({ return true } if (!isFromSameSender) return true - if (ChatBskyConvoDefs.isMessageView(adjacentMessage)) { + if (bsky.isType(chat.bsky.convo.defs.messageView, adjacentMessage)) { const currentSentAt = message.sentAt const thisDate = new Date(currentSentAt) const adjDate = new Date(adjacentMessage.sentAt) @@ -137,7 +135,7 @@ let MessageItem = ({ isGroupChat?: boolean prevMessage: MessageItemNeighbor nextMessage: MessageItemNeighbor - relatedProfiles: Map + relatedProfiles: Map }): React.ReactNode => { const t = useTheme() const {currentAccount} = useSession() @@ -155,13 +153,17 @@ let MessageItem = ({ // tombstone, or a before-joined placeholder. Narrow away the open-union // fallback so we only render shapes we understand. const replyTo = - ChatBskyConvoDefs.isMessageView(message.replyTo) || - ChatBskyConvoDefs.isDeletedMessageView(message.replyTo) || - ChatBskyConvoDefs.isMessageBeforeUserJoinedGroupView(message.replyTo) + bsky.isType(chat.bsky.convo.defs.messageView, message.replyTo) || + bsky.isType(chat.bsky.convo.defs.deletedMessageView, message.replyTo) || + bsky.isType( + chat.bsky.convo.defs.messageBeforeUserJoinedGroupView, + message.replyTo, + ) ? message.replyTo : undefined const replyToMessageId = - replyTo && !ChatBskyConvoDefs.isMessageBeforeUserJoinedGroupView(replyTo) + replyTo && + !bsky.isType(chat.bsky.convo.defs.messageBeforeUserJoinedGroupView, replyTo) ? replyTo.id : undefined const onPressReplyTo = replyToMessageId @@ -175,8 +177,14 @@ let MessageItem = ({ const isFromSelf = message.sender?.did != null && message.sender.did === currentAccount?.did - const prevIsMessage = ChatBskyConvoDefs.isMessageView(prevMessage) - const nextIsMessage = ChatBskyConvoDefs.isMessageView(nextMessage) + const prevIsMessage = bsky.isType( + chat.bsky.convo.defs.messageView, + prevMessage, + ) + const nextIsMessage = bsky.isType( + chat.bsky.convo.defs.messageView, + nextMessage, + ) const isPrevFromSameSender = prevIsMessage && @@ -204,7 +212,7 @@ let MessageItem = ({ }) const hasLargeGapFromPrev = - !ChatBskyConvoDefs.isMessageView(prevMessage) || + !bsky.isType(chat.bsky.convo.defs.messageView, prevMessage) || new Date(message.sentAt).getTime() - new Date(prevMessage.sentAt).getTime() > MESSAGE_GAP_THRESHOLD_MS @@ -248,14 +256,14 @@ let MessageItem = ({ const rt = new RichTextAPI({ text: message.text, - facets: asSdkFacets(message.facets), + facets: message.facets, }) const isEmojiOnly = isOnlyEmoji(message.text) const hasEmbed = - AppBskyEmbedRecord.isView(message.embed) || - ChatBskyEmbedJoinLink.isView(message.embed) + bsky.isType(app.bsky.embed.record.view, message.embed) || + bsky.isType(chat.bsky.embed.joinLink.view, message.embed) const hasEmbedAndText = hasEmbed && rt.text.length > 0 const targetBottomRadius = squaredBottomCorner @@ -516,7 +524,7 @@ let MessageItem = ({ message={message} senderProfile={profile} moderationOpts={moderationOpts}> - {AppBskyEmbedRecord.isView(message.embed) && ( + {bsky.isType(app.bsky.embed.record.view, message.embed) && ( )} - {ChatBskyEmbedJoinLink.isView(message.embed) && ( + {bsky.isType( + chat.bsky.embed.joinLink.view, + message.embed, + ) && ( + profile: Shadow style?: AnimatedStyle }) { const {t: l} = useLingui() @@ -776,13 +787,13 @@ function ReplyCaption({ onPress, }: { replyTo: - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | ChatBskyConvoDefs.MessageBeforeUserJoinedGroupView + | chat.bsky.convo.defs.MessageView + | chat.bsky.convo.defs.DeletedMessageView + | chat.bsky.convo.defs.MessageBeforeUserJoinedGroupView isFromSelf: boolean isGroupChat: boolean replierDisplayName: string | null - relatedProfiles: Map + relatedProfiles: Map onPress?: () => void }) { const t = useTheme() @@ -791,8 +802,8 @@ function ReplyCaption({ let caption: string = '' if ( - ChatBskyConvoDefs.isMessageView(replyTo) || - ChatBskyConvoDefs.isDeletedMessageView(replyTo) + bsky.isType(chat.bsky.convo.defs.messageView, replyTo) || + bsky.isType(chat.bsky.convo.defs.deletedMessageView, replyTo) ) { const originalSenderIsSelf = replyTo.sender.did === currentAccount?.did const originalProfile = relatedProfiles.get(replyTo.sender.did) @@ -865,11 +876,11 @@ function ReplyQuote({ onPress, }: { replyTo: - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | ChatBskyConvoDefs.MessageBeforeUserJoinedGroupView + | chat.bsky.convo.defs.MessageView + | chat.bsky.convo.defs.DeletedMessageView + | chat.bsky.convo.defs.MessageBeforeUserJoinedGroupView isFromSelf: boolean - relatedProfiles: Map + relatedProfiles: Map onPress?: () => void }) { const t = useTheme() @@ -877,8 +888,8 @@ function ReplyQuote({ const getReplyPreviewText = useReplyPreviewText() const senderDid = - ChatBskyConvoDefs.isMessageView(replyTo) || - ChatBskyConvoDefs.isDeletedMessageView(replyTo) + bsky.isType(chat.bsky.convo.defs.messageView, replyTo) || + bsky.isType(chat.bsky.convo.defs.deletedMessageView, replyTo) ? replyTo.sender.did : undefined const senderProfile = useMaybeProfileShadow( @@ -908,9 +919,11 @@ function ReplyQuote({ comment: 'A reply summary in chat', }) subtle = true - } else if (ChatBskyConvoDefs.isMessageView(replyTo)) { + } else if (bsky.isType(chat.bsky.convo.defs.messageView, replyTo)) { ;({text, subtle} = getReplyPreviewText(replyTo)) - } else if (ChatBskyConvoDefs.isMessageBeforeUserJoinedGroupView(replyTo)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.messageBeforeUserJoinedGroupView, replyTo) + ) { text = l({ message: `(message sent before you joined)`, comment: 'A reply summary in chat', diff --git a/src/components/dms/MessageItemEmbed.tsx b/src/components/dms/MessageItemEmbed.tsx index 5014a9ff11..22c3f7462d 100644 --- a/src/components/dms/MessageItemEmbed.tsx +++ b/src/components/dms/MessageItemEmbed.tsx @@ -5,10 +5,11 @@ import Animated, { type SharedValue, useAnimatedStyle, } from 'react-native-reanimated' -import {type $Typed, type AppBskyEmbedRecord} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {atoms as a, native, useTheme, web} from '#/alf' import {Embed, PostEmbedViewContext} from '#/components/Post/Embed' +import {type app} from '#/lexicons' import {MessageContextProvider} from './MessageContext' const BORDER_RADIUS = 20 @@ -22,7 +23,7 @@ let MessageItemEmbed = ({ squaredBottomCorner, highlightSV, }: { - embed: $Typed + embed: $Typed isFromSelf: boolean isGroupChat: boolean squaredTopCorner: boolean diff --git a/src/components/dms/MessageItemInviteEmbed.tsx b/src/components/dms/MessageItemInviteEmbed.tsx index 5d6acd93d1..ba6a2b0630 100644 --- a/src/components/dms/MessageItemInviteEmbed.tsx +++ b/src/components/dms/MessageItemInviteEmbed.tsx @@ -5,12 +5,13 @@ import Animated, { type SharedValue, useAnimatedStyle, } from 'react-native-reanimated' -import {type $Typed, type ChatBskyEmbedJoinLink} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {useConvoActive} from '#/state/messages/convo' import {isKnownJoinLinkPreview} from '#/state/queries/join-links' import {atoms as a, native, useTheme, web} from '#/alf' import * as ChatInvite from '#/components/dms/ChatInvite' +import {type chat} from '#/lexicons' import {MessageContextProvider} from './MessageContext' const BORDER_RADIUS = 20 @@ -24,7 +25,7 @@ let MessageItemInviteEmbed = ({ squaredBottomCorner, highlightSV, }: { - embed: $Typed + embed: $Typed isFromSelf: boolean isGroupChat: boolean squaredTopCorner: boolean diff --git a/src/components/dms/MessageOverlays.tsx b/src/components/dms/MessageOverlays.tsx index 2384aad88d..ddb6fa9acf 100644 --- a/src/components/dms/MessageOverlays.tsx +++ b/src/components/dms/MessageOverlays.tsx @@ -7,7 +7,6 @@ import { useState, } from 'react' import {LayoutAnimation} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' @@ -20,15 +19,16 @@ import {ReportDialog} from '#/components/moderation/ReportDialog' import * as Prompt from '#/components/Prompt' import {usePromptControl} from '#/components/Prompt' import * as Toast from '#/components/Toast' +import {type chat} from '#/lexicons' import type * as bsky from '#/types/bsky' type MessageDialogsContextType = { - openDeleteMessage: (message: ChatBskyConvoDefs.MessageView) => void + openDeleteMessage: (message: chat.bsky.convo.defs.MessageView) => void openReportMessage: ( - message: ChatBskyConvoDefs.MessageView, + message: chat.bsky.convo.defs.MessageView, senderProfile: bsky.profile.AnyProfileView | undefined, ) => void - openReactions: (message: ChatBskyConvoDefs.MessageView) => void + openReactions: (message: chat.bsky.convo.defs.MessageView) => void } const Context = createContext(null) @@ -52,18 +52,18 @@ export function MessageOverlays({children}: {children: React.ReactNode}) { const reactionsControl = useDialogControl() const [deleteTarget, setDeleteTarget] = - useState(null) + useState(null) const [reportTarget, setReportTarget] = useState<{ - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView senderProfile: bsky.profile.AnyProfileView | undefined } | null>(null) const [afterReportTarget, setAfterReportTarget] = - useState(null) + useState(null) const [reactionsTarget, setReactionsTarget] = - useState(null) + useState(null) const openDeleteMessage = useCallback( - (message: ChatBskyConvoDefs.MessageView) => { + (message: chat.bsky.convo.defs.MessageView) => { setDeleteTarget(message) deleteControl.open() }, @@ -72,7 +72,7 @@ export function MessageOverlays({children}: {children: React.ReactNode}) { const openReportMessage = useCallback( ( - message: ChatBskyConvoDefs.MessageView, + message: chat.bsky.convo.defs.MessageView, senderProfile: bsky.profile.AnyProfileView | undefined, ) => { setReportTarget({message, senderProfile}) @@ -82,7 +82,7 @@ export function MessageOverlays({children}: {children: React.ReactNode}) { ) const openReactions = useCallback( - (message: ChatBskyConvoDefs.MessageView) => { + (message: chat.bsky.convo.defs.MessageView) => { setReactionsTarget(message) }, [], diff --git a/src/components/dms/MessageProfileButton.tsx b/src/components/dms/MessageProfileButton.tsx index ede352218b..189f0da632 100644 --- a/src/components/dms/MessageProfileButton.tsx +++ b/src/components/dms/MessageProfileButton.tsx @@ -1,6 +1,5 @@ import {useCallback} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -16,11 +15,12 @@ import {canBeMessaged} from '#/components/dms/util' import {Message_Stroke2_Corner0_Rounded as Message} from '#/components/icons/Message' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' export function MessageProfileButton({ profile, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed }) { const {_} = useLingui() const t = useTheme() diff --git a/src/components/dms/MessageReplies.tsx b/src/components/dms/MessageReplies.tsx index 5093d8d157..e5bde7e078 100644 --- a/src/components/dms/MessageReplies.tsx +++ b/src/components/dms/MessageReplies.tsx @@ -7,7 +7,8 @@ import { useRef, useState, } from 'react' -import {type ChatBskyConvoDefs} from '@atproto/api' + +import {type chat} from '#/lexicons' /** * How long a message stays highlighted after scrolling to it, before the flash @@ -28,8 +29,8 @@ type MessageRepliesContextType = { /** * The message currently staged for reply in the composer, or null. */ - replyTo: ChatBskyConvoDefs.MessageView | null - setReply: (message: ChatBskyConvoDefs.MessageView) => void + replyTo: chat.bsky.convo.defs.MessageView | null + setReply: (message: chat.bsky.convo.defs.MessageView) => void clearReply: () => void /** * Scroll the list to a message, if it's currently loaded, and flash it. No-op @@ -66,9 +67,8 @@ export function MessageRepliesProvider({ */ scrollToMessage: (messageId: string) => boolean }) { - const [replyTo, setReplyTo] = useState( - null, - ) + const [replyTo, setReplyTo] = + useState(null) const [highlightedMessage, setHighlightedMessage] = useState(null) const highlightKey = useRef(0) @@ -76,7 +76,7 @@ export function MessageRepliesProvider({ null, ) - const setReply = useCallback((message: ChatBskyConvoDefs.MessageView) => { + const setReply = useCallback((message: chat.bsky.convo.defs.MessageView) => { setReplyTo(message) }, []) diff --git a/src/components/dms/MessagesListHeader.tsx b/src/components/dms/MessagesListHeader.tsx index 75a534b790..adf8e3b1f5 100644 --- a/src/components/dms/MessagesListHeader.tsx +++ b/src/components/dms/MessagesListHeader.tsx @@ -1,10 +1,9 @@ import {useMemo} from 'react' import {View} from 'react-native' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, 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/ReactionsDialog.tsx b/src/components/dms/ReactionsDialog.tsx index 3918143d37..5a9f83c6e9 100644 --- a/src/components/dms/ReactionsDialog.tsx +++ b/src/components/dms/ReactionsDialog.tsx @@ -6,7 +6,6 @@ import { useWindowDimensions, View, } from 'react-native' -import {type ChatBskyActorDefs, type ChatBskyConvoDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {HITSLOP_10} from '#/lib/constants' @@ -23,12 +22,13 @@ import {filterBlockedReactions} from '#/components/dms/util' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {IS_NATIVE, IS_WEB} from '#/env' +import {type chat} from '#/lexicons' import type * as bsky from '#/types/bsky' type Reaction = { key: string value: string - senders: ChatBskyConvoDefs.ReactionViewSender[] + senders: chat.bsky.convo.defs.ReactionViewSender[] count: number } @@ -41,8 +41,8 @@ export function ReactionsDialog({ onClose, }: { control: Dialog.DialogControlProps - relatedProfiles: Map - message: ChatBskyConvoDefs.MessageView + relatedProfiles: Map + message: chat.bsky.convo.defs.MessageView onClose?: () => void }) { const {t: l} = useLingui() @@ -142,10 +142,10 @@ function ReactionRow({ control: Dialog.DialogControlProps convo: ActiveConvoStates currentAccount?: SessionAccount - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView profile: bsky.profile.AnyProfileView - reaction: ChatBskyConvoDefs.ReactionView - allReactions: ChatBskyConvoDefs.ReactionView[] + reaction: chat.bsky.convo.defs.ReactionView + allReactions: chat.bsky.convo.defs.ReactionView[] selected: string setSelected: React.Dispatch> }) { @@ -398,7 +398,7 @@ function ReactionTab({ } export function groupReactions( - reactions: ChatBskyConvoDefs.ReactionView[] | undefined, + reactions: chat.bsky.convo.defs.ReactionView[] | undefined, ): Reaction[] { const grouped = new Map() for (const reaction of reactions ?? []) { diff --git a/src/components/dms/SystemMessageGroup.tsx b/src/components/dms/SystemMessageGroup.tsx index 930242ca46..5305572c57 100644 --- a/src/components/dms/SystemMessageGroup.tsx +++ b/src/components/dms/SystemMessageGroup.tsx @@ -7,7 +7,6 @@ import Animated, { useDerivedValue, withTiming, } from 'react-native-reanimated' -import {type ChatBskyActorDefs} from '@atproto/api' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' @@ -17,6 +16,7 @@ import {atoms as a, useTheme} from '#/alf' import {SystemMessageItem} from '#/components/dms/SystemMessageItem' import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' const ANIMATION_DURATION_MS = 200 @@ -29,7 +29,7 @@ export function SystemMessageGroup({ item: SystemMessageGroupItem expanded: boolean onToggle: (key: string) => void - relatedProfiles: Map + relatedProfiles: Map }) { const t = useTheme() const {t: l} = useLingui() diff --git a/src/components/dms/SystemMessageItem.tsx b/src/components/dms/SystemMessageItem.tsx index 7de6a0cd91..716c26f474 100644 --- a/src/components/dms/SystemMessageItem.tsx +++ b/src/components/dms/SystemMessageItem.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type ChatBskyActorDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {makeProfileLink} from '#/lib/routes/links' @@ -10,13 +9,14 @@ import {Button} from '#/components/Button' import {getSystemMessageInfo} from '#/components/dms/getSystemMessageInfo' import {Link} from '#/components/Link' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' export function SystemMessageItem({ item, relatedProfiles, }: { item: ConvoItem & {type: 'system-message'} - relatedProfiles: Map + relatedProfiles: Map }) { const t = useTheme() const {i18n, t: l} = useLingui() diff --git a/src/components/dms/components/GroupChatProfileCard.tsx b/src/components/dms/components/GroupChatProfileCard.tsx index 63fbbbd20c..90d15c4b5f 100644 --- a/src/components/dms/components/GroupChatProfileCard.tsx +++ b/src/components/dms/components/GroupChatProfileCard.tsx @@ -1,8 +1,7 @@ import {View} from 'react-native' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, 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/getMessageInfo.ts b/src/components/dms/getMessageInfo.ts index 5aaa3a8d7d..afe41c625e 100644 --- a/src/components/dms/getMessageInfo.ts +++ b/src/components/dms/getMessageInfo.ts @@ -1,9 +1,3 @@ -import { - AppBskyEmbedRecord, - type ChatBskyActorDefs, - ChatBskyConvoDefs, - ChatBskyEmbedJoinLink, -} from '@atproto/api' import {type I18n} from '@lingui/core' import {msg} from '@lingui/core/macro' @@ -14,12 +8,13 @@ import { toBskyAppUrl, toShortUrl, } from '#/lib/strings/url-helpers' -import type * as bsky from '#/types/bsky' +import {app, chat} from '#/lexicons' +import * as bsky from '#/types/bsky' export type UserMessageInfo = { message: string | null sentAt: string - reportableMessage?: ChatBskyConvoDefs.MessageView + reportableMessage?: chat.bsky.convo.defs.MessageView isBlockedMessage: boolean } @@ -36,7 +31,7 @@ export function isDidBlockedInConvo({ primaryProfile, }: { did: string | undefined - members: ChatBskyActorDefs.ProfileViewBasic[] + members: chat.bsky.actor.defs.ProfileViewBasic[] primaryProfile?: bsky.profile.AnyProfileView }): boolean { if (!did) return false @@ -53,12 +48,12 @@ export function getMessageInfo({ primaryProfile, i18n, }: { - convo: ChatBskyConvoDefs.ConvoView + convo: chat.bsky.convo.defs.ConvoView currentAccountDid: string | undefined primaryProfile?: bsky.profile.AnyProfileView i18n: I18n }): UserMessageInfo | null { - if (!ChatBskyConvoDefs.isMessageView(convo.lastMessage)) { + if (!bsky.isType(chat.bsky.convo.defs.messageView, convo.lastMessage)) { return null } @@ -67,7 +62,7 @@ export function getMessageInfo({ const senderDid = lastMessage.sender?.did const sender = convo.members.find(m => m.did === senderDid) const name = sender ? createSanitizedDisplayName(sender) : null - const isGroup = ChatBskyConvoDefs.isGroupConvo(convo.kind) + const isGroup = bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind) const reportableMessage = isFromMe ? undefined : lastMessage const isBlockedMessage = isDidBlockedInConvo({ @@ -105,10 +100,10 @@ export function getMessageInfo({ msg`(contains embedded content)`, ) - if (AppBskyEmbedRecord.isView(lastMessage.embed)) { + if (bsky.isType(app.bsky.embed.record.view, lastMessage.embed)) { const embed = lastMessage.embed - if (AppBskyEmbedRecord.isViewRecord(embed.record)) { + if (bsky.isType(app.bsky.embed.record.viewRecord, embed.record)) { const record = embed.record const path = postUriToRelativePath(record.uri, { handle: record.author.handle, @@ -119,7 +114,7 @@ export function getMessageInfo({ } else { message = prefix(defaultEmbeddedContentMessage) } - } else if (ChatBskyEmbedJoinLink.isView(lastMessage.embed)) { + } else if (bsky.isType(chat.bsky.embed.joinLink.view, lastMessage.embed)) { message = prefix(i18n._(msg`(chat invite link)`)) } else { message = prefix(defaultEmbeddedContentMessage) diff --git a/src/components/dms/getReactionInfo.ts b/src/components/dms/getReactionInfo.ts index 307eebdea7..477c44fbc3 100644 --- a/src/components/dms/getReactionInfo.ts +++ b/src/components/dms/getReactionInfo.ts @@ -1,10 +1,10 @@ -import {ChatBskyConvoDefs} from '@atproto/api' import {type I18n} from '@lingui/core' import {msg} from '@lingui/core/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' import {isDidBlockedInConvo} from '#/components/dms/getMessageInfo' -import type * as bsky from '#/types/bsky' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' export type UserReactionInfo = { message: string @@ -18,12 +18,17 @@ export function getReactionInfo({ primaryProfile, i18n, }: { - convo: ChatBskyConvoDefs.ConvoView + convo: chat.bsky.convo.defs.ConvoView currentAccountDid: string | undefined primaryProfile?: bsky.profile.AnyProfileView i18n: I18n }): UserReactionInfo | null { - if (!ChatBskyConvoDefs.isMessageAndReactionView(convo.lastReaction)) { + if ( + !bsky.isType( + chat.bsky.convo.defs.messageAndReactionView, + convo.lastReaction, + ) + ) { return null } diff --git a/src/components/dms/getSystemMessageInfo.ts b/src/components/dms/getSystemMessageInfo.ts index 756f178566..2262a3eb22 100644 --- a/src/components/dms/getSystemMessageInfo.ts +++ b/src/components/dms/getSystemMessageInfo.ts @@ -1,4 +1,3 @@ -import {type ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api' import {type MessageDescriptor} from '@lingui/core' import {msg} from '@lingui/core/macro' @@ -15,11 +14,13 @@ import { Unlock_Stroke2_Corner2_Rounded as UnlockIcon, } from '#/components/icons/Lock' import {PencilLine_Stroke2_Corner0_Rounded as PencilIcon} from '#/components/icons/Pencil' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' export type SystemMessageAction = | { kind: 'profile' - profile: ChatBskyActorDefs.ProfileViewBasic + profile: chat.bsky.actor.defs.ProfileViewBasic displayName: string } | {kind: 'inviteLink'} @@ -31,8 +32,8 @@ export type SystemMessageInfo = { } function getProfileAction( - user: ChatBskyConvoDefs.SystemMessageReferredUser, - relatedProfiles: Map, + user: chat.bsky.convo.defs.SystemMessageReferredUser, + relatedProfiles: Map, ): Extract | null { const profile = relatedProfiles.get(user.did) if (!profile) return null @@ -44,11 +45,11 @@ function getProfileAction( } export function getSystemMessageInfo( - data: ChatBskyConvoDefs.SystemMessageView['data'], - relatedProfiles: Map, + data: chat.bsky.convo.defs.SystemMessageView['data'], + relatedProfiles: Map, opts = {short: false}, ): SystemMessageInfo | null { - if (ChatBskyConvoDefs.isSystemMessageDataAddMember(data)) { + if (bsky.isType(chat.bsky.convo.defs.systemMessageDataAddMember, data)) { const action = getProfileAction(data.member, relatedProfiles) return { Icon: JoinIcon, @@ -61,7 +62,9 @@ export function getSystemMessageInfo( : msg`Someone was added to the group`, action: action ?? undefined, } - } else if (ChatBskyConvoDefs.isSystemMessageDataRemoveMember(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataRemoveMember, data) + ) { const action = getProfileAction(data.member, relatedProfiles) return { Icon: LeaveIcon, @@ -74,7 +77,9 @@ export function getSystemMessageInfo( : msg`Someone was removed from the group`, action: action ?? undefined, } - } else if (ChatBskyConvoDefs.isSystemMessageDataMemberJoin(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataMemberJoin, data) + ) { const action = getProfileAction(data.member, relatedProfiles) return { Icon: JoinIcon, @@ -87,7 +92,9 @@ export function getSystemMessageInfo( : msg`Someone joined the group`, action: action ?? undefined, } - } else if (ChatBskyConvoDefs.isSystemMessageDataMemberLeave(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataMemberLeave, data) + ) { const action = getProfileAction(data.member, relatedProfiles) return { Icon: LeaveIcon, @@ -100,13 +107,24 @@ export function getSystemMessageInfo( : msg`Someone left the group`, action: action ?? undefined, } - } else if (ChatBskyConvoDefs.isSystemMessageDataLockConvo(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataLockConvo, data) + ) { return {Icon: LockIcon, message: msg`Chat locked`} - } else if (ChatBskyConvoDefs.isSystemMessageDataUnlockConvo(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataUnlockConvo, data) + ) { return {Icon: UnlockIcon, message: msg`Chat unlocked`} - } else if (ChatBskyConvoDefs.isSystemMessageDataLockConvoPermanently(data)) { + } else if ( + bsky.isType( + chat.bsky.convo.defs.systemMessageDataLockConvoPermanently, + data, + ) + ) { return {Icon: LockIcon, message: msg`Chat ended`} - } else if (ChatBskyConvoDefs.isSystemMessageDataEditGroup(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataEditGroup, data) + ) { return { Icon: PencilIcon, message: @@ -114,25 +132,33 @@ export function getSystemMessageInfo( ? msg`Chat title changed to ${data.newName}` : msg`Chat title changed`, } - } else if (ChatBskyConvoDefs.isSystemMessageDataCreateJoinLink(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataCreateJoinLink, data) + ) { return { Icon: ChainLinkIcon, message: msg`Invite link created`, action: {kind: 'inviteLink'}, } - } else if (ChatBskyConvoDefs.isSystemMessageDataEditJoinLink(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataEditJoinLink, data) + ) { return { Icon: ChainLinkIcon, message: msg`Invite link edited`, action: {kind: 'inviteLink'}, } - } else if (ChatBskyConvoDefs.isSystemMessageDataEnableJoinLink(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataEnableJoinLink, data) + ) { return { Icon: ChainLinkIcon, message: msg`Invite link enabled`, action: {kind: 'inviteLink'}, } - } else if (ChatBskyConvoDefs.isSystemMessageDataDisableJoinLink(data)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.systemMessageDataDisableJoinLink, data) + ) { return { Icon: ChainLinkBrokenIcon, message: msg`Invite link disabled`, diff --git a/src/components/dms/replyPreview.ts b/src/components/dms/replyPreview.ts index 53c78b2c46..e33dd75bae 100644 --- a/src/components/dms/replyPreview.ts +++ b/src/components/dms/replyPreview.ts @@ -1,13 +1,8 @@ -import { - AppBskyEmbedExternal, - AppBskyEmbedRecord, - type ChatBskyConvoDefs, - ChatBskyEmbedJoinLink, - ChatBskyGroupDefs, -} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {BSKY_APP_HOST, toShortUrl} from '#/lib/strings/url-helpers' +import {app, chat} from '#/lexicons' +import * as bsky from '#/types/bsky' /** * Describes the embed of a quoted message that has no text of its own, so the @@ -21,13 +16,13 @@ type ReplyEmbedSummary = | {type: 'unknown'} function summarizeReplyEmbed( - embed: ChatBskyConvoDefs.MessageView['embed'], + embed: chat.bsky.convo.defs.MessageView['embed'], ): ReplyEmbedSummary { - if (!AppBskyEmbedRecord.isView(embed)) return {type: 'unknown'} + if (!bsky.isType(app.bsky.embed.record.view, embed)) return {type: 'unknown'} const {record} = embed - if (AppBskyEmbedRecord.isViewRecord(record)) { + if (bsky.isType(app.bsky.embed.record.viewRecord, record)) { const inner = record.embeds?.[0] - if (AppBskyEmbedExternal.isView(inner)) { + if (bsky.isType(app.bsky.embed.external.view, inner)) { return {type: 'external', uri: inner.external.uri} } return {type: 'post'} @@ -45,25 +40,32 @@ function summarizeReplyEmbed( * callers can render it in a muted/italic style. */ export function useReplyPreviewText(): ( - message: ChatBskyConvoDefs.MessageView, + message: chat.bsky.convo.defs.MessageView, ) => {text: string; subtle: boolean} { const {t: l} = useLingui() - return (message: ChatBskyConvoDefs.MessageView) => { + return (message: chat.bsky.convo.defs.MessageView) => { const text = message.text if (text.trim()) { return {text, subtle: false} } - if (ChatBskyEmbedJoinLink.isView(message.embed)) { + if (bsky.isType(chat.bsky.embed.joinLink.view, message.embed)) { const {joinLinkPreview} = message.embed - if (ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview)) { + if ( + bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, joinLinkPreview) + ) { return { text: `${BSKY_APP_HOST}/chat/${joinLinkPreview.code}`, subtle: true, } } - if (ChatBskyGroupDefs.isDisabledJoinLinkPreviewView(joinLinkPreview)) { + if ( + bsky.isType( + chat.bsky.group.defs.disabledJoinLinkPreviewView, + joinLinkPreview, + ) + ) { return { text: l({ message: '(disabled chat invite link)', @@ -72,7 +74,12 @@ export function useReplyPreviewText(): ( subtle: true, } } - if (ChatBskyGroupDefs.isInvalidJoinLinkPreviewView(joinLinkPreview)) { + if ( + bsky.isType( + chat.bsky.group.defs.invalidJoinLinkPreviewView, + joinLinkPreview, + ) + ) { return { text: l({ message: '(invalid chat invite link)', diff --git a/src/components/dms/util.ts b/src/components/dms/util.ts index 7347d1512d..135fb0dfaf 100644 --- a/src/components/dms/util.ts +++ b/src/components/dms/util.ts @@ -1,14 +1,14 @@ -import {type $Typed, ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {type $Typed} from '@atproto/lex' +import {moderateProfile, 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' import {platform} from '#/alf' import {type ReportSubject} from '#/components/moderation/ReportDialog/types' +import {chat} from '#/lexicons' import * as bsky from '#/types/bsky' export const MESSAGE_GAP_THRESHOLD_MS = 60 * 60 * 1000 @@ -77,7 +77,7 @@ export function localDateString(date: Date) { } export function hasAlreadyReacted( - message: ChatBskyConvoDefs.MessageView, + message: chat.bsky.convo.defs.MessageView, myDid: string | undefined, emoji: string, ): boolean { @@ -98,9 +98,9 @@ export function hasAlreadyReacted( * already render anonymously ("Someone reacted"). */ export function filterBlockedReactions( - reactions: ChatBskyConvoDefs.ReactionView[] | undefined, - relatedProfiles: Map, -): ChatBskyConvoDefs.ReactionView[] { + reactions: chat.bsky.convo.defs.ReactionView[] | undefined, + relatedProfiles: Map, +): chat.bsky.convo.defs.ReactionView[] { if (!reactions) return [] return reactions.filter(reaction => { const profile = relatedProfiles.get(reaction.sender.did) @@ -109,7 +109,7 @@ export function filterBlockedReactions( } export function hasReachedReactionLimit( - message: ChatBskyConvoDefs.MessageView, + message: chat.bsky.convo.defs.MessageView, myDid: string | undefined, ): boolean { if (!message.reactions) { @@ -170,25 +170,25 @@ export function canReact({ return true } -export type GroupConvoMember = ChatBskyActorDefs.ProfileViewBasic & { +export type GroupConvoMember = chat.bsky.actor.defs.ProfileViewBasic & { // can be missing if account deleted - kind?: $Typed + kind?: $Typed } -export type DirectConvoMember = ChatBskyActorDefs.ProfileViewBasic & { - kind: $Typed +export type DirectConvoMember = chat.bsky.actor.defs.ProfileViewBasic & { + kind: $Typed } -export type ConvoWithDetails = {view: ChatBskyConvoDefs.ConvoView} & ( +export type ConvoWithDetails = {view: chat.bsky.convo.defs.ConvoView} & ( | { kind: 'group' - details: $Typed + details: $Typed primaryMember?: GroupConvoMember // the owner - may have left, thus optional members: Array } | { kind: 'direct' - details: $Typed + details: $Typed primaryMember: DirectConvoMember // the other user members: Array } @@ -199,24 +199,14 @@ export type ConvoWithDetails = {view: ChatBskyConvoDefs.ConvoView} & ( * and enforces the correct type for convo members. */ export function parseConvoView( - convoView: ChatBskyConvoDefs.ConvoView, + convoView: chat.bsky.convo.defs.ConvoView, ownDid: string | undefined, ): ConvoWithDetails | null { - if ( - bsky.dangerousIsType( - convoView.kind, - ChatBskyConvoDefs.isGroupConvo, - ) - ) { + if (bsky.isType(chat.bsky.convo.defs.groupConvo, convoView.kind)) { let owner: GroupConvoMember | undefined = undefined for (const member of convoView.members) { - if ( - bsky.dangerousIsType( - member.kind, - ChatBskyActorDefs.isGroupConvoMember, - ) - ) { + if (bsky.isType(chat.bsky.actor.defs.groupConvoMember, member.kind)) { if (member.kind.role === 'owner') { // have to do a type assertion here // this works: {...member, kind: member.kind} @@ -238,12 +228,7 @@ export function parseConvoView( primaryMember: owner, members: convoView.members as Array, } - } else if ( - bsky.dangerousIsType( - convoView.kind, - ChatBskyConvoDefs.isDirectConvo, - ) - ) { + } else if (bsky.isType(chat.bsky.convo.defs.directConvo, convoView.kind)) { const otherUser = convoView.members.find(m => m.did !== ownDid) if (!otherUser) { @@ -286,7 +271,7 @@ export function getConvoReportSubject( const lastMessage = convo.view.lastMessage const reportableMessage = - ChatBskyConvoDefs.isMessageView(lastMessage) && + bsky.isType(chat.bsky.convo.defs.messageView, lastMessage) && lastMessage.sender?.did !== ownDid ? lastMessage : null diff --git a/src/components/feeds/PostFeedVideoGridRow.tsx b/src/components/feeds/PostFeedVideoGridRow.tsx index 53bc99d2f0..54c79c0afe 100644 --- a/src/components/feeds/PostFeedVideoGridRow.tsx +++ b/src/components/feeds/PostFeedVideoGridRow.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {AppBskyEmbedVideo} from '@atproto/api' import {type FeedPostSliceItem} from '#/state/queries/post-feed' import {type VideoFeedSourceContext} from '#/screens/VideoFeed/types' @@ -10,6 +9,8 @@ import { VideoPostCardPlaceholder, } from '#/components/VideoPostCard' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' export function PostFeedVideoGridRow({ items: slices, @@ -21,7 +22,7 @@ export function PostFeedVideoGridRow({ const ax = useAnalytics() const gutters = useGutters(['base', 'base', 0, 'base']) const posts = slices - .filter(slice => AppBskyEmbedVideo.isView(slice.post.embed)) + .filter(slice => bsky.isType(app.bsky.embed.video.view, slice.post.embed)) .map(slice => ({ post: slice.post, moderation: slice.moderation, diff --git a/src/components/images/AutoSizedImage.tsx b/src/components/images/AutoSizedImage.tsx index fdc7d1988a..0842d2ebde 100644 --- a/src/components/images/AutoSizedImage.tsx +++ b/src/components/images/AutoSizedImage.tsx @@ -5,7 +5,6 @@ import Animated, { useAnimatedRef, } from 'react-native-reanimated' import {Image} from 'expo-image' -import {type AppBskyEmbedImages} from '@atproto/api' import {utils} from '@bsky.app/alf' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -18,6 +17,7 @@ import {ArrowsDiagonalOut_Stroke2_Corner0_Rounded as Fullscreen} from '#/compone import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' export function ConstrainedImage({ aspectRatio, @@ -71,7 +71,7 @@ export function AutoSizedImage({ onContainerRef, onDimsChange, }: { - image: AppBskyEmbedImages.ViewImage + image: app.bsky.embed.images.ViewImage crop?: 'none' | 'square' | 'constrained' onPress?: ( containerRef: AnimatedRef, diff --git a/src/components/images/Gallery/index.tsx b/src/components/images/Gallery/index.tsx index c87957503f..aad2f86b29 100644 --- a/src/components/images/Gallery/index.tsx +++ b/src/components/images/Gallery/index.tsx @@ -14,7 +14,6 @@ import Animated, { useAnimatedRef, } from 'react-native-reanimated' import {Image} from 'expo-image' -import {type AppBskyEmbedImages} from '@atproto/api' import {utils} from '@bsky.app/alf' import {Trans, useLingui} from '@lingui/react/macro' import debounce from 'lodash.debounce' @@ -41,12 +40,13 @@ import {PostEmbedViewContext} from '#/components/Post/Embed/types' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_ANDROID, IS_WEB} from '#/env' +import {type app} from '#/lexicons' export * from './const' export * from './maybeApplyGalleryOffsetStyles' interface GalleryProps { - images: AppBskyEmbedImages.ViewImage[] + images: app.bsky.embed.images.ViewImage[] onPress?: ( index: number, containerRefs: AnimatedRef[], @@ -404,7 +404,7 @@ function GalleryImage({ onPreviewPress, }: { contentHeight: number - image: AppBskyEmbedImages.ViewImage + image: app.bsky.embed.images.ViewImage index: number imageCount: number onWidthChange: (index: number, width: number) => void diff --git a/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts b/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts index 5989497c12..75f5421257 100644 --- a/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts +++ b/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts @@ -1,15 +1,9 @@ -import { - AppBskyEmbedGallery, - AppBskyEmbedImages, - AppBskyEmbedRecordWithMedia, - type AppBskyFeedDefs, - AppBskyFeedPost, -} from '@atproto/api' import {type ModerationCause, type ModerationUI} from '@bsky.app/sdk/moderation' import {unique} from '#/lib/moderation' import {type AppModerationCause} from '#/components/Pills' import {Features, features} from '#/analytics/features' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' export const POST_META_NO_CONTENT_OFFSET = {paddingTop: 10} @@ -22,17 +16,12 @@ export function maybeApplyGalleryOffsetStyles( modui, additionalCauses, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView modui: ModerationUI additionalCauses?: ModerationCause[] | AppModerationCause[] }, ) { - if ( - !bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) - ) { + if (!bsky.isType(app.bsky.feed.post, post.record)) { return } @@ -47,24 +36,11 @@ export function maybeApplyGalleryOffsetStyles( * First check if we even have images */ const embed = post.record.embed - const isImageEmbed = - embed && - bsky.dangerousIsType( - embed, - AppBskyEmbedImages.isMain, - ) + const isImageEmbed = embed && bsky.isType(app.bsky.embed.images.main, embed) const isGalleryEmbed = - embed && - bsky.dangerousIsType( - embed, - AppBskyEmbedGallery.isMain, - ) + embed && bsky.isType(app.bsky.embed.gallery.main, embed) const isRecordWithMedia = - embed && - bsky.dangerousIsType( - embed, - AppBskyEmbedRecordWithMedia.isMain, - ) + embed && bsky.isType(app.bsky.embed.recordWithMedia.main, embed) let hasImages = false if (isImageEmbed) { if (!isPostGalleryEmbedEnabled) return @@ -78,23 +54,13 @@ export function maybeApplyGalleryOffsetStyles( hasImages = true } if (isRecordWithMedia) { - if ( - bsky.dangerousIsType( - embed.media, - AppBskyEmbedImages.isMain, - ) - ) { + if (bsky.isType(app.bsky.embed.images.main, embed.media)) { if (!isPostGalleryEmbedEnabled) return // one image, not a gallery if (embed.media.images.length === 1) return hasImages = true } - if ( - bsky.dangerousIsType( - embed.media, - AppBskyEmbedGallery.isMain, - ) - ) { + if (bsky.isType(app.bsky.embed.gallery.main, embed.media)) { // single (or empty) gallery - no offset needed if (embed.media.items.length <= 1) return hasImages = true diff --git a/src/components/images/ImageLayoutGrid.tsx b/src/components/images/ImageLayoutGrid.tsx index f69de63bd1..e01e853447 100644 --- a/src/components/images/ImageLayoutGrid.tsx +++ b/src/components/images/ImageLayoutGrid.tsx @@ -1,15 +1,15 @@ import {useRef} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' import {type AnimatedRef, useAnimatedRef} from 'react-native-reanimated' -import {type AppBskyEmbedImages} from '@atproto/api' import {atoms as a, useBreakpoints} from '#/alf' import {type Dimensions} from '#/components/Lightbox/types' import {type PostEmbedViewContext} from '#/components/Post/Embed/types' +import {type app} from '#/lexicons' import {GalleryItem} from './ImageLayoutGridItem' interface ImageLayoutGridProps { - images: AppBskyEmbedImages.ViewImage[] + images: app.bsky.embed.images.ViewImage[] onPress?: ( index: number, containerRefs: AnimatedRef[], @@ -45,7 +45,7 @@ export function ImageLayoutGrid({ } interface ImageLayoutGridInnerProps { - images: AppBskyEmbedImages.ViewImage[] + images: app.bsky.embed.images.ViewImage[] onPress?: ( index: number, containerRefs: AnimatedRef[], diff --git a/src/components/images/ImageLayoutGridItem.tsx b/src/components/images/ImageLayoutGridItem.tsx index 8a3202df37..69f3c7a5bf 100644 --- a/src/components/images/ImageLayoutGridItem.tsx +++ b/src/components/images/ImageLayoutGridItem.tsx @@ -1,7 +1,6 @@ import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native' import {type AnimatedRef} from 'react-native-reanimated' import {Image, type ImageStyle} from 'expo-image' -import {type AppBskyEmbedImages} from '@atproto/api' import {utils} from '@bsky.app/alf' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -14,11 +13,12 @@ import {MediaInsetBorder} from '#/components/MediaInsetBorder' import {ImageContextMenu} from '#/components/Post/Embed/ImageContextMenu' import {type PostEmbedViewContext} from '#/components/Post/Embed/types' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' type EventFunction = (index: number) => void interface Props { - images: AppBskyEmbedImages.ViewImage[] + images: app.bsky.embed.images.ViewImage[] index: number onPress?: ( index: number, diff --git a/src/components/intents/GroupChatJoinDialog.tsx b/src/components/intents/GroupChatJoinDialog.tsx index b2ec975503..c698219a43 100644 --- a/src/components/intents/GroupChatJoinDialog.tsx +++ b/src/components/intents/GroupChatJoinDialog.tsx @@ -1,12 +1,11 @@ import {useEffect} from 'react' import {View} from 'react-native' -import {ChatBskyGroupDefs} from '@atproto/api' +import {moderateProfile} from '@bsky.app/sdk/moderation' 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' @@ -47,6 +46,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {ProfileBadges} from '../ProfileBadges' export function GroupChatJoinDialog() { @@ -163,7 +163,10 @@ function GroupChatJoinDialogContent({code}: {code?: string}) { errorMessage = l`The member limit has been reached.` const preview = data?.joinLinkPreviews[0] if ( - ChatBskyGroupDefs.isJoinLinkPreviewView(preview) && + bsky.isType( + chat.bsky.group.defs.joinLinkPreviewView, + preview, + ) && preview.convo?.id ) { ax.metric('groupchat:join:memberLimitReached', { @@ -254,7 +257,7 @@ function GroupChatJoinDialogContent({code}: {code?: string}) { const joinLinkPreview = data.joinLinkPreviews[0] - if (!ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview)) { + if (!bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, joinLinkPreview)) { return ( <> diff --git a/src/components/interstitials/FeedTrendingTopics.tsx b/src/components/interstitials/FeedTrendingTopics.tsx index a50b8652d3..a069a1b851 100644 --- a/src/components/interstitials/FeedTrendingTopics.tsx +++ b/src/components/interstitials/FeedTrendingTopics.tsx @@ -1,9 +1,8 @@ import {useMemo} from 'react' import {Pressable, View} from 'react-native' -import {type AppBskyUnspeccedDefs} from '@atproto/api' +import {moderateProfile} from '@bsky.app/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' -import {moderateProfile} from '#/lib/moderation/subjects' import {useModerationOpts} from '#/state/preferences/moderation-opts' import { useTrendingSettings, @@ -30,6 +29,7 @@ import {SubtleHover} from '#/components/SubtleHover' import {useTrendingTopicSeen} from '#/components/TrendingTopics' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' const TOPIC_COUNT = 3 @@ -183,7 +183,7 @@ function TrendRow({ recId, onPress, }: ViewStyleProp & { - trend: AppBskyUnspeccedDefs.TrendView + trend: app.bsky.unspecced.defs.TrendView rank: number feedSliceIndex: number recId?: string @@ -291,7 +291,7 @@ function TrendingTopicRowSkeleton({rank}: {rank: number}) { } function useModerateTrendingActors( - actors: AppBskyUnspeccedDefs.TrendView['actors'], + actors: app.bsky.unspecced.defs.TrendView['actors'], ) { const moderationOpts = useModerationOpts() diff --git a/src/components/interstitials/TrendingVideos.tsx b/src/components/interstitials/TrendingVideos.tsx index d3047001a4..c719f6c66c 100644 --- a/src/components/interstitials/TrendingVideos.tsx +++ b/src/components/interstitials/TrendingVideos.tsx @@ -1,6 +1,6 @@ import {useCallback, useEffect, useMemo} from 'react' import {ScrollView, View} from 'react-native' -import {AppBskyEmbedVideo, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {Trans, useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' @@ -21,6 +21,8 @@ import { CompactVideoPostCardPlaceholder, } from '#/components/VideoPostCard' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' const CARD_WIDTH = 108 @@ -153,7 +155,7 @@ function VideoCards({ .flatMap(page => page.slices) .map(slice => slice.items[0]) .filter(Boolean) - .filter(item => AppBskyEmbedVideo.isView(item.post.embed)) + .filter(item => bsky.isType(app.bsky.embed.video.view, item.post.embed)) .slice(0, 8) }, [data]) diff --git a/src/components/moderation/AppealForm.tsx b/src/components/moderation/AppealForm.tsx index 606e8162ec..f40740f179 100644 --- a/src/components/moderation/AppealForm.tsx +++ b/src/components/moderation/AppealForm.tsx @@ -1,6 +1,5 @@ import {useState} from 'react' import {View} from 'react-native' -import {type ComAtprotoLabelDefs, ToolsOzoneReportDefs} from '@atproto/api' import {XrpcResponseError} from '@atproto/lex' import {type AtUriString, type DidString} from '@atproto/syntax' import {msg} from '@lingui/core/macro' @@ -23,14 +22,14 @@ import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {IS_ANDROID} from '#/env' -import {com} from '#/lexicons' +import {com, tools} from '#/lexicons' export function AppealForm({ label, control, onPressBack, }: { - label: ComAtprotoLabelDefs.Label + label: com.atproto.label.defs.Label control: Dialog.DialogOuterProps['control'] onPressBack: () => void }) { @@ -51,7 +50,7 @@ export function AppealForm({ await client.call( com.atproto.moderation.createReport, { - reasonType: ToolsOzoneReportDefs.REASONAPPEAL, + reasonType: tools.ozone.report.defs.reasonAppeal.value, /* * `useLabelSubject` derives one shape or the other from the label's * `cid`: an at-uri plus cid for a record, or the label's `uri` reused @@ -70,7 +69,7 @@ export function AppealForm({ reason: details, }, // the appeal goes to the labeler that applied the label - {service: `${label.src as DidString}#atproto_labeler`}, + {service: `${label.src}#atproto_labeler`}, ) }, onError: err => { diff --git a/src/components/moderation/BlockDialog.tsx b/src/components/moderation/BlockDialog.tsx index d576e071d7..b49f6444c6 100644 --- a/src/components/moderation/BlockDialog.tsx +++ b/src/components/moderation/BlockDialog.tsx @@ -1,6 +1,5 @@ import {useState} from 'react' import {View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' @@ -27,7 +26,7 @@ import {Text} from '#/components/Typography' import {chat} from '#/lexicons' import {type AnyProfileView} from '#/types/bsky/profile' -type Item = ChatBskyConvoDefs.ConvoView +type Item = chat.bsky.convo.defs.ConvoView type BlockDialogProps = { control: DialogControlProps @@ -252,7 +251,7 @@ function MutualGroupChat({ onOptimisticallyRemoveConvo, onRestoreConvo, }: { - view: ChatBskyConvoDefs.ConvoView + view: chat.bsky.convo.defs.ConvoView profileDid: string currentConvoId?: string onOptimisticallyRemoveConvo: (convoId: string) => void diff --git a/src/components/moderation/LabelsOnMe.tsx b/src/components/moderation/LabelsOnMe.tsx index 85cb1e4a3a..ee56d1bae2 100644 --- a/src/components/moderation/LabelsOnMe.tsx +++ b/src/components/moderation/LabelsOnMe.tsx @@ -1,5 +1,4 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' -import {type ComAtprotoLabelDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural} from '@lingui/react/macro' @@ -18,13 +17,14 @@ import { LabelsOnMeDialog, useLabelsOnMeDialogControl, } from '#/components/moderation/LabelsOnMeDialog' +import {type com} from '#/lexicons' export function LabelsOnMe({ labels, size, style, }: { - labels: ComAtprotoLabelDefs.Label[] | undefined + labels: com.atproto.label.defs.Label[] | undefined size?: ButtonSize style?: StyleProp }) { diff --git a/src/components/moderation/LabelsOnMeDialog.tsx b/src/components/moderation/LabelsOnMeDialog.tsx index 963e4b762a..a12e129925 100644 --- a/src/components/moderation/LabelsOnMeDialog.tsx +++ b/src/components/moderation/LabelsOnMeDialog.tsx @@ -1,6 +1,5 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' -import {type ComAtprotoLabelDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -17,13 +16,14 @@ import * as Dialog from '#/components/Dialog' import {InlineLinkText} from '#/components/Link' import {AppealForm} from '#/components/moderation/AppealForm' import {Text} from '#/components/Typography' +import {type com} from '#/lexicons' import {Divider} from '../Divider' export {useDialogControl as useLabelsOnMeDialogControl} from '#/components/Dialog' export interface LabelsOnMeDialogProps { control: Dialog.DialogOuterProps['control'] - labels: ComAtprotoLabelDefs.Label[] + labels: com.atproto.label.defs.Label[] /** * Whether the labels are being shown on the user's account or on a post. * With `content`, the list may include account-level labels, which get a @@ -47,7 +47,7 @@ function LabelsOnMeDialogInner(props: LabelsOnMeDialogProps) { const {_} = useLingui() const {currentAccount} = useSession() const [appealingLabel, setAppealingLabel] = useState< - ComAtprotoLabelDefs.Label | undefined + com.atproto.label.defs.Label | undefined >(undefined) const {labels} = props const isAccount = props.type === 'account' @@ -118,7 +118,7 @@ function Label({ control, onPressAppeal, }: { - label: ComAtprotoLabelDefs.Label + label: com.atproto.label.defs.Label isSelfLabel: boolean /** * Call out that the label applies to the whole account, for contexts that @@ -126,7 +126,7 @@ function Label({ */ showAccountCallout?: boolean control: Dialog.DialogOuterProps['control'] - onPressAppeal: (label: ComAtprotoLabelDefs.Label) => void + onPressAppeal: (label: com.atproto.label.defs.Label) => void }) { const t = useTheme() const {_} = useLingui() diff --git a/src/components/moderation/PostAlerts.tsx b/src/components/moderation/PostAlerts.tsx index 4aa3c92a6c..95f4c0e56e 100644 --- a/src/components/moderation/PostAlerts.tsx +++ b/src/components/moderation/PostAlerts.tsx @@ -1,5 +1,4 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' -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' @@ -16,6 +15,7 @@ import { useLabelsOnMeDialogControl, } from '#/components/moderation/LabelsOnMeDialog' import * as Pills from '#/components/Pills' +import {type app, type com} from '#/lexicons' export function PostAlerts({ post, @@ -24,7 +24,7 @@ export function PostAlerts({ style, additionalCauses, }: { - post?: AppBskyFeedDefs.PostView + post?: app.bsky.feed.defs.PostView modui: ModerationUI /** * Expanded views (e.g. the thread anchor post) render larger pills and @@ -48,7 +48,7 @@ export function PostAlerts({ * author's only entry point to appeal labels on their content. */ const isOwnPost = !!post && post.author.did === currentAccount?.did - const allLabels: ComAtprotoLabelDefs.Label[] = + const allLabels: com.atproto.label.defs.Label[] = isOwnPost && view === 'expanded' ? [ ...(post.labels ?? []), @@ -134,7 +134,7 @@ function AdditionalLabels({ size, hasPrecedingPills, }: { - labels: ComAtprotoLabelDefs.Label[] + labels: com.atproto.label.defs.Label[] size?: Pills.CommonProps['size'] /** * The compact "+n" syntax only makes sense as a continuation of other diff --git a/src/components/moderation/PostHider.tsx b/src/components/moderation/PostHider.tsx index 54d4acf945..874345e084 100644 --- a/src/components/moderation/PostHider.tsx +++ b/src/components/moderation/PostHider.tsx @@ -7,7 +7,6 @@ import { View, type ViewStyle, } from 'react-native' -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' @@ -25,13 +24,14 @@ import { useModerationDetailsDialogControl, } from '#/components/moderation/ModerationDetailsDialog' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' interface Props extends React.ComponentProps { disabled: boolean iconSize: number iconStyles: StyleProp modui: ModerationUI - profile: AppBskyActorDefs.ProfileViewBasic + profile: app.bsky.actor.defs.ProfileViewBasic interpretFilterAsBlur?: boolean hiderStyle?: StyleProp } diff --git a/src/components/moderation/ReportDialog/action.ts b/src/components/moderation/ReportDialog/action.ts index 6113a280cb..c22a93fce9 100644 --- a/src/components/moderation/ReportDialog/action.ts +++ b/src/components/moderation/ReportDialog/action.ts @@ -1,5 +1,5 @@ -import {BSKY_LABELER_DID} from '@atproto/api' import {type AtUriString, type DidString} from '@atproto/syntax' +import {api} from '@bsky.app/sdk' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useMutation} from '@tanstack/react-query' @@ -121,7 +121,7 @@ export function useSubmitReportMutation() { state.includeVideoTimestamp && videoTimestampSeconds != null && subject.type === 'post' && - labeler.creator.did === BSKY_LABELER_DID + labeler.creator.did === api.moderation.did ? {videoTimestampSeconds} : undefined diff --git a/src/components/moderation/ReportDialog/const.ts b/src/components/moderation/ReportDialog/const.ts index 727b2f90e9..34f736a86a 100644 --- a/src/components/moderation/ReportDialog/const.ts +++ b/src/components/moderation/ReportDialog/const.ts @@ -1,11 +1,8 @@ import {applicationId} from 'expo-application' -import { - ComAtprotoModerationDefs as RootReportDefs, - ToolsOzoneReportDefs as OzoneReportDefs, -} from '@atproto/api' import {type ParsedReportSubject} from '#/components/moderation/ReportDialog/types' import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env' +import {com, tools} from '#/lexicons' export const DMCA_LINK = 'https://bsky.social/about/support/copyright' export const SUPPORT_PAGE = 'https://bsky.social/about/support' @@ -31,59 +28,96 @@ export const NEW_TO_OLD_REASON_MAPPING: Record = {} * @see https://github.com/bluesky-social/atproto/blob/4c15fb47cec26060bff2e710e95869a90c9d7fdd/packages/ozone/src/mod-service/profile.ts#L16-L64 */ export const NEW_TO_OLD_REASONS_MAP: Record< - OzoneReportDefs.ReasonType, - RootReportDefs.ReasonType + tools.ozone.report.defs.ReasonType, + com.atproto.moderation.defs.ReasonType > = { - [OzoneReportDefs.REASONAPPEAL]: RootReportDefs.REASONAPPEAL, - [OzoneReportDefs.REASONOTHER]: RootReportDefs.REASONOTHER, + [tools.ozone.report.defs.reasonAppeal.value]: + com.atproto.moderation.defs.reasonAppeal.value, + [tools.ozone.report.defs.reasonOther.value]: + com.atproto.moderation.defs.reasonOther.value, - [OzoneReportDefs.REASONVIOLENCEANIMAL]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONVIOLENCETHREATS]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONVIOLENCEGRAPHICCONTENT]: - RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONVIOLENCEGLORIFICATION]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONVIOLENCEEXTREMISTCONTENT]: - RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONVIOLENCETRAFFICKING]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONVIOLENCEOTHER]: RootReportDefs.REASONVIOLATION, + [tools.ozone.report.defs.reasonViolenceAnimal.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonViolenceThreats.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonViolenceGraphicContent.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonViolenceGlorification.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonViolenceExtremistContent.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonViolenceTrafficking.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonViolenceOther.value]: + com.atproto.moderation.defs.reasonViolation.value, - [OzoneReportDefs.REASONSEXUALABUSECONTENT]: RootReportDefs.REASONSEXUAL, - [OzoneReportDefs.REASONSEXUALNCII]: RootReportDefs.REASONSEXUAL, - [OzoneReportDefs.REASONSEXUALDEEPFAKE]: RootReportDefs.REASONSEXUAL, - [OzoneReportDefs.REASONSEXUALANIMAL]: RootReportDefs.REASONSEXUAL, - [OzoneReportDefs.REASONSEXUALUNLABELED]: RootReportDefs.REASONSEXUAL, - [OzoneReportDefs.REASONSEXUALOTHER]: RootReportDefs.REASONSEXUAL, + [tools.ozone.report.defs.reasonSexualAbuseContent.value]: + com.atproto.moderation.defs.reasonSexual.value, + [tools.ozone.report.defs.reasonSexualNCII.value]: + com.atproto.moderation.defs.reasonSexual.value, + [tools.ozone.report.defs.reasonSexualDeepfake.value]: + com.atproto.moderation.defs.reasonSexual.value, + [tools.ozone.report.defs.reasonSexualAnimal.value]: + com.atproto.moderation.defs.reasonSexual.value, + [tools.ozone.report.defs.reasonSexualUnlabeled.value]: + com.atproto.moderation.defs.reasonSexual.value, + [tools.ozone.report.defs.reasonSexualOther.value]: + com.atproto.moderation.defs.reasonSexual.value, - [OzoneReportDefs.REASONCHILDSAFETYCSAM]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONCHILDSAFETYGROOM]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONCHILDSAFETYPRIVACY]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONCHILDSAFETYHARASSMENT]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONCHILDSAFETYOTHER]: RootReportDefs.REASONVIOLATION, + [tools.ozone.report.defs.reasonChildSafetyCSAM.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonChildSafetyGroom.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonChildSafetyPrivacy.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonChildSafetyHarassment.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonChildSafetyOther.value]: + com.atproto.moderation.defs.reasonViolation.value, - [OzoneReportDefs.REASONHARASSMENTTROLL]: RootReportDefs.REASONRUDE, - [OzoneReportDefs.REASONHARASSMENTTARGETED]: RootReportDefs.REASONRUDE, - [OzoneReportDefs.REASONHARASSMENTHATESPEECH]: RootReportDefs.REASONRUDE, - [OzoneReportDefs.REASONHARASSMENTDOXXING]: RootReportDefs.REASONRUDE, - [OzoneReportDefs.REASONHARASSMENTOTHER]: RootReportDefs.REASONRUDE, + [tools.ozone.report.defs.reasonHarassmentTroll.value]: + com.atproto.moderation.defs.reasonRude.value, + [tools.ozone.report.defs.reasonHarassmentTargeted.value]: + com.atproto.moderation.defs.reasonRude.value, + [tools.ozone.report.defs.reasonHarassmentHateSpeech.value]: + com.atproto.moderation.defs.reasonRude.value, + [tools.ozone.report.defs.reasonHarassmentDoxxing.value]: + com.atproto.moderation.defs.reasonRude.value, + [tools.ozone.report.defs.reasonHarassmentOther.value]: + com.atproto.moderation.defs.reasonRude.value, - [OzoneReportDefs.REASONMISLEADINGBOT]: RootReportDefs.REASONMISLEADING, - [OzoneReportDefs.REASONMISLEADINGIMPERSONATION]: - RootReportDefs.REASONMISLEADING, - [OzoneReportDefs.REASONMISLEADINGSPAM]: RootReportDefs.REASONSPAM, - [OzoneReportDefs.REASONMISLEADINGSCAM]: RootReportDefs.REASONMISLEADING, - [OzoneReportDefs.REASONMISLEADINGELECTIONS]: RootReportDefs.REASONMISLEADING, - [OzoneReportDefs.REASONMISLEADINGOTHER]: RootReportDefs.REASONMISLEADING, + [tools.ozone.report.defs.reasonMisleadingBot.value]: + com.atproto.moderation.defs.reasonMisleading.value, + [tools.ozone.report.defs.reasonMisleadingImpersonation.value]: + com.atproto.moderation.defs.reasonMisleading.value, + [tools.ozone.report.defs.reasonMisleadingSpam.value]: + com.atproto.moderation.defs.reasonSpam.value, + [tools.ozone.report.defs.reasonMisleadingScam.value]: + com.atproto.moderation.defs.reasonMisleading.value, + [tools.ozone.report.defs.reasonMisleadingElections.value]: + com.atproto.moderation.defs.reasonMisleading.value, + [tools.ozone.report.defs.reasonMisleadingOther.value]: + com.atproto.moderation.defs.reasonMisleading.value, - [OzoneReportDefs.REASONRULESITESECURITY]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONRULEPROHIBITEDSALES]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONRULEBANEVASION]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONRULEOTHER]: RootReportDefs.REASONVIOLATION, + [tools.ozone.report.defs.reasonRuleSiteSecurity.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonRuleProhibitedSales.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonRuleBanEvasion.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonRuleOther.value]: + com.atproto.moderation.defs.reasonViolation.value, - [OzoneReportDefs.REASONSELFHARMCONTENT]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONSELFHARMED]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONSELFHARMSTUNTS]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONSELFHARMSUBSTANCES]: RootReportDefs.REASONVIOLATION, - [OzoneReportDefs.REASONSELFHARMOTHER]: RootReportDefs.REASONVIOLATION, + [tools.ozone.report.defs.reasonSelfHarmContent.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonSelfHarmED.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonSelfHarmStunts.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonSelfHarmSubstances.value]: + com.atproto.moderation.defs.reasonViolation.value, + [tools.ozone.report.defs.reasonSelfHarmOther.value]: + com.atproto.moderation.defs.reasonViolation.value, } /** @@ -91,42 +125,60 @@ export const NEW_TO_OLD_REASONS_MAP: Record< * @see https://github.com/bluesky-social/proposals/tree/main/0009-mod-report-granularity#backwards-compatibility */ export const OLD_TO_NEW_REASONS_MAP: Record< - Exclude, - OzoneReportDefs.ReasonType + Exclude< + com.atproto.moderation.defs.ReasonType, + tools.ozone.report.defs.ReasonType + >, + tools.ozone.report.defs.ReasonType > = { - [RootReportDefs.REASONSPAM]: [OzoneReportDefs.REASONMISLEADINGSPAM], - [RootReportDefs.REASONVIOLATION]: [OzoneReportDefs.REASONRULEOTHER], - [RootReportDefs.REASONMISLEADING]: [OzoneReportDefs.REASONMISLEADINGOTHER], - [RootReportDefs.REASONSEXUAL]: [OzoneReportDefs.REASONSEXUALUNLABELED], - [RootReportDefs.REASONRUDE]: [OzoneReportDefs.REASONHARASSMENTOTHER], - [RootReportDefs.REASONOTHER]: [OzoneReportDefs.REASONOTHER], - [RootReportDefs.REASONAPPEAL]: [OzoneReportDefs.REASONAPPEAL], + [com.atproto.moderation.defs.reasonSpam.value]: [ + tools.ozone.report.defs.reasonMisleadingSpam.value, + ], + [com.atproto.moderation.defs.reasonViolation.value]: [ + tools.ozone.report.defs.reasonRuleOther.value, + ], + [com.atproto.moderation.defs.reasonMisleading.value]: [ + tools.ozone.report.defs.reasonMisleadingOther.value, + ], + [com.atproto.moderation.defs.reasonSexual.value]: [ + tools.ozone.report.defs.reasonSexualUnlabeled.value, + ], + [com.atproto.moderation.defs.reasonRude.value]: [ + tools.ozone.report.defs.reasonHarassmentOther.value, + ], + [com.atproto.moderation.defs.reasonOther.value]: [ + tools.ozone.report.defs.reasonOther.value, + ], + [com.atproto.moderation.defs.reasonAppeal.value]: [ + tools.ozone.report.defs.reasonAppeal.value, + ], } /** * Set of report reasons that should optionally include additional details from * the reporter. */ -export const OTHER_REPORT_REASONS: Set = new Set([ - OzoneReportDefs.REASONVIOLENCEOTHER, - OzoneReportDefs.REASONSEXUALOTHER, - OzoneReportDefs.REASONCHILDSAFETYOTHER, - OzoneReportDefs.REASONHARASSMENTOTHER, - OzoneReportDefs.REASONMISLEADINGOTHER, - OzoneReportDefs.REASONRULEOTHER, - OzoneReportDefs.REASONSELFHARMOTHER, - OzoneReportDefs.REASONOTHER, -]) +export const OTHER_REPORT_REASONS: Set = + new Set([ + tools.ozone.report.defs.reasonViolenceOther.value, + tools.ozone.report.defs.reasonSexualOther.value, + tools.ozone.report.defs.reasonChildSafetyOther.value, + tools.ozone.report.defs.reasonHarassmentOther.value, + tools.ozone.report.defs.reasonMisleadingOther.value, + tools.ozone.report.defs.reasonRuleOther.value, + tools.ozone.report.defs.reasonSelfHarmOther.value, + tools.ozone.report.defs.reasonOther.value, + ]) /** * Set of report reasons that should only be sent to Bluesky's moderation service. */ -export const BSKY_LABELER_ONLY_REPORT_REASONS: Set = +export const BSKY_LABELER_ONLY_REPORT_REASONS: Set = new Set([ - OzoneReportDefs.REASONCHILDSAFETYCSAM, - OzoneReportDefs.REASONCHILDSAFETYGROOM, - OzoneReportDefs.REASONCHILDSAFETYOTHER, - OzoneReportDefs.REASONVIOLENCEEXTREMISTCONTENT, + tools.ozone.report.defs.reasonChildSafetyCSAM.value, + tools.ozone.report.defs.reasonChildSafetyGroom.value, + tools.ozone.report.defs.reasonChildSafetyOther.value, + tools.ozone.report.defs.reasonViolenceExtremistContent.value, ]) /** diff --git a/src/components/moderation/ReportDialog/errors.test.ts b/src/components/moderation/ReportDialog/errors.test.ts index 4a469c5eb2..bef4d2a38d 100644 --- a/src/components/moderation/ReportDialog/errors.test.ts +++ b/src/components/moderation/ReportDialog/errors.test.ts @@ -1,11 +1,24 @@ -import {XRPCError} from '@atproto/api' +import {XrpcResponseError} from '@atproto/lex' +import {com} from '#/lexicons' import {classifyReportError} from './errors' +/** + * An error as the lex client builds one from a JSON error response body. The + * classifier only reads `error`, `message` and `status` back off it. + */ +function xrpcError(status: number, error: string, message: string) { + return new XrpcResponseError( + com.atproto.moderation.createReport.main, + new Response(null, {status}), + {encoding: 'application/json', body: {error, message}}, + ) +} + describe('classifyReportError', () => { it('treats account takedown as an expected rejection', () => { const result = classifyReportError( - new XRPCError( + xrpcError( 403, 'AccountTakedown', 'Report not accepted from takendown account', @@ -27,7 +40,7 @@ describe('classifyReportError', () => { it.each([ { - error: new XRPCError( + error: xrpcError( 502, 'InternalServerError', 'Failed to perform upstream request', @@ -35,11 +48,11 @@ describe('classifyReportError', () => { bucket: 'upstream-fetch', }, { - error: new XRPCError(502, 'UpstreamFailure', 'Internal Server Error'), + error: xrpcError(502, 'UpstreamFailure', 'Internal Server Error'), bucket: 'upstream-internal', }, { - error: new XRPCError( + error: xrpcError( 502, 'UpstreamFailure', 'Upstream server responded with a 502 error', @@ -47,7 +60,7 @@ describe('classifyReportError', () => { bucket: 'upstream-http-502', }, { - error: new XRPCError( + error: xrpcError( 504, 'UpstreamTimeout', 'Upstream server responded with a 504 error', @@ -64,7 +77,7 @@ describe('classifyReportError', () => { it('classifies an invalid reason type separately', () => { const result = classifyReportError( - new XRPCError( + xrpcError( 400, 'InvalidRequest', 'Invalid reason type: tools.ozone.report.defs#reasonOther', @@ -82,7 +95,7 @@ describe('classifyReportError', () => { 'separates a non-retryable upstream %i without calling it temporary', status => { const result = classifyReportError( - new XRPCError( + xrpcError( 502, 'UpstreamFailure', `Upstream server responded with a ${status} error`, diff --git a/src/components/moderation/ReportDialog/errors.ts b/src/components/moderation/ReportDialog/errors.ts index 2dc71c8d23..2edb2bfc95 100644 --- a/src/components/moderation/ReportDialog/errors.ts +++ b/src/components/moderation/ReportDialog/errors.ts @@ -1,4 +1,4 @@ -import {XRPCError} from '@atproto/api' +import {XrpcResponseError} from '@atproto/lex' import {isRetryableHttpStatus, shouldRetryError} from '#/lib/strings/errors' @@ -16,7 +16,7 @@ export type ReportErrorClassification = { } export function classifyReportError(error: unknown): ReportErrorClassification { - if (!(error instanceof XRPCError)) { + if (!(error instanceof XrpcResponseError)) { return classification('unexpected', 'unexpected', true) } diff --git a/src/components/moderation/ReportDialog/index.tsx b/src/components/moderation/ReportDialog/index.tsx index 79ac0be690..01014a0d75 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 {BSKY_LABELER_DID} from '@atproto/api' +import {api} from '@bsky.app/sdk' import {Trans, useLingui} from '@lingui/react/macro' import {wait} from '#/lib/async/wait' @@ -223,7 +223,7 @@ function Inner( .filter(l => { if (!state.selectedOption) return false if (isBskyOnlyReason || isBskyOnlySubject) { - return l.creator.did === BSKY_LABELER_DID + return l.creator.did === api.moderation.did } const supportedReasonTypes: string[] | undefined = l.reasonTypes if (supportedReasonTypes === undefined) return true @@ -644,7 +644,7 @@ function Inner( {videoTimestampSeconds !== undefined && - state.selectedLabeler?.creator.did === BSKY_LABELER_DID && ( + state.selectedLabeler?.creator.did === api.moderation.did && ( - | $Typed - | $Typed - | $Typed - | $Typed - | $Typed - | $Typed - | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed + | $Typed | ReportSubjectConvoMessage | ReportSubjectConvo diff --git a/src/components/moderation/ReportDialog/utils/parseReportSubject.ts b/src/components/moderation/ReportDialog/utils/parseReportSubject.ts index a7d4b94c32..8b03c7b02a 100644 --- a/src/components/moderation/ReportDialog/utils/parseReportSubject.ts +++ b/src/components/moderation/ReportDialog/utils/parseReportSubject.ts @@ -1,14 +1,8 @@ -import { - AppBskyActorDefs, - AppBskyFeedDefs, - AppBskyFeedPost, - AppBskyGraphDefs, -} from '@atproto/api' - import { type ParsedReportSubject, type ReportSubject, } from '#/components/moderation/ReportDialog/types' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' export function parseReportSubject( @@ -31,16 +25,16 @@ export function parseReportSubject( } if ( - AppBskyActorDefs.isProfileViewBasic(subject) || - AppBskyActorDefs.isProfileView(subject) || - AppBskyActorDefs.isProfileViewDetailed(subject) + bsky.isType(app.bsky.actor.defs.profileViewBasic, subject) || + bsky.isType(app.bsky.actor.defs.profileView, subject) || + bsky.isType(app.bsky.actor.defs.profileViewDetailed, subject) ) { return { type: 'account', did: subject.did, nsid: 'app.bsky.actor.profile', } - } else if (AppBskyActorDefs.isStatusView(subject)) { + } else if (bsky.isType(app.bsky.actor.defs.statusView, subject)) { if (!subject.uri || !subject.cid) return return { type: 'status', @@ -48,36 +42,31 @@ export function parseReportSubject( cid: subject.cid, nsid: 'app.bsky.actor.status', } - } else if (AppBskyGraphDefs.isListView(subject)) { + } else if (bsky.isType(app.bsky.graph.defs.listView, subject)) { return { type: 'list', uri: subject.uri, cid: subject.cid, nsid: 'app.bsky.graph.list', } - } else if (AppBskyFeedDefs.isGeneratorView(subject)) { + } else if (bsky.isType(app.bsky.feed.defs.generatorView, subject)) { return { type: 'feed', uri: subject.uri, cid: subject.cid, nsid: 'app.bsky.feed.generator', } - } else if (AppBskyGraphDefs.isStarterPackView(subject)) { + } else if (bsky.isType(app.bsky.graph.defs.starterPackView, subject)) { return { type: 'starterPack', uri: subject.uri, cid: subject.cid, nsid: 'app.bsky.graph.starterPack', } - } else if (AppBskyFeedDefs.isPostView(subject)) { + } else if (bsky.isType(app.bsky.feed.defs.postView, subject)) { const record = subject.record const embed = bsky.post.parseEmbed(subject.embed) - if ( - bsky.dangerousIsType( - record, - AppBskyFeedPost.isRecord, - ) - ) { + if (bsky.isType(app.bsky.feed.post, record)) { return { type: 'post', uri: subject.uri, diff --git a/src/components/moderation/ReportDialog/utils/useReportOptions.ts b/src/components/moderation/ReportDialog/utils/useReportOptions.ts index 87553ce6ed..b4a2033780 100644 --- a/src/components/moderation/ReportDialog/utils/useReportOptions.ts +++ b/src/components/moderation/ReportDialog/utils/useReportOptions.ts @@ -1,8 +1,9 @@ import {useMemo} from 'react' -import {ToolsOzoneReportDefs as OzoneReportDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' +import {tools} from '#/lexicons' + export type ReportCategory = | 'childSafety' | 'violencePhysicalHarm' @@ -22,7 +23,7 @@ export type ReportCategoryConfig = { export type ReportOption = { title: string - reason: OzoneReportDefs.ReasonType + reason: tools.ozone.report.defs.ReasonType } export function useReportOptions() { @@ -37,27 +38,27 @@ export function useReportOptions() { options: [ { title: _(msg`Spam`), - reason: OzoneReportDefs.REASONMISLEADINGSPAM, + reason: tools.ozone.report.defs.reasonMisleadingSpam.value, }, { title: _(msg`Scam`), - reason: OzoneReportDefs.REASONMISLEADINGSCAM, + reason: tools.ozone.report.defs.reasonMisleadingScam.value, }, { title: _(msg`Fake account or bot`), - reason: OzoneReportDefs.REASONMISLEADINGBOT, + reason: tools.ozone.report.defs.reasonMisleadingBot.value, }, { title: _(msg`Impersonation`), - reason: OzoneReportDefs.REASONMISLEADINGIMPERSONATION, + reason: tools.ozone.report.defs.reasonMisleadingImpersonation.value, }, { title: _(msg`False information about elections`), - reason: OzoneReportDefs.REASONMISLEADINGELECTIONS, + reason: tools.ozone.report.defs.reasonMisleadingElections.value, }, { title: _(msg`Other misleading content`), - reason: OzoneReportDefs.REASONMISLEADINGOTHER, + reason: tools.ozone.report.defs.reasonMisleadingOther.value, }, ], }, @@ -70,27 +71,27 @@ export function useReportOptions() { options: [ { title: _(msg`Unlabeled adult content`), - reason: OzoneReportDefs.REASONSEXUALUNLABELED, + reason: tools.ozone.report.defs.reasonSexualUnlabeled.value, }, { title: _(msg`Adult sexual abuse content`), - reason: OzoneReportDefs.REASONSEXUALABUSECONTENT, + reason: tools.ozone.report.defs.reasonSexualAbuseContent.value, }, { title: _(msg`Non-consensual intimate imagery`), - reason: OzoneReportDefs.REASONSEXUALNCII, + reason: tools.ozone.report.defs.reasonSexualNCII.value, }, { title: _(msg`Deepfake adult content`), - reason: OzoneReportDefs.REASONSEXUALDEEPFAKE, + reason: tools.ozone.report.defs.reasonSexualDeepfake.value, }, { title: _(msg`Animal sexual abuse`), - reason: OzoneReportDefs.REASONSEXUALANIMAL, + reason: tools.ozone.report.defs.reasonSexualAnimal.value, }, { title: _(msg`Other sexual violence content`), - reason: OzoneReportDefs.REASONSEXUALOTHER, + reason: tools.ozone.report.defs.reasonSexualOther.value, }, ], }, @@ -101,23 +102,23 @@ export function useReportOptions() { options: [ { title: _(msg`Trolling`), - reason: OzoneReportDefs.REASONHARASSMENTTROLL, + reason: tools.ozone.report.defs.reasonHarassmentTroll.value, }, { title: _(msg`Targeted harassment`), - reason: OzoneReportDefs.REASONHARASSMENTTARGETED, + reason: tools.ozone.report.defs.reasonHarassmentTargeted.value, }, { title: _(msg`Hate speech`), - reason: OzoneReportDefs.REASONHARASSMENTHATESPEECH, + reason: tools.ozone.report.defs.reasonHarassmentHateSpeech.value, }, { title: _(msg`Doxxing`), - reason: OzoneReportDefs.REASONHARASSMENTDOXXING, + reason: tools.ozone.report.defs.reasonHarassmentDoxxing.value, }, { title: _(msg`Other harassing or hateful content`), - reason: OzoneReportDefs.REASONHARASSMENTOTHER, + reason: tools.ozone.report.defs.reasonHarassmentOther.value, }, ], }, @@ -128,31 +129,32 @@ export function useReportOptions() { options: [ { title: _(msg`Animal welfare`), - reason: OzoneReportDefs.REASONVIOLENCEANIMAL, + reason: tools.ozone.report.defs.reasonViolenceAnimal.value, }, { title: _(msg`Threats or incitement`), - reason: OzoneReportDefs.REASONVIOLENCETHREATS, + reason: tools.ozone.report.defs.reasonViolenceThreats.value, }, { title: _(msg`Graphic violent content`), - reason: OzoneReportDefs.REASONVIOLENCEGRAPHICCONTENT, + reason: tools.ozone.report.defs.reasonViolenceGraphicContent.value, }, { title: _(msg`Glorification of violence`), - reason: OzoneReportDefs.REASONVIOLENCEGLORIFICATION, + reason: tools.ozone.report.defs.reasonViolenceGlorification.value, }, { title: _(msg`Extremist content`), - reason: OzoneReportDefs.REASONVIOLENCEEXTREMISTCONTENT, + reason: + tools.ozone.report.defs.reasonViolenceExtremistContent.value, }, { title: _(msg`Human trafficking`), - reason: OzoneReportDefs.REASONVIOLENCETRAFFICKING, + reason: tools.ozone.report.defs.reasonViolenceTrafficking.value, }, { title: _(msg`Other violent content`), - reason: OzoneReportDefs.REASONVIOLENCEOTHER, + reason: tools.ozone.report.defs.reasonViolenceOther.value, }, ], }, @@ -163,23 +165,23 @@ export function useReportOptions() { options: [ { title: _(msg`Child Sexual Abuse Material (CSAM)`), - reason: OzoneReportDefs.REASONCHILDSAFETYCSAM, + reason: tools.ozone.report.defs.reasonChildSafetyCSAM.value, }, { title: _(msg`Grooming or predatory behavior`), - reason: OzoneReportDefs.REASONCHILDSAFETYGROOM, + reason: tools.ozone.report.defs.reasonChildSafetyGroom.value, }, { title: _(msg`Privacy violation of a minor`), - reason: OzoneReportDefs.REASONCHILDSAFETYPRIVACY, + reason: tools.ozone.report.defs.reasonChildSafetyPrivacy.value, }, { title: _(msg`Minor harassment or bullying`), - reason: OzoneReportDefs.REASONCHILDSAFETYHARASSMENT, + reason: tools.ozone.report.defs.reasonChildSafetyHarassment.value, }, { title: _(msg`Other child safety issue`), - reason: OzoneReportDefs.REASONCHILDSAFETYOTHER, + reason: tools.ozone.report.defs.reasonChildSafetyOther.value, }, ], }, @@ -190,23 +192,23 @@ export function useReportOptions() { options: [ { title: _(msg`Content promoting or depicting self-harm`), - reason: OzoneReportDefs.REASONSELFHARMCONTENT, + reason: tools.ozone.report.defs.reasonSelfHarmContent.value, }, { title: _(msg`Eating disorders`), - reason: OzoneReportDefs.REASONSELFHARMED, + reason: tools.ozone.report.defs.reasonSelfHarmED.value, }, { title: _(msg`Dangerous challenges or activities`), - reason: OzoneReportDefs.REASONSELFHARMSTUNTS, + reason: tools.ozone.report.defs.reasonSelfHarmStunts.value, }, { title: _(msg`Dangerous substances or drug abuse`), - reason: OzoneReportDefs.REASONSELFHARMSUBSTANCES, + reason: tools.ozone.report.defs.reasonSelfHarmSubstances.value, }, { title: _(msg`Other dangerous content`), - reason: OzoneReportDefs.REASONSELFHARMOTHER, + reason: tools.ozone.report.defs.reasonSelfHarmOther.value, }, ], }, @@ -217,19 +219,19 @@ export function useReportOptions() { options: [ { title: _(msg`Hacking or system attacks`), - reason: OzoneReportDefs.REASONRULESITESECURITY, + reason: tools.ozone.report.defs.reasonRuleSiteSecurity.value, }, { title: _(msg`Promoting or selling prohibited items or services`), - reason: OzoneReportDefs.REASONRULEPROHIBITEDSALES, + reason: tools.ozone.report.defs.reasonRuleProhibitedSales.value, }, { title: _(msg`Banned user returning`), - reason: OzoneReportDefs.REASONRULEBANEVASION, + reason: tools.ozone.report.defs.reasonRuleBanEvasion.value, }, { title: _(msg`Other network rule-breaking`), - reason: OzoneReportDefs.REASONRULEOTHER, + reason: tools.ozone.report.defs.reasonRuleOther.value, }, ], }, @@ -240,7 +242,7 @@ export function useReportOptions() { options: [ { title: _(msg`Other`), - reason: OzoneReportDefs.REASONOTHER, + reason: tools.ozone.report.defs.reasonOther.value, }, ], }, diff --git a/src/components/verification/VerificationRemovePrompt.tsx b/src/components/verification/VerificationRemovePrompt.tsx index 96b119ceb1..5eeafecb3a 100644 --- a/src/components/verification/VerificationRemovePrompt.tsx +++ b/src/components/verification/VerificationRemovePrompt.tsx @@ -1,5 +1,4 @@ import {useCallback} from 'react' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -8,6 +7,7 @@ import {useVerificationsRemoveMutation} from '#/state/queries/verification/useVe import {type DialogControlProps} from '#/components/Dialog' import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' export {useDialogControl as usePromptControl} from '#/components/Dialog' @@ -20,7 +20,7 @@ export function VerificationRemovePrompt({ }: { control: DialogControlProps profile: bsky.profile.AnyProfileView - verifications: AppBskyActorDefs.VerificationView[] + verifications: app.bsky.actor.defs.VerificationView[] onConfirm?: () => void }) { const {_} = useLingui() diff --git a/src/components/verification/VerificationsDialog.tsx b/src/components/verification/VerificationsDialog.tsx index cb24b182a5..5f3562ee6e 100644 --- a/src/components/verification/VerificationsDialog.tsx +++ b/src/components/verification/VerificationsDialog.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -21,6 +20,7 @@ import {Text} from '#/components/Typography' import {type FullVerificationState} from '#/components/verification' import {VerificationRemovePrompt} from '#/components/verification/VerificationRemovePrompt' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' export {useDialogControl} from '#/components/Dialog' @@ -180,7 +180,7 @@ function VerifierCard({ subject, outerDialogControl, }: { - verification: AppBskyActorDefs.VerificationView + verification: app.bsky.actor.defs.VerificationView subject: bsky.profile.AnyProfileView outerDialogControl: Dialog.DialogControlProps }) { diff --git a/src/env/common.ts b/src/env/common.ts index 5d8d89aace..bcda2e4982 100644 --- a/src/env/common.ts +++ b/src/env/common.ts @@ -1,4 +1,4 @@ -import {type Did} from '@atproto/api' +import {type DidString} from '@atproto/syntax' import packageJson from '#/../package.json' @@ -75,14 +75,16 @@ export const LOG_DEBUG: string = process.env.EXPO_PUBLIC_LOG_DEBUG || '' /** * The DID of the Bluesky appview to proxy to */ -export const BLUESKY_PROXY_DID: Did = - process.env.EXPO_PUBLIC_BLUESKY_PROXY_DID || 'did:web:api.bsky.app' +export const BLUESKY_PROXY_DID: DidString = + (process.env.EXPO_PUBLIC_BLUESKY_PROXY_DID as DidString) || + 'did:web:api.bsky.app' /** * The DID of the chat service to proxy to */ -export const CHAT_PROXY_DID: Did = - process.env.EXPO_PUBLIC_CHAT_PROXY_DID || 'did:web:api.bsky.chat' +export const CHAT_PROXY_DID: DidString = + (process.env.EXPO_PUBLIC_CHAT_PROXY_DID as DidString) || + 'did:web:api.bsky.chat' /** * Metrics API host diff --git a/src/features/inviteFriends/urls.ts b/src/features/inviteFriends/urls.ts index 7a22b8faf4..776a8bdf16 100644 --- a/src/features/inviteFriends/urls.ts +++ b/src/features/inviteFriends/urls.ts @@ -7,7 +7,7 @@ * label simply drops the `https://` scheme for readability. * * Kept as a dependency-free leaf module (no #/lib/strings/url-helpers import) - * so its unit tests stay fast and isolated from the heavy @atproto/api graph. + * so its unit tests stay fast and isolated from the lexicon graph. */ function stripLeadingAt(handle: string): string { diff --git a/src/features/liveNow/components/EditLiveDialog.tsx b/src/features/liveNow/components/EditLiveDialog.tsx index cf913541a1..f5c5a38462 100644 --- a/src/features/liveNow/components/EditLiveDialog.tsx +++ b/src/features/liveNow/components/EditLiveDialog.tsx @@ -1,6 +1,5 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, type AppBskyEmbedExternal} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -26,6 +25,7 @@ import { useUpsertLiveStatusMutation, } from '#/features/liveNow' import {LinkPreview} from '#/features/liveNow/components/LinkPreview' +import {type app} from '#/lexicons' export function EditLiveDialog({ control, @@ -33,8 +33,8 @@ export function EditLiveDialog({ embed, }: { control: Dialog.DialogControlProps - status: AppBskyActorDefs.StatusView - embed: AppBskyEmbedExternal.View + status: app.bsky.actor.defs.StatusView + embed: app.bsky.embed.external.View }) { return ( @@ -48,14 +48,16 @@ function DialogInner({ status, embed, }: { - status: AppBskyActorDefs.StatusView - embed: AppBskyEmbedExternal.View + status: app.bsky.actor.defs.StatusView + embed: app.bsky.embed.external.View }) { const control = Dialog.useDialogContext() const {_, i18n} = useLingui() const t = useTheme() - const [liveLink, setLiveLink] = useState(embed.external.uri) + /* Holds the user-editable link text, so it is a plain string rather than + * the branded `uri` the view it was seeded from carries. */ + const [liveLink, setLiveLink] = useState(embed.external.uri) const [liveLinkError, setLiveLinkError] = useState('') const tick = useTickEveryMinute() diff --git a/src/features/liveNow/components/GoLiveDisabledDialog.tsx b/src/features/liveNow/components/GoLiveDisabledDialog.tsx index fda33a9067..3de918d0bd 100644 --- a/src/features/liveNow/components/GoLiveDisabledDialog.tsx +++ b/src/features/liveNow/components/GoLiveDisabledDialog.tsx @@ -1,7 +1,5 @@ import {useCallback, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, ToolsOzoneReportDefs} from '@atproto/api' -import {type AtUriString} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -16,14 +14,14 @@ import * as Dialog from '#/components/Dialog' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' -import {com} from '#/lexicons' +import {type app, com, tools} from '#/lexicons' export function GoLiveDisabledDialog({ control, status, }: { control: Dialog.DialogControlProps - status: AppBskyActorDefs.StatusView + status: app.bsky.actor.defs.StatusView }) { return ( @@ -38,7 +36,7 @@ export function DialogInner({ status, }: { control: Dialog.DialogControlProps - status: AppBskyActorDefs.StatusView + status: app.bsky.actor.defs.StatusView }) { const {_} = useLingui() const client = useAppviewClient() @@ -61,11 +59,11 @@ export function DialogInner({ await client.call( com.atproto.moderation.createReport, { - reasonType: ToolsOzoneReportDefs.REASONAPPEAL, + reasonType: tools.ozone.report.defs.reasonAppeal.value, subject: { $type: 'com.atproto.repo.strongRef', // a status view's uri is an at-uri produced by the appview - uri: status.uri as AtUriString, + uri: status.uri, cid: status.cid, }, reason: details, diff --git a/src/features/liveNow/components/LiveStatusDialog.tsx b/src/features/liveNow/components/LiveStatusDialog.tsx index ba69b35ff9..9559ca34fd 100644 --- a/src/features/liveNow/components/LiveStatusDialog.tsx +++ b/src/features/liveNow/components/LiveStatusDialog.tsx @@ -1,13 +1,12 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' -import {type AppBskyActorDefs, type AppBskyEmbedExternal} from '@atproto/api' +import {moderateStatus} from '@bsky.app/sdk/moderation' 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' @@ -27,6 +26,7 @@ import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {LiveIndicator} from '#/features/liveNow/components/LiveIndicator' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' export function LiveStatusDialog({ @@ -37,8 +37,8 @@ export function LiveStatusDialog({ }: { control: Dialog.DialogControlProps profile: bsky.profile.AnyProfileView - status: AppBskyActorDefs.StatusView - embed: AppBskyEmbedExternal.View + status: app.bsky.actor.defs.StatusView + embed: app.bsky.embed.external.View }) { const navigation = useNavigation() return ( @@ -61,9 +61,9 @@ function DialogInner({ status, }: { profile: bsky.profile.AnyProfileView - embed: AppBskyEmbedExternal.View + embed: app.bsky.embed.external.View navigation: NavigationProp - status: AppBskyActorDefs.StatusView + status: app.bsky.actor.defs.StatusView }) { const {t: l} = useLingui() const control = Dialog.useDialogContext() @@ -99,9 +99,9 @@ export function LiveStatus({ padding = 'xl', onPressOpenProfile, }: { - status: AppBskyActorDefs.StatusView + status: app.bsky.actor.defs.StatusView profile: bsky.profile.AnyProfileView - embed: AppBskyEmbedExternal.View + embed: app.bsky.embed.external.View padding?: 'lg' | 'xl' onPressOpenProfile: () => void }) { diff --git a/src/features/liveNow/index.tsx b/src/features/liveNow/index.tsx index ec96777e85..88daad4df7 100644 --- a/src/features/liveNow/index.tsx +++ b/src/features/liveNow/index.tsx @@ -1,12 +1,8 @@ import {useMemo} from 'react' -import { - type $Typed, - type AppBskyActorDefs, - AppBskyEmbedExternal, -} from '@atproto/api' import {retry} from '@atproto/common-web' -import {type l} from '@atproto/lex' +import {type $Typed, type l, type UriString} from '@atproto/lex' import {type AtIdentifierString, AtUri, toDatetimeString} from '@atproto/syntax' +import {moderateStatus} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' @@ -15,7 +11,6 @@ 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 { @@ -30,7 +25,7 @@ import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' import {getLiveNowHost, getLiveServiceNames} from '#/features/liveNow/utils' import {app, com} from '#/lexicons' -import type * as bsky from '#/types/bsky' +import * as bsky from '#/types/bsky' export * from '#/features/liveNow/utils' @@ -48,7 +43,7 @@ const DEFAULT_STATE = { isDisabled: false, isActive: false, record: {}, -} satisfies AppBskyActorDefs.StatusView +} satisfies app.bsky.actor.defs.StatusView export type LiveNowConfig = { canGoLive: boolean @@ -135,10 +130,10 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) { isDisabled: false, isActive: true, status: 'app.bsky.actor.status#live', - embed: shadowed.status.embed as $Typed, // temp_isStatusValid asserts this + embed: shadowed.status.embed as $Typed, // temp_isStatusValid asserts this expiresAt: shadowed.status.expiresAt!, // isStatusStillActive asserts this record: shadowed.status.record, - } satisfies AppBskyActorDefs.StatusView + } satisfies app.bsky.actor.defs.StatusView } return { uri: shadowed.status.uri, @@ -146,10 +141,10 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) { isDisabled, isActive: false, status: 'app.bsky.actor.status#live', - embed: shadowed.status.embed as $Typed, // temp_isStatusValid asserts this + embed: shadowed.status.embed as $Typed, // temp_isStatusValid asserts this expiresAt: shadowed.status.expiresAt!, // isStatusStillActive asserts this record: shadowed.status.record, - } satisfies AppBskyActorDefs.StatusView + } satisfies app.bsky.actor.defs.StatusView } else { return DEFAULT_STATE } @@ -169,14 +164,14 @@ export function isStatusStillActive(timeStr: string | undefined) { * validate if the status is valid for the acting user e.g. as they go live. */ export function isStatusValidForViewers( - status: AppBskyActorDefs.StatusView, + status: app.bsky.actor.defs.StatusView, config: LiveNowConfig, ) { if (status.status !== 'app.bsky.actor.status#live') return false if (!status.uri) return false // should not happen, just backwards compat try { const {host: liveDid} = new AtUri(status.uri) - if (AppBskyEmbedExternal.isView(status.embed)) { + if (bsky.isType(app.bsky.embed.external.view, status.embed)) { const host = getLiveNowHost(status.embed.external.uri) const exception = config.allowedHostsExceptionsByDid.get(liveDid) const isValidException = exception ? exception.has(host) : false @@ -340,7 +335,7 @@ export function useUpsertLiveStatusMutation( $type: 'app.bsky.actor.defs#statusView', status: 'app.bsky.actor.status#live', isActive: true, - expiresAt: expiresAt.toISOString(), + expiresAt: toDatetimeString(expiresAt), embed: record.embed && image ? { @@ -348,7 +343,8 @@ export function useUpsertLiveStatusMutation( external: { ...record.embed.external, $type: 'app.bsky.embed.external#viewExternal', - thumb: image, + // an opengraph image URL from link resolution + thumb: image as UriString, }, } : undefined, diff --git a/src/features/liveNow/utils.ts b/src/features/liveNow/utils.ts index a7326cd8e9..a4fb0bbf62 100644 --- a/src/features/liveNow/utils.ts +++ b/src/features/liveNow/utils.ts @@ -1,19 +1,19 @@ -import {AppBskyActorStatus, AppBskyEmbedExternal} from '@atproto/api' import {type I18n} from '@lingui/core' import {plural} from '@lingui/core/macro' import psl from 'psl' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' + /** * Validates a raw status record and returns the typed record, or null if the * value is not a valid `app.bsky.actor.status` record. */ export function getValidLiveStatusRecord( statusRecord: unknown, -): AppBskyActorStatus.Record | null { - if (!AppBskyActorStatus.isRecord(statusRecord)) return null - const validation = AppBskyActorStatus.validateRecord(statusRecord) - if (!validation.success) return null - return validation.value +): app.bsky.actor.status.Main | null { + if (!bsky.matches(app.bsky.actor.status, statusRecord)) return null + return statusRecord } /** @@ -23,7 +23,7 @@ export function getValidLiveStatusRecord( export function getLiveLinkFromStatusRecord(statusRecord: unknown): string { const record = getValidLiveStatusRecord(statusRecord) if (!record) return '' - if (!AppBskyEmbedExternal.isMain(record.embed)) return '' + if (!bsky.isType(app.bsky.embed.external.main, record.embed)) return '' return record.embed.external.uri } diff --git a/src/lib/api/__tests__/computeCid.test.ts b/src/lib/api/__tests__/computeCid.test.ts index ce0adc4a85..d5f8b50726 100644 --- a/src/lib/api/__tests__/computeCid.test.ts +++ b/src/lib/api/__tests__/computeCid.test.ts @@ -7,7 +7,6 @@ jest.unmock('multiformats/cid') jest.unmock('multiformats/hashes/hasher') -import {BlobRef} from '@atproto/api' import {CID} from 'multiformats/cid' import {computeCid} from '#/lib/api/computeCid' @@ -67,8 +66,8 @@ describe('computeCid', () => { * `{$type: 'blob', ref, mimeType, size}` with `ref` a parsed CID. The * structural lex-blob guard passes it through `prepareForHashing` * untouched and DAG-CBOR encodes its CID `ref` as a CID link. The golden - * CID below is the byte-identical value the pre-migration `BlobRef` class - * instance produced via `.ipld()`. + * CID below is the byte-identical value the pre-migration legacy blob + * class instance produced via `.ipld()`. */ const blob = { $type: 'blob' as const, @@ -81,18 +80,6 @@ describe('computeCid', () => { ) }) - it('case 2b: a legacy BlobRef instance hashes to the same CID', async () => { - /* - * The video pipeline still yields legacy `BlobRef` class instances, so - * `prepareForHashing` keeps its `instanceof` guard. Both branches must - * agree: this asserts the SAME golden CID as case 2. - */ - const blob = new BlobRef(CID.parse(BLOB_CID), 'image/jpeg', 12345) - expect(await computeCid(postWithImageBlob(blob))).toBe( - 'bafyreiem7g6vja66nebr7he4fshfnlyndyldbvle2n265oixscmepjcbii', - ) - }) - it('case 3: three-post thread chains reply StrongRef CIDs', async () => { const did = 'did:plc:abc123' const base = new Date('2024-01-01T00:00:00.000Z') diff --git a/src/lib/api/computeCid.ts b/src/lib/api/computeCid.ts index 18821793ae..edd23e5901 100644 --- a/src/lib/api/computeCid.ts +++ b/src/lib/api/computeCid.ts @@ -1,4 +1,3 @@ -import {BlobRef} from '@atproto/api' import {sha256} from 'js-sha256' import {CID} from 'multiformats/cid' import * as Hasher from 'multiformats/hashes/hasher' @@ -73,17 +72,6 @@ function prepareForHashing(v: any): any { return v } - /* - * The video pipeline still reads its blob off the legacy agent - * (`app.bsky.video.getJobStatus` in composer `state/video`), which yields a - * `BlobRef` class instance. `ipld()` gives the same IPLD shape a lex blob - * already has, so both branches hash identically. Drop this guard once the - * video client is migrated. - */ - if (v instanceof BlobRef) { - return v.ipld() - } - // Walk through arrays if (Array.isArray(v)) { let pure = true diff --git a/src/lib/api/feed-manip.ts b/src/lib/api/feed-manip.ts index 335bf28c84..cd99fc9d57 100644 --- a/src/lib/api/feed-manip.ts +++ b/src/lib/api/feed-manip.ts @@ -1,17 +1,10 @@ -import { - type AppBskyActorDefs, - AppBskyEmbedRecord, - AppBskyEmbedRecordWithMedia, - AppBskyFeedDefs, - AppBskyFeedPost, -} from '@atproto/api' - +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' import {isPostInLanguage} from '../../locale/helpers' import {FALLBACK_MARKER_POST} from './feed/home' import {type ReasonFeedSource} from './feed/types' -type FeedViewPost = AppBskyFeedDefs.FeedViewPost +type FeedViewPost = app.bsky.feed.defs.FeedViewPost export type FeedTunerFn = ( tuner: FeedTuner, @@ -20,18 +13,18 @@ export type FeedTunerFn = ( ) => FeedViewPostsSlice[] type FeedSliceItem = { - post: AppBskyFeedDefs.PostView - record: AppBskyFeedPost.Record - parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined + post: app.bsky.feed.defs.PostView + record: app.bsky.feed.post.Main + parentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined isParentBlocked: boolean isParentNotFound: boolean } type AuthorContext = { - author: AppBskyActorDefs.ProfileViewBasic - parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined - grandparentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined - rootAuthor: AppBskyActorDefs.ProfileViewBasic | undefined + author: app.bsky.actor.defs.ProfileViewBasic + parentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined + grandparentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined + rootAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined } export class FeedViewPostsSlice { @@ -53,7 +46,7 @@ export class FeedViewPostsSlice { this.isOrphan = false this.isThreadMuted = post.viewer?.threadMuted ?? false this.feedPostUri = post.uri - if (AppBskyFeedDefs.isPostView(reply?.root)) { + if (bsky.isType(app.bsky.feed.defs.postView, reply?.root)) { this.rootUri = reply.root.uri } else { this.rootUri = post.uri @@ -69,16 +62,19 @@ export class FeedViewPostsSlice { return } if ( - !AppBskyFeedPost.isRecord(post.record) || - !bsky.validate(post.record, AppBskyFeedPost.validateRecord) + !bsky.isType(app.bsky.feed.post, post.record) || + !bsky.matches(app.bsky.feed.post, post.record) ) { return } const parent = reply?.parent - const isParentBlocked = AppBskyFeedDefs.isBlockedPost(parent) - const isParentNotFound = AppBskyFeedDefs.isNotFoundPost(parent) - let parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined - if (AppBskyFeedDefs.isPostView(parent)) { + const isParentBlocked = bsky.isType(app.bsky.feed.defs.blockedPost, parent) + const isParentNotFound = bsky.isType( + app.bsky.feed.defs.notFoundPost, + parent, + ) + let parentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined + if (bsky.isType(app.bsky.feed.defs.postView, parent)) { parentAuthor = parent.author } this.items.push({ @@ -100,18 +96,18 @@ export class FeedViewPostsSlice { return } if ( - !AppBskyFeedDefs.isPostView(parent) || - !AppBskyFeedPost.isRecord(parent.record) || - !bsky.validate(parent.record, AppBskyFeedPost.validateRecord) + !bsky.isType(app.bsky.feed.defs.postView, parent) || + !bsky.isType(app.bsky.feed.post, parent.record) || + !bsky.matches(app.bsky.feed.post, parent.record) ) { this.isOrphan = true return } const root = reply.root const rootIsView = - AppBskyFeedDefs.isPostView(root) || - AppBskyFeedDefs.isBlockedPost(root) || - AppBskyFeedDefs.isNotFoundPost(root) + bsky.isType(app.bsky.feed.defs.postView, root) || + bsky.isType(app.bsky.feed.defs.blockedPost, root) || + bsky.isType(app.bsky.feed.defs.notFoundPost, root) /* * If the parent is also the root, we just so happen to have the data we * need to compute if the parent's parent (grandparent) is blocked. This @@ -124,10 +120,10 @@ export class FeedViewPostsSlice { : undefined const grandparentAuthor = reply.grandparentAuthor const isGrandparentBlocked = Boolean( - grandparent && AppBskyFeedDefs.isBlockedPost(grandparent), + grandparent && bsky.isType(app.bsky.feed.defs.blockedPost, grandparent), ) const isGrandparentNotFound = Boolean( - grandparent && AppBskyFeedDefs.isNotFoundPost(grandparent), + grandparent && bsky.isType(app.bsky.feed.defs.notFoundPost, grandparent), ) this.items.unshift({ post: parent, @@ -142,9 +138,9 @@ export class FeedViewPostsSlice { // de-deduping } if ( - !AppBskyFeedDefs.isPostView(root) || - !AppBskyFeedPost.isRecord(root.record) || - !bsky.validate(root.record, AppBskyFeedPost.validateRecord) + !bsky.isType(app.bsky.feed.defs.postView, root) || + !bsky.isType(app.bsky.feed.post, root.record) || + !bsky.matches(app.bsky.feed.post, root.record) ) { this.isOrphan = true return @@ -167,14 +163,14 @@ export class FeedViewPostsSlice { get isQuotePost() { const embed = this._feedPost.post.embed return ( - AppBskyEmbedRecord.isView(embed) || - AppBskyEmbedRecordWithMedia.isView(embed) + bsky.isType(app.bsky.embed.record.view, embed) || + bsky.isType(app.bsky.embed.recordWithMedia.view, embed) ) } get isReply() { return ( - AppBskyFeedPost.isRecord(this._feedPost.post.record) && + bsky.isType(app.bsky.feed.post, this._feedPost.post.record) && !!this._feedPost.post.record.reply ) } @@ -195,7 +191,7 @@ export class FeedViewPostsSlice { get isRepost() { const reason = this._feedPost.reason - return AppBskyFeedDefs.isReasonRepost(reason) + return bsky.isType(app.bsky.feed.defs.reasonRepost, reason) } get likeCount() { @@ -208,18 +204,18 @@ export class FeedViewPostsSlice { getAuthors(): AuthorContext { const feedPost = this._feedPost - let author: AppBskyActorDefs.ProfileViewBasic = feedPost.post.author - let parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined - let grandparentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined - let rootAuthor: AppBskyActorDefs.ProfileViewBasic | undefined + let author: app.bsky.actor.defs.ProfileViewBasic = feedPost.post.author + let parentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined + let grandparentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined + let rootAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined if (feedPost.reply) { - if (AppBskyFeedDefs.isPostView(feedPost.reply.parent)) { + if (bsky.isType(app.bsky.feed.defs.postView, feedPost.reply.parent)) { parentAuthor = feedPost.reply.parent.author } if (feedPost.reply.grandparentAuthor) { grandparentAuthor = feedPost.reply.grandparentAuthor } - if (AppBskyFeedDefs.isPostView(feedPost.reply.root)) { + if (bsky.isType(app.bsky.feed.defs.postView, feedPost.reply.root)) { rootAuthor = feedPost.reply.root.author } } @@ -514,7 +510,7 @@ function shouldDisplayReplyInFollowing( } function isSelfOrFollowing( - profile: AppBskyActorDefs.ProfileViewBasic, + profile: app.bsky.actor.defs.ProfileViewBasic, userDid: string, ) { return Boolean(profile.did === userDid || profile.viewer?.following) diff --git a/src/lib/api/feed/author.ts b/src/lib/api/feed/author.ts index 92f8d327b4..76e6433f69 100644 --- a/src/lib/api/feed/author.ts +++ b/src/lib/api/feed/author.ts @@ -1,7 +1,7 @@ -import {AppBskyFeedDefs} from '@atproto/api' import {type Client, type XrpcRequestParams} from '@atproto/lex' import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {type FeedAPI, type FeedAPIResponse} from './types' type GetAuthorFeedParams = XrpcRequestParams< @@ -29,7 +29,7 @@ export class AuthorFeedAPI implements FeedAPI { return params } - async peekLatest(): Promise { + async peekLatest(): Promise { const data = await this.client.call(app.bsky.feed.getAuthorFeed, { ...this.params, limit: 1, @@ -61,12 +61,15 @@ export class AuthorFeedAPI implements FeedAPI { } } - _filter(feed: AppBskyFeedDefs.FeedViewPost[]) { + _filter(feed: app.bsky.feed.defs.FeedViewPost[]) { if (this.params.filter === 'posts_and_author_threads') { return feed.filter(post => { const isReply = post.reply - const isRepost = AppBskyFeedDefs.isReasonRepost(post.reason) - const isPin = AppBskyFeedDefs.isReasonPin(post.reason) + const isRepost = bsky.isType( + app.bsky.feed.defs.reasonRepost, + post.reason, + ) + const isPin = bsky.isType(app.bsky.feed.defs.reasonPin, post.reason) if (!isReply) return true if (isRepost || isPin) return true return isReply && isAuthorReplyChain(this.params.actor, post, feed) @@ -79,15 +82,15 @@ export class AuthorFeedAPI implements FeedAPI { function isAuthorReplyChain( actor: string, - post: AppBskyFeedDefs.FeedViewPost, - posts: AppBskyFeedDefs.FeedViewPost[], + post: app.bsky.feed.defs.FeedViewPost, + posts: app.bsky.feed.defs.FeedViewPost[], ): boolean { // current post is by a different user (shouldn't happen) if (post.post.author.did !== actor) return false const replyParent = post.reply?.parent - if (AppBskyFeedDefs.isPostView(replyParent)) { + if (bsky.isType(app.bsky.feed.defs.postView, replyParent)) { // reply parent is by a different user if (replyParent.author.did !== actor) return false diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts index 6d960813d3..6185f788df 100644 --- a/src/lib/api/feed/custom.ts +++ b/src/lib/api/feed/custom.ts @@ -1,5 +1,4 @@ -import {type AppBskyFeedDefs, jsonStringToLex} from '@atproto/api' -import {Client, type XrpcRequestParams} from '@atproto/lex' +import {Client, lexParse, type XrpcRequestParams} from '@atproto/lex' import { getAppLanguageAsContentLanguage, @@ -30,7 +29,7 @@ export class CustomFeedAPI implements FeedAPI { this.userInterests = userInterests } - async peekLatest(): Promise { + async peekLatest(): Promise { const contentLangs = getContentLanguages().join(',') const data = await this.client.call( app.bsky.feed.getFeed, @@ -140,7 +139,7 @@ async function loggedOutFetch({ * is asserted here just as the old-world one was. */ let data = res.ok - ? (jsonStringToLex(await res.text()) as app.bsky.feed.getFeed.$OutputBody) + ? (lexParse(await res.text()) as app.bsky.feed.getFeed.$OutputBody) : null if (data?.feed?.length) { return data @@ -154,7 +153,7 @@ async function loggedOutFetch({ {method: 'GET', headers: {'Accept-Language': '', ...labelersHeader}}, ) data = res.ok - ? (jsonStringToLex(await res.text()) as app.bsky.feed.getFeed.$OutputBody) + ? (lexParse(await res.text()) as app.bsky.feed.getFeed.$OutputBody) : null if (data?.feed?.length) { return data diff --git a/src/lib/api/feed/demo.ts b/src/lib/api/feed/demo.ts index a4c1c360e9..5d549c69b7 100644 --- a/src/lib/api/feed/demo.ts +++ b/src/lib/api/feed/demo.ts @@ -1,7 +1,7 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client} from '@atproto/lex' import {DEMO_FEED} from '#/lib/demo' +import {type app} from '#/lexicons' import {type FeedAPI, type FeedAPIResponse} from './types' export class DemoFeedAPI implements FeedAPI { @@ -11,7 +11,7 @@ export class DemoFeedAPI implements FeedAPI { this.client = client } - async peekLatest(): Promise { + async peekLatest(): Promise { return DEMO_FEED.feed[0] } diff --git a/src/lib/api/feed/following.ts b/src/lib/api/feed/following.ts index 596483577a..27a08c6985 100644 --- a/src/lib/api/feed/following.ts +++ b/src/lib/api/feed/following.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client} from '@atproto/lex' import {app} from '#/lexicons' @@ -11,7 +10,7 @@ export class FollowingFeedAPI implements FeedAPI { this.client = client } - async peekLatest(): Promise { + async peekLatest(): Promise { const data = await this.client.call(app.bsky.feed.getTimeline, { limit: 1, }) diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts index a5ff0f3b93..9c90b213aa 100644 --- a/src/lib/api/feed/home.ts +++ b/src/lib/api/feed/home.ts @@ -1,8 +1,8 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client} from '@atproto/lex' import {type AtUriString} from '@atproto/syntax' import {PROD_DEFAULT_FEED} from '#/lib/constants' +import {type app} from '#/lexicons' import {CustomFeedAPI} from './custom' import {FollowingFeedAPI} from './following' import {type FeedAPI, type FeedAPIResponse} from './types' @@ -15,7 +15,12 @@ import {type FeedAPI, type FeedAPIResponse} from './types' // we use this fallback marker post to drive this instead. see Feed.tsx // for the usage. // -prf -export const FALLBACK_MARKER_POST: AppBskyFeedDefs.FeedViewPost = { +/* + * A synthetic marker, not a real view: its `uri`/`did`/`indexedAt` are + * deliberately not well-formed, so the literal is asserted rather than branded. + * Only `post.uri` is ever read (see Feed.tsx). + */ +export const FALLBACK_MARKER_POST = { post: { uri: 'fallback-marker-post', cid: 'fake', @@ -26,7 +31,7 @@ export const FALLBACK_MARKER_POST: AppBskyFeedDefs.FeedViewPost = { }, indexedAt: new Date().toISOString(), }, -} +} as unknown as app.bsky.feed.defs.FeedViewPost export class HomeFeedAPI implements FeedAPI { client: Client @@ -63,7 +68,7 @@ export class HomeFeedAPI implements FeedAPI { this.itemCursor = 0 } - async peekLatest(): Promise { + async peekLatest(): Promise { if (this.usingDiscover) { return this.discover.peekLatest() } @@ -82,7 +87,7 @@ export class HomeFeedAPI implements FeedAPI { } let returnCursor - let posts: AppBskyFeedDefs.FeedViewPost[] = [] + let posts: app.bsky.feed.defs.FeedViewPost[] = [] if (!this.usingDiscover) { const res = await this.following.fetch({cursor, limit}) diff --git a/src/lib/api/feed/likes.ts b/src/lib/api/feed/likes.ts index d7d63fc4db..d8bba416ac 100644 --- a/src/lib/api/feed/likes.ts +++ b/src/lib/api/feed/likes.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client, type XrpcRequestParams} from '@atproto/lex' import {app} from '#/lexicons' @@ -23,7 +22,7 @@ export class LikesFeedAPI implements FeedAPI { this.params = feedParams } - async peekLatest(): Promise { + async peekLatest(): Promise { const data = await this.client.call(app.bsky.feed.getActorLikes, { ...this.params, limit: 1, diff --git a/src/lib/api/feed/list.ts b/src/lib/api/feed/list.ts index e4d07d73d7..51e67bb4e8 100644 --- a/src/lib/api/feed/list.ts +++ b/src/lib/api/feed/list.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client, type XrpcRequestParams} from '@atproto/lex' import {app} from '#/lexicons' @@ -23,7 +22,7 @@ export class ListFeedAPI implements FeedAPI { this.params = feedParams } - async peekLatest(): Promise { + async peekLatest(): Promise { const data = await this.client.call(app.bsky.feed.getListFeed, { ...this.params, limit: 1, diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts index 2e3abcb8e3..91d300afb8 100644 --- a/src/lib/api/feed/merge.ts +++ b/src/lib/api/feed/merge.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client} from '@atproto/lex' import {type AtUriString} from '@atproto/syntax' import shuffle from 'lodash.shuffle' @@ -28,7 +27,7 @@ const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours */ type MergeFeedPage = { cursor?: string - feed: AppBskyFeedDefs.FeedViewPost[] + feed: app.bsky.feed.defs.FeedViewPost[] } | null export class MergeFeedAPI implements FeedAPI { @@ -89,7 +88,7 @@ export class MergeFeedAPI implements FeedAPI { } } - async peekLatest(): Promise { + async peekLatest(): Promise { const data = await this.client.call(app.bsky.feed.getTimeline, { limit: 1, }) @@ -136,7 +135,7 @@ export class MergeFeedAPI implements FeedAPI { await Promise.all(promises) // assemble a response by sampling from feeds with content - const posts: AppBskyFeedDefs.FeedViewPost[] = [] + const posts: app.bsky.feed.defs.FeedViewPost[] = [] while (posts.length < limit) { let slice = this.sampleItem() if (slice[0]) { @@ -188,7 +187,7 @@ class MergeFeedSource { feedTuners: FeedTunerFn[] sourceInfo: ReasonFeedSource | undefined cursor: string | undefined = undefined - queue: AppBskyFeedDefs.FeedViewPost[] = [] + queue: app.bsky.feed.defs.FeedViewPost[] = [] hasMore = true constructor({ @@ -210,7 +209,7 @@ class MergeFeedSource { return this.hasMore && this.queue.length === 0 } - take(n: number): AppBskyFeedDefs.FeedViewPost[] { + take(n: number): app.bsky.feed.defs.FeedViewPost[] { return this.queue.splice(0, n) } @@ -327,7 +326,7 @@ class MergeFeedSource_Custom extends MergeFeedSource { // some custom feeds fail to enforce the pagination limit // so we manually truncate here // -prf - let feed: AppBskyFeedDefs.FeedViewPost[] = + let feed: app.bsky.feed.defs.FeedViewPost[] = limit && data.feed.length > limit ? data.feed.slice(0, limit) : data.feed diff --git a/src/lib/api/feed/posts.ts b/src/lib/api/feed/posts.ts index d2bcfc4317..18835ecbd2 100644 --- a/src/lib/api/feed/posts.ts +++ b/src/lib/api/feed/posts.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client, type XrpcRequestParams} from '@atproto/lex' import {logger} from '#/logger' @@ -10,7 +9,7 @@ type GetPostsParams = XrpcRequestParams export class PostListFeedAPI implements FeedAPI { client: Client params: GetPostsParams - peek: AppBskyFeedDefs.FeedViewPost | null = null + peek: app.bsky.feed.defs.FeedViewPost | null = null constructor({ client, @@ -30,7 +29,7 @@ export class PostListFeedAPI implements FeedAPI { } } - async peekLatest(): Promise { + async peekLatest(): Promise { if (this.peek) return this.peek throw new Error('Has not fetched yet') } diff --git a/src/lib/api/feed/types.ts b/src/lib/api/feed/types.ts index 27fa066fbd..59803a992a 100644 --- a/src/lib/api/feed/types.ts +++ b/src/lib/api/feed/types.ts @@ -1,12 +1,12 @@ -import {type AppBskyFeedDefs} from '@atproto/api' +import {type app} from '#/lexicons' export interface FeedAPIResponse { cursor?: string - feed: AppBskyFeedDefs.FeedViewPost[] + feed: app.bsky.feed.defs.FeedViewPost[] } export interface FeedAPI { - peekLatest(): Promise + peekLatest(): Promise fetch({ cursor, limit, diff --git a/src/lib/api/feed/utils.ts b/src/lib/api/feed/utils.ts index c52f402326..b6ef738fcc 100644 --- a/src/lib/api/feed/utils.ts +++ b/src/lib/api/feed/utils.ts @@ -1,4 +1,4 @@ -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants' import {type UsePreferencesQueryResponse} from '#/state/queries/preferences' diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts index e03a40c391..af2d54e65a 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -1,4 +1,3 @@ -import {ChatBskyGroupDefs} from '@atproto/api' import {TID} from '@atproto/common-web' import {type $Typed, type Client} from '@atproto/lex' import { @@ -29,7 +28,7 @@ import { type PostDraft, type ThreadDraft, } from '#/view/com/composer/state/composer' -import {app, com} from '#/lexicons' +import {app, chat, com} from '#/lexicons' import * as bsky from '#/types/bsky' import {createGIFDescription} from '../gif-alt-text' import {computeCid} from './computeCid' @@ -313,7 +312,7 @@ async function resolveEmbed( * unbranded strings. Assert at the boundary until it moves to the * clients. */ - record: resolvedLink.record as com.atproto.repo.strongRef.Main, + record: resolvedLink.record, } } } @@ -475,15 +474,13 @@ async function resolveMedia( title: resolvedLink.title, description: resolvedLink.description, thumb: blob, - associatedRefs: resolvedLink.associatedRefs as - | com.atproto.repo.strongRef.Main[] - | undefined, + associatedRefs: resolvedLink.associatedRefs, }, } } if ( resolvedLink.type === 'chat-invite' && - ChatBskyGroupDefs.isJoinLinkPreviewView(resolvedLink.view) + bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, resolvedLink.view) ) { return { $type: 'app.bsky.embed.external', @@ -512,5 +509,5 @@ async function resolveRecord( if (resolvedLink.type !== 'record') { throw Error(t`Expected uri to resolve to a record`) } - return resolvedLink.record as com.atproto.repo.strongRef.Main + return resolvedLink.record } diff --git a/src/lib/api/resolve.ts b/src/lib/api/resolve.ts index 297388edb0..4d9ec3e457 100644 --- a/src/lib/api/resolve.ts +++ b/src/lib/api/resolve.ts @@ -1,11 +1,5 @@ -import { - type AppBskyFeedDefs, - type AppBskyGraphDefs, - type ComAtprotoRepoStrongRef, -} from '@atproto/api' -import {AtUri} from '@atproto/api' import {type Client} from '@atproto/lex' -import {type AtUriString, type HandleString} from '@atproto/syntax' +import {AtUri, type AtUriString, type HandleString} from '@atproto/syntax' import {IMAGE_SIZE_CONFIG_2K_1MB} from '#/lib/constants' import {getLinkMeta, type LinkMeta} from '#/lib/link-meta/link-meta' @@ -49,30 +43,30 @@ type ResolvedExternalLink = { type ResolvedPostRecord = { type: 'record' - record: ComAtprotoRepoStrongRef.Main + record: com.atproto.repo.strongRef.Main kind: 'post' - view: AppBskyFeedDefs.PostView + view: app.bsky.feed.defs.PostView } type ResolvedFeedRecord = { type: 'record' - record: ComAtprotoRepoStrongRef.Main + record: com.atproto.repo.strongRef.Main kind: 'feed' - view: AppBskyFeedDefs.GeneratorView + view: app.bsky.feed.defs.GeneratorView } type ResolvedListRecord = { type: 'record' - record: ComAtprotoRepoStrongRef.Main + record: com.atproto.repo.strongRef.Main kind: 'list' - view: AppBskyGraphDefs.ListView + view: app.bsky.graph.defs.ListView } type ResolvedStarterPackRecord = { type: 'record' - record: ComAtprotoRepoStrongRef.Main + record: com.atproto.repo.strongRef.Main kind: 'starter-pack' - view: AppBskyGraphDefs.StarterPackView + view: app.bsky.graph.defs.StarterPackView } type ResolvedChatInvite = { diff --git a/src/lib/constants.ts b/src/lib/constants.ts index a490d92719..a25f42f6bc 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,8 +1,9 @@ import {type Insets, Platform} from 'react-native' -import {type AppBskyActorDefs, BSKY_LABELER_DID} from '@atproto/api' import {type Service} from '@atproto/lex' +import {api} from '@bsky.app/sdk' import {BLUESKY_PROXY_DID, CHAT_PROXY_DID, IS_DEV} from '#/env' +import {type app} from '#/lexicons' export const LOCAL_DEV_SERVICE = Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583' @@ -173,7 +174,7 @@ export const VIDEO_SAVED_FEED = { } export const RECOMMENDED_SAVED_FEEDS: Pick< - AppBskyActorDefs.SavedFeed, + app.bsky.actor.defs.SavedFeed, 'type' | 'value' | 'pinned' >[] = [DISCOVER_SAVED_FEED, TIMELINE_SAVED_FEED] @@ -269,7 +270,7 @@ export const CHAT_PROXY_SERVICE: Service = `${CHAT_PROXY_DID}#bsky_chat` * that labeler's creator did instead, so this is a per-call option rather than a * client-level one like {@link CHAT_PROXY_SERVICE}. */ -export const MOD_PROXY_SERVICE: Service = `${BSKY_LABELER_DID}#atproto_labeler` +export const MOD_PROXY_SERVICE: Service = `${api.moderation.did}#atproto_labeler` /** * The notification service's proxy target, in the `did#service_id` form a lex diff --git a/src/lib/demo.ts b/src/lib/demo.ts index 5ead62c9d3..826049045a 100644 --- a/src/lib/demo.ts +++ b/src/lib/demo.ts @@ -1,11 +1,13 @@ -import {type AppBskyFeedGetFeed} from '@atproto/api' +import {toDatetimeString} from '@atproto/syntax' import {subDays, subMinutes} from 'date-fns' +import {type app} from '#/lexicons' + const DID = `did:plc:z72i7hdynmk6r22z27h6tvur` const NOW = new Date() -const POST_1_DATE = subMinutes(NOW, 2).toISOString() -const POST_2_DATE = subMinutes(NOW, 4).toISOString() -const POST_3_DATE = subMinutes(NOW, 5).toISOString() +const POST_1_DATE = toDatetimeString(subMinutes(NOW, 2)) +const POST_2_DATE = toDatetimeString(subMinutes(NOW, 4)) +const POST_3_DATE = toDatetimeString(subMinutes(NOW, 5)) export const DEMO_FEED = { feed: [ @@ -31,7 +33,7 @@ export const DEMO_FEED = { issuer: DID, uri: `at://${DID}/app.bsky.graph.verification/post1`, isValid: true, - createdAt: subDays(NOW, 11).toISOString(), + createdAt: toDatetimeString(subDays(NOW, 11)), }, ], verifiedStatus: 'valid', @@ -197,6 +199,6 @@ export const DEMO_FEED = { }, }, ], -} satisfies AppBskyFeedGetFeed.OutputSchema +} satisfies app.bsky.feed.getFeed.$OutputBody export const BOTTOM_BAR_AVI = 'https://bsky.social/about/adi/user_avi.jpg' diff --git a/src/lib/hooks/useNotificationHandler.ts b/src/lib/hooks/useNotificationHandler.ts index e705f0ce95..3689d608e0 100644 --- a/src/lib/hooks/useNotificationHandler.ts +++ b/src/lib/hooks/useNotificationHandler.ts @@ -1,6 +1,6 @@ import {useEffect} from 'react' import * as Notifications from 'expo-notifications' -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {useLingui} from '@lingui/react/macro' import {CommonActions, useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' diff --git a/src/lib/hooks/usePostViewTracking.ts b/src/lib/hooks/usePostViewTracking.ts index bbe8bb9f24..28fe8ba448 100644 --- a/src/lib/hooks/usePostViewTracking.ts +++ b/src/lib/hooks/usePostViewTracking.ts @@ -1,7 +1,7 @@ import {useCallback, useRef} from 'react' -import {type AppBskyFeedDefs} from '@atproto/api' import {type Metrics, useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' /** * Hook that returns a callback to track post:view events. @@ -17,7 +17,7 @@ export function usePostViewTracking( const seenUrisRef = useRef(new Set()) const trackPostView = useCallback( - (post: AppBskyFeedDefs.PostView) => { + (post: app.bsky.feed.defs.PostView) => { if (seenUrisRef.current.has(post.uri)) return seenUrisRef.current.add(post.uri) diff --git a/src/lib/link-meta/link-meta.ts b/src/lib/link-meta/link-meta.ts index ee96d6db43..72c43593cf 100644 --- a/src/lib/link-meta/link-meta.ts +++ b/src/lib/link-meta/link-meta.ts @@ -1,8 +1,7 @@ -import {type AppBskyEmbedExternal} from '@atproto/api' - import {LINK_META_PROXY} from '#/lib/constants' import {getGiphyMetaUri} from '#/lib/strings/embed-player' import {parseStarterPackUri} from '#/lib/strings/starter-pack' +import {type app} from '#/lexicons' import {isBskyAppUrl} from '../strings/url-helpers' export enum LikelyType { @@ -26,8 +25,8 @@ export interface LinkMeta { * The AT-URI of the Atmosphere record representing this external content, if * it exists. Example: a site.standard.document record. */ - associatedRefs?: AppBskyEmbedExternal.External['associatedRefs'] - view?: AppBskyEmbedExternal.View + associatedRefs?: app.bsky.embed.external.External['associatedRefs'] + view?: app.bsky.embed.external.View } export async function getLinkMeta( diff --git a/src/lib/moderation.ts b/src/lib/moderation.ts index b2d4c194e0..9bfbf5423b 100644 --- a/src/lib/moderation.ts +++ b/src/lib/moderation.ts @@ -1,5 +1,4 @@ import {useMemo} from 'react' -import {type ComAtprotoLabelDefs} from '@atproto/api' import {Client} from '@atproto/lex' import {type DidString} from '@atproto/syntax' import { @@ -13,7 +12,7 @@ import { import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {type AppModerationCause} from '#/components/Pills' -import {type app} from '#/lexicons' +import {type app, type com} from '#/lexicons' export const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn'] as const export const OTHER_SELF_LABELS = ['graphic-media'] as const @@ -54,7 +53,7 @@ export function moduiContainsHideableOffense(modui: ModerationUI): boolean { } export function labelIsHideableOffense( - label: ComAtprotoLabelDefs.Label, + label: com.atproto.label.defs.Label, ): boolean { return ['!hide', '!takedown'].includes(label.val) } @@ -64,9 +63,9 @@ export function labelIsHideableOffense( * with `!`) and the user's own "bot" self-label. */ export function filterUserFacingLabels( - labels: ComAtprotoLabelDefs.Label[], + labels: com.atproto.label.defs.Label[], currentAccountDid: string | undefined, -): ComAtprotoLabelDefs.Label[] { +): com.atproto.label.defs.Label[] { return labels.filter( label => !label.val.startsWith('!') && @@ -135,7 +134,11 @@ export type Subject = did: string } -export function useLabelSubject({label}: {label: ComAtprotoLabelDefs.Label}): { +export function useLabelSubject({ + label, +}: { + label: com.atproto.label.defs.Label +}): { subject: Subject } { return useMemo(() => { diff --git a/src/lib/moderation/subjects.ts b/src/lib/moderation/subjects.ts deleted file mode 100644 index c1621b300c..0000000000 --- a/src/lib/moderation/subjects.ts +++ /dev/null @@ -1,133 +0,0 @@ -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/useLabelInfo.ts b/src/lib/moderation/useLabelInfo.ts index fc565c3d45..27ecf0ff83 100644 --- a/src/lib/moderation/useLabelInfo.ts +++ b/src/lib/moderation/useLabelInfo.ts @@ -1,4 +1,3 @@ -import {type ComAtprotoLabelDefs} from '@atproto/api' import { type InterpretedLabelValueDefinition, interpretLabelValueDefinition, @@ -12,16 +11,16 @@ import { useGlobalLabelStrings, } from '#/lib/moderation/useGlobalLabelStrings' import {useLabelDefinitions} from '#/state/preferences' -import {type app} from '#/lexicons' +import {type app, type com} from '#/lexicons' export interface LabelInfo { - label: ComAtprotoLabelDefs.Label + label: com.atproto.label.defs.Label def: InterpretedLabelValueDefinition - strings: ComAtprotoLabelDefs.LabelValueDefinitionStrings + strings: com.atproto.label.defs.LabelValueDefinitionStrings labeler: app.bsky.labeler.defs.LabelerViewDetailed | undefined } -export function useLabelInfo(label: ComAtprotoLabelDefs.Label): LabelInfo { +export function useLabelInfo(label: com.atproto.label.defs.Label): LabelInfo { const {i18n} = useLingui() const {labelDefs, labelers} = useLabelDefinitions() const globalLabelStrings = useGlobalLabelStrings() @@ -36,7 +35,7 @@ export function useLabelInfo(label: ComAtprotoLabelDefs.Label): LabelInfo { export function getDefinition( labelDefs: Record, - label: ComAtprotoLabelDefs.Label, + label: com.atproto.label.defs.Label, ): InterpretedLabelValueDefinition { // check local definitions const customDef = @@ -71,13 +70,13 @@ export function getLabelStrings( locale: string, globalLabelStrings: GlobalLabelStrings, def: InterpretedLabelValueDefinition, -): ComAtprotoLabelDefs.LabelValueDefinitionStrings { +): com.atproto.label.defs.LabelValueDefinitionStrings { if (!def.definedBy) { // global definition, look up strings if (def.identifier in globalLabelStrings) { return globalLabelStrings[ def.identifier - ] as ComAtprotoLabelDefs.LabelValueDefinitionStrings + ] as com.atproto.label.defs.LabelValueDefinitionStrings } } else { // try to find locale match in the definition's strings diff --git a/src/lib/moderation/useModerationCauseDescription.ts b/src/lib/moderation/useModerationCauseDescription.ts index 3cdbb30961..c3e7d19640 100644 --- a/src/lib/moderation/useModerationCauseDescription.ts +++ b/src/lib/moderation/useModerationCauseDescription.ts @@ -1,5 +1,5 @@ import {useMemo} from 'react' -import {BSKY_LABELER_DID} from '@atproto/api' +import {api} from '@bsky.app/sdk' import {type ModerationCause} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -135,7 +135,7 @@ export function useModerationCauseDescription( : undefined let sourceDisplayName = labeler?.creator.displayName if (!source) { - if (cause.label.src === BSKY_LABELER_DID) { + if (cause.label.src === api.moderation.did) { source = 'moderation.bsky.app' sourceDisplayName = 'Bluesky Moderation Service' } else { diff --git a/src/lib/routes/links.ts b/src/lib/routes/links.ts index c87ccb5a39..e568c66ff6 100644 --- a/src/lib/routes/links.ts +++ b/src/lib/routes/links.ts @@ -1,6 +1,7 @@ -import {type AppBskyGraphDefs, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {isInvalidHandle} from '#/lib/strings/handles' +import {type app} from '#/lexicons' export function makeProfileLink( info: { @@ -44,8 +45,8 @@ export function makeSearchLink(props: {query: string; from?: 'me' | string}) { export function makeStarterPackLink( starterPackOrName: - | AppBskyGraphDefs.StarterPackViewBasic - | AppBskyGraphDefs.StarterPackView + | app.bsky.graph.defs.StarterPackViewBasic + | app.bsky.graph.defs.StarterPackView | string, rkey?: string, ) { diff --git a/src/lib/strings/__tests__/errors.test.ts b/src/lib/strings/__tests__/errors.test.ts index 1d9ccd8a3d..703cc955fb 100644 --- a/src/lib/strings/__tests__/errors.test.ts +++ b/src/lib/strings/__tests__/errors.test.ts @@ -1,4 +1,3 @@ -import {XRPCError} from '@atproto/api' import {LexError, XrpcResponseError} from '@atproto/lex' import {beforeAll, describe, expect, it} from '@jest/globals' import {i18n} from '@lingui/core' @@ -72,20 +71,10 @@ describe('cleanError', () => { ) }) - it('matches the upstream-failure branch on a legacy XRPC error', () => { - const e = new XRPCError(502) - expect(cleanError(e)).toBe( - 'The server appears to be experiencing issues. Please try again in a few moments.', - ) - }) - - it('matches NotEnoughResources on both error shapes', () => { + it('matches NotEnoughResources', () => { expect(cleanError(xrpcStatusError(503))).toBe( 'The server appears to be experiencing issues. Please try again in a few moments.', ) - expect(cleanError(new XRPCError(503))).toBe( - 'The server appears to be experiencing issues. Please try again in a few moments.', - ) }) it('matches the app-password branch on a lex error message', () => { @@ -105,7 +94,8 @@ describe('cleanError', () => { it('surfaces the authentication-required code of a lex error', () => { /* * The lex client derives `AuthenticationRequired` from a 401 with no XRPC - * payload, where `@atproto/api` used the spaced "Authentication Required". + * payload, where the pre-migration client used the spaced + * "Authentication Required". * Neither is special-cased in `cleanError`, so what matters is that the * class- and code-prefixed stringification does not reach the user. */ diff --git a/src/lib/strings/errors.ts b/src/lib/strings/errors.ts index fb2b0c5b9d..365a069e2d 100644 --- a/src/lib/strings/errors.ts +++ b/src/lib/strings/errors.ts @@ -1,7 +1,8 @@ -import {XRPCError} from '@atproto/api' import {LexError} from '@atproto/lex' import {t} from '@lingui/core/macro' +import {isXrpcError} from '#/lib/xrpc-error' + /** * The text to show the user when no special case applies. * @@ -39,7 +40,7 @@ export function cleanError(e: unknown): string { return t`Unable to connect. Please check your internet connection and try again.` } /* - * `@atproto/api` names these with spaces ("Upstream Failure"); lexicon error + * The legacy client named these with spaces ("Upstream Failure"); lexicon error * codes are space-free ("UpstreamFailure"). Match both while the app throws * both shapes. */ @@ -97,9 +98,20 @@ export function isNetworkError(e: unknown) { return false } +/** + * The PDS answers an app-password-scope rejection with the lexicon code + * `InvalidToken` and a message of 'Bad token scope' or 'Bad token method' + * (pipethrough), so the typed path matches the code AND the message. The + * pre-migration check compared against 'TokenInvalid', which the PDS never + * sends - the string fallback was doing all the work. + */ export function isErrorMaybeAppPasswordPermissions(e: unknown) { - if (e instanceof XRPCError && e.error === 'TokenInvalid') { - return true + if (isXrpcError(e)) { + return ( + e.error === 'InvalidToken' && + (e.message.includes('Bad token scope') || + e.message.includes('Bad token method')) + ) } const str = String(e) return str.includes('Bad token scope') || str.includes('Bad token method') @@ -124,5 +136,5 @@ export function isRetryableHttpStatus(status: number) { } export function shouldRetryError(e: unknown) { - return e instanceof XRPCError && isRetryableHttpStatus(e.status) + return isXrpcError(e) && e.shouldRetry() } diff --git a/src/lib/strings/rich-text-helpers.ts b/src/lib/strings/rich-text-helpers.ts index a6341025ff..078b91e400 100644 --- a/src/lib/strings/rich-text-helpers.ts +++ b/src/lib/strings/rich-text-helpers.ts @@ -30,26 +30,3 @@ export function richTextToString(rt: RichText, loose: boolean): string { return result } - -/** - * Widens facets typed by the legacy `@atproto/api` codegen into the shape the - * SDK's `RichText` accepts. - * - * The two are the same lexicon and identical at runtime; they differ only in - * that the SDK brands `did`/`uri` as template literal types, which makes the - * legacy `string` versions unassignable. Call this where facets read off an - * `@atproto/api` view type are handed to `new RichText(...)`. - * - * Transitional: it goes away once the view types come from the SDK too. - */ -export function asSdkFacets( - facets: {index: {byteStart: number; byteEnd: number}; features: unknown[]}[], -): app.bsky.richtext.facet.Main[] -export function asSdkFacets( - facets: - | {index: {byteStart: number; byteEnd: number}; features: unknown[]}[] - | undefined, -): app.bsky.richtext.facet.Main[] | undefined -export function asSdkFacets(facets: unknown) { - return facets as app.bsky.richtext.facet.Main[] | undefined -} diff --git a/src/lib/strings/starter-pack.ts b/src/lib/strings/starter-pack.ts index 475b000336..0dc6218114 100644 --- a/src/lib/strings/starter-pack.ts +++ b/src/lib/strings/starter-pack.ts @@ -1,4 +1,4 @@ -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import type * as bsky from '#/types/bsky' diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts index 4fb8034a51..c90f1a6fb9 100644 --- a/src/lib/strings/url-helpers.ts +++ b/src/lib/strings/url-helpers.ts @@ -1,4 +1,4 @@ -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {parse} from 'psl' import TLDs from 'tlds' diff --git a/src/lib/xrpc-error.ts b/src/lib/xrpc-error.ts index 855aaf803e..ec4fd7e872 100644 --- a/src/lib/xrpc-error.ts +++ b/src/lib/xrpc-error.ts @@ -1,14 +1,46 @@ import { getMain, type InferMethodError, + LexError, type Main, type Procedure, type Query, + XrpcError, XrpcResponseError, } from '@atproto/lex' import {com} from '#/lexicons' +/** + * True for an XRPC error from a lex `Client` (`XrpcError` is the abstract base + * of `XrpcResponseError`/`XrpcInvalidResponseError`/`XrpcInternalError`). + */ +export function isXrpcError(e: unknown): e is XrpcError { + return e instanceof XrpcError +} + +/** + * HTTP status, or undefined when `e` is not an XRPC error carrying a response. + * Only `XrpcResponseError` (a genuine server response) has a status; the + * internal/fetch lex errors do not. + */ +export function getErrorStatus(e: unknown): number | undefined { + return e instanceof XrpcResponseError ? e.status : undefined +} + +/** + * The lexicon error code (`err.error`), or undefined when `e` carries none. + * Gated on `LexError` (the base of every `XrpcError`) rather than `XrpcError`, + * so the non-XRPC lex errors are covered too. + * + * Prefer {@link matchXrpcError} where the method that threw is known: it + * constrains the compared name to that method's declared errors. Use this only + * where the source method is genuinely ambiguous. + */ +export function getErrorName(e: unknown): string | undefined { + return e instanceof LexError ? e.error : undefined +} + /** * Same nsid means `e` was thrown for this method schema, so `e` can be * treated as an `XrpcResponseError` - which is what lets the SDK's diff --git a/src/locale/helpers.ts b/src/locale/helpers.ts index 63b09b3141..e91c540e83 100644 --- a/src/locale/helpers.ts +++ b/src/locale/helpers.ts @@ -1,8 +1,9 @@ -import {type AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api' import * as bcp47Match from 'bcp-47-match' import lande from 'lande' import {hasProp} from '#/lib/type-guards' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import { AppLanguage, type Language, @@ -61,8 +62,8 @@ function getLocalizedLanguage( } } -export function getPostLanguageTags(post: AppBskyFeedDefs.PostView) { - return AppBskyFeedPost.isRecord(post.record) && +export function getPostLanguageTags(post: app.bsky.feed.defs.PostView) { + return bsky.isType(app.bsky.feed.post, post.record) && hasProp(post.record, 'langs') && Array.isArray(post.record.langs) ? post.record.langs @@ -86,7 +87,7 @@ export function codeToLanguageName(lang2or3: string, appLang: string): string { } export function getPostLanguage( - post: AppBskyFeedDefs.PostView, + post: app.bsky.feed.defs.PostView, ): string | undefined { let candidates: string[] = getPostLanguageTags(post) let postText: string = '' @@ -121,7 +122,7 @@ export function getPostLanguage( } export function isPostInLanguage( - post: AppBskyFeedDefs.PostView, + post: app.bsky.feed.defs.PostView, targetLangs: string[], ): boolean { const lang = getPostLanguage(post) diff --git a/src/screens/Bookmarks.tsx b/src/screens/Bookmarks.tsx index fdeb91777c..c36cb0c0a5 100644 --- a/src/screens/Bookmarks.tsx +++ b/src/screens/Bookmarks.tsx @@ -1,10 +1,6 @@ import {useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import { - type $Typed, - type AppBskyBookmarkDefs, - AppBskyFeedDefs, -} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -38,6 +34,8 @@ import * as toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' type Props = NativeStackScreenProps @@ -78,15 +76,15 @@ type ListItem = | { type: 'bookmark' key: string - bookmark: Omit & { - item: $Typed + bookmark: Omit & { + item: $Typed } } | { type: 'bookmarkNotFound' key: string - bookmark: Omit & { - item: $Typed + bookmark: Omit & { + item: $Typed } } @@ -132,7 +130,7 @@ function BookmarksInner() { if (bookmarks.length > 0) { for (const bookmark of bookmarks) { - if (AppBskyFeedDefs.isNotFoundPost(bookmark.item)) { + if (bsky.isType(app.bsky.feed.defs.notFoundPost, bookmark.item)) { i.push({ type: 'bookmarkNotFound', key: bookmark.item.uri, @@ -142,7 +140,7 @@ function BookmarksInner() { }, }) } - if (AppBskyFeedDefs.isPostView(bookmark.item)) { + if (bsky.isType(app.bsky.feed.defs.postView, bookmark.item)) { i.push({ type: 'bookmark', key: bookmark.item.uri, @@ -199,7 +197,7 @@ function BookmarkNotFound({ post, }: { hideTopBorder: boolean - post: $Typed + post: $Typed }) { const t = useTheme() const {_} = useLingui() diff --git a/src/screens/CustomFeed/components/CustomFeedHeader.tsx b/src/screens/CustomFeed/components/CustomFeedHeader.tsx index 7d6b7da7de..a2d4dc56dd 100644 --- a/src/screens/CustomFeed/components/CustomFeedHeader.tsx +++ b/src/screens/CustomFeed/components/CustomFeedHeader.tsx @@ -1,6 +1,6 @@ import {useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {TRENDING_HANDLE} from '#/lib/constants' diff --git a/src/screens/CustomFeed/index.tsx b/src/screens/CustomFeed/index.tsx index 4f2a6e0903..112ea1b5ff 100644 --- a/src/screens/CustomFeed/index.tsx +++ b/src/screens/CustomFeed/index.tsx @@ -1,6 +1,5 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {useAnimatedRef} from 'react-native-reanimated' -import {AppBskyFeedDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {useIsFocused} from '@react-navigation/native' import {type NativeStackScreenProps} from '@react-navigation/native-stack' @@ -39,6 +38,7 @@ import {EditBig_Stroke2_Corner2_Rounded as EditBigIcon} from '#/components/icons import {HashtagWide_Stroke1_Corner0_Rounded as HashtagWideIcon} from '#/components/icons/Hashtag' import * as Layout from '#/components/Layout' import {IS_NATIVE} from '#/env' +import {app} from '#/lexicons' import { CustomFeedHeader, CustomFeedHeaderSkeleton, @@ -175,7 +175,7 @@ export function CustomFeedScreenInner({ const isVideoFeed = useMemo(() => { const isBskyVideoFeed = VIDEO_FEED_URIS.includes(feedInfo.uri) const feedIsVideoMode = - feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO + feedInfo.contentMode === app.bsky.feed.defs.contentModeVideo.value const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode return IS_NATIVE && _isVideoFeed }, [feedInfo]) diff --git a/src/screens/Hashtag.tsx b/src/screens/Hashtag.tsx index 39c2341f88..10d0c91884 100644 --- a/src/screens/Hashtag.tsx +++ b/src/screens/Hashtag.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {type ListRenderItemInfo, View} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {type NativeStackScreenProps} from '@react-navigation/native-stack' @@ -28,12 +27,15 @@ import {InlineLinkText} from '#/components/Link' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' import {SearchError} from '#/components/SearchError' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' -const renderItem = ({item}: ListRenderItemInfo) => { +const renderItem = ({ + item, +}: ListRenderItemInfo) => { return } -const keyExtractor = (item: AppBskyFeedDefs.PostView, index: number) => { +const keyExtractor = (item: app.bsky.feed.defs.PostView, index: number) => { return `${item.uri}-${index}` } diff --git a/src/screens/List/ListHiddenScreen.tsx b/src/screens/List/ListHiddenScreen.tsx index aea2465d7f..f562ad1d96 100644 --- a/src/screens/List/ListHiddenScreen.tsx +++ b/src/screens/List/ListHiddenScreen.tsx @@ -1,6 +1,5 @@ import {useState} from 'react' import {View} from 'react-native' -import {AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -27,12 +26,13 @@ import {Loader} from '#/components/Loader' import {useHider} from '#/components/moderation/Hider' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {app} from '#/lexicons' export function ListHiddenScreen({ list, preferences, }: { - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView preferences: UsePreferencesQueryResponse }) { const {_} = useLingui() @@ -43,7 +43,7 @@ export function ListHiddenScreen({ const goBack = useGoBack() const queryClient = useQueryClient() - const isModList = list.purpose === AppBskyGraphDefs.MODLIST + const isModList = list.purpose === app.bsky.graph.defs.modlist.value const [isProcessing, setIsProcessing] = useState(false) const listBlockMutation = useListBlockMutation() diff --git a/src/screens/Login/LoginForm.tsx b/src/screens/Login/LoginForm.tsx index ad50179bee..dfd6ccc227 100644 --- a/src/screens/Login/LoginForm.tsx +++ b/src/screens/Login/LoginForm.tsx @@ -1,6 +1,5 @@ import {useRef, useState} from 'react' import {Keyboard, type TextInput, View} from 'react-native' -import {type ComAtprotoServerDescribeServer} from '@atproto/api' import {LexAuthFactorError} from '@atproto/lex-password-session' import {Trans, useLingui} from '@lingui/react/macro' @@ -33,11 +32,12 @@ import {createStaticClick, InlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {IS_IOS, IS_NATIVE} from '#/env' +import {type com} from '#/lexicons' import {ConfirmHostingProviderDialog} from './components/ConfirmHostingProviderDialog' import {HostingProviderDialog} from './components/HostingProviderDialog' import {FormContainer} from './FormContainer' -type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema +type ServiceDescription = com.atproto.server.describeServer.$OutputBody export const LoginForm = ({ error, diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx index b7eb113a49..c0bfa156db 100644 --- a/src/screens/Messages/ChatList.tsx +++ b/src/screens/Messages/ChatList.tsx @@ -1,6 +1,5 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react' import {View} from 'react-native' -import {type ChatBskyActorGetStatus, type ChatBskyConvoDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import { useFocusEffect, @@ -57,17 +56,18 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAgeAssurance} from '#/ageAssurance' import {IS_NATIVE, IS_WEB} from '#/env' +import {type chat} from '#/lexicons' import {ChatDisabled} from './components/ChatDisabled' import {ChatListItem} from './components/ChatListItem' import {InboxRequests} from './components/InboxRequests' import {useIsWithinSplitView} from './components/splitView/context' import {splitViewLeftScroll} from './components/splitView/leftColumnScroll' -type ChatStatus = ChatBskyActorGetStatus.OutputSchema +type ChatStatus = chat.bsky.actor.getStatus.$OutputBody type ListItem = { type: 'CONVERSATION' - conversation: ChatBskyConvoDefs.ConvoView + conversation: chat.bsky.convo.defs.ConvoView selected: boolean } diff --git a/src/screens/Messages/Conversation.tsx b/src/screens/Messages/Conversation.tsx index 503248754c..a846817f50 100644 --- a/src/screens/Messages/Conversation.tsx +++ b/src/screens/Messages/Conversation.tsx @@ -1,11 +1,11 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {type LayoutChangeEvent, View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {ChatBskyConvoDefs} from '@atproto/api' import { ScrollEdgeEffect, ScrollEdgeEffectProvider, } from '@bsky.app/expo-scroll-edge-effect' +import {moderateProfile} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import { type RouteProp, @@ -18,7 +18,6 @@ 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, @@ -46,6 +45,8 @@ import {type ConvoWithDetails, parseConvoView} from '#/components/dms/util' import {Error} from '#/components/Error' import * as Layout from '#/components/Layout' import {IS_LIQUID_GLASS} from '#/env' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {ChatDisabled} from './components/ChatDisabled' import {ChatEnded} from './components/ChatEnded' import {ChatLocked} from './components/ChatLocked' @@ -179,7 +180,8 @@ function InnerReady({ const emailDialogControl = useEmailDialogControl() const unreadRequestCount = - convo?.kind === 'group' && ChatBskyConvoDefs.isGroupConvo(convo.view.kind) + convo?.kind === 'group' && + bsky.isType(chat.bsky.convo.defs.groupConvo, convo.view.kind) ? (convo.view.kind.unreadJoinRequestCount ?? 0) : 0 const {mutate: markJoinRequestsRead} = useMarkJoinRequestsRead(convo?.view.id) diff --git a/src/screens/Messages/ConversationSettings/Member.tsx b/src/screens/Messages/ConversationSettings/Member.tsx index 8fe07978e8..ad6fdc6136 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 '@bsky.app/sdk/moderation' 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 80806ae534..335affc9ff 100644 --- a/src/screens/Messages/ConversationSettings/index.tsx +++ b/src/screens/Messages/ConversationSettings/index.tsx @@ -1,6 +1,5 @@ import {useEffect, useState} from 'react' import {Pressable, View} from 'react-native' -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' @@ -171,15 +170,12 @@ function keyExtractor(item: Item) { } function isGroupMember( - member: ChatBskyActorDefs.ProfileViewBasic, + member: chat.bsky.actor.defs.ProfileViewBasic, ): member is GroupConvoMember { // Kind is missing when the account has been deleted. return ( member.kind === undefined || - bsky.dangerousIsType( - member.kind, - ChatBskyActorDefs.isGroupConvoMember, - ) + bsky.isType(chat.bsky.actor.defs.groupConvoMember, member.kind) ) } @@ -415,7 +411,7 @@ function SettingsHeader({ isPending: isLocking, } = useLockConvo(convoId, { onSuccess: (data, {silent}) => { - if (!ChatBskyConvoDefs.isGroupConvo(data.convo.kind)) return + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, data.convo.kind)) return if (silent) return if (data.convo.kind.lockStatus === 'locked') { ax.metric('groupchat:owner:lock', {convoId}) diff --git a/src/screens/Messages/Inbox.tsx b/src/screens/Messages/Inbox.tsx index f6b4216b2c..30e06e3ab2 100644 --- a/src/screens/Messages/Inbox.tsx +++ b/src/screens/Messages/Inbox.tsx @@ -1,10 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import { - ChatBskyConvoDefs, - type ChatBskyConvoListConvoRequests, - ChatBskyGroupDefs, -} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useFocusEffect, useNavigation} from '@react-navigation/native' import { @@ -44,6 +39,8 @@ import {ListFooter} from '#/components/Lists' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {IncomingRequestListItem} from './components/IncomingRequestListItem' import {OutgoingRequestListItem} from './components/OutgoingRequestListItem' import {useIsWithinSplitView} from './components/splitView/context' @@ -51,8 +48,8 @@ import {useIsWithinSplitView} from './components/splitView/context' type Props = NativeStackScreenProps type RequestItem = - | {type: 'incoming'; view: ChatBskyConvoDefs.ConvoView} - | {type: 'outgoing'; view: ChatBskyGroupDefs.JoinRequestConvoView} + | {type: 'incoming'; view: chat.bsky.convo.defs.ConvoView} + | {type: 'outgoing'; view: chat.bsky.group.defs.JoinRequestConvoView} export function MessagesInboxScreen(props: Props) { const {t: l} = useLingui() @@ -75,9 +72,11 @@ export function MessagesInboxScreenInner({}: Props) { const items: RequestItem[] = [] for (const page of data.pages) { for (const item of page.requests) { - if (ChatBskyConvoDefs.isConvoView(item)) { + if (bsky.isType(chat.bsky.convo.defs.convoView, item)) { items.push({type: 'incoming', view: item}) - } else if (ChatBskyGroupDefs.isJoinRequestConvoView(item)) { + } else if ( + bsky.isType(chat.bsky.group.defs.joinRequestConvoView, item) + ) { items.push({type: 'outgoing', view: item}) } } @@ -112,7 +111,7 @@ function RequestList({ conversations, }: { listConvosQuery: UseInfiniteQueryResult< - InfiniteData, + InfiniteData, Error > conversations: RequestItem[] diff --git a/src/screens/Messages/JoinRequest.tsx b/src/screens/Messages/JoinRequest.tsx index 06faecaffe..f2cb6b67dc 100644 --- a/src/screens/Messages/JoinRequest.tsx +++ b/src/screens/Messages/JoinRequest.tsx @@ -1,12 +1,11 @@ import {useEffect} from 'react' import {View} from 'react-native' import {ImageBackground} from 'expo-image' -import {ChatBskyGroupDefs} from '@atproto/api' import {type ThemeName} from '@bsky.app/alf' +import {moderateProfile} from '@bsky.app/sdk/moderation' 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' @@ -21,6 +20,8 @@ import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon} from '#/componen import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' const desktopDarkBg = require('../../../assets/images/chat-desktop-bg-dark.webp') const desktopDimBg = require('../../../assets/images/chat-desktop-bg-dim.webp') @@ -84,7 +85,10 @@ export function JoinRequest({setScreenState}: Props) { ]}> {error || (data && - !ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview)) ? ( + !bsky.isType( + chat.bsky.group.defs.joinLinkPreviewView, + joinLinkPreview, + )) ? ( ) : data && moderationOpts && - ChatBskyGroupDefs.isJoinLinkPreviewView(joinLinkPreview) ? ( + bsky.isType( + chat.bsky.group.defs.joinLinkPreviewView, + joinLinkPreview, + ) ? ( { const data = queryClient.getQueryData< - InfiniteData + InfiniteData >(createListJoinRequestsQueryKey({convoId})) return data?.pages.reduce((sum, page) => sum + page.requests.length, 0) ?? 0 } diff --git a/src/screens/Messages/components/ChatDisabled.tsx b/src/screens/Messages/components/ChatDisabled.tsx index 62f353d1a8..31bdebc4d2 100644 --- a/src/screens/Messages/components/ChatDisabled.tsx +++ b/src/screens/Messages/components/ChatDisabled.tsx @@ -1,6 +1,5 @@ import {useCallback, useState} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -import {ToolsOzoneReportDefs} from '@atproto/api' import {type DidString} from '@atproto/syntax' import {Trans, useLingui} from '@lingui/react/macro' import {useMutation} from '@tanstack/react-query' @@ -15,7 +14,7 @@ import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' -import {com} from '#/lexicons' +import {com, tools} from '#/lexicons' export function ChatDisabled({ shape = 'pill', @@ -104,7 +103,7 @@ function DialogInner() { await client.call( com.atproto.moderation.createReport, { - reasonType: ToolsOzoneReportDefs.REASONAPPEAL, + reasonType: tools.ozone.report.defs.reasonAppeal.value, subject: { $type: 'com.atproto.admin.defs#repoRef', // the persisted account did is already resolved diff --git a/src/screens/Messages/components/ChatListItem.tsx b/src/screens/Messages/components/ChatListItem.tsx index ad2a1d731c..657fa8bae3 100644 --- a/src/screens/Messages/components/ChatListItem.tsx +++ b/src/screens/Messages/components/ChatListItem.tsx @@ -1,7 +1,7 @@ import {useCallback, useMemo, useState} from 'react' import {type GestureResponderEvent, View} from 'react-native' -import {ChatBskyConvoDefs} from '@atproto/api' import { + moderateProfile, type ModerationDecision, type ModerationOpts, } from '@bsky.app/sdk/moderation' @@ -12,7 +12,6 @@ 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 { @@ -53,7 +52,8 @@ import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' -import type * as bsky from '#/types/bsky' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {useIsWithinSplitView} from './splitView/context' export const ChatListItemPortal = createPortalGroup() @@ -64,7 +64,7 @@ export function ChatListItem({ selected = false, children, }: { - convo: ChatBskyConvoDefs.ConvoView + convo: chat.bsky.convo.defs.ConvoView showMenu?: boolean selected?: boolean children?: React.ReactNode @@ -314,7 +314,12 @@ function BaseChatItem({ let lastMessageSentAt: string | null = null // Deleted message - if (ChatBskyConvoDefs.isDeletedMessageView(convo.view.lastMessage)) { + if ( + bsky.isType( + chat.bsky.convo.defs.deletedMessageView, + convo.view.lastMessage, + ) + ) { lastMessageSentAt = convo.view.lastMessage.sentAt lastMessage = isDeletedAccount @@ -323,7 +328,7 @@ function BaseChatItem({ } // Message - if (ChatBskyConvoDefs.isMessageView(convo.view.lastMessage)) { + if (bsky.isType(chat.bsky.convo.defs.messageView, convo.view.lastMessage)) { const info = getMessageInfo({ convo: convo.view, currentAccountDid: currentAccount?.did, @@ -339,7 +344,12 @@ function BaseChatItem({ } // Reaction - if (ChatBskyConvoDefs.isMessageAndReactionView(convo.view.lastReaction)) { + if ( + bsky.isType( + chat.bsky.convo.defs.messageAndReactionView, + convo.view.lastReaction, + ) + ) { const info = getReactionInfo({ convo: convo.view, currentAccountDid: currentAccount?.did, @@ -358,7 +368,12 @@ function BaseChatItem({ } // System message - if (ChatBskyConvoDefs.isSystemMessageView(convo.view.lastMessage)) { + if ( + bsky.isType( + chat.bsky.convo.defs.systemMessageView, + convo.view.lastMessage, + ) + ) { const info = getSystemMessageInfo( convo.view.lastMessage.data, new Map(convo.view.members.map(m => [m.did, m])), diff --git a/src/screens/Messages/components/ChatStatusInfo.tsx b/src/screens/Messages/components/ChatStatusInfo.tsx index 4d3ebe2ab8..1910159a49 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 '@bsky.app/sdk/moderation' 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/IncomingRequestListItem.tsx b/src/screens/Messages/components/IncomingRequestListItem.tsx index aa4618188a..4753b92aea 100644 --- a/src/screens/Messages/components/IncomingRequestListItem.tsx +++ b/src/screens/Messages/components/IncomingRequestListItem.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import {Trans} from '@lingui/react/macro' import {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -8,13 +7,14 @@ import {atoms as a, tokens} from '#/alf' import {parseConvoView} from '#/components/dms/util' import {KnownFollowers} from '#/components/KnownFollowers' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' import {ChatListItem, ChatListItemPortal} from './ChatListItem' import {AcceptChatButton, DeleteChatButton, RejectMenu} from './RequestButtons' export function IncomingRequestListItem({ convo: convoView, }: { - convo: ChatBskyConvoDefs.ConvoView + convo: chat.bsky.convo.defs.ConvoView }) { const {currentAccount} = useSession() const moderationOpts = useModerationOpts() diff --git a/src/screens/Messages/components/InviteLinkDialog.tsx b/src/screens/Messages/components/InviteLinkDialog.tsx index a2a32a41c5..c30f5dac55 100644 --- a/src/screens/Messages/components/InviteLinkDialog.tsx +++ b/src/screens/Messages/components/InviteLinkDialog.tsx @@ -1,13 +1,11 @@ import {useState} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' -import {type ChatBskyGroupDefs} from '@atproto/api' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, 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' @@ -35,6 +33,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {type chat} from '#/lexicons' import {CopyTextButton} from './CopyTextButton' import {EditTextButton} from './EditTextButton' @@ -560,13 +559,13 @@ export function InviteLinkDialog({ ) } -function joinLinkToKey(joinLink: ChatBskyGroupDefs.JoinLinkView): string { +function joinLinkToKey(joinLink: chat.bsky.group.defs.JoinLinkView): string { return `${joinLink.joinRule}${joinLink.requireApproval ? ':requireApproval' : ''}` } function keyToJoinLink( key: string, -): Pick { +): Pick { const [joinRule, requireApproval] = key.split(':') return { joinRule, diff --git a/src/screens/Messages/components/MessageComposer.tsx b/src/screens/Messages/components/MessageComposer.tsx index 1bc5e20e1e..b2f3a1d4e3 100644 --- a/src/screens/Messages/components/MessageComposer.tsx +++ b/src/screens/Messages/components/MessageComposer.tsx @@ -13,7 +13,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context' import {scheduleOnRN} from 'react-native-worklets' import {GlassContainer} from 'expo-glass-effect' import {LinearGradient} from 'expo-linear-gradient' -import {type $Typed, type ChatBskyConvoDefs} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {ScrollEdgeEffect} from '@bsky.app/expo-scroll-edge-effect' import {useLingui} from '@lingui/react/macro' import {countGraphemes} from 'unicode-segmenter/grapheme' @@ -37,6 +37,7 @@ import {PaperPlaneVertical_Filled_Stroke2_Corner1_Rounded as PaperPlaneIcon} fro import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {IS_ANDROID, IS_IOS, IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env' +import {type chat} from '#/lexicons' import {type MessageEmbedState} from './MessageInputEmbed' const MIN_HEIGHT = 40 @@ -53,7 +54,7 @@ export function MessageComposer({ onSendMessage: ( message: string, embed?: MessageEmbedState, - replyTo?: $Typed, + replyTo?: $Typed, ) => void messageEmbed: MessageEmbedState | undefined setEmbed: (embedUrl: string | undefined) => void @@ -106,7 +107,7 @@ export function MessageComposer({ const onSubmit = ( message: string, embed: MessageEmbedState | undefined, - replyTo: ChatBskyConvoDefs.MessageView | null, + replyTo: chat.bsky.convo.defs.MessageView | null, ) => { if (!editable) return if (!embed && message.trim() === '') return diff --git a/src/screens/Messages/components/MessageInputEmbed.tsx b/src/screens/Messages/components/MessageInputEmbed.tsx index 7740c7ea50..e47373af48 100644 --- a/src/screens/Messages/components/MessageInputEmbed.tsx +++ b/src/screens/Messages/components/MessageInputEmbed.tsx @@ -1,18 +1,17 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {LayoutAnimation, View} from 'react-native' -import {AppBskyFeedPost, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {moderatePost} from '@bsky.app/sdk/moderation' 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, type NavigationProp, } from '#/lib/routes/types' -import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import { convertBskyAppUrlIfNeeded, getChatInviteCodeFromUrl, @@ -164,17 +163,11 @@ function MessageInputPostEmbed({ ) const {rt, record} = useMemo(() => { - if ( - post && - bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) - ) { + if (post && bsky.isType(app.bsky.feed.post, post.record)) { return { rt: new RichTextAPI({ text: post.record.text, - facets: asSdkFacets(post.record.facets), + facets: post.record.facets, }), record: post.record, } diff --git a/src/screens/Messages/components/MessageInputReply.tsx b/src/screens/Messages/components/MessageInputReply.tsx index 6b28b2f7b1..ff0f2d0b23 100644 --- a/src/screens/Messages/components/MessageInputReply.tsx +++ b/src/screens/Messages/components/MessageInputReply.tsx @@ -1,5 +1,4 @@ import {LayoutAnimation, View} from 'react-native' -import {type ChatBskyConvoDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {HITSLOP_20} from '#/lib/constants' @@ -11,6 +10,7 @@ import {useMessageReplies} from '#/components/dms/MessageReplies' import {useReplyPreviewText} from '#/components/dms/replyPreview' import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import {Text} from '#/components/Typography' +import {type chat} from '#/lexicons' /** * The reply staged in the message composer. Renders a preview of the message @@ -29,7 +29,7 @@ export function MessageInputReply() { function MessageInputReplyInner({ replyTo, }: { - replyTo: ChatBskyConvoDefs.MessageView + replyTo: chat.bsky.convo.defs.MessageView }) { const t = useTheme() const {t: l} = useLingui() diff --git a/src/screens/Messages/components/MessagesList.tsx b/src/screens/Messages/components/MessagesList.tsx index eab025665c..1ef81f006d 100644 --- a/src/screens/Messages/components/MessagesList.tsx +++ b/src/screens/Messages/components/MessagesList.tsx @@ -22,13 +22,7 @@ import Animated, { } from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {scheduleOnRN} from 'react-native-worklets' -import { - type $Typed, - type AppBskyEmbedRecord, - ChatBskyConvoDefs, - type ChatBskyEmbedJoinLink, - ChatBskyGroupDefs, -} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {useScrollEdgeEffectRef} from '@bsky.app/expo-scroll-edge-effect' import {RichText} from '@bsky.app/sdk/richtext' @@ -70,7 +64,7 @@ import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_ANDROID, IS_NATIVE, IS_WEB} from '#/env' -import {app, type chat, type com} from '#/lexicons' +import {app, chat} from '#/lexicons' import * as bsky from '#/types/bsky' import {ChatStatusInfo} from './ChatStatusInfo' import {groupSystemMessages, type RenderItem} from './groupSystemMessages' @@ -117,8 +111,8 @@ function getNeighborMessage( neighbor.type === 'deleted-message' ) { if ( - ChatBskyConvoDefs.isMessageView(neighbor.message) || - ChatBskyConvoDefs.isDeletedMessageView(neighbor.message) + bsky.isType(chat.bsky.convo.defs.messageView, neighbor.message) || + bsky.isType(chat.bsky.convo.defs.deletedMessageView, neighbor.message) ) { return neighbor.message } @@ -525,7 +519,7 @@ export function MessagesList({ async ( text: string, embedState?: MessageEmbedState, - reply?: $Typed, + reply?: $Typed, ) => { let rt = new RichText({text: text.trimEnd()}, {cleanNewlines: true}) @@ -539,10 +533,10 @@ export function MessagesList({ let embed: chat.bsky.convo.defs.MessageInput['embed'] let embedView: - | $Typed - | $Typed + | $Typed + | $Typed | undefined - let replyTo: ChatBskyConvoDefs.ReplyRef | undefined + let replyTo: chat.bsky.convo.defs.ReplyRef | undefined /** * Find the embedded link facet and, if it's at the start or end of the @@ -574,14 +568,13 @@ export function MessagesList({ embed = { $type: 'app.bsky.embed.record', /* - * `getPost` still returns an `@atproto/api` view, whose `uri` and - * `cid` are plain strings rather than the branded syntax types - * the lexicon input declares. + * `getPost` hands back `uri` and `cid` as plain strings rather + * than the branded syntax types the lexicon input declares. */ record: { uri: post.uri, cid: post.cid, - } as com.atproto.repo.strongRef.Main, + }, } embedView = { @@ -666,7 +659,10 @@ export function MessagesList({ } if ( embedView?.$type === 'chat.bsky.embed.joinLink#view' && - ChatBskyGroupDefs.isJoinLinkPreviewView(embedView.joinLinkPreview) + bsky.isType( + chat.bsky.group.defs.joinLinkPreviewView, + embedView.joinLinkPreview, + ) ) { ax.metric('groupchat:inviteLink:shared', { convoId: embedView.joinLinkPreview.convoId, @@ -904,7 +900,7 @@ function Composer({ onSendMessage: ( message: string, embed?: MessageEmbedState, - replyTo?: $Typed, + replyTo?: $Typed, ) => Promise messageEmbed: MessageEmbedState | undefined setEmbed: (embedUrl: string | undefined) => void @@ -914,7 +910,7 @@ function Composer({ ( message: string, embed?: MessageEmbedState, - replyTo?: $Typed, + replyTo?: $Typed, ) => { void onSendMessage(message, embed, replyTo) }, diff --git a/src/screens/Messages/components/MessagesListInfoPanel.tsx b/src/screens/Messages/components/MessagesListInfoPanel.tsx index 297ef8632f..e0e5545336 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 '@bsky.app/sdk/moderation' 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/Messages/components/OutgoingRequestListItem.tsx b/src/screens/Messages/components/OutgoingRequestListItem.tsx index eb62f24b81..9aa08fd0cf 100644 --- a/src/screens/Messages/components/OutgoingRequestListItem.tsx +++ b/src/screens/Messages/components/OutgoingRequestListItem.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type ChatBskyGroupDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {isNetworkError} from '#/lib/strings/errors' @@ -17,7 +16,7 @@ import {chat} from '#/lexicons' export function OutgoingRequestListItem({ convo: convoView, }: { - convo: ChatBskyGroupDefs.JoinRequestConvoView + convo: chat.bsky.group.defs.JoinRequestConvoView }) { const t = useTheme() const {t: l} = useLingui() diff --git a/src/screens/Messages/components/RequestButtons.tsx b/src/screens/Messages/components/RequestButtons.tsx index c5e0e82ece..d0a5cb486f 100644 --- a/src/screens/Messages/components/RequestButtons.tsx +++ b/src/screens/Messages/components/RequestButtons.tsx @@ -1,5 +1,4 @@ import {useCallback} from 'react' -import {type ChatBskyActorDefs, type ChatBskyConvoDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {StackActions, useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' @@ -41,6 +40,7 @@ import {Loader} from '#/components/Loader' import * as Menu from '#/components/Menu' import {ReportDialog} from '#/components/moderation/ReportDialog' import * as Toast from '#/components/Toast' +import {type chat} from '#/lexicons' export function RejectMenu({ convo, @@ -56,7 +56,7 @@ export function RejectMenu({ label?: string icon?: boolean convo: ConvoWithDetails - profile: ChatBskyActorDefs.ProfileViewBasic + profile: chat.bsky.actor.defs.ProfileViewBasic showDeleteConvo?: boolean currentScreen: 'list' | 'conversation' }) { @@ -214,7 +214,7 @@ export function AcceptChatButton({ }: Omit & { label?: string icon?: boolean - convo: ChatBskyConvoDefs.ConvoView + convo: chat.bsky.convo.defs.ConvoView onAcceptConvo?: () => void currentScreen: 'list' | 'conversation' }) { @@ -301,7 +301,7 @@ export function DeleteChatButton({ }: Omit & { label?: string icon?: boolean - convo: ChatBskyConvoDefs.ConvoView + convo: chat.bsky.convo.defs.ConvoView currentScreen: 'list' | 'conversation' }) { const {t: l} = useLingui() diff --git a/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx b/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx index dc208917f5..450bf6da15 100644 --- a/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx +++ b/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx @@ -1,7 +1,5 @@ import {useState} from 'react' import {View} from 'react-native' -import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' -import {type AtUriString} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -22,6 +20,7 @@ import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' const IGNORED_ACCOUNT = 'did:plc:pifkcjimdcfwaxkanzhwxufp' @@ -29,7 +28,7 @@ const IGNORED_ACCOUNT = 'did:plc:pifkcjimdcfwaxkanzhwxufp' export function StarterPackCard({ view, }: { - view: AppBskyGraphDefs.StarterPackView + view: app.bsky.graph.defs.StarterPackView }) { const t = useTheme() const {_} = useLingui() @@ -48,7 +47,7 @@ export function StarterPackCard({ setIsProcessing(true) - let listItems: AppBskyGraphDefs.ListItemView[] = [] + let listItems: app.bsky.graph.defs.ListItemView[] = [] try { listItems = await getAllListMembers(appviewClient, view.list.uri) } catch (e) { @@ -77,7 +76,7 @@ export function StarterPackCard({ try { followUris = await bulkWriteFollows(pdsClient, appviewClient, dids, { // the starter pack view is still legacy-typed - uri: view.uri as AtUriString, + uri: view.uri, cid: view.cid, }) } catch (e) { @@ -105,12 +104,7 @@ export function StarterPackCard({ }) } - if ( - !bsky.dangerousIsType( - record, - AppBskyGraphStarterpack.isRecord, - ) - ) { + if (!bsky.isType(app.bsky.graph.starterpack, record)) { return null } diff --git a/src/screens/PostThread/components/LikesStat.tsx b/src/screens/PostThread/components/LikesStat.tsx index de219f4743..afc4a79cad 100644 --- a/src/screens/PostThread/components/LikesStat.tsx +++ b/src/screens/PostThread/components/LikesStat.tsx @@ -1,8 +1,8 @@ import {View} from 'react-native' -import {type AppBskyFeedDefs, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {moderateProfile} 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 {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -15,6 +15,7 @@ import {useFormatPostStatCount} from '#/components/PostControls/util' import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' const AVI_SIZE = 20 @@ -22,7 +23,7 @@ const AVI_SIZE = 20 * The plain "N likes" stat for the expanded anchor post, linking to the likes * list. Renders nothing when the post has no likes. */ -export function LikesStat({post}: {post: AppBskyFeedDefs.PostView}) { +export function LikesStat({post}: {post: app.bsky.feed.defs.PostView}) { const t = useTheme() const {t: l} = useLingui() const formatPostStatCount = useFormatPostStatCount() @@ -62,7 +63,7 @@ export function LikesStat({post}: {post: AppBskyFeedDefs.PostView}) { * likes, the API max per page), so they are a sample of the most recent * likers, not an exhaustive list. */ -export function KnownLikers({post}: {post: AppBskyFeedDefs.PostView}) { +export function KnownLikers({post}: {post: app.bsky.feed.defs.PostView}) { const t = useTheme() const {t: l} = useLingui() const {hasSession, currentAccount} = useSession() diff --git a/src/screens/PostThread/components/ThreadItemAnchor.tsx b/src/screens/PostThread/components/ThreadItemAnchor.tsx index 4a2f6513e5..40894999ca 100644 --- a/src/screens/PostThread/components/ThreadItemAnchor.tsx +++ b/src/screens/PostThread/components/ThreadItemAnchor.tsx @@ -1,11 +1,6 @@ import {memo, useMemo} from 'react' import {Text as RNText, View} from 'react-native' -import { - AppBskyFeedDefs, - AppBskyFeedPost, - type AppBskyFeedThreadgate, - AtUri, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Plural, Trans, useLingui} from '@lingui/react/macro' @@ -14,7 +9,6 @@ import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' -import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import {niceDate} from '#/lib/strings/time' import { POST_TOMBSTONE, @@ -65,6 +59,7 @@ import {Text} from '#/components/Typography' import {WhoCanReply} from '#/components/WhoCanReply' import {useAnalytics} from '#/analytics' import {useActorStatus} from '#/features/liveNow' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' export function ThreadItemAnchor({ @@ -75,7 +70,7 @@ export function ThreadItemAnchor({ }: { item: Extract onPostSuccess?: (data: OnPostSuccessData) => void - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main postSource?: PostSource }) { const postShadow = usePostShadow(item.value.post) @@ -176,9 +171,9 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ }: { item: Extract isRoot: boolean - postShadow: Shadow + postShadow: Shadow onPostSuccess?: (data: OnPostSuccessData) => void - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main postSource?: PostSource }) { const t = useTheme() @@ -200,7 +195,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ () => new RichTextAPI({ text: record.text, - facets: asSdkFacets(record.facets), + facets: record.facets, }), [record], ) @@ -244,7 +239,11 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ const viaRepost = useMemo(() => { const reason = postSource?.post.reason - if (AppBskyFeedDefs.isReasonRepost(reason) && reason.uri && reason.cid) { + if ( + bsky.isType(app.bsky.feed.defs.reasonRepost, reason) && + reason.uri && + reason.cid + ) { return { uri: reason.uri, cid: reason.cid, @@ -574,16 +573,13 @@ function ExpandedPostDetails({ ) } -function BackdatedPostIndicator({post}: {post: AppBskyFeedDefs.PostView}) { +function BackdatedPostIndicator({post}: {post: app.bsky.feed.defs.PostView}) { const t = useTheme() const {t: l, i18n} = useLingui() const control = Prompt.usePromptControl() const indexedAt = new Date(post.indexedAt) - const createdAt = bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) + const createdAt = bsky.isType(app.bsky.feed.post, post.record) ? new Date(post.record.createdAt) : new Date(post.indexedAt) @@ -664,8 +660,8 @@ function BackdatedPostIndicator({post}: {post: AppBskyFeedDefs.PostView}) { } function getThreadAuthor( - post: AppBskyFeedDefs.PostView, - record: AppBskyFeedPost.Record, + post: app.bsky.feed.defs.PostView, + record: app.bsky.feed.post.Main, ): string { if (!record.reply) { return post.author.did diff --git a/src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx b/src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx index f7ea1c53fb..fcf873f97c 100644 --- a/src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx +++ b/src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx @@ -1,5 +1,4 @@ import {useCallback, useEffect, useMemo, useState} from 'react' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -18,6 +17,7 @@ import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Che import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' import * as Toast from '#/components/Toast' import {IS_IOS} from '#/env' +import {type app} from '#/lexicons' import {GrowthHack} from './GrowthHack' export function ThreadItemAnchorFollowButton({ @@ -57,7 +57,7 @@ export function ThreadItemAnchorFollowButtonInner({ function PostThreadFollowBtnLoaded({ profile: profileUnshadowed, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed }) { const navigation = useNavigation() const {_} = useLingui() diff --git a/src/screens/PostThread/components/ThreadItemPost.tsx b/src/screens/PostThread/components/ThreadItemPost.tsx index bd256e43b0..a3ee448ed9 100644 --- a/src/screens/PostThread/components/ThreadItemPost.tsx +++ b/src/screens/PostThread/components/ThreadItemPost.tsx @@ -1,10 +1,6 @@ import {memo, type ReactNode, useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedThreadgate, - AtUri, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans} from '@lingui/react/macro' @@ -12,7 +8,6 @@ import {MAX_POST_LINES} from '#/lib/constants' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {makeProfileLink} from '#/lib/routes/links' import {countLines} from '#/lib/strings/helpers' -import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import { POST_TOMBSTONE, type Shadow, @@ -55,6 +50,7 @@ import * as Skele from '#/components/Skeleton' import {SubtleHover} from '#/components/SubtleHover' import {Text} from '#/components/Typography' import {useActorStatus} from '#/features/liveNow' +import {type app} from '#/lexicons' export type ThreadItemPostProps = { item: Extract @@ -63,7 +59,7 @@ export type ThreadItemPostProps = { topBorder?: boolean } onPostSuccess?: (data: OnPostSuccessData) => void - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main } export function ThreadItemPost({ @@ -197,7 +193,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({ onPostSuccess, threadgateRecord, }: ThreadItemPostProps & { - postShadow: Shadow + postShadow: Shadow }) { const t = useTheme() const {openComposer} = useOpenComposer() @@ -212,7 +208,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({ () => new RichTextAPI({ text: record.text, - facets: asSdkFacets(record.facets), + facets: record.facets, }), [record], ) diff --git a/src/screens/PostThread/components/ThreadItemPostNumber.tsx b/src/screens/PostThread/components/ThreadItemPostNumber.tsx index 12be469598..7d55adf50b 100644 --- a/src/screens/PostThread/components/ThreadItemPostNumber.tsx +++ b/src/screens/PostThread/components/ThreadItemPostNumber.tsx @@ -1,9 +1,9 @@ import {Text, View} from 'react-native' -import {type AppBskyUnspeccedDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {atoms as a, ios, platform, useTheme} from '#/alf' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' /** * How far the inline badge is nudged below the text baseline. Android's @@ -12,7 +12,7 @@ import {useAnalytics} from '#/analytics' export const POST_NUMBER_INLINE_OFFSET = 6 export function useHasThreadItemPostNumber( - value: AppBskyUnspeccedDefs.ThreadItemPost, + value: app.bsky.unspecced.defs.ThreadItemPost, ) { const ax = useAnalytics() const index = value.opThreadPostIndex @@ -32,7 +32,7 @@ export function ThreadItemPostNumber({ value, inline = true, }: { - value: AppBskyUnspeccedDefs.ThreadItemPost + value: app.bsky.unspecced.defs.ThreadItemPost inline?: boolean }) { const t = useTheme() diff --git a/src/screens/PostThread/components/ThreadItemTreePost.tsx b/src/screens/PostThread/components/ThreadItemTreePost.tsx index 3f4ae03741..1e20eb5841 100644 --- a/src/screens/PostThread/components/ThreadItemTreePost.tsx +++ b/src/screens/PostThread/components/ThreadItemTreePost.tsx @@ -1,10 +1,6 @@ import {memo, useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedThreadgate, - AtUri, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans} from '@lingui/react/macro' @@ -12,7 +8,6 @@ import {MAX_POST_LINES} from '#/lib/constants' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {makeProfileLink} from '#/lib/routes/links' import {countLines} from '#/lib/strings/helpers' -import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import { POST_TOMBSTONE, type Shadow, @@ -51,6 +46,7 @@ import {RichText} from '#/components/RichText' import * as Skele from '#/components/Skeleton' import {SubtleHover} from '#/components/SubtleHover' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' /** * Mimic the space in PostMeta @@ -69,7 +65,7 @@ export function ThreadItemTreePost({ topBorder?: boolean } onPostSuccess?: (data: OnPostSuccessData) => void - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main }) { const postShadow = usePostShadow(item.value.post) @@ -252,13 +248,13 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({ threadgateRecord, }: { item: Extract - postShadow: Shadow + postShadow: Shadow overrides?: { moderation?: boolean topBorder?: boolean } onPostSuccess?: (data: OnPostSuccessData) => void - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main }): React.ReactNode { const {openComposer} = useOpenComposer() const {currentAccount} = useSession() @@ -272,7 +268,7 @@ const ThreadItemTreePostInner = memo(function ThreadItemTreePostInner({ () => new RichTextAPI({ text: record.text, - facets: asSdkFacets(record.facets), + facets: record.facets, }), [record], ) diff --git a/src/screens/Profile/Header/DisplayName.tsx b/src/screens/Profile/Header/DisplayName.tsx index ac99ba82b4..b9a0fca221 100644 --- a/src/screens/Profile/Header/DisplayName.tsx +++ b/src/screens/Profile/Header/DisplayName.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {sanitizeDisplayName} from '#/lib/strings/display-names' @@ -8,12 +7,13 @@ import {type Shadow} from '#/state/cache/types' import {atoms as a, platform, useBreakpoints, useTheme} from '#/alf' import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' export function ProfileHeaderDisplayName({ profile, moderation, }: { - profile: Shadow + profile: Shadow moderation: ModerationDecision }) { const t = useTheme() diff --git a/src/screens/Profile/Header/EditProfileDialog.tsx b/src/screens/Profile/Header/EditProfileDialog.tsx index 1715328f54..d110753143 100644 --- a/src/screens/Profile/Header/EditProfileDialog.tsx +++ b/src/screens/Profile/Header/EditProfileDialog.tsx @@ -1,6 +1,5 @@ import {useCallback, useEffect, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' @@ -25,13 +24,14 @@ import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useSimpleVerificationState} from '#/components/verification' +import {type app} from '#/lexicons' export function EditProfileDialog({ profile, control, onUpdate, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed control: Dialog.DialogControlProps onUpdate?: () => void }) { @@ -89,7 +89,7 @@ function DialogInner({ setDirty, onPressCancel, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed onUpdate?: () => void setDirty: (dirty: boolean) => void onPressCancel: () => void diff --git a/src/screens/Profile/Header/Handle.tsx b/src/screens/Profile/Header/Handle.tsx index f659b923cd..33e7cbcb8b 100644 --- a/src/screens/Profile/Header/Handle.tsx +++ b/src/screens/Profile/Header/Handle.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -10,12 +9,13 @@ import {atoms as a, useTheme, web} from '#/alf' import {NewskieDialog} from '#/components/NewskieDialog' import {Text} from '#/components/Typography' import {IS_IOS, IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' export function ProfileHeaderHandle({ profile, disableTaps, }: { - profile: Shadow + profile: Shadow disableTaps?: boolean }) { const t = useTheme() diff --git a/src/screens/Profile/Header/Metrics.tsx b/src/screens/Profile/Header/Metrics.tsx index 45f09597bc..84d25f761e 100644 --- a/src/screens/Profile/Header/Metrics.tsx +++ b/src/screens/Profile/Header/Metrics.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {msg, plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -9,11 +8,12 @@ import {formatCount} from '#/view/com/util/numeric/format' import {atoms as a, useTheme} from '#/alf' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' export function ProfileHeaderMetrics({ profile, }: { - profile: Shadow + profile: Shadow }) { const t = useTheme() const {_, i18n} = useLingui() diff --git a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx index a7979f17ae..d5341e2ad7 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -1,7 +1,6 @@ import {memo, useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, 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' @@ -11,7 +10,6 @@ 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' @@ -42,7 +40,7 @@ import {ProfileHeaderMetrics} from './Metrics' import {ProfileHeaderShell} from './Shell' interface Props { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed labeler: app.bsky.labeler.defs.LabelerViewDetailed descriptionRT: RichTextAPI | null moderationOpts: ModerationOpts @@ -58,7 +56,7 @@ let ProfileHeaderLabeler = ({ hideBackButton = false, isPlaceholderProfile, }: Props): React.ReactNode => { - const profile: Shadow = + const profile: Shadow = useProfileShadow(profileUnshadowed) const t = useTheme() const ax = useAnalytics() @@ -232,7 +230,7 @@ export function HeaderLabelerButtons({ profile, minimal = false, }: { - profile: Shadow + profile: Shadow /** disable the subscribe button */ minimal?: boolean }) { diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx index b7cfff7ab9..b8cba7c4ce 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -1,7 +1,7 @@ import {memo, useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import { + moderateProfile, type ModerationDecision, type ModerationOpts, } from '@bsky.app/sdk/moderation' @@ -11,7 +11,6 @@ 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' @@ -40,6 +39,7 @@ import {useAnalytics} from '#/analytics' import {IS_IOS, IS_NATIVE} from '#/env' import {InviteFriendsDialog} from '#/features/inviteFriends' import {useActorStatus} from '#/features/liveNow' +import {type app} from '#/lexicons' import {GermButton} from '../components/GermButton' import {ProfileHeaderDisplayName} from './DisplayName' import {EditProfileDialog} from './EditProfileDialog' @@ -49,7 +49,7 @@ import {ProfileHeaderShell} from './Shell' import {ProfileHeaderSuggestedFollows} from './SuggestedFollows' interface Props { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed descriptionRT: RichTextAPI | null moderationOpts: ModerationOpts hideBackButton?: boolean @@ -64,7 +64,7 @@ let ProfileHeaderStandard = ({ isPlaceholderProfile, }: Props): React.ReactNode => { const profile = - useProfileShadow(profileUnshadowed) + useProfileShadow(profileUnshadowed) const {currentAccount} = useSession() const {_} = useLingui() const moderation = useMemo( @@ -213,7 +213,7 @@ export function HeaderStandardButtons({ onUnfollow, minimal, }: { - profile: Shadow + profile: Shadow moderation: ModerationDecision moderationOpts: ModerationOpts onFollow?: () => void diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx index 4521ba9edf..46ea78c9a0 100644 --- a/src/screens/Profile/Header/Shell.tsx +++ b/src/screens/Profile/Header/Shell.tsx @@ -5,7 +5,6 @@ import Animated, { useAnimatedRef, } from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' -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' @@ -32,12 +31,13 @@ import {useActorStatus} from '#/features/liveNow' import {EditLiveDialog} from '#/features/liveNow/components/EditLiveDialog' import {LiveIndicator} from '#/features/liveNow/components/LiveIndicator' import {LiveStatusDialog} from '#/features/liveNow/components/LiveStatusDialog' +import {type app} from '#/lexicons' import {GrowableAvatar} from './GrowableAvatar' import {GrowableBanner} from './GrowableBanner' import {StatusBarShadow} from './StatusBarShadow' interface Props { - profile: Shadow + profile: Shadow moderation: ModerationDecision hideBackButton?: boolean isPlaceholderProfile?: boolean diff --git a/src/screens/Profile/Header/index.tsx b/src/screens/Profile/Header/index.tsx index eff65c854d..dfd84b3530 100644 --- a/src/screens/Profile/Header/index.tsx +++ b/src/screens/Profile/Header/index.tsx @@ -7,12 +7,10 @@ import Animated, { } from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {scheduleOnRN} from 'react-native-worklets' -import {type AppBskyActorDefs} from '@atproto/api' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, 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' @@ -58,7 +56,7 @@ ProfileHeaderLoading = memo(ProfileHeaderLoading) export {ProfileHeaderLoading} interface Props { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed labeler: app.bsky.labeler.defs.LabelerViewDetailed | undefined descriptionRT: RichTextAPI | null moderationOpts: ModerationOpts @@ -103,7 +101,7 @@ const MinimalHeader = memo(function MinimalHeader({ hideBackButton = false, }: { onLayout: (e: LayoutChangeEvent) => void - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed labeler?: app.bsky.labeler.defs.LabelerViewDetailed hideBackButton?: boolean }) { diff --git a/src/screens/Profile/KnownFollowers.tsx b/src/screens/Profile/KnownFollowers.tsx index 09c1034b07..5ccc70c16f 100644 --- a/src/screens/Profile/KnownFollowers.tsx +++ b/src/screens/Profile/KnownFollowers.tsx @@ -1,5 +1,4 @@ import {useMemo, useState} from 'react' -import {type AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -17,12 +16,13 @@ import {List} from '#/view/com/util/List' import {ViewHeader} from '#/view/com/util/ViewHeader' import * as Layout from '#/components/Layout' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {type app} from '#/lexicons' function renderItem({ item, index, }: { - item: AppBskyActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number }) { return ( @@ -34,7 +34,7 @@ function renderItem({ ) } -function keyExtractor(item: AppBskyActorDefs.ProfileView) { +function keyExtractor(item: app.bsky.actor.defs.ProfileView) { return item.did } diff --git a/src/screens/Profile/components/GermButton.tsx b/src/screens/Profile/components/GermButton.tsx index 5a69ec42c9..f79c503251 100644 --- a/src/screens/Profile/components/GermButton.tsx +++ b/src/screens/Profile/components/GermButton.tsx @@ -1,6 +1,5 @@ import {Platform, View} from 'react-native' import {Image} from 'expo-image' -import {type AppBskyActorDefs} from '@atproto/api' import {type Client} from '@atproto/lex' import {type DidString} from '@atproto/syntax' import {msg} from '@lingui/core/macro' @@ -29,7 +28,7 @@ export function GermButton({ germ, profile, }: { - germ: AppBskyActorDefs.ProfileAssociatedGerm + germ: app.bsky.actor.defs.ProfileAssociatedGerm profile: bsky.profile.AnyProfileView }) { const t = useTheme() @@ -279,7 +278,7 @@ function GermSelfButton({did}: {did: string}) { } function constructGermUrl( - declaration: AppBskyActorDefs.ProfileAssociatedGerm, + declaration: app.bsky.actor.defs.ProfileAssociatedGerm, profile: bsky.profile.AnyProfileView, viewerDid?: string, ) { diff --git a/src/screens/ProfileList/AboutSection.tsx b/src/screens/ProfileList/AboutSection.tsx index d7f6be5c33..6ec615c4c0 100644 --- a/src/screens/ProfileList/AboutSection.tsx +++ b/src/screens/ProfileList/AboutSection.tsx @@ -1,6 +1,5 @@ import {useCallback, useImperativeHandle, useState} from 'react' import {View} from 'react-native' -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -15,6 +14,7 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' interface SectionRef { scrollToTop: () => void @@ -22,7 +22,7 @@ interface SectionRef { interface AboutSectionProps { ref?: React.Ref - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView onPressAddUser: () => void headerHeight: number scrollElRef: ListRef diff --git a/src/screens/ProfileList/components/Header.tsx b/src/screens/ProfileList/components/Header.tsx index 55f572c554..8335cf4851 100644 --- a/src/screens/ProfileList/components/Header.tsx +++ b/src/screens/ProfileList/components/Header.tsx @@ -1,6 +1,5 @@ import {useMemo} from 'react' import {View} from 'react-native' -import {AppBskyGraphDefs} from '@atproto/api' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -8,7 +7,6 @@ import {Trans} from '@lingui/react/macro' import {useHaptics} from '#/lib/haptics' import {makeListLink} from '#/lib/routes/links' -import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import {logger} from '#/logger' import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list' import { @@ -25,6 +23,7 @@ import {Loader} from '#/components/Loader' import {RichText} from '#/components/RichText' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' import {MoreOptionsMenu} from './MoreOptionsMenu' import {SubscribeMenu} from './SubscribeMenu' @@ -34,14 +33,14 @@ export function Header({ preferences, }: { rkey: string - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView preferences: UsePreferencesQueryResponse }) { const {_} = useLingui() const ax = useAnalytics() const {currentAccount} = useSession() - const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST - const isModList = list.purpose === AppBskyGraphDefs.MODLIST + const isCurateList = list.purpose === app.bsky.graph.defs.curatelist.value + const isModList = list.purpose === app.bsky.graph.defs.modlist.value const isBlocking = !!list.viewer?.blocked const isMuting = !!list.viewer?.muted const playHaptic = useHaptics() @@ -130,7 +129,7 @@ export function Header({ list.description ? new RichTextAPI({ text: list.description, - facets: asSdkFacets(list.descriptionFacets), + facets: list.descriptionFacets, }) : undefined, [list], diff --git a/src/screens/ProfileList/components/MoreOptionsMenu.tsx b/src/screens/ProfileList/components/MoreOptionsMenu.tsx index 8bc40682d1..02d9cc7212 100644 --- a/src/screens/ProfileList/components/MoreOptionsMenu.tsx +++ b/src/screens/ProfileList/components/MoreOptionsMenu.tsx @@ -1,4 +1,4 @@ -import {type AppBskyActorDefs, AppBskyGraphDefs, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -36,13 +36,14 @@ import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {app} from '#/lexicons' export function MoreOptionsMenu({ list, savedFeedConfig, }: { - list: AppBskyGraphDefs.ListView - savedFeedConfig?: AppBskyActorDefs.SavedFeed + list: app.bsky.graph.defs.ListView + savedFeedConfig?: app.bsky.actor.defs.SavedFeed }) { const {_} = useLingui() const ax = useAnalytics() @@ -57,8 +58,8 @@ export function MoreOptionsMenu({ const {mutateAsync: muteList} = useListMuteMutation() const {mutateAsync: blockList} = useListBlockMutation() - const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST - const isModList = list.purpose === AppBskyGraphDefs.MODLIST + const isCurateList = list.purpose === app.bsky.graph.defs.curatelist.value + const isModList = list.purpose === app.bsky.graph.defs.modlist.value const isBlocking = !!list.viewer?.blocked const isMuting = !!list.viewer?.muted const isPinned = Boolean(savedFeedConfig?.pinned) diff --git a/src/screens/ProfileList/components/SubscribeMenu.tsx b/src/screens/ProfileList/components/SubscribeMenu.tsx index af16e0de0b..1553e2bc1e 100644 --- a/src/screens/ProfileList/components/SubscribeMenu.tsx +++ b/src/screens/ProfileList/components/SubscribeMenu.tsx @@ -1,4 +1,3 @@ -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -13,8 +12,9 @@ import * as Menu from '#/components/Menu' import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' -export function SubscribeMenu({list}: {list: AppBskyGraphDefs.ListView}) { +export function SubscribeMenu({list}: {list: app.bsky.graph.defs.ListView}) { const {_} = useLingui() const ax = useAnalytics() const subscribeMutePromptControl = Prompt.usePromptControl() diff --git a/src/screens/ProfileList/index.tsx b/src/screens/ProfileList/index.tsx index ef0221631d..f91e2fe896 100644 --- a/src/screens/ProfileList/index.tsx +++ b/src/screens/ProfileList/index.tsx @@ -1,8 +1,8 @@ import {useCallback, useMemo, useRef, useState} from 'react' import {View} from 'react-native' import {useAnimatedRef} from 'react-native-reanimated' -import {AppBskyGraphDefs, AtUri} from '@atproto/api' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {AtUri} from '@atproto/syntax' +import {moderateUserList, 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,7 +11,6 @@ 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, @@ -39,6 +38,7 @@ import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' import * as Hider from '#/components/moderation/Hider' import {IS_WEB} from '#/env' +import {app} from '#/lexicons' import {AboutSection} from './AboutSection' import {ErrorScreen} from './components/ErrorScreen' import {Header} from './components/Header' @@ -143,7 +143,7 @@ function ProfileListScreenLoaded({ preferences, }: Props & { uri: string - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView moderationOpts: ModerationOpts preferences: UsePreferencesQueryResponse }) { @@ -155,7 +155,7 @@ function ProfileListScreenLoaded({ const {rkey} = route.params const feedSectionRef = useRef(null) const aboutSectionRef = useRef(null) - const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST + const isCurateList = list.purpose === app.bsky.graph.defs.curatelist.value const isScreenFocused = useIsFocused() const isHidden = list.labels?.findIndex(l => l.val === '!hide') !== -1 const isOwner = currentAccount?.did === list.creator.did diff --git a/src/screens/SavedFeeds.tsx b/src/screens/SavedFeeds.tsx index db574318f3..26ea5c5e18 100644 --- a/src/screens/SavedFeeds.tsx +++ b/src/screens/SavedFeeds.tsx @@ -2,7 +2,6 @@ import {useState} from 'react' import {View} from 'react-native' import type Animated from 'react-native-reanimated' import {useAnimatedRef, useScrollOffset} from 'react-native-reanimated' -import {type AppBskyActorDefs} from '@atproto/api' import {TID} from '@atproto/common-web' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -43,6 +42,7 @@ import {InlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' type Props = NativeStackScreenProps export function SavedFeeds({}: Props) { @@ -424,10 +424,10 @@ function PinnedFeedItem({ onMoveUp, onMoveDown, }: { - feed: AppBskyActorDefs.SavedFeed - currentFeeds: AppBskyActorDefs.SavedFeed[] + feed: app.bsky.actor.defs.SavedFeed + currentFeeds: app.bsky.actor.defs.SavedFeed[] setCurrentFeeds: React.Dispatch< - React.SetStateAction + React.SetStateAction > dragHandle?: React.ReactNode index?: number @@ -507,10 +507,10 @@ function UnpinnedFeedItem({ currentFeeds, setCurrentFeeds, }: { - feed: AppBskyActorDefs.SavedFeed - currentFeeds: AppBskyActorDefs.SavedFeed[] + feed: app.bsky.actor.defs.SavedFeed + currentFeeds: app.bsky.actor.defs.SavedFeed[] setCurrentFeeds: React.Dispatch< - React.SetStateAction + React.SetStateAction > }) { const {_} = useLingui() diff --git a/src/screens/Search/Explore.tsx b/src/screens/Search/Explore.tsx index 4540da35a7..144b9343a7 100644 --- a/src/screens/Search/Explore.tsx +++ b/src/screens/Search/Explore.tsx @@ -1,10 +1,5 @@ import {useCallback, useMemo, useRef, useState} from 'react' import {View, type ViewabilityConfig} from 'react-native' -import { - type AppBskyActorDefs, - type AppBskyFeedDefs, - type AppBskyGraphDefs, -} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' import * as bcp47Match from 'bcp-47-match' @@ -68,6 +63,7 @@ import {SubtleHover} from '#/components/SubtleHover' import {Text} from '#/components/Typography' import {type Metrics, useAnalytics} from '#/analytics' import {ExploreScreenLiveEventFeedsBanner} from '#/features/liveEvents/components/ExploreScreenLiveEventFeedsBanner' +import {type app} from '#/lexicons' import * as ModuleHeader from './components/ModuleHeader' import { SuggestedAccountsTabBar, @@ -155,7 +151,7 @@ type ExploreScreenItems = | { type: 'profile' key: string - profile: AppBskyActorDefs.ProfileView + profile: app.bsky.actor.defs.ProfileView recId?: string } | { @@ -165,7 +161,7 @@ type ExploreScreenItems = | { type: 'feed' key: string - feed: AppBskyFeedDefs.GeneratorView + feed: app.bsky.feed.defs.GeneratorView } | { type: 'loadMore' @@ -191,7 +187,7 @@ type ExploreScreenItems = | { type: 'starterPack' key: string - view: AppBskyGraphDefs.StarterPackView + view: app.bsky.graph.defs.StarterPackView } | { type: 'starterPackSkeleton' diff --git a/src/screens/Search/SearchResults.tsx b/src/screens/Search/SearchResults.tsx index cb7ac53d09..300c36fd65 100644 --- a/src/screens/Search/SearchResults.tsx +++ b/src/screens/Search/SearchResults.tsx @@ -1,6 +1,5 @@ import {memo, useCallback, useMemo, useState} from 'react' import {ActivityIndicator, View} from 'react-native' -import {type AppBskyFeedDefs, type AppBskyGraphDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {urls} from '#/lib/constants' @@ -37,6 +36,7 @@ import {ListFooter} from '#/components/Lists' import {SearchError} from '#/components/SearchError' import {Text} from '#/components/Typography' import {type Metrics, useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' let SearchResults = ({ @@ -292,7 +292,7 @@ type SearchResultSlice = | { type: 'post' key: string - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView } | { type: 'loadingMore' @@ -504,7 +504,7 @@ function SearchPost({ }: { from: Metrics['search:result:press']['tab'] position: Metrics['search:result:press']['position'] - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView }) { const ax = useAnalytics() @@ -672,7 +672,7 @@ let SearchScreenFeedsResults = ({ item, index, }: { - item: AppBskyFeedDefs.GeneratorView + item: app.bsky.feed.defs.GeneratorView index: number }) => ( )} - keyExtractor={(item: AppBskyFeedDefs.GeneratorView) => item.uri} + keyExtractor={(item: app.bsky.feed.defs.GeneratorView) => item.uri} desktopFixedHeight ListFooterComponent={} /> @@ -704,7 +704,7 @@ function SearchFeedCard({ view, }: { position: number - view: AppBskyFeedDefs.GeneratorView + view: app.bsky.feed.defs.GeneratorView }) { const ax = useAnalytics() @@ -790,14 +790,14 @@ let SearchScreenStarterPackResults = ({ item, index, }: { - item: AppBskyGraphDefs.StarterPackView + item: app.bsky.graph.defs.StarterPackView index: number }) => ( )} - keyExtractor={(item: AppBskyGraphDefs.StarterPackView) => item.uri} + keyExtractor={(item: app.bsky.graph.defs.StarterPackView) => item.uri} refreshing={isPTR} onRefresh={() => void onPullToRefresh()} onEndReached={onEndReached} @@ -824,7 +824,7 @@ function SearchStarterPack({ view, }: { position: number - view: AppBskyGraphDefs.StarterPackView + view: app.bsky.graph.defs.StarterPackView }) { const ax = useAnalytics() diff --git a/src/screens/Search/components/ModuleHeader.tsx b/src/screens/Search/components/ModuleHeader.tsx index ce40af98b8..a0e02f6b3a 100644 --- a/src/screens/Search/components/ModuleHeader.tsx +++ b/src/screens/Search/components/ModuleHeader.tsx @@ -1,6 +1,6 @@ import {useMemo} from 'react' import {View} from 'react-native' -import {type AppBskyFeedDefs, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {PressableScale} from '#/lib/custom-animations/PressableScale' import {makeCustomFeedLink} from '#/lib/routes/links' @@ -14,6 +14,7 @@ import {MagnifyingGlass_Stroke2_Corner0_Rounded as SearchIcon} from '#/component import {Link} from '#/components/Link' import {Text, type TextProps} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' export function Container({ style, @@ -46,7 +47,7 @@ export function FeedLink({ feed, children, }: { - feed: AppBskyFeedDefs.GeneratorView + feed: app.bsky.feed.defs.GeneratorView children?: React.ReactNode }) { const t = useTheme() @@ -75,7 +76,7 @@ export function FeedLink({ ) } -export function FeedAvatar({feed}: {feed: AppBskyFeedDefs.GeneratorView}) { +export function FeedAvatar({feed}: {feed: app.bsky.feed.defs.GeneratorView}) { return } @@ -178,7 +179,7 @@ export function EllipsisButton({ ) } -export function PinButton({feed}: {feed: AppBskyFeedDefs.GeneratorView}) { +export function PinButton({feed}: {feed: app.bsky.feed.defs.GeneratorView}) { return ( void }) { const t = useTheme() @@ -35,12 +35,7 @@ export function StarterPackCard({ const link = useStarterPackLink({view}) const record = view.record - if ( - !bsky.dangerousIsType( - record, - AppBskyGraphStarterpack.isRecord, - ) - ) { + if (!bsky.isType(app.bsky.graph.starterpack, record)) { return null } diff --git a/src/screens/Search/modules/ExploreSuggestedAccounts.tsx b/src/screens/Search/modules/ExploreSuggestedAccounts.tsx index ef6dc0cba9..ad8a8b8a72 100644 --- a/src/screens/Search/modules/ExploreSuggestedAccounts.tsx +++ b/src/screens/Search/modules/ExploreSuggestedAccounts.tsx @@ -1,6 +1,5 @@ import {memo, useEffect} from 'react' import {View} from 'react-native' -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' @@ -14,6 +13,7 @@ import {boostInterests, InterestTabs} from '#/components/InterestTabs' import * as ProfileCard from '#/components/ProfileCard' import {SubtleHover} from '#/components/SubtleHover' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' export function useLoadEnoughProfiles({ @@ -25,7 +25,7 @@ export function useLoadEnoughProfiles({ fetchNextPage, }: { interest: string | null - data?: InfiniteData + data?: InfiniteData isLoading: boolean isFetchingNextPage: boolean hasNextPage: boolean diff --git a/src/screens/Search/modules/ExploreTrendingTopics.tsx b/src/screens/Search/modules/ExploreTrendingTopics.tsx index 27356efff9..c8e0ee20ec 100644 --- a/src/screens/Search/modules/ExploreTrendingTopics.tsx +++ b/src/screens/Search/modules/ExploreTrendingTopics.tsx @@ -1,11 +1,10 @@ import {useMemo} from 'react' import {Pressable, View} from 'react-native' import {Image} from 'expo-image' -import {type AppBskyUnspeccedDefs} from '@atproto/api' +import {moderateProfile} from '@bsky.app/sdk/moderation' 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, @@ -28,6 +27,7 @@ import {SubtleHover} from '#/components/SubtleHover' import {useTrendingTopicSeen} from '#/components/TrendingTopics' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import * as ModuleHeader from '../components/ModuleHeader' const IMAGE_SIZE = 56 @@ -118,7 +118,7 @@ export function TrendRow({ children, onPress, }: ViewStyleProp & { - trend: AppBskyUnspeccedDefs.TrendView + trend: app.bsky.unspecced.defs.TrendView rank: number recId?: string children?: React.ReactNode @@ -229,7 +229,7 @@ export function TrendRow({ // Unused atm, but leaving here so we don't lose localization. -dsb export function useCategoryDisplayName( - category: AppBskyUnspeccedDefs.TrendView['category'], + category: app.bsky.unspecced.defs.TrendView['category'], ) { const {t: l} = useLingui() @@ -297,7 +297,7 @@ export function TrendingTopicRowSkeleton() { } function useModerateTrendingActors( - actors: AppBskyUnspeccedDefs.TrendView['actors'], + actors: app.bsky.unspecced.defs.TrendView['actors'], ) { const moderationOpts = useModerationOpts() diff --git a/src/screens/Search/modules/ExploreTrendingVideos.tsx b/src/screens/Search/modules/ExploreTrendingVideos.tsx index 6f173a7825..86eafbc54c 100644 --- a/src/screens/Search/modules/ExploreTrendingVideos.tsx +++ b/src/screens/Search/modules/ExploreTrendingVideos.tsx @@ -1,6 +1,6 @@ import {useMemo} from 'react' import {ScrollView, View} from 'react-native' -import {AppBskyEmbedVideo, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -21,6 +21,8 @@ import { CompactVideoPostCardPlaceholder, } from '#/components/VideoPostCard' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' const CARD_WIDTH = 100 @@ -158,7 +160,7 @@ function VideoCards({ .flatMap(page => page.slices) .map(slice => slice.items[0]) .filter(Boolean) - .filter(item => AppBskyEmbedVideo.isView(item.post.embed)) + .filter(item => bsky.isType(app.bsky.embed.video.view, item.post.embed)) .slice(0, 8) }, [data]) const href = useMemo(() => { diff --git a/src/screens/Settings/ActivityPrivacySettings.tsx b/src/screens/Settings/ActivityPrivacySettings.tsx index b1d90cf9c1..06515b6efc 100644 --- a/src/screens/Settings/ActivityPrivacySettings.tsx +++ b/src/screens/Settings/ActivityPrivacySettings.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyNotificationDeclaration} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -18,6 +17,7 @@ import * as Toggle from '#/components/forms/Toggle' import {BellRinging_Stroke2_Corner0_Rounded as BellRingingIcon} from '#/components/icons/BellRinging' import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' +import {type app} from '#/lexicons' import * as SettingsList from './components/SettingsList' import {ItemTextWithSubtitle} from './NotificationSettings/components/ItemTextWithSubtitle' @@ -85,7 +85,7 @@ export function Inner({ notificationDeclaration: { uri?: string cid?: string - value: AppBskyNotificationDeclaration.Record + value: app.bsky.notification.declaration.Main } }) { const t = useTheme() diff --git a/src/screens/Settings/AutomationLabelSettings.tsx b/src/screens/Settings/AutomationLabelSettings.tsx index 423bc0073a..480f35a972 100644 --- a/src/screens/Settings/AutomationLabelSettings.tsx +++ b/src/screens/Settings/AutomationLabelSettings.tsx @@ -1,5 +1,5 @@ import {View} from 'react-native' -import {type $Typed, ComAtprotoLabelDefs} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {Trans, useLingui} from '@lingui/react/macro' import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {useQueryClient} from '@tanstack/react-query' @@ -22,6 +22,7 @@ import {Text} from '#/components/Typography' import {useSimpleVerificationState} from '#/components/verification' import {VerificationCheck} from '#/components/verification/VerificationCheck' import {useAnalytics} from '#/analytics' +import {com} from '#/lexicons' import * as bsky from '#/types/bsky' type Props = NativeStackScreenProps< @@ -53,15 +54,13 @@ export function AutomationLabelSettingsScreen({}: Props) { { profile, updates: existing => { - const labels: $Typed = bsky.validate( - existing.labels, - ComAtprotoLabelDefs.validateSelfLabels, - ) - ? existing.labels - : { - $type: 'com.atproto.label.defs#selfLabels', - values: [], - } + const labels: $Typed = + bsky.matches(com.atproto.label.defs.selfLabels, existing.labels) + ? existing.labels + : { + $type: 'com.atproto.label.defs#selfLabels', + values: [], + } const hasLabel = labels.values.some(l => l.val === 'bot') if (hasLabel) { @@ -80,8 +79,8 @@ export function AutomationLabelSettingsScreen({}: Props) { return existing }, - checkCommitted: res => { - const exists = !!res.data.labels?.some(l => l.val === 'bot') + checkCommitted: profile => { + const exists = !!profile.labels?.some(l => l.val === 'bot') return exists === wasAdded }, }, diff --git a/src/screens/Settings/FindContactsSettings.tsx b/src/screens/Settings/FindContactsSettings.tsx index 1fb0f3b2a8..9574c3a759 100644 --- a/src/screens/Settings/FindContactsSettings.tsx +++ b/src/screens/Settings/FindContactsSettings.tsx @@ -1,7 +1,6 @@ import {useCallback, useEffect, useState} from 'react' import {type ListRenderItemInfo, View} from 'react-native' import * as Contacts from 'expo-contacts' -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' @@ -189,7 +188,7 @@ function SyncStatus({ info, refetchStatus, }: { - info: AppBskyContactDefs.SyncStatus + info: app.bsky.contact.defs.SyncStatus refetchStatus: () => Promise }) { const ax = useAnalytics() diff --git a/src/screens/Settings/PrivacyAndSecuritySettings.tsx b/src/screens/Settings/PrivacyAndSecuritySettings.tsx index 2979ace213..9d2a7c8a11 100644 --- a/src/screens/Settings/PrivacyAndSecuritySettings.tsx +++ b/src/screens/Settings/PrivacyAndSecuritySettings.tsx @@ -1,4 +1,3 @@ -import {type AppBskyNotificationDeclaration} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -17,6 +16,7 @@ import {Key_Stroke2_Corner2_Rounded as KeyIcon} from '#/components/icons/Key' import {ShieldCheck_Stroke2_Corner0_Rounded as ShieldIcon} from '#/components/icons/Shield' import * as Layout from '#/components/Layout' import {InlineLinkText} from '#/components/Link' +import {type app} from '#/lexicons' import {Email2FAToggle} from './components/Email2FAToggle' import {PwiOptOut} from './components/PwiOptOut' import {ItemTextWithSubtitle} from './NotificationSettings/components/ItemTextWithSubtitle' @@ -146,7 +146,7 @@ function NotificationDeclaration({ isError, }: { data?: { - value: AppBskyNotificationDeclaration.Record + value: app.bsky.notification.declaration.Main } isError?: boolean }) { diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index 0fff031f87..fdb77c9908 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -1,8 +1,8 @@ import {useState} from 'react' import {Alert, LayoutAnimation, Linking, Pressable, View} from 'react-native' import {useReducedMotion} from 'react-native-reanimated' -import {type AppBskyActorDefs} from '@atproto/api' import {removeNuxs} from '@bsky.app/sdk' +import {moderateProfile} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {type NativeStackScreenProps} from '@react-navigation/native-stack' @@ -10,7 +10,6 @@ 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, @@ -69,6 +68,7 @@ import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_INTERNAL, IS_IOS, IS_NATIVE} from '#/env' import {useActorStatus} from '#/features/liveNow' +import {type app} from '#/lexicons' import {device, useStorage} from '#/storage' import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscriptions-nudged' @@ -321,7 +321,7 @@ export function SettingsScreen({}: Props) { function ProfilePreview({ profile, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed }) { const t = useTheme() const {gtMobile} = useBreakpoints() @@ -608,7 +608,7 @@ function AccountRow({ pendingDid, onPressSwitchAccount, }: { - profile?: AppBskyActorDefs.ProfileViewDetailed + profile?: app.bsky.actor.defs.ProfileViewDetailed account: SessionAccount pendingDid: string | null onPressSwitchAccount: ( diff --git a/src/screens/Settings/components/ChangeHandleDialog.tsx b/src/screens/Settings/components/ChangeHandleDialog.tsx index 8cff915390..b519d0322a 100644 --- a/src/screens/Settings/components/ChangeHandleDialog.tsx +++ b/src/screens/Settings/components/ChangeHandleDialog.tsx @@ -10,7 +10,6 @@ import Animated, { SlideOutLeft, SlideOutRight, } from 'react-native-reanimated' -import {type ComAtprotoServerDescribeServer} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -46,6 +45,7 @@ import {InlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {useSimpleVerificationState} from '#/components/verification' +import {type com} from '#/lexicons' import {CopyButton} from './CopyButton' export function ChangeHandleDialog({ @@ -147,7 +147,7 @@ function ProvidedHandlePage({ serviceInfo, goToOwnHandle, }: { - serviceInfo: ComAtprotoServerDescribeServer.OutputSchema + serviceInfo: com.atproto.server.describeServer.$OutputBody goToOwnHandle: () => void }) { const {_} = useLingui() diff --git a/src/screens/Settings/components/PwiOptOut.tsx b/src/screens/Settings/components/PwiOptOut.tsx index 193f26994e..826b427e31 100644 --- a/src/screens/Settings/components/PwiOptOut.tsx +++ b/src/screens/Settings/components/PwiOptOut.tsx @@ -1,6 +1,6 @@ import {useCallback} from 'react' import {View} from 'react-native' -import {type $Typed, ComAtprotoLabelDefs} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -13,6 +13,7 @@ import {useSession} from '#/state/session' import {atoms as a, useTheme} from '#/alf' import * as Toggle from '#/components/forms/Toggle' import {Text} from '#/components/Typography' +import {com} from '#/lexicons' import * as bsky from '#/types/bsky' export function PwiOptOut() { @@ -35,9 +36,9 @@ export function PwiOptOut() { profile, updates: existing => { // create labels attr if needed - const labels: $Typed = bsky.validate( + const labels: $Typed = bsky.matches( + com.atproto.label.defs.selfLabels, existing.labels, - ComAtprotoLabelDefs.validateSelfLabels, ) ? existing.labels : { @@ -68,8 +69,8 @@ export function PwiOptOut() { return existing }, - checkCommitted: res => { - const exists = !!res.data.labels?.some( + checkCommitted: profile => { + const exists = !!profile.labels?.some( l => l.val === '!no-unauthenticated', ) return exists === wasAdded diff --git a/src/screens/Signup/StepHandle/HandleSuggestions/index.tsx b/src/screens/Signup/StepHandle/HandleSuggestions/index.tsx index 0efe113d09..0e9da8a306 100644 --- a/src/screens/Signup/StepHandle/HandleSuggestions/index.tsx +++ b/src/screens/Signup/StepHandle/HandleSuggestions/index.tsx @@ -1,13 +1,13 @@ -import {type ComAtprotoTempCheckHandleAvailability} from '@atproto/api' import {Sift, SiftItem} from '@bsky.app/sift' import {Trans, useLingui} from '@lingui/react/macro' import {atoms as a, useTheme} from '#/alf' import {Portal} from '#/components/Portal' import {Text} from '#/components/Typography' +import {type com} from '#/lexicons' import {type HandleSuggestionsProps} from './shared' -type Suggestion = ComAtprotoTempCheckHandleAvailability.Suggestion & { +type Suggestion = com.atproto.temp.checkHandleAvailability.Suggestion & { key: string } diff --git a/src/screens/Signup/StepHandle/HandleSuggestions/shared.ts b/src/screens/Signup/StepHandle/HandleSuggestions/shared.ts index 38644bdb93..a1a5e168e0 100644 --- a/src/screens/Signup/StepHandle/HandleSuggestions/shared.ts +++ b/src/screens/Signup/StepHandle/HandleSuggestions/shared.ts @@ -1,10 +1,11 @@ -import {type ComAtprotoTempCheckHandleAvailability} from '@atproto/api' import {type UseSiftReturn} from '@bsky.app/sift' +import {type com} from '#/lexicons' + export type HandleSuggestionsProps = { - suggestions: ComAtprotoTempCheckHandleAvailability.Suggestion[] + suggestions: com.atproto.temp.checkHandleAvailability.Suggestion[] onSelect: ( - suggestion: ComAtprotoTempCheckHandleAvailability.Suggestion, + suggestion: com.atproto.temp.checkHandleAvailability.Suggestion, ) => void /** * Web only: the Sift instance shared with the handle input. It carries the diff --git a/src/screens/Signup/StepInfo/Policies.tsx b/src/screens/Signup/StepInfo/Policies.tsx index 9d7b7e9ff1..c84e0d0404 100644 --- a/src/screens/Signup/StepInfo/Policies.tsx +++ b/src/screens/Signup/StepInfo/Policies.tsx @@ -1,6 +1,5 @@ import {type ReactElement} from 'react' import {View} from 'react-native' -import {type ComAtprotoServerDescribeServer} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -9,11 +8,12 @@ import {atoms as a, useTheme} from '#/alf' import {Admonition} from '#/components/Admonition' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' +import {type com} from '#/lexicons' export const Policies = ({ serviceDescription, }: { - serviceDescription: ComAtprotoServerDescribeServer.OutputSchema + serviceDescription: com.atproto.server.describeServer.$OutputBody }) => { const t = useTheme() const {_} = useLingui() diff --git a/src/screens/Signup/index.tsx b/src/screens/Signup/index.tsx index bcdca45814..644af956a2 100644 --- a/src/screens/Signup/index.tsx +++ b/src/screens/Signup/index.tsx @@ -3,7 +3,6 @@ import {AppState, type AppStateStatus, View} from 'react-native' import ReactNativeDeviceAttest from 'react-native-device-attest' import {KeyboardAvoidingView} from 'react-native-keyboard-controller' import Animated, {FadeIn, LayoutAnimationConfig} from 'react-native-reanimated' -import {AppBskyGraphStarterpack} from '@atproto/api' import {tokens} from '@bsky.app/alf' import {Trans, useLingui} from '@lingui/react/macro' @@ -32,6 +31,7 @@ import {ScreenTransition} from '#/components/ScreenTransition' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {GCP_PROJECT_ID, IS_ANDROID} from '#/env' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' export function Signup({onPressBack}: {onPressBack: () => void}) { @@ -141,10 +141,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) { scrollable> {showStarterPackCard && - bsky.dangerousIsType( - starterPack.record, - AppBskyGraphStarterpack.isRecord, - ) ? ( + bsky.isType(app.bsky.graph.starterpack, starterPack.record) ? ( { if (isErrorStarterPack || (starterPack && !isValid)) { @@ -82,12 +83,7 @@ export function LandingScreen({ } // Just for types, this cannot be hit - if ( - !bsky.dangerousIsType( - starterPack.record, - AppBskyGraphStarterpack.isRecord, - ) - ) { + if (!bsky.isType(app.bsky.graph.starterpack, starterPack.record)) { return null } @@ -109,8 +105,8 @@ function LandingScreenLoaded({ moderationOpts, }: { - starterPack: AppBskyGraphDefs.StarterPackView - starterPackRecord: AppBskyGraphStarterpack.Record + starterPack: app.bsky.graph.defs.StarterPackView + starterPackRecord: app.bsky.graph.starterpack.Main setScreenState: (state: LoggedOutScreenState) => void moderationOpts: ModerationOpts }) { diff --git a/src/screens/StarterPack/StarterPackScreen.tsx b/src/screens/StarterPack/StarterPackScreen.tsx index e392f395db..658241c660 100644 --- a/src/screens/StarterPack/StarterPackScreen.tsx +++ b/src/screens/StarterPack/StarterPackScreen.tsx @@ -1,8 +1,7 @@ import {useCallback, useEffect, useState} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' -import {AppBskyGraphDefs, AppBskyGraphStarterpack, AtUri} from '@atproto/api' -import {type AtUriString} from '@atproto/syntax' +import {AtUri} 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' @@ -21,7 +20,6 @@ import { type NavigationProp, } from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' -import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import {getStarterPackOgCard} from '#/lib/strings/starter-pack' import {logger} from '#/logger' import {updateProfileShadow} from '#/state/cache/profile-shadow' @@ -75,6 +73,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' type StarterPackScreeProps = NativeStackScreenProps< @@ -147,8 +146,8 @@ export function StarterPackScreenInner({ const isValid = starterPack && (starterPack.list || starterPack?.creator?.did === currentAccount?.did) && - AppBskyGraphDefs.validateStarterPackView(starterPack) && - AppBskyGraphStarterpack.validateRecord(starterPack.record) + bsky.matches(app.bsky.graph.defs.starterPackView, starterPack) && + bsky.matches(app.bsky.graph.starterpack, starterPack.record) if (!did || !starterPack || !isValid || !moderationOpts) { return ( @@ -179,7 +178,7 @@ function StarterPackScreenLoaded({ routeParams, moderationOpts, }: { - starterPack: AppBskyGraphDefs.StarterPackView + starterPack: app.bsky.graph.defs.StarterPackView routeParams: StarterPackScreeProps['route']['params'] moderationOpts: ModerationOpts }) { @@ -301,7 +300,7 @@ function Header({ routeParams, onOpenShareDialog, }: { - starterPack: AppBskyGraphDefs.StarterPackView + starterPack: app.bsky.graph.defs.StarterPackView routeParams: StarterPackScreeProps['route']['params'] onOpenShareDialog: () => void }) { @@ -350,7 +349,7 @@ function Header({ setIsProcessing(true) - let listItems: AppBskyGraphDefs.ListItemView[] = [] + let listItems: app.bsky.graph.defs.ListItemView[] = [] try { listItems = await getAllListMembers(appviewClient, starterPack.list.uri) } catch (e) { @@ -378,7 +377,7 @@ function Header({ try { followUris = await bulkWriteFollows(pdsClient, appviewClient, dids, { // the starter pack view is still legacy-typed - uri: starterPack.uri as AtUriString, + uri: starterPack.uri, cid: starterPack.cid, }) } catch (e) { @@ -406,19 +405,14 @@ function Header({ }) } - if ( - !bsky.dangerousIsType( - record, - AppBskyGraphStarterpack.isRecord, - ) - ) { + if (!bsky.isType(app.bsky.graph.starterpack, record)) { return null } const richText = record.description ? new RichTextAPI({ text: record.description, - facets: asSdkFacets(record.descriptionFacets), + facets: record.descriptionFacets, }) : undefined @@ -522,7 +516,7 @@ function OverflowMenu({ routeParams, onOpenShareDialog, }: { - starterPack: AppBskyGraphDefs.StarterPackView + starterPack: app.bsky.graph.defs.StarterPackView routeParams: StarterPackScreeProps['route']['params'] onOpenShareDialog: () => void }) { diff --git a/src/screens/StarterPack/Wizard/State.tsx b/src/screens/StarterPack/Wizard/State.tsx index 0ec8700057..b28a0bd728 100644 --- a/src/screens/StarterPack/Wizard/State.tsx +++ b/src/screens/StarterPack/Wizard/State.tsx @@ -1,13 +1,9 @@ import {createContext, useContext, useReducer} from 'react' -import { - type AppBskyFeedDefs, - type AppBskyGraphDefs, - AppBskyGraphStarterpack, -} from '@atproto/api' import {msg, plural} from '@lingui/core/macro' import {STARTER_PACK_MAX_SIZE} from '#/lib/constants' import * as Toast from '#/components/Toast' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' const steps = ['Details', 'Profiles', 'Feeds'] as const @@ -21,7 +17,7 @@ type Action = | {type: 'SetDescription'; description: string} | {type: 'AddProfile'; profile: bsky.profile.AnyProfileView} | {type: 'RemoveProfile'; profileDid: string} - | {type: 'AddFeed'; feed: AppBskyFeedDefs.GeneratorView} + | {type: 'AddFeed'; feed: app.bsky.feed.defs.GeneratorView} | {type: 'RemoveFeed'; feedUri: string} | {type: 'SetProcessing'; processing: boolean} | {type: 'SetError'; error: string} @@ -32,7 +28,7 @@ interface State { name?: string description?: string profiles: bsky.profile.AnyProfileView[] - feeds: AppBskyFeedDefs.GeneratorView[] + feeds: app.bsky.feed.defs.GeneratorView[] processing: boolean error?: string transitionDirection: 'Backward' | 'Forward' @@ -125,8 +121,8 @@ export function Provider({ targetProfile, children, }: { - starterPack?: AppBskyGraphDefs.StarterPackView - listItems?: AppBskyGraphDefs.ListItemView[] + starterPack?: app.bsky.graph.defs.StarterPackView + listItems?: app.bsky.graph.defs.ListItemView[] targetProfile: bsky.profile.AnyProfileView children: React.ReactNode }) { @@ -135,7 +131,7 @@ export function Provider({ if ( starterPack && - bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord) + bsky.matches(app.bsky.graph.starterpack, starterPack.record) ) { return { canNext: true, diff --git a/src/screens/StarterPack/Wizard/StepFeeds.tsx b/src/screens/StarterPack/Wizard/StepFeeds.tsx index 183f9fb843..788f740765 100644 --- a/src/screens/StarterPack/Wizard/StepFeeds.tsx +++ b/src/screens/StarterPack/Wizard/StepFeeds.tsx @@ -1,7 +1,6 @@ import {useState} from 'react' import {type ListRenderItemInfo, View} from 'react-native' import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' -import {type AppBskyFeedDefs} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import {Trans} from '@lingui/react/macro' @@ -21,8 +20,9 @@ import {Loader} from '#/components/Loader' import {ScreenTransition} from '#/components/ScreenTransition' import {WizardFeedCard} from '#/components/StarterPack/Wizard/WizardListCard' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' -function keyExtractor(item: AppBskyFeedDefs.GeneratorView) { +function keyExtractor(item: app.bsky.feed.defs.GeneratorView) { return item.uri } @@ -37,7 +37,7 @@ export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) { useSavedFeeds() const savedFeeds = savedFeedsAndLists?.feeds .filter(f => f.type === 'feed' && f.view.uri !== DISCOVER_FEED_URI) - .map(f => f.view) as AppBskyFeedDefs.GeneratorView[] + .map(f => f.view) as app.bsky.feed.defs.GeneratorView[] const { data: popularFeedsPages, @@ -67,7 +67,7 @@ export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) { const renderItem = ({ item, - }: ListRenderItemInfo) => { + }: ListRenderItemInfo) => { return ( void @@ -625,7 +621,7 @@ function Footer({ } function getName( - item: bsky.profile.AnyProfileView | AppBskyFeedDefs.GeneratorView, + item: bsky.profile.AnyProfileView | app.bsky.feed.defs.GeneratorView, ) { if (typeof item.displayName === 'string') { return enforceLen(sanitizeDisplayName(item.displayName), 28, true) diff --git a/src/screens/Takendown.tsx b/src/screens/Takendown.tsx index 5873b00473..9843e9337d 100644 --- a/src/screens/Takendown.tsx +++ b/src/screens/Takendown.tsx @@ -2,7 +2,6 @@ import {useState} from 'react' import {View} from 'react-native' import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {ToolsOzoneReportDefs} from '@atproto/api' import {type DidString} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -25,7 +24,7 @@ import {SimpleInlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import {P, Text} from '#/components/Typography' import {IS_WEB} from '#/env' -import {com} from '#/lexicons' +import {com, tools} from '#/lexicons' const COL_WIDTH = 400 @@ -55,7 +54,7 @@ export function Takendown() { await client.call( com.atproto.moderation.createReport, { - reasonType: ToolsOzoneReportDefs.REASONAPPEAL, + reasonType: tools.ozone.report.defs.reasonAppeal.value, subject: { $type: 'com.atproto.admin.defs#repoRef', // the persisted account did is already resolved diff --git a/src/screens/Topic.tsx b/src/screens/Topic.tsx index 8336cb1356..6e3be7c2a7 100644 --- a/src/screens/Topic.tsx +++ b/src/screens/Topic.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {type ListRenderItemInfo, View} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {type NativeStackScreenProps} from '@react-navigation/native-stack' @@ -21,12 +20,15 @@ import {Button, ButtonIcon} from '#/components/Button' import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as Share} from '#/components/icons/ArrowOutOfBox' import * as Layout from '#/components/Layout' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {type app} from '#/lexicons' -const renderItem = ({item}: ListRenderItemInfo) => { +const renderItem = ({ + item, +}: ListRenderItemInfo) => { return } -const keyExtractor = (item: AppBskyFeedDefs.PostView, index: number) => { +const keyExtractor = (item: app.bsky.feed.defs.PostView, index: number) => { return `${item.uri}-${index}` } diff --git a/src/screens/VideoFeed/index.tsx b/src/screens/VideoFeed/index.tsx index 9f4771ddce..0f8f8d1a39 100644 --- a/src/screens/VideoFeed/index.tsx +++ b/src/screens/VideoFeed/index.tsx @@ -25,12 +25,7 @@ import {useEvent, useEventListener} from 'expo' import {Image, type ImageStyle} from 'expo-image' import {LinearGradient} from 'expo-linear-gradient' import {createVideoPlayer, type VideoPlayer, VideoView} from 'expo-video' -import { - AppBskyEmbedVideo, - type AppBskyFeedDefs, - AppBskyFeedPost, - AtUri, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {Trans, useLingui} from '@lingui/react/macro' @@ -58,7 +53,6 @@ import { import {sanitizeDisplayName} from '#/lib/strings/display-names' import {cleanError} from '#/lib/strings/errors' import {sanitizeHandle} from '#/lib/strings/handles' -import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import {logger} from '#/logger' import {useA11y} from '#/state/a11y' import { @@ -104,6 +98,7 @@ import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_ANDROID} from '#/env' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' import {Scrubber, VIDEO_PLAYER_BOTTOM_INSET} from './components/Scrubber' @@ -177,8 +172,8 @@ type CurrentSource = { type VideoItem = { moderation: ModerationDecision - post: AppBskyFeedDefs.PostView - video: AppBskyEmbedVideo.View + post: app.bsky.feed.defs.PostView + video: app.bsky.embed.video.View feedContext: string | undefined reqId: string | undefined } @@ -216,8 +211,8 @@ function Feed() { const items: { _reactKey: string moderation: ModerationDecision - post: AppBskyFeedDefs.PostView - video: AppBskyEmbedVideo.View + post: app.bsky.feed.defs.PostView + video: app.bsky.embed.video.View feedContext: string | undefined reqId: string | undefined }[] = [] @@ -225,7 +220,10 @@ function Feed() { const feedPost = slice.items.find( item => item.uri === slice.feedPostUri, ) - if (feedPost && AppBskyEmbedVideo.isView(feedPost.post.embed)) { + if ( + feedPost && + bsky.isType(app.bsky.embed.video.view, feedPost.post.embed) + ) { items.push({ _reactKey: feedPost._reactKey, moderation: feedPost.moderation, @@ -294,14 +292,14 @@ function Feed() { const prevPost = prevSlice?.post const prevEmbed = prevPost?.embed const prevVideo = - prevEmbed && AppBskyEmbedVideo.isView(prevEmbed) + prevEmbed && bsky.isType(app.bsky.embed.video.view, prevEmbed) ? prevEmbed.playlist : null const currSlice = videos.at(index) const currPost = currSlice?.post const currEmbed = currPost?.embed const currVideo = - currEmbed && AppBskyEmbedVideo.isView(currEmbed) + currEmbed && bsky.isType(app.bsky.embed.video.view, currEmbed) ? currEmbed.playlist : null const currVideoModeration = currSlice?.moderation @@ -309,7 +307,7 @@ function Feed() { const nextPost = nextSlice?.post const nextEmbed = nextPost?.embed const nextVideo = - nextEmbed && AppBskyEmbedVideo.isView(nextEmbed) + nextEmbed && bsky.isType(app.bsky.embed.video.view, nextEmbed) ? nextEmbed.playlist : null @@ -479,8 +477,8 @@ let VideoItem = ({ reqId, }: { player?: VideoPlayer - post: AppBskyFeedDefs.PostView - embed: AppBskyEmbedVideo.View + post: app.bsky.feed.defs.PostView + embed: app.bsky.embed.video.View active: boolean adjacent: boolean scrollGesture: NativeGesture @@ -590,7 +588,7 @@ function VideoItemInner({ active, }: { player: VideoPlayer - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View active: boolean }) { const {bottom} = useSafeAreaInsets() @@ -707,7 +705,7 @@ function ModerationOverlay({ embed, onPressShow, }: { - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View onPressShow: () => void }) { const {t: l} = useLingui() @@ -805,8 +803,8 @@ function Overlay({ reqId, }: { player?: VideoPlayer - post: Shadow - embed: AppBskyEmbedVideo.View + post: Shadow + embed: app.bsky.embed.video.View active: boolean scrollGesture: NativeGesture moderation: ModerationDecision @@ -827,15 +825,12 @@ function Overlay({ ) const rkey = new AtUri(post.uri).rkey - const record = bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) + const record = bsky.isType(app.bsky.feed.post, post.record) ? post.record : undefined const richText = new RichTextAPI({ text: record?.text || '', - facets: asSdkFacets(record?.facets), + facets: record?.facets, }) const handle = sanitizeHandle(post.author.handle, '@') @@ -1090,7 +1085,7 @@ function VideoItemPlaceholder({ style, blur, }: { - embed: AppBskyEmbedVideo.View + embed: app.bsky.embed.video.View style?: ImageStyle blur?: boolean }) { @@ -1131,7 +1126,7 @@ function PlayPauseTapArea({ reqId, }: { player: VideoPlayer - post: Shadow + post: Shadow feedContext: string | undefined reqId: string | undefined }) { @@ -1273,7 +1268,9 @@ function EndMessage() { /* * If the video is taller than 9:16 */ -function isTallAspectRatio(aspectRatio: AppBskyEmbedVideo.View['aspectRatio']) { +function isTallAspectRatio( + aspectRatio: app.bsky.embed.video.View['aspectRatio'], +) { const videoAspectRatio = (aspectRatio?.width ?? 1) / (aspectRatio?.height ?? 1) return videoAspectRatio <= 9 / 16 diff --git a/src/state/cache/post-shadow.ts b/src/state/cache/post-shadow.ts index 99b913f7de..72d9571486 100644 --- a/src/state/cache/post-shadow.ts +++ b/src/state/cache/post-shadow.ts @@ -1,9 +1,5 @@ import {useEffect, useMemo, useState} from 'react' -import { - AppBskyEmbedRecord, - AppBskyEmbedRecordWithMedia, - type AppBskyFeedDefs, -} from '@atproto/api' +import {type AtUriString} from '@atproto/syntax' import {type QueryClient} from '@tanstack/react-query' import {EventEmitter} from 'eventemitter3' @@ -15,14 +11,23 @@ import {findAllPostsInQueryData as findAllPostsInFeedQueryData} from '#/state/qu import {findAllPostsInQueryData as findAllPostsInQuoteQueryData} from '#/state/queries/post-quotes' import {findAllPostsInQueryData as findAllPostsInSearchQueryData} from '#/state/queries/search-posts-v2' import {findAllPostsInQueryData as findAllPostsInThreadV2QueryData} from '#/state/queries/usePostThread/queryCache' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {castAsShadow, type Shadow} from './types' export type {Shadow} from './types' +/** + * `'pending'` is the optimistic sentinel written before the real record uri + * comes back, so the two uri slots admit it alongside a real at-uri. + */ export interface PostShadow { - likeUri: string | undefined - repostUri: string | undefined + likeUri: AtUriString | 'pending' | undefined + repostUri: AtUriString | 'pending' | undefined isDeleted: boolean - embed: AppBskyEmbedRecord.View | AppBskyEmbedRecordWithMedia.View | undefined + embed: + | app.bsky.embed.record.View + | app.bsky.embed.recordWithMedia.View + | undefined pinned: boolean optimisticReplyCount: number | undefined bookmarked: boolean | undefined @@ -32,7 +37,7 @@ export const POST_TOMBSTONE = Symbol('PostTombstone') const emitter = new EventEmitter() const shadows: WeakMap< - AppBskyFeedDefs.PostView, + app.bsky.feed.defs.PostView, Partial > = new WeakMap() @@ -40,13 +45,13 @@ const shadows: WeakMap< * Use with caution! This function returns the raw shadow data for a post. * Prefer using `usePostShadow`. */ -export function dangerousGetPostShadow(post: AppBskyFeedDefs.PostView) { +export function dangerousGetPostShadow(post: app.bsky.feed.defs.PostView) { return shadows.get(post) } export function usePostShadow( - post: AppBskyFeedDefs.PostView, -): Shadow | typeof POST_TOMBSTONE { + post: app.bsky.feed.defs.PostView, +): Shadow | typeof POST_TOMBSTONE { const [shadow, setShadow] = useState(() => shadows.get(post)) const [prevPost, setPrevPost] = useState(post) if (post !== prevPost) { @@ -74,9 +79,9 @@ export function usePostShadow( } function mergeShadow( - post: AppBskyFeedDefs.PostView, + post: app.bsky.feed.defs.PostView, shadow: Partial, -): Shadow | typeof POST_TOMBSTONE { +): Shadow | typeof POST_TOMBSTONE { if (shadow.isDeleted) { return POST_TOMBSTONE } @@ -125,12 +130,17 @@ function mergeShadow( let embed: typeof post.embed if ('embed' in shadow) { if ( - (AppBskyEmbedRecord.isView(post.embed) && - AppBskyEmbedRecord.isView(shadow.embed)) || - (AppBskyEmbedRecordWithMedia.isView(post.embed) && - AppBskyEmbedRecordWithMedia.isView(shadow.embed)) + (bsky.isType(app.bsky.embed.record.view, post.embed) && + bsky.isType(app.bsky.embed.record.view, shadow.embed)) || + (bsky.isType(app.bsky.embed.recordWithMedia.view, post.embed) && + bsky.isType(app.bsky.embed.recordWithMedia.view, shadow.embed)) ) { - embed = shadow.embed + /* + * `isType` asserts a present, matching `$type` at runtime but narrows to + * the schema's input type, whose `$type` is optional - so the value does + * not satisfy the `$Typed` arm of `PostView['embed']` without this cast. + */ + embed = shadow.embed as typeof post.embed } } @@ -143,8 +153,15 @@ function mergeShadow( bookmarkCount: bookmarkCount, viewer: { ...(post.viewer || {}), - like: 'likeUri' in shadow ? shadow.likeUri : post.viewer?.like, - repost: 'repostUri' in shadow ? shadow.repostUri : post.viewer?.repost, + /* + * The optimistic `'pending'` sentinel is not a real at-uri; consumers only + * test these for presence while the write is in flight. + */ + like: (('likeUri' in shadow ? shadow.likeUri : post.viewer?.like) ?? + undefined) as AtUriString | undefined, + repost: (('repostUri' in shadow + ? shadow.repostUri + : post.viewer?.repost) ?? undefined) as AtUriString | undefined, pinned: 'pinned' in shadow ? shadow.pinned : post.viewer?.pinned, bookmarked: 'bookmarked' in shadow ? shadow.bookmarked : post.viewer?.bookmarked, @@ -169,7 +186,7 @@ export function updatePostShadow( function* findPostsInCache( queryClient: QueryClient, uri: string, -): Generator { +): Generator { for (let post of findAllPostsInFeedQueryData(queryClient, uri)) { yield post } diff --git a/src/state/cache/profile-shadow.ts b/src/state/cache/profile-shadow.ts index cb3b4eb7bd..ca516024d7 100644 --- a/src/state/cache/profile-shadow.ts +++ b/src/state/cache/profile-shadow.ts @@ -1,5 +1,5 @@ import {useEffect, useMemo, useState} from 'react' -import {type AppBskyActorDefs, type AppBskyNotificationDefs} from '@atproto/api' +import {type AtUriString} from '@atproto/lex' import {type QueryClient} from '@tanstack/react-query' import {EventEmitter} from 'eventemitter3' @@ -33,6 +33,7 @@ import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersForDiscover import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersForExploreQueryData} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery' import {findAllProfilesInQueryData as findAllProfilesInSuggestedUsersForSeeMoreQueryData} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery' import {findAllProfilesInQueryData as findAllProfilesInPostThreadV2QueryData} from '#/state/queries/usePostThread/queryCache' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' import {castAsShadow, type Shadow} from './types' @@ -43,9 +44,11 @@ export interface ProfileShadow { muted: boolean | undefined mutedOnlyReposts: boolean | undefined blockingUri: string | undefined - verification: AppBskyActorDefs.VerificationState - status: AppBskyActorDefs.StatusView | undefined - activitySubscription: AppBskyNotificationDefs.ActivitySubscription | undefined + verification: app.bsky.actor.defs.VerificationState + status: app.bsky.actor.defs.StatusView | undefined + activitySubscription: + | app.bsky.notification.defs.ActivitySubscription + | undefined } const shadows: WeakMap< @@ -277,7 +280,7 @@ export function mergeShadow( ...(profile.viewer || {}), following: 'followingUri' in shadow - ? shadow.followingUri + ? (shadow.followingUri as AtUriString) : profile.viewer?.following, muted: 'muted' in shadow ? shadow.muted : profile.viewer?.muted, mutedOnlyReposts: @@ -285,12 +288,14 @@ export function mergeShadow( ? shadow.mutedOnlyReposts : profile.viewer?.mutedOnlyReposts, blocking: - 'blockingUri' in shadow ? shadow.blockingUri : profile.viewer?.blocking, + 'blockingUri' in shadow + ? (shadow.blockingUri as AtUriString) + : profile.viewer?.blocking, activitySubscription: 'activitySubscription' in shadow ? shadow.activitySubscription : profile.viewer?.activitySubscription, - } satisfies AppBskyActorDefs.ViewerState, + } satisfies app.bsky.actor.defs.ViewerState, verification: 'verification' in shadow ? shadow.verification : profile.verification, status: diff --git a/src/state/feed-feedback.tsx b/src/state/feed-feedback.tsx index dbd10b3c9d..bcaa8d03c5 100644 --- a/src/state/feed-feedback.tsx +++ b/src/state/feed-feedback.tsx @@ -7,7 +7,6 @@ import { useRef, } from 'react' import {AppState, type AppStateStatus} from 'react-native' -import {type AppBskyFeedDefs} from '@atproto/api' import {type AtUriString, type DidString} from '@atproto/syntax' import throttle from 'lodash.throttle' @@ -29,7 +28,7 @@ import {useAppviewClient} from './session' export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS] export const THIRD_PARTY_ALLOWED_INTERACTIONS = new Set< - AppBskyFeedDefs.Interaction['event'] + app.bsky.feed.defs.Interaction['event'] >([ // These are explicit actions and are therefore fine to send. 'app.bsky.feed.defs#requestLess', @@ -47,7 +46,7 @@ export const THIRD_PARTY_ALLOWED_INTERACTIONS = new Set< export type StateContext = { enabled: boolean onItemSeen: (item: any) => void - sendInteraction: (interaction: AppBskyFeedDefs.Interaction) => void + sendInteraction: (interaction: app.bsky.feed.defs.Interaction) => void feedDescriptor: FeedDescriptor | undefined feedSourceInfo: FeedSourceInfo | undefined } @@ -55,7 +54,7 @@ export type StateContext = { const stateContext = createContext({ enabled: false, onItemSeen: (_item: any) => {}, - sendInteraction: (_interaction: AppBskyFeedDefs.Interaction) => {}, + sendInteraction: (_interaction: app.bsky.feed.defs.Interaction) => {}, feedDescriptor: undefined, feedSourceInfo: undefined, }) @@ -84,7 +83,7 @@ export function useFeedFeedback( const history = useRef< // Use a WeakSet so that we don't need to clear it. // This assumes that referential identity of slice items maps 1:1 to feed (re)fetches. - WeakSet + WeakSet >(new WeakSet()) const flushEvents = useCallback( @@ -225,7 +224,7 @@ export function useFeedFeedback( ) const sendInteraction = useCallback( - (interaction: AppBskyFeedDefs.Interaction) => { + (interaction: app.bsky.feed.defs.Interaction) => { if (!enabled) { return } @@ -273,7 +272,7 @@ export function isDiscoverFeed(feed?: FeedDescriptor) { function isInteractionAllowed( enabled: boolean, feed: FeedSourceFeedInfo | undefined, - interaction: AppBskyFeedDefs.Interaction['event'], + interaction: app.bsky.feed.defs.Interaction['event'], ) { if (!enabled || !feed) { return false @@ -282,7 +281,7 @@ function isInteractionAllowed( return isDiscover ? true : THIRD_PARTY_ALLOWED_INTERACTIONS.has(interaction) } -function toString(interaction: AppBskyFeedDefs.Interaction): string { +function toString(interaction: app.bsky.feed.defs.Interaction): string { return `${interaction.item}|${interaction.event}|${ interaction.feedContext || '' }|${interaction.reqId || ''}` @@ -313,7 +312,7 @@ function createAggregatedStats(): AggregatedStats { function sendOrAggregateInteractionsForStats( stats: AggregatedStats, - interactions: AppBskyFeedDefs.Interaction[], + interactions: app.bsky.feed.defs.Interaction[], ) { for (let interaction of interactions) { switch (interaction.event) { diff --git a/src/state/messages/convo/agent.ts b/src/state/messages/convo/agent.ts index 521e7f77ea..83ca5ecf64 100644 --- a/src/state/messages/convo/agent.ts +++ b/src/state/messages/convo/agent.ts @@ -1,13 +1,5 @@ -import { - type $Typed, - type AppBskyEmbedRecord, - type ChatBskyActorDefs, - ChatBskyConvoDefs, - type ChatBskyConvoGetLog, - type ChatBskyEmbedJoinLink, - type ChatBskyGroupDefs, -} from '@atproto/api' -import {type Client, XrpcResponseError} from '@atproto/lex' +import {type $Typed, type Client, XrpcResponseError} from '@atproto/lex' +import {type DidString, toDatetimeString} from '@atproto/syntax' import {EventEmitter} from 'eventemitter3' import {nanoid} from 'nanoid/non-secure' @@ -49,7 +41,8 @@ import { parseConvoView, } from '#/components/dms/util' import {IS_NATIVE} from '#/env' -import {chat} from '#/lexicons' +import {type app, chat} from '#/lexicons' +import * as bsky from '#/types/bsky' const logger = Logger.create(Logger.Context.ConversationAgent) @@ -65,21 +58,21 @@ export function isConvoItemMessage( } function toSystemMessageView( - ev: ChatBskyConvoGetLog.OutputSchema['logs'][number], -): ChatBskyConvoDefs.SystemMessageView | null { + ev: chat.bsky.convo.getLog.$OutputBody['logs'][number], +): chat.bsky.convo.defs.SystemMessageView | null { const isSystem = - ChatBskyConvoDefs.isLogAddMember(ev) || - ChatBskyConvoDefs.isLogRemoveMember(ev) || - ChatBskyConvoDefs.isLogMemberJoin(ev) || - ChatBskyConvoDefs.isLogMemberLeave(ev) || - ChatBskyConvoDefs.isLogLockConvo(ev) || - ChatBskyConvoDefs.isLogUnlockConvo(ev) || - ChatBskyConvoDefs.isLogLockConvoPermanently(ev) || - ChatBskyConvoDefs.isLogEditGroup(ev) || - ChatBskyConvoDefs.isLogCreateJoinLink(ev) || - ChatBskyConvoDefs.isLogEditJoinLink(ev) || - ChatBskyConvoDefs.isLogEnableJoinLink(ev) || - ChatBskyConvoDefs.isLogDisableJoinLink(ev) + bsky.isType(chat.bsky.convo.defs.logAddMember, ev) || + bsky.isType(chat.bsky.convo.defs.logRemoveMember, ev) || + bsky.isType(chat.bsky.convo.defs.logMemberJoin, ev) || + bsky.isType(chat.bsky.convo.defs.logMemberLeave, ev) || + bsky.isType(chat.bsky.convo.defs.logLockConvo, ev) || + bsky.isType(chat.bsky.convo.defs.logUnlockConvo, ev) || + bsky.isType(chat.bsky.convo.defs.logLockConvoPermanently, ev) || + bsky.isType(chat.bsky.convo.defs.logEditGroup, ev) || + bsky.isType(chat.bsky.convo.defs.logCreateJoinLink, ev) || + bsky.isType(chat.bsky.convo.defs.logEditJoinLink, ev) || + bsky.isType(chat.bsky.convo.defs.logEnableJoinLink, ev) || + bsky.isType(chat.bsky.convo.defs.logDisableJoinLink, ev) if (!isSystem) return null return ev.message } @@ -89,8 +82,8 @@ function toSystemMessageView( * the fields the deleted view carries so a reply can render it as deleted. */ function toDeletedMessageView( - m: ChatBskyConvoDefs.MessageView, -): $Typed { + m: chat.bsky.convo.defs.MessageView, +): $Typed { return { $type: 'chat.bsky.convo.defs#deletedMessageView', id: m.id, @@ -105,7 +98,7 @@ export class Convo { private chatClient: Client private events: MessagesEventBus - private senderUserDid: string + private senderUserDid: DidString private status: ConvoStatus = ConvoStatus.Uninitialized private error: ConvoError | undefined @@ -115,15 +108,15 @@ export class Convo { private pastMessages: Map< string, - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | ChatBskyConvoDefs.SystemMessageView + | chat.bsky.convo.defs.MessageView + | chat.bsky.convo.defs.DeletedMessageView + | chat.bsky.convo.defs.SystemMessageView > = new Map() private newMessages: Map< string, - | ChatBskyConvoDefs.MessageView - | ChatBskyConvoDefs.DeletedMessageView - | ChatBskyConvoDefs.SystemMessageView + | chat.bsky.convo.defs.MessageView + | chat.bsky.convo.defs.DeletedMessageView + | chat.bsky.convo.defs.SystemMessageView > = new Map() private pendingMessages: Map< string, @@ -131,13 +124,13 @@ export class Convo { id: string message: chat.bsky.convo.defs.MessageInput optimisticEmbedView?: - | $Typed - | $Typed - optimisticReplyTo?: $Typed + | $Typed + | $Typed + optimisticReplyTo?: $Typed } > = new Map() private deletedMessages: Set = new Set() - private relatedProfiles: Map = + private relatedProfiles: Map = new Map() /** * Accumulated profile shadow state, keyed by did. The profiles this agent @@ -157,8 +150,8 @@ export class Convo { convoId: string convo: ConvoWithDetails | undefined - sender: ChatBskyActorDefs.ProfileViewBasic | undefined - recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined + sender: chat.bsky.actor.defs.ProfileViewBasic | undefined + recipients: chat.bsky.actor.defs.ProfileViewBasic[] | undefined snapshot: ConvoState | undefined constructor(params: ConvoParams) { @@ -596,7 +589,7 @@ export class Convo { } } - private setConvo(convo: ChatBskyConvoDefs.ConvoView) { + private setConvo(convo: chat.bsky.convo.defs.ConvoView) { this.convo = parseConvoView(convo, this.senderUserDid) ?? this.convo if (this.convo) { for (const member of this.convo.members) { @@ -606,7 +599,7 @@ export class Convo { this.applyProfileShadows() } - private updateConvo(convo: Partial) { + private updateConvo(convo: Partial) { if (this.convo) { this.convo = parseConvoView({...this.convo.view, ...convo}, this.senderUserDid) ?? @@ -716,7 +709,7 @@ export class Convo { } private pendingFetchConvo: - | Promise<{convo: ChatBskyConvoDefs.ConvoView}> + | Promise<{convo: chat.bsky.convo.defs.ConvoView}> | undefined async fetchConvo() { if (this.pendingFetchConvo) return this.pendingFetchConvo @@ -836,9 +829,9 @@ export class Convo { for (const message of messages) { if ( - ChatBskyConvoDefs.isMessageView(message) || - ChatBskyConvoDefs.isDeletedMessageView(message) || - ChatBskyConvoDefs.isSystemMessageView(message) + bsky.isType(chat.bsky.convo.defs.messageView, message) || + bsky.isType(chat.bsky.convo.defs.deletedMessageView, message) || + bsky.isType(chat.bsky.convo.defs.systemMessageView, message) ) { /* * If this message is already in new messages, it was added by the @@ -913,7 +906,7 @@ export class Convo { this.commit() } - ingestFirehose(events: ChatBskyConvoGetLog.OutputSchema['logs']) { + ingestFirehose(events: chat.bsky.convo.getLog.$OutputBody['logs']) { let needsCommit = false for (const ev of events) { @@ -950,8 +943,8 @@ export class Convo { } if ( - ChatBskyConvoDefs.isLogCreateMessage(ev) && - ChatBskyConvoDefs.isMessageView(ev.message) + bsky.isType(chat.bsky.convo.defs.logCreateMessage, ev) && + bsky.isType(chat.bsky.convo.defs.messageView, ev.message) ) { /* * If this message is already in past messages, the initial @@ -976,8 +969,8 @@ export class Convo { } needsCommit = true } else if ( - ChatBskyConvoDefs.isLogDeleteMessage(ev) && - ChatBskyConvoDefs.isDeletedMessageView(ev.message) + bsky.isType(chat.bsky.convo.defs.logDeleteMessage, ev) && + bsky.isType(chat.bsky.convo.defs.deletedMessageView, ev.message) ) { /* * Remove the message itself, and keep its id in `deletedMessages` @@ -992,9 +985,9 @@ export class Convo { this.deletedMessages.add(ev.message.id) needsCommit = true } else if ( - (ChatBskyConvoDefs.isLogAddReaction(ev) || - ChatBskyConvoDefs.isLogRemoveReaction(ev)) && - ChatBskyConvoDefs.isMessageView(ev.message) + (bsky.isType(chat.bsky.convo.defs.logAddReaction, ev) || + bsky.isType(chat.bsky.convo.defs.logRemoveReaction, ev)) && + bsky.isType(chat.bsky.convo.defs.messageView, ev.message) ) { /* * Update if we have this in state - replace message wholesale. If we don't, don't worry about it. @@ -1033,9 +1026,9 @@ export class Convo { sendMessage( message: chat.bsky.convo.defs.MessageInput, optimisticEmbedView?: - | $Typed - | $Typed, - optimisticReplyTo?: $Typed, + | $Typed + | $Typed, + optimisticReplyTo?: $Typed, ) { // Ignore empty messages for now since they have no other purpose atm if (!message.text.trim() && !message.embed) return @@ -1110,7 +1103,7 @@ export class Convo { this.commit() } - updateJoinLink(joinLink: ChatBskyGroupDefs.JoinLinkView | undefined) { + updateJoinLink(joinLink: chat.bsky.group.defs.JoinLinkView | undefined) { if (this.convo?.kind !== 'group') { throw new Error('updateJoinLink can only be called on group convo') } @@ -1126,7 +1119,7 @@ export class Convo { } updateLockStatus( - lockStatus: ChatBskyConvoDefs.ConvoLockStatus, + lockStatus: chat.bsky.convo.defs.ConvoLockStatus, lockStatusModerationOverride: boolean, ) { if (this.convo?.kind !== 'group') { @@ -1194,8 +1187,8 @@ export class Convo { } /* - * The lex client throws `XrpcResponseError`, not `@atproto/api`'s - * `XRPCError`, so the status/message branch narrows on the lex class. Only + * The lex client throws `XrpcResponseError`, so the status/message branch + * narrows on that class. Only * a genuine server response carries a status: transport and internal lex * failures are `XrpcInternalError`s and fall through to the generic arm, * where `isNetworkError` keeps them out of the logs. @@ -1340,11 +1333,11 @@ export class Convo { * matching what the server returns on refresh. */ private tombstoneDeletedReplyTo( - m: ChatBskyConvoDefs.MessageView, - ): ChatBskyConvoDefs.MessageView { + m: chat.bsky.convo.defs.MessageView, + ): chat.bsky.convo.defs.MessageView { const {replyTo} = m if ( - !ChatBskyConvoDefs.isMessageView(replyTo) || + !bsky.isType(chat.bsky.convo.defs.messageView, replyTo) || !this.deletedMessages.has(replyTo.id) ) { return m @@ -1359,19 +1352,19 @@ export class Convo { const items: ConvoItem[] = [] this.pastMessages.forEach(m => { - if (ChatBskyConvoDefs.isMessageView(m)) { + if (bsky.isType(chat.bsky.convo.defs.messageView, m)) { items.unshift({ type: 'message', key: m.id, message: this.tombstoneDeletedReplyTo(m), }) - } else if (ChatBskyConvoDefs.isDeletedMessageView(m)) { + } else if (bsky.isType(chat.bsky.convo.defs.deletedMessageView, m)) { items.unshift({ type: 'deleted-message', key: m.id, message: m, }) - } else if (ChatBskyConvoDefs.isSystemMessageView(m)) { + } else if (bsky.isType(chat.bsky.convo.defs.systemMessageView, m)) { items.unshift({ type: 'system-message', key: m.id, @@ -1392,19 +1385,19 @@ export class Convo { } this.newMessages.forEach(m => { - if (ChatBskyConvoDefs.isMessageView(m)) { + if (bsky.isType(chat.bsky.convo.defs.messageView, m)) { items.push({ type: 'message', key: m.id, message: this.tombstoneDeletedReplyTo(m), }) - } else if (ChatBskyConvoDefs.isDeletedMessageView(m)) { + } else if (bsky.isType(chat.bsky.convo.defs.deletedMessageView, m)) { items.push({ type: 'deleted-message', key: m.id, message: m, }) - } else if (ChatBskyConvoDefs.isSystemMessageView(m)) { + } else if (bsky.isType(chat.bsky.convo.defs.systemMessageView, m)) { items.push({ type: 'system-message', key: m.id, @@ -1428,7 +1421,7 @@ export class Convo { $type: 'chat.bsky.convo.defs#messageView', id: nanoid(), rev: '__fake__', - sentAt: new Date().toISOString(), + sentAt: toDatetimeString(new Date()), sender: { $type: 'chat.bsky.convo.defs#messageViewSender', did: this.senderUserDid, @@ -1470,16 +1463,16 @@ export class Convo { * @param emoji - must be one grapheme */ async addReaction(messageId: string, emoji: string) { - const optimisticReaction = { + const optimisticReaction: chat.bsky.convo.defs.ReactionView = { value: emoji, sender: {did: this.senderUserDid}, - createdAt: new Date().toISOString(), + createdAt: toDatetimeString(new Date()), } let restore: null | (() => void) = null if (this.pastMessages.has(messageId)) { const prevMessage = this.pastMessages.get(messageId) if ( - ChatBskyConvoDefs.isMessageView(prevMessage) && + bsky.isType(chat.bsky.convo.defs.messageView, prevMessage) && // skip optimistic update if reaction already exists !prevMessage.reactions?.find( reaction => @@ -1509,7 +1502,7 @@ export class Convo { } else if (this.newMessages.has(messageId)) { const prevMessage = this.newMessages.get(messageId) if ( - ChatBskyConvoDefs.isMessageView(prevMessage) && + bsky.isType(chat.bsky.convo.defs.messageView, prevMessage) && !prevMessage.reactions?.find(reaction => reaction.value === emoji) ) { if (prevMessage.reactions && prevMessage.reactions.length >= 5) @@ -1533,7 +1526,7 @@ export class Convo { value: emoji, convoId: this.convoId, }) - if (ChatBskyConvoDefs.isMessageView(data.message)) { + if (bsky.isType(chat.bsky.convo.defs.messageView, data.message)) { if (this.pastMessages.has(messageId)) { this.pastMessages.set(messageId, data.message) this.commit() @@ -1558,7 +1551,7 @@ export class Convo { let restore: null | (() => void) = null if (this.pastMessages.has(messageId)) { const prevMessage = this.pastMessages.get(messageId) - if (ChatBskyConvoDefs.isMessageView(prevMessage)) { + if (bsky.isType(chat.bsky.convo.defs.messageView, prevMessage)) { this.pastMessages.set(messageId, { ...prevMessage, reactions: prevMessage.reactions?.filter( @@ -1575,7 +1568,7 @@ export class Convo { } } else if (this.newMessages.has(messageId)) { const prevMessage = this.newMessages.get(messageId) - if (ChatBskyConvoDefs.isMessageView(prevMessage)) { + if (bsky.isType(chat.bsky.convo.defs.messageView, prevMessage)) { this.newMessages.set(messageId, { ...prevMessage, reactions: prevMessage.reactions?.filter( diff --git a/src/state/messages/convo/index.tsx b/src/state/messages/convo/index.tsx index b37b060701..58a465dc13 100644 --- a/src/state/messages/convo/index.tsx +++ b/src/state/messages/convo/index.tsx @@ -6,7 +6,6 @@ import { useState, useSyncExternalStore, } from 'react' -import {ChatBskyConvoDefs} from '@atproto/api' import {useFocusEffect} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' @@ -30,12 +29,14 @@ import {RQKEY_ROOT as ListConvosQueryKeyRoot} from '#/state/queries/messages/lis import {RQKEY as createProfileQueryKey} from '#/state/queries/profile' import {useChatClient} from '#/state/session' import {type GroupConvoMember} from '#/components/dms/util' +import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' export * from '#/state/messages/convo/util' function membersChanged( - a: ChatBskyConvoDefs.ConvoView['members'], - b: ChatBskyConvoDefs.ConvoView['members'], + a: chat.bsky.convo.defs.ConvoView['members'], + b: chat.bsky.convo.defs.ConvoView['members'], ) { if (a.length !== b.length) return true const aDids = new Set(a.map(m => m.did)) @@ -83,9 +84,10 @@ export function ConvoProvider({ const chatClient = useChatClient() const events = useMessagesEventBus() const [convo] = useState(() => { - const placeholder = queryClient.getQueryData( - getConvoKey(convoId), - ) + const placeholder = + queryClient.getQueryData( + getConvoKey(convoId), + ) return new Convo({ convoId, chatClient, @@ -151,14 +153,14 @@ export function ConvoProvider({ const queryKey = event.query.queryKey as string[] if (queryKey[0] === root && queryKey[1] === id) { const data = event.query.state.data as - | ChatBskyConvoDefs.ConvoView + | chat.bsky.convo.defs.ConvoView | undefined if (data && convo.convo && data.muted !== convo.convo.view.muted) { convo.updateMuted(data.muted) } if ( data && - ChatBskyConvoDefs.isGroupConvo(data.kind) && + bsky.isType(chat.bsky.convo.defs.groupConvo, data.kind) && convo.convo?.kind === 'group' ) { if (data.kind.name !== convo.convo.details.name) { @@ -180,7 +182,7 @@ export function ConvoProvider({ } if ( data && - ChatBskyConvoDefs.isGroupConvo(data.kind) && + bsky.isType(chat.bsky.convo.defs.groupConvo, data.kind) && convo.convo?.kind === 'group' && (membersChanged(data.members, convo.convo.members) || data.kind.memberCount !== convo.convo.details.memberCount) diff --git a/src/state/messages/convo/types.ts b/src/state/messages/convo/types.ts index 5bc5777e82..464cc83b4a 100644 --- a/src/state/messages/convo/types.ts +++ b/src/state/messages/convo/types.ts @@ -1,15 +1,8 @@ -import { - type $Typed, - type AppBskyEmbedRecord, - type ChatBskyActorDefs, - type ChatBskyConvoDefs, - type ChatBskyEmbedJoinLink, -} from '@atproto/api' -import {type Client} from '@atproto/lex' +import {type $Typed, type Client} from '@atproto/lex' import {type MessagesEventBus} from '#/state/messages/events/agent' import {type ConvoWithDetails} from '#/components/dms/util' -import {type chat} from '#/lexicons' +import {type app, type chat} from '#/lexicons' export type ConvoParams = { convoId: string @@ -17,7 +10,7 @@ export type ConvoParams = { chatClient: Client events: MessagesEventBus placeholderData?: { - convo: ChatBskyConvoDefs.ConvoView + convo: chat.bsky.convo.defs.ConvoView } } @@ -75,12 +68,12 @@ export type ConvoItem = | { type: 'message' key: string - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView } | { type: 'pending-message' key: string - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView failed: boolean /** * Retry sending the message. If present, the message is in a failed state. @@ -90,12 +83,12 @@ export type ConvoItem = | { type: 'deleted-message' key: string - message: ChatBskyConvoDefs.DeletedMessageView + message: chat.bsky.convo.defs.DeletedMessageView } | { type: 'system-message' key: string - message: ChatBskyConvoDefs.SystemMessageView + message: chat.bsky.convo.defs.SystemMessageView } | { type: 'error' @@ -111,10 +104,10 @@ type DeleteMessage = (messageId: string) => Promise type SendMessage = ( message: chat.bsky.convo.defs.MessageInput, optimisticEmbedView: - | $Typed - | $Typed + | $Typed + | $Typed | undefined, - optimisticReplyTo?: $Typed, + optimisticReplyTo?: $Typed, ) => void type FetchMessageHistory = () => Promise type MarkConvoAccepted = () => void @@ -153,7 +146,7 @@ export type ConvoStateReady = { status: ConvoStatus.Ready items: ConvoItem[] convo: ConvoWithDetails - relatedProfiles: Map + relatedProfiles: Map error: undefined isFetchingHistory: boolean hasAllHistory: boolean @@ -168,7 +161,7 @@ export type ConvoStateBackgrounded = { status: ConvoStatus.Backgrounded items: ConvoItem[] convo: ConvoWithDetails - relatedProfiles: Map + relatedProfiles: Map error: undefined isFetchingHistory: boolean hasAllHistory: boolean @@ -183,7 +176,7 @@ export type ConvoStateSuspended = { status: ConvoStatus.Suspended items: ConvoItem[] convo: ConvoWithDetails - relatedProfiles: Map + relatedProfiles: Map error: undefined isFetchingHistory: boolean hasAllHistory: boolean @@ -212,7 +205,7 @@ export type ConvoStateDisabled = { status: ConvoStatus.Disabled items: ConvoItem[] convo: ConvoWithDetails - relatedProfiles: Map + relatedProfiles: Map error: undefined isFetchingHistory: boolean hasAllHistory: boolean diff --git a/src/state/queries/activity-subscriptions.ts b/src/state/queries/activity-subscriptions.ts index f77d1001cb..31f542bda8 100644 --- a/src/state/queries/activity-subscriptions.ts +++ b/src/state/queries/activity-subscriptions.ts @@ -1,4 +1,3 @@ -import {type AppBskyActorDefs} from '@atproto/api' import {type AtIdentifierString} from '@atproto/syntax' import {t} from '@lingui/core/macro' import { @@ -106,7 +105,7 @@ export function useNotificationDeclarationMutation() { export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ diff --git a/src/state/queries/actor-autocomplete.ts b/src/state/queries/actor-autocomplete.ts index 111a8aaab4..cdb2584803 100644 --- a/src/state/queries/actor-autocomplete.ts +++ b/src/state/queries/actor-autocomplete.ts @@ -1,10 +1,8 @@ import {useCallback} from 'react' -import {type AppBskyActorDefs} from '@atproto/api' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, 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' @@ -34,7 +32,7 @@ export function useActorAutocompleteQuery( prefix = prefix.slice(0, -1) } - return useQuery({ + return useQuery({ staleTime: STALE.MINUTES.ONE, queryKey: RQKEY(prefix || ''), async queryFn() { @@ -47,7 +45,7 @@ export function useActorAutocompleteQuery( return data?.actors || [] }, select: useCallback( - (data: AppBskyActorDefs.ProfileViewBasic[]) => { + (data: app.bsky.actor.defs.ProfileViewBasic[]) => { return computeSuggestions({ q: prefix, searched: data, @@ -104,10 +102,10 @@ function computeSuggestions({ moderationOpts, }: { q?: string - searched?: AppBskyActorDefs.ProfileViewBasic[] + searched?: app.bsky.actor.defs.ProfileViewBasic[] moderationOpts: ModerationOpts }) { - let items: AppBskyActorDefs.ProfileViewBasic[] = [] + let items: app.bsky.actor.defs.ProfileViewBasic[] = [] for (const item of searched) { if (!items.find(item2 => item2.handle === item.handle)) { items.push(item) diff --git a/src/state/queries/bookmarks/useBookmarkMutation.ts b/src/state/queries/bookmarks/useBookmarkMutation.ts index af5a4a2d8d..4ba0d1d7f5 100644 --- a/src/state/queries/bookmarks/useBookmarkMutation.ts +++ b/src/state/queries/bookmarks/useBookmarkMutation.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type AtUriString} from '@atproto/syntax' import {useMutation, useQueryClient} from '@tanstack/react-query' @@ -13,7 +12,7 @@ import {useAppviewClient} from '#/state/session' import {app} from '#/lexicons' type MutationArgs = - | {action: 'create'; post: AppBskyFeedDefs.PostView} + | {action: 'create'; post: app.bsky.feed.defs.PostView} | { action: 'delete' /** @@ -32,7 +31,7 @@ export function useBookmarkMutation() { if (args.action === 'create') { updatePostShadow(qc, args.post.uri, {bookmarked: true}) await client.call(app.bsky.bookmark.createBookmark, { - uri: args.post.uri as AtUriString, + uri: args.post.uri, cid: args.post.cid, }) } else if (args.action === 'delete') { diff --git a/src/state/queries/bookmarks/useBookmarksQuery.ts b/src/state/queries/bookmarks/useBookmarksQuery.ts index 14323625f9..adb12a47ce 100644 --- a/src/state/queries/bookmarks/useBookmarksQuery.ts +++ b/src/state/queries/bookmarks/useBookmarksQuery.ts @@ -1,4 +1,5 @@ -import {type $Typed, AppBskyFeedDefs, AtUri} from '@atproto/api' +import {type $Typed} from '@atproto/lex' +import {AtUri} from '@atproto/syntax' import { type InfiniteData, type QueryClient, @@ -57,7 +58,7 @@ export async function truncateAndInvalidate(qc: QueryClient) { export async function optimisticallySaveBookmark( qc: QueryClient, - post: AppBskyFeedDefs.PostView, + post: app.bsky.feed.defs.PostView, ) { qc.setQueriesData>( { @@ -71,9 +72,9 @@ export async function optimisticallySaveBookmark( if (index === 0) { post.$type = 'app.bsky.feed.defs#postView' /* - * The optimistic entry is synthesized from an `@atproto/api` - * `PostView`, whose string fields are unbranded, so it is asserted - * to the vendored view type the query data is now keyed on. + * The optimistic entry is synthesized with unbranded string + * fields, so it is asserted to the generated view type the query + * data is keyed on. */ const bookmark = { createdAt: new Date().toISOString(), @@ -81,7 +82,7 @@ export async function optimisticallySaveBookmark( uri: post.uri, cid: post.cid, }, - item: post as $Typed, + item: post as $Typed, } as unknown as app.bsky.bookmark.defs.BookmarkView return { ...page, @@ -121,7 +122,7 @@ export async function optimisticallyDeleteBookmark( export function* findAllPostsInQueryData( queryClient: QueryClient, uri: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ @@ -135,13 +136,7 @@ export function* findAllPostsInQueryData( } for (const page of queryData?.pages) { for (const bookmark of page.bookmarks) { - if ( - !bsky.dangerousIsType( - bookmark.item, - AppBskyFeedDefs.isPostView, - ) - ) - continue + if (!bsky.isType(app.bsky.feed.defs.postView, bookmark.item)) continue if (didOrHandleUriMatches(atUri, bookmark.item)) { yield bookmark.item diff --git a/src/state/queries/explore-feed-previews.tsx b/src/state/queries/explore-feed-previews.tsx index 884b46d2df..0977fdcbdc 100644 --- a/src/state/queries/explore-feed-previews.tsx +++ b/src/state/queries/explore-feed-previews.tsx @@ -1,6 +1,6 @@ import {useMemo, useRef} from 'react' -import {type AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api' -import {type AtUriString} from '@atproto/syntax' +import {AtUri} from '@atproto/syntax' +import {moderatePost} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import { @@ -12,7 +12,6 @@ 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 { @@ -26,6 +25,8 @@ import { getEmbeddedPost, } from '#/state/queries/util' import {useAppviewClient} from '#/state/session' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' const RQKEY_ROOT = 'feed-previews' const RQKEY = (feeds: string[]) => [RQKEY_ROOT, feeds] @@ -82,7 +83,7 @@ export type FeedPreviewItem = | { type: 'preview:header' key: string - feed: AppBskyFeedDefs.GeneratorView + feed: app.bsky.feed.defs.GeneratorView } | { type: 'preview:footer' @@ -94,7 +95,7 @@ export type FeedPreviewItem = key: string slice: FeedPostSlice indexInSlice: number - feed: AppBskyFeedDefs.GeneratorView + feed: app.bsky.feed.defs.GeneratorView showReplyTo: boolean hideTopBorder: boolean } @@ -105,7 +106,7 @@ export type FeedPreviewItem = } export function useFeedPreviews( - feedsMaybeWithDuplicates: AppBskyFeedDefs.GeneratorView[], + feedsMaybeWithDuplicates: app.bsky.feed.defs.GeneratorView[], isEnabled: boolean = true, ) { const feeds = useMemo( @@ -127,8 +128,8 @@ export function useFeedPreviews( const processedPageCache = useRef( new Map< { - feed: AppBskyFeedDefs.GeneratorView - posts: AppBskyFeedDefs.FeedViewPost[] + feed: app.bsky.feed.defs.GeneratorView + posts: app.bsky.feed.defs.FeedViewPost[] }, FeedPreviewItem[] >(), @@ -141,7 +142,7 @@ export function useFeedPreviews( const feed = feeds[pageParam] const api = new CustomFeedAPI({ client, - feedParams: {feed: feed.uri as AtUriString}, + feedParams: {feed: feed.uri}, userInterests, }) const data = await api.fetch({cursor: undefined, limit: LIMIT}) @@ -346,13 +347,13 @@ export function useFeedPreviews( export function* findAllPostsInQueryData( queryClient: QueryClient, uri: string, -): Generator { +): Generator { const atUri = new AtUri(uri) const queryDatas = queryClient.getQueriesData< InfiniteData<{ - feed: AppBskyFeedDefs.GeneratorView - posts: AppBskyFeedDefs.FeedViewPost[] + feed: app.bsky.feed.defs.GeneratorView + posts: app.bsky.feed.defs.FeedViewPost[] }> >({ queryKey: [RQKEY_ROOT], @@ -372,7 +373,7 @@ export function* findAllPostsInQueryData( yield embedViewRecordToPostView(quotedPost) } - if (AppBskyFeedDefs.isPostView(item.reply?.parent)) { + if (bsky.isType(app.bsky.feed.defs.postView, item.reply?.parent)) { if (didOrHandleUriMatches(atUri, item.reply.parent)) { yield item.reply.parent } @@ -386,7 +387,7 @@ export function* findAllPostsInQueryData( } } - if (AppBskyFeedDefs.isPostView(item.reply?.root)) { + if (bsky.isType(app.bsky.feed.defs.postView, item.reply?.root)) { if (didOrHandleUriMatches(atUri, item.reply.root)) { yield item.reply.root } @@ -404,11 +405,11 @@ export function* findAllPostsInQueryData( export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData<{ - feed: AppBskyFeedDefs.GeneratorView - posts: AppBskyFeedDefs.FeedViewPost[] + feed: app.bsky.feed.defs.GeneratorView + posts: app.bsky.feed.defs.FeedViewPost[] }> >({ queryKey: [RQKEY_ROOT], @@ -427,13 +428,13 @@ export function* findAllProfilesInQueryData( yield quotedPost.author } if ( - AppBskyFeedDefs.isPostView(item.reply?.parent) && + bsky.isType(app.bsky.feed.defs.postView, item.reply?.parent) && item.reply?.parent?.author.did === did ) { yield item.reply.parent.author } if ( - AppBskyFeedDefs.isPostView(item.reply?.root) && + bsky.isType(app.bsky.feed.defs.postView, item.reply?.root) && item.reply?.root?.author.did === did ) { yield item.reply.root.author diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index 669ec04263..c0ace8f911 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -1,11 +1,6 @@ import {useCallback, useEffect, useMemo, useRef} from 'react' -import { - type AppBskyActorDefs, - type AppBskyFeedDefs, - type AppBskyGraphDefs, - AtUri, -} from '@atproto/api' -import {type AtUriString} from '@atproto/syntax' +import {AtUri, type AtUriString} from '@atproto/syntax' +import {moderateFeedGenerator} from '@bsky.app/sdk/moderation' import {RichText} from '@bsky.app/sdk/richtext' import {t} from '@lingui/core/macro' import { @@ -19,10 +14,8 @@ 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' import {GCTIME, STALE} from '#/state/queries' import {RQKEY as listQueryKey} from '#/state/queries/list' import {usePreferencesQuery} from '#/state/queries/preferences' @@ -36,7 +29,7 @@ import {precacheResolvedUri} from './resolve-uri' export type FeedSourceFeedInfo = { type: 'feed' - view?: AppBskyFeedDefs.GeneratorView + view?: app.bsky.feed.defs.GeneratorView uri: string feedDescriptor: FeedDescriptor route: { @@ -53,12 +46,12 @@ export type FeedSourceFeedInfo = { likeCount: number | undefined acceptsInteractions?: boolean likeUri: string | undefined - contentMode: AppBskyFeedDefs.GeneratorView['contentMode'] + contentMode: app.bsky.feed.defs.GeneratorView['contentMode'] } export type FeedSourceListInfo = { type: 'list' - view?: AppBskyGraphDefs.ListView + view?: app.bsky.graph.defs.ListView uri: string feedDescriptor: FeedDescriptor route: { @@ -95,7 +88,7 @@ const feedSourceNSIDs = { } export function hydrateFeedGenerator( - view: AppBskyFeedDefs.GeneratorView, + view: app.bsky.feed.defs.GeneratorView, ): FeedSourceInfo { const urip = new AtUri(view.uri) const collection = @@ -105,7 +98,7 @@ export function hydrateFeedGenerator( const description = new RichText({ text: view.description || '', - facets: asSdkFacets((view.descriptionFacets || [])?.slice()), + facets: (view.descriptionFacets || [])?.slice(), }) if (!view.descriptionFacets) { @@ -137,7 +130,9 @@ export function hydrateFeedGenerator( } } -export function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo { +export function hydrateList( + view: app.bsky.graph.defs.ListView, +): FeedSourceInfo { const urip = new AtUri(view.uri) const collection = urip.collection === 'app.bsky.feed.generator' ? 'feed' : 'lists' @@ -146,7 +141,7 @@ export function hydrateList(view: AppBskyGraphDefs.ListView): FeedSourceInfo { const description = new RichText({ text: view.description || '', - facets: asSdkFacets((view.descriptionFacets || [])?.slice()), + facets: (view.descriptionFacets || [])?.slice(), }) if (!view.descriptionFacets) { @@ -410,7 +405,7 @@ export function usePopularFeedsSearch({ } export type SavedFeedSourceInfo = FeedSourceInfo & { - savedFeed: AppBskyActorDefs.SavedFeed + savedFeed: app.bsky.actor.defs.SavedFeed } const PWI_DISCOVER_FEED_STUB: SavedFeedSourceInfo = { @@ -549,17 +544,17 @@ export function usePinnedFeedsInfos() { export type SavedFeedItem = | { type: 'feed' - config: AppBskyActorDefs.SavedFeed - view: AppBskyFeedDefs.GeneratorView + config: app.bsky.actor.defs.SavedFeed + view: app.bsky.feed.defs.GeneratorView } | { type: 'list' - config: AppBskyActorDefs.SavedFeed - view: AppBskyGraphDefs.ListView + config: app.bsky.actor.defs.SavedFeed + view: app.bsky.graph.defs.ListView } | { type: 'timeline' - config: AppBskyActorDefs.SavedFeed + config: app.bsky.actor.defs.SavedFeed view: undefined } @@ -587,8 +582,8 @@ export function useSavedFeeds() { ) }, queryFn: async () => { - const resolvedFeeds = new Map() - const resolvedLists = new Map() + const resolvedFeeds = new Map() + const resolvedLists = new Map() const savedFeeds = savedItems.filter(feed => feed.type === 'feed') const savedLists = savedItems.filter(feed => feed.type === 'list') @@ -703,10 +698,10 @@ function precacheFeed(queryClient: QueryClient, hydratedFeed: FeedSourceInfo) { export function precacheList( queryClient: QueryClient, - list: AppBskyGraphDefs.ListView, + list: app.bsky.graph.defs.ListView, ) { precacheResolvedUri(queryClient, list.creator.handle, list.creator.did) - queryClient.setQueryData( + queryClient.setQueryData( listQueryKey(list.uri), list, ) @@ -714,7 +709,7 @@ export function precacheList( export function precacheFeedFromGeneratorView( queryClient: QueryClient, - view: AppBskyFeedDefs.GeneratorView, + view: app.bsky.feed.defs.GeneratorView, ) { const hydratedFeed = hydrateFeedGenerator(view) precacheFeed(queryClient, hydratedFeed) diff --git a/src/state/queries/join-links.ts b/src/state/queries/join-links.ts index e5dbbc9bed..91ae82fb6c 100644 --- a/src/state/queries/join-links.ts +++ b/src/state/queries/join-links.ts @@ -1,10 +1,6 @@ import {useCallback} from 'react' -import { - type $Typed, - ChatBskyGroupDefs, - type ChatBskyGroupGetJoinLinkPreviews, -} from '@atproto/api' -import {type Client} from '@atproto/lex' +import {type $Typed, type Client, type Unknown$TypedObject} from '@atproto/lex' +import {toDatetimeString} from '@atproto/syntax' import {type QueryClient, useQuery, useQueryClient} from '@tanstack/react-query' import {CHAT_SERVICE} from '#/lib/constants' @@ -14,6 +10,7 @@ import {STALE} from '#/state/queries/index' import {createQueryKey, type StructuredQueryKey} from '#/state/queries/util' import {useChatClient} from '#/state/session' import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' /** * The three preview shapes we currently support. Excludes the `{$type: string}` @@ -21,15 +18,15 @@ import {chat} from '#/lexicons' * `ChatInvitePreview` for that. */ export type KnownChatInvitePreview = - | $Typed - | $Typed - | $Typed + | $Typed + | $Typed + | $Typed /** - * The full open-union shape, including the `{$type: string}` fallback for - * future variants. + * The full open-union shape, including lex's `Unknown$TypedObject` fallback for + * unrecognized future variants. */ -export type ChatInvitePreview = KnownChatInvitePreview | {$type: string} +export type ChatInvitePreview = KnownChatInvitePreview | Unknown$TypedObject /** * Narrows a preview to one of the three known variants, filtering out the @@ -39,9 +36,9 @@ export function isKnownJoinLinkPreview( preview: unknown, ): preview is KnownChatInvitePreview { return ( - ChatBskyGroupDefs.isJoinLinkPreviewView(preview) || - ChatBskyGroupDefs.isDisabledJoinLinkPreviewView(preview) || - ChatBskyGroupDefs.isInvalidJoinLinkPreviewView(preview) + bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview) || + bsky.isType(chat.bsky.group.defs.disabledJoinLinkPreviewView, preview) || + bsky.isType(chat.bsky.group.defs.invalidJoinLinkPreviewView, preview) ) } @@ -90,7 +87,7 @@ export function setJoinLinkPreviewRequestedForCode( code: string, requested: boolean, ) { - queryClient.setQueriesData( + queryClient.setQueriesData( { predicate: query => { const [root, args] = query.queryKey as Partial< @@ -109,14 +106,16 @@ export function setJoinLinkPreviewRequestedForCode( ...old, joinLinkPreviews: old.joinLinkPreviews.map(preview => { if ( - ChatBskyGroupDefs.isJoinLinkPreviewView(preview) && + bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview) && preview.code === code ) { return { ...preview, viewer: { ...preview.viewer, - requestedAt: requested ? new Date().toISOString() : undefined, + requestedAt: requested + ? toDatetimeString(new Date()) + : undefined, }, } } @@ -143,12 +142,12 @@ export function invalidateJoinLinkPreviewsForConvo( const [root] = query.queryKey if (root !== joinLinkPreviewQueryKeyRoot) return false const data = query.state.data as - | ChatBskyGroupGetJoinLinkPreviews.OutputSchema + | chat.bsky.group.getJoinLinkPreviews.$OutputBody | undefined return ( data?.joinLinkPreviews.some( preview => - ChatBskyGroupDefs.isJoinLinkPreviewView(preview) && + bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, preview) && preview.convoId === convoId, ) ?? false ) @@ -204,7 +203,7 @@ export function useJoinLinkPreviewsQuery({ * Seed the query with an already-known preview (e.g. a DM message embed * already carries the resolved view), avoiding a duplicate fetch. */ - initialData?: ChatBskyGroupGetJoinLinkPreviews.OutputSchema + initialData?: chat.bsky.group.getJoinLinkPreviews.$OutputBody }) { const client = useChatClient() diff --git a/src/state/queries/known-followers.ts b/src/state/queries/known-followers.ts index 3106456f9d..d7e1fdb1ba 100644 --- a/src/state/queries/known-followers.ts +++ b/src/state/queries/known-followers.ts @@ -1,4 +1,3 @@ -import {type AppBskyActorDefs} from '@atproto/api' import {type DidString} from '@atproto/syntax' import { type InfiniteData, @@ -43,7 +42,7 @@ export function useProfileKnownFollowersQuery(did: string | undefined) { export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ diff --git a/src/state/queries/list-members.ts b/src/state/queries/list-members.ts index d466b92484..1d76296623 100644 --- a/src/state/queries/list-members.ts +++ b/src/state/queries/list-members.ts @@ -1,4 +1,3 @@ -import {type AppBskyActorDefs, type AppBskyGraphDefs} from '@atproto/api' import {type Client} from '@atproto/lex' import {type AtUriString} from '@atproto/syntax' import { @@ -91,7 +90,7 @@ export async function invalidateListMembersQuery({ export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ @@ -114,7 +113,7 @@ export function* findAllProfilesInQueryData( } const allQueryData = queryClient.getQueriesData< - AppBskyGraphDefs.ListItemView[] + app.bsky.graph.defs.ListItemView[] >({ queryKey: [RQKEY_ROOT_ALL], }) diff --git a/src/state/queries/list-memberships.ts b/src/state/queries/list-memberships.ts index 68a02fd051..2bc62f3712 100644 --- a/src/state/queries/list-memberships.ts +++ b/src/state/queries/list-memberships.ts @@ -1,7 +1,3 @@ -import { - type AppBskyActorDefs, - type AppBskyGraphGetStarterPacksWithMembership, -} from '@atproto/api' import { AtUri, type AtUriString, @@ -32,14 +28,14 @@ export function useListMembershipAddMutation({ * Needed for optimistic update of starter pack query */ subject?: bsky.profile.AnyProfileView - onSuccess?: (data: {uri: string; cid: string}) => void + onSuccess?: (data: {uri: AtUriString; cid: string}) => void onError?: (error: Error) => void } = {}) { const {currentAccount} = useSession() const pdsClient = usePdsClient() const queryClient = useQueryClient() return useMutation< - {uri: string; cid: string}, + {uri: AtUriString; cid: string}, Error, {listUri: string; actorDid: string} >({ @@ -73,7 +69,7 @@ export function useListMembershipAddMutation({ // update WITH_MEMBERSHIPS query for starter packs if (subject) { queryClient.setQueryData< - InfiniteData + InfiniteData >(STARTER_PACKS_WITH_MEMBERSHIPS_RKEY(variables.actorDid), old => { if (!old) return old @@ -94,7 +90,7 @@ export function useListMembershipAddMutation({ listItemsSample: [ { uri: data.uri, - subject: subject as AppBskyActorDefs.ProfileView, + subject: subject as app.bsky.actor.defs.ProfileView, }, ...(spWithMembership.starterPack.listItemsSample?.filter( item => item.subject.did !== variables.actorDid, @@ -109,7 +105,7 @@ export function useListMembershipAddMutation({ }, listItem: { uri: data.uri, - subject: subject as AppBskyActorDefs.ProfileView, + subject: subject as app.bsky.actor.defs.ProfileView, }, } } @@ -167,7 +163,7 @@ export function useListMembershipRemoveMutation({ // update WITH_MEMBERSHIPS query for starter packs queryClient.setQueryData< - InfiniteData + InfiniteData >(STARTER_PACKS_WITH_MEMBERSHIPS_RKEY(variables.actorDid), old => { if (!old) return old diff --git a/src/state/queries/list.ts b/src/state/queries/list.ts index 8c9829d0aa..4a9499117c 100644 --- a/src/state/queries/list.ts +++ b/src/state/queries/list.ts @@ -1,4 +1,3 @@ -import {type AppBskyGraphDefs} from '@atproto/api' import {type $Typed, type Client} from '@atproto/lex' import { type AtIdentifierString, @@ -30,7 +29,7 @@ export const RQKEY = (uri: string) => [RQKEY_ROOT, uri] export function useListQuery(uri?: string) { const client = useAppviewClient() - return useQuery({ + return useQuery({ staleTime: STALE.MINUTES.ONE, queryKey: RQKEY(uri || ''), async queryFn() { diff --git a/src/state/queries/lists-with-membership.ts b/src/state/queries/lists-with-membership.ts index 1bd09704b9..9aac39197c 100644 --- a/src/state/queries/lists-with-membership.ts +++ b/src/state/queries/lists-with-membership.ts @@ -1,4 +1,3 @@ -import {type AppBskyActorDefs} from '@atproto/api' import {type AtIdentifierString} from '@atproto/syntax' import { type InfiniteData, @@ -60,7 +59,7 @@ export function updateListMembershipOptimistically({ actor: string listUri: string membershipUri: string - subject: AppBskyActorDefs.ProfileView + subject: app.bsky.actor.defs.ProfileView }) { queryClient.setQueryData< InfiniteData diff --git a/src/state/queries/messages/accept-conversation.ts b/src/state/queries/messages/accept-conversation.ts index 391f3e49f5..f059322f7c 100644 --- a/src/state/queries/messages/accept-conversation.ts +++ b/src/state/queries/messages/accept-conversation.ts @@ -1,7 +1,3 @@ -import { - type ChatBskyConvoAcceptConvo, - type ChatBskyConvoDefs, -} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' @@ -29,7 +25,7 @@ export function useAcceptConversation( onError, }: { onMutate?: () => void - onSuccess?: (data: ChatBskyConvoAcceptConvo.OutputSchema) => void + onSuccess?: (data: chat.bsky.convo.acceptConvo.$OutputBody) => void onError?: (error: Error) => void }, ) { @@ -47,7 +43,7 @@ export function useAcceptConversation( queryClient.getQueriesData({ queryKey: [CONVO_LIST_ROOT_KEY], }) - let convoBeingAccepted: ChatBskyConvoDefs.ConvoView | null = null + let convoBeingAccepted: chat.bsky.convo.defs.ConvoView | null = null for (const [_key, data] of queryClient.getQueriesData( {queryKey: CONVO_LIST_PARTIAL_KEY('request')}, )) { @@ -60,7 +56,7 @@ export function useAcceptConversation( (old?: ConvoListQueryData) => optimisticDelete(convoId, old), ) if (convoBeingAccepted) { - const acceptedConvo: ChatBskyConvoDefs.ConvoView = { + const acceptedConvo: chat.bsky.convo.defs.ConvoView = { ...convoBeingAccepted, status: 'accepted', } diff --git a/src/state/queries/messages/actor-declaration.ts b/src/state/queries/messages/actor-declaration.ts index 0824e52aae..7f0f363c82 100644 --- a/src/state/queries/messages/actor-declaration.ts +++ b/src/state/queries/messages/actor-declaration.ts @@ -1,4 +1,3 @@ -import {type AppBskyActorDefs} from '@atproto/api' import {type Client} from '@atproto/lex' import {type DidString} from '@atproto/syntax' import {useMutation, useQueryClient} from '@tanstack/react-query' @@ -6,7 +5,7 @@ import {useMutation, useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' import {usePdsClient, useSession} from '#/state/session' import {resolveAllowGroupInvites} from '#/components/dms/util' -import {chat, com} from '#/lexicons' +import {type app, chat, com} from '#/lexicons' import {RQKEY as PROFILE_RKEY} from '../profile' export function useUpdateActorDeclaration({ @@ -27,7 +26,7 @@ export function useUpdateActorDeclaration({ }) => { if (!currentAccount) throw new Error('Not signed in') const current = - queryClient.getQueryData( + queryClient.getQueryData( PROFILE_RKEY(currentAccount.did), ) const allowIncoming = @@ -57,7 +56,7 @@ export function useUpdateActorDeclaration({ if (!currentAccount) return queryClient.setQueryData( PROFILE_RKEY(currentAccount?.did), - (old?: AppBskyActorDefs.ProfileViewDetailed) => { + (old?: app.bsky.actor.defs.ProfileViewDetailed) => { if (!old) return old const allowIncoming = update.allowIncoming ?? @@ -81,7 +80,7 @@ export function useUpdateActorDeclaration({ allowGroupInvites, }, }, - } satisfies AppBskyActorDefs.ProfileViewDetailed + } satisfies app.bsky.actor.defs.ProfileViewDetailed }, ) }, diff --git a/src/state/queries/messages/add-group-members.ts b/src/state/queries/messages/add-group-members.ts index bf4f8a8f4e..2b7610be16 100644 --- a/src/state/queries/messages/add-group-members.ts +++ b/src/state/queries/messages/add-group-members.ts @@ -1,9 +1,3 @@ -import { - type ChatBskyActorDefs, - ChatBskyConvoDefs, - type ChatBskyConvoListConvos, - type ChatBskyGroupAddMembers, -} from '@atproto/api' import {type DidString} from '@atproto/syntax' import { type InfiniteData, @@ -15,7 +9,7 @@ import {logger} from '#/logger' import {useProfileQuery} from '#/state/queries/profile' import {useChatClient, useSession} from '#/state/session' import {chat} from '#/lexicons' -import type * as bsky from '#/types/bsky' +import * as bsky from '#/types/bsky' import {RQKEY as CONVO_KEY} from './conversation' import {RQKEY_ROOT as CONVO_LIST_KEY} from './list-conversations' import {listConvoMembersQueryKey} from './list-convo-members' @@ -26,7 +20,7 @@ export function useAddGroupMembers( onSuccess, onError, }: { - onSuccess?: (data: ChatBskyGroupAddMembers.OutputSchema) => void + onSuccess?: (data: chat.bsky.group.addMembers.$OutputBody) => void onError?: (error: Error) => void }, ) { @@ -52,24 +46,26 @@ export function useAddGroupMembers( onMutate: ({profiles}) => { if (!convoId) return - const prevConvo = queryClient.getQueryData( - CONVO_KEY(convoId), - ) + const prevConvo = + queryClient.getQueryData( + CONVO_KEY(convoId), + ) const prevListEntries = queryClient.getQueriesData< - InfiniteData + InfiniteData >({queryKey: [CONVO_LIST_KEY]}) const prevMemberList = queryClient.getQueryData< - ChatBskyActorDefs.ProfileViewBasic[] + chat.bsky.actor.defs.ProfileViewBasic[] >(listConvoMembersQueryKey(convoId)) - const addedBy: ChatBskyActorDefs.ProfileViewBasic | undefined = myProfile - ? { - ...myProfile, - $type: 'chat.bsky.actor.defs#profileViewBasic', - } - : undefined + const addedBy: chat.bsky.actor.defs.ProfileViewBasic | undefined = + myProfile + ? { + ...myProfile, + $type: 'chat.bsky.actor.defs#profileViewBasic', + } + : undefined - const optimisticMembers: ChatBskyActorDefs.ProfileViewBasic[] = + const optimisticMembers: chat.bsky.actor.defs.ProfileViewBasic[] = profiles.map(profile => ({ ...profile, $type: 'chat.bsky.actor.defs#profileViewBasic', @@ -80,11 +76,12 @@ export function useAddGroupMembers( }, })) - queryClient.setQueryData( + queryClient.setQueryData( CONVO_KEY(convoId), prev => { if (!prev) return - if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return prev + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind)) + return prev return { ...prev, members: [...prev.members, ...optimisticMembers], @@ -97,7 +94,7 @@ export function useAddGroupMembers( ) queryClient.setQueriesData< - InfiniteData + InfiniteData >({queryKey: [CONVO_LIST_KEY]}, prev => { if (!prev?.pages) return return { @@ -106,7 +103,8 @@ export function useAddGroupMembers( ...page, convos: page.convos.map(convo => { if (convo.id !== convoId) return convo - if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) return convo + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) + return convo return { ...convo, members: [...convo.members, ...optimisticMembers], @@ -121,7 +119,7 @@ export function useAddGroupMembers( } }) - queryClient.setQueryData( + queryClient.setQueryData( listConvoMembersQueryKey(convoId), prev => { if (!prev) return @@ -133,13 +131,13 @@ export function useAddGroupMembers( }, onSuccess: data => { if (convoId) { - queryClient.setQueryData( + queryClient.setQueryData( CONVO_KEY(convoId), data.convo, ) queryClient.setQueriesData< - InfiniteData + InfiniteData >({queryKey: [CONVO_LIST_KEY]}, prev => { if (!prev?.pages) return return { diff --git a/src/state/queries/messages/conversation.ts b/src/state/queries/messages/conversation.ts index c0bf8f3551..04e3a7129b 100644 --- a/src/state/queries/messages/conversation.ts +++ b/src/state/queries/messages/conversation.ts @@ -1,9 +1,3 @@ -import { - type ChatBskyActorDefs, - type ChatBskyConvoDefs, - type ChatBskyConvoGetConvo, - type ChatBskyConvoGetUnreadCounts, -} from '@atproto/api' import { type QueryClient, useMutation, @@ -44,7 +38,7 @@ export function useConvoQuery({convoId}: {convoId: string}) { export function precacheConvoQuery( queryClient: QueryClient, - convo: ChatBskyConvoDefs.ConvoView, + convo: chat.bsky.convo.defs.ConvoView, ) { queryClient.setQueryData(RQKEY(convo.id), convo) } @@ -81,7 +75,7 @@ export function useMarkAsReadMutation() { // find the convo so we know which badge counter (if any) to decrement. // keep scanning past a stale unreadCount === 0 cache so another cache // holding the true unread state still drives the decrement - let unreadStatus: ChatBskyConvoDefs.ConvoView['status'] | undefined + let unreadStatus: chat.bsky.convo.defs.ConvoView['status'] | undefined for (const [, data] of prevListQueries) { if (!data) continue const convo = getConvoFromQueryData(convoId, data) @@ -96,11 +90,13 @@ export function useMarkAsReadMutation() { // the badge count query is a separate server query that the list caches // don't feed, so decrement it here to keep the badge in sync const prevUnreadCountsQueries = - queryClient.getQueriesData({ - queryKey: UNREAD_COUNTS_PARTIAL_KEY, - }) + queryClient.getQueriesData( + { + queryKey: UNREAD_COUNTS_PARTIAL_KEY, + }, + ) if (unreadStatus) { - queryClient.setQueriesData( + queryClient.setQueriesData( {queryKey: UNREAD_COUNTS_PARTIAL_KEY}, old => { if (!old) return old @@ -184,9 +180,9 @@ export function useMarkAsReadMutation() { export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< - ChatBskyConvoGetConvo.OutputSchema['convo'] + chat.bsky.convo.getConvo.$OutputBody['convo'] >({ queryKey: [RQKEY_ROOT], }) diff --git a/src/state/queries/messages/create-group-chat.ts b/src/state/queries/messages/create-group-chat.ts index 4a78a2be6a..3a12a976e6 100644 --- a/src/state/queries/messages/create-group-chat.ts +++ b/src/state/queries/messages/create-group-chat.ts @@ -1,4 +1,3 @@ -import {type ChatBskyGroupCreateGroup} from '@atproto/api' import {type DidString} from '@atproto/syntax' import {useMutation, useQueryClient} from '@tanstack/react-query' @@ -11,7 +10,7 @@ export function useCreateGroupChat({ onSuccess, onError, }: { - onSuccess?: (data: ChatBskyGroupCreateGroup.OutputSchema) => void + onSuccess?: (data: chat.bsky.group.createGroup.$OutputBody) => void onError?: (error: Error) => void }) { const queryClient = useQueryClient() diff --git a/src/state/queries/messages/create-join-link.ts b/src/state/queries/messages/create-join-link.ts index 57269b93e3..5f52cadf8f 100644 --- a/src/state/queries/messages/create-join-link.ts +++ b/src/state/queries/messages/create-join-link.ts @@ -1,13 +1,10 @@ -import { - ChatBskyConvoDefs, - type ChatBskyGroupCreateJoinLink, - type ChatBskyGroupDefs, -} from '@atproto/api' +import {toDatetimeString} from '@atproto/syntax' import {useMutation, useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' import {useChatClient} from '#/state/session' import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import { rollbackConvoOptimistic, updateConvoOptimistic, @@ -19,7 +16,7 @@ export function useCreateJoinLink( onSuccess, onError, }: { - onSuccess?: (data: ChatBskyGroupCreateJoinLink.OutputSchema) => void + onSuccess?: (data: chat.bsky.group.createJoinLink.$OutputBody) => void onError?: (error: Error) => void }, ) { @@ -31,7 +28,7 @@ export function useCreateJoinLink( joinRule, requireApproval, }: { - joinRule: ChatBskyGroupDefs.JoinRule + joinRule: chat.bsky.group.defs.JoinRule requireApproval: boolean }) => { if (!convoId) throw new Error('No convoId provided') @@ -44,7 +41,8 @@ export function useCreateJoinLink( onMutate: ({joinRule, requireApproval}) => { if (!convoId) return return updateConvoOptimistic(queryClient, convoId, prev => { - if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind)) + return undefined return { ...prev, kind: { @@ -55,7 +53,7 @@ export function useCreateJoinLink( enabledStatus: 'enabled', joinRule, requireApproval, - createdAt: new Date().toISOString(), + createdAt: toDatetimeString(new Date()), }, }, } @@ -64,7 +62,8 @@ export function useCreateJoinLink( onSuccess: data => { if (convoId) { updateConvoOptimistic(queryClient, convoId, prev => { - if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind)) + return undefined return { ...prev, kind: {...prev.kind, joinLink: data.joinLink}, diff --git a/src/state/queries/messages/disable-join-link.ts b/src/state/queries/messages/disable-join-link.ts index b995dbcc6f..d58edca37b 100644 --- a/src/state/queries/messages/disable-join-link.ts +++ b/src/state/queries/messages/disable-join-link.ts @@ -1,13 +1,10 @@ -import { - ChatBskyConvoDefs, - type ChatBskyGroupDisableJoinLink, -} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' import {invalidateJoinLinkPreviewsForCode} from '#/state/queries/join-links' import {useChatClient} from '#/state/session' import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import { rollbackConvoOptimistic, updateConvoOptimistic, @@ -19,7 +16,7 @@ export function useDisableJoinLink( onSuccess, onError, }: { - onSuccess?: (data: ChatBskyGroupDisableJoinLink.OutputSchema) => void + onSuccess?: (data: chat.bsky.group.disableJoinLink.$OutputBody) => void onError?: (error: Error) => void }, ) { @@ -34,7 +31,10 @@ export function useDisableJoinLink( onMutate: () => { if (!convoId) return return updateConvoOptimistic(queryClient, convoId, prev => { - if (!ChatBskyConvoDefs.isGroupConvo(prev.kind) || !prev.kind.joinLink) { + if ( + !bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind) || + !prev.kind.joinLink + ) { return undefined } return { @@ -49,7 +49,8 @@ export function useDisableJoinLink( onSuccess: data => { if (convoId) { updateConvoOptimistic(queryClient, convoId, prev => { - if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind)) + return undefined return { ...prev, kind: {...prev.kind, joinLink: data.joinLink}, diff --git a/src/state/queries/messages/edit-group-chat-name.ts b/src/state/queries/messages/edit-group-chat-name.ts index ecc73fd3d3..88c950c7ee 100644 --- a/src/state/queries/messages/edit-group-chat-name.ts +++ b/src/state/queries/messages/edit-group-chat-name.ts @@ -1,9 +1,9 @@ -import {ChatBskyConvoDefs, type ChatBskyGroupEditGroup} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' import {useChatClient} from '#/state/session' import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import { rollbackConvoOptimistic, updateConvoOptimistic, @@ -15,7 +15,7 @@ export function useEditGroupChatName( onSuccess, onError, }: { - onSuccess?: (data: ChatBskyGroupEditGroup.OutputSchema) => void + onSuccess?: (data: chat.bsky.group.editGroup.$OutputBody) => void onError?: (error: Error) => void }, ) { @@ -33,7 +33,8 @@ export function useEditGroupChatName( onMutate: ({name: groupName}) => { if (!convoId) return return updateConvoOptimistic(queryClient, convoId, prev => { - if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind)) + return undefined return { ...prev, kind: {...prev.kind, name: groupName}, diff --git a/src/state/queries/messages/edit-join-link.ts b/src/state/queries/messages/edit-join-link.ts index 7870c1f7e3..f921b3062b 100644 --- a/src/state/queries/messages/edit-join-link.ts +++ b/src/state/queries/messages/edit-join-link.ts @@ -1,13 +1,9 @@ -import { - ChatBskyConvoDefs, - type ChatBskyGroupDefs, - type ChatBskyGroupEditJoinLink, -} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' import {useChatClient} from '#/state/session' import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import { rollbackConvoOptimistic, updateConvoOptimistic, @@ -19,7 +15,7 @@ export function useEditJoinLink( onSuccess, onError, }: { - onSuccess?: (data: ChatBskyGroupEditJoinLink.OutputSchema) => void + onSuccess?: (data: chat.bsky.group.editJoinLink.$OutputBody) => void onError?: (error: Error) => void }, ) { @@ -31,7 +27,7 @@ export function useEditJoinLink( joinRule, requireApproval, }: { - joinRule: ChatBskyGroupDefs.JoinRule + joinRule: chat.bsky.group.defs.JoinRule requireApproval: boolean }) => { if (!convoId) throw new Error('No convoId provided') @@ -44,7 +40,10 @@ export function useEditJoinLink( onMutate: ({joinRule, requireApproval}) => { if (!convoId) return return updateConvoOptimistic(queryClient, convoId, prev => { - if (!ChatBskyConvoDefs.isGroupConvo(prev.kind) || !prev.kind.joinLink) { + if ( + !bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind) || + !prev.kind.joinLink + ) { return undefined } return { @@ -59,7 +58,8 @@ export function useEditJoinLink( onSuccess: data => { if (convoId) { updateConvoOptimistic(queryClient, convoId, prev => { - if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind)) + return undefined return { ...prev, kind: {...prev.kind, joinLink: data.joinLink}, diff --git a/src/state/queries/messages/enable-join-link.ts b/src/state/queries/messages/enable-join-link.ts index ef3b168068..d426adf5d7 100644 --- a/src/state/queries/messages/enable-join-link.ts +++ b/src/state/queries/messages/enable-join-link.ts @@ -1,10 +1,10 @@ -import {ChatBskyConvoDefs, type ChatBskyGroupEnableJoinLink} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' import {invalidateJoinLinkPreviewsForCode} from '#/state/queries/join-links' import {useChatClient} from '#/state/session' import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import { rollbackConvoOptimistic, updateConvoOptimistic, @@ -16,7 +16,7 @@ export function useEnableJoinLink( onSuccess, onError, }: { - onSuccess?: (data: ChatBskyGroupEnableJoinLink.OutputSchema) => void + onSuccess?: (data: chat.bsky.group.enableJoinLink.$OutputBody) => void onError?: (error: Error) => void }, ) { @@ -31,7 +31,10 @@ export function useEnableJoinLink( onMutate: () => { if (!convoId) return return updateConvoOptimistic(queryClient, convoId, prev => { - if (!ChatBskyConvoDefs.isGroupConvo(prev.kind) || !prev.kind.joinLink) { + if ( + !bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind) || + !prev.kind.joinLink + ) { return undefined } return { @@ -46,7 +49,8 @@ export function useEnableJoinLink( onSuccess: data => { if (convoId) { updateConvoOptimistic(queryClient, convoId, prev => { - if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind)) + return undefined return { ...prev, kind: {...prev.kind, joinLink: data.joinLink}, diff --git a/src/state/queries/messages/get-convo-for-members.ts b/src/state/queries/messages/get-convo-for-members.ts index 20f5380126..803fb3e04f 100644 --- a/src/state/queries/messages/get-convo-for-members.ts +++ b/src/state/queries/messages/get-convo-for-members.ts @@ -1,4 +1,3 @@ -import {type ChatBskyConvoGetConvoForMembers} from '@atproto/api' import {type DidString} from '@atproto/syntax' import {useMutation, useQueryClient} from '@tanstack/react-query' @@ -11,7 +10,7 @@ export function useGetConvoForMembers({ onSuccess, onError, }: { - onSuccess?: (data: ChatBskyConvoGetConvoForMembers.OutputSchema) => void + onSuccess?: (data: chat.bsky.convo.getConvoForMembers.$OutputBody) => void onError?: (error: Error) => void }) { const queryClient = useQueryClient() diff --git a/src/state/queries/messages/join-requests.ts b/src/state/queries/messages/join-requests.ts index d36ebd60ae..03be537426 100644 --- a/src/state/queries/messages/join-requests.ts +++ b/src/state/queries/messages/join-requests.ts @@ -1,9 +1,3 @@ -import { - type ChatBskyActorDefs, - type ChatBskyGroupApproveJoinRequest, - type ChatBskyGroupListJoinRequests, - type ChatBskyGroupRejectJoinRequest, -} from '@atproto/api' import {type DidString} from '@atproto/syntax' import { type InfiniteData, @@ -20,8 +14,8 @@ import {createListJoinRequestsQueryKey} from './list-join-requests' type JoinRequestAction = 'approve' | 'reject' type JoinRequestOutput = A extends 'approve' - ? ChatBskyGroupApproveJoinRequest.OutputSchema - : ChatBskyGroupRejectJoinRequest.OutputSchema + ? chat.bsky.group.approveJoinRequest.$OutputBody + : chat.bsky.group.rejectJoinRequest.$OutputBody export function useJoinRequestMutation( action: A, @@ -65,7 +59,7 @@ export function useJoinRequestMutation( const requestsKey = createListJoinRequestsQueryKey({convoId}) const prevRequests = queryClient.getQueryData< - InfiniteData + InfiniteData >(requestsKey) const requestedByProfile = prevRequests?.pages @@ -73,7 +67,7 @@ export function useJoinRequestMutation( .find(request => request.requestedBy.did === member)?.requestedBy queryClient.setQueryData< - InfiniteData + InfiniteData >(requestsKey, prev => { if (!prev?.pages) return prev return { @@ -87,14 +81,14 @@ export function useJoinRequestMutation( } }) - let prevMembers: ChatBskyActorDefs.ProfileViewBasic[] | undefined + let prevMembers: chat.bsky.actor.defs.ProfileViewBasic[] | undefined if (action === 'approve' && requestedByProfile) { const membersKey = listConvoMembersQueryKey(convoId) prevMembers = - queryClient.getQueryData( + queryClient.getQueryData( membersKey, ) - queryClient.setQueryData( + queryClient.setQueryData( membersKey, prev => { if (!prev) return prev diff --git a/src/state/queries/messages/leave-conversation.ts b/src/state/queries/messages/leave-conversation.ts index 72379d8b30..a0c5f2118d 100644 --- a/src/state/queries/messages/leave-conversation.ts +++ b/src/state/queries/messages/leave-conversation.ts @@ -1,7 +1,3 @@ -import { - type ChatBskyConvoLeaveConvo, - type ChatBskyConvoListConvos, -} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' @@ -22,7 +18,7 @@ export function RQKEY(convoId: string | undefined) { type ConvoListQueryData = { pageParams: Array - pages: Array + pages: Array } export function useLeaveConvo( @@ -33,7 +29,7 @@ export function useLeaveConvo( onError, }: { onMutate?: () => void - onSuccess?: (data: ChatBskyConvoLeaveConvo.OutputSchema) => void + onSuccess?: (data: chat.bsky.convo.leaveConvo.$OutputBody) => void onError?: (error: Error) => void }, ) { diff --git a/src/state/queries/messages/list-conversation-requests.tsx b/src/state/queries/messages/list-conversation-requests.tsx index 38ae8e6c3d..4c8d5d5caa 100644 --- a/src/state/queries/messages/list-conversation-requests.tsx +++ b/src/state/queries/messages/list-conversation-requests.tsx @@ -1,8 +1,3 @@ -import { - ChatBskyConvoDefs, - type ChatBskyConvoListConvoRequests, - ChatBskyGroupDefs, -} from '@atproto/api' import { type InfiniteData, type QueryClient, @@ -11,6 +6,7 @@ import { import {useChatClient} from '#/state/session' import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' const DEFAULT_LIMIT = 10 @@ -44,16 +40,18 @@ export function useListConvoRequests({ export type ConvoRequestListQueryData = { pageParams: Array - pages: Array + pages: Array } export type ConvoRequestItem = - ChatBskyConvoListConvoRequests.OutputSchema['requests'][number] + chat.bsky.convo.listConvoRequests.$OutputBody['requests'][number] export function optimisticUpdate( chatId: string, old: ConvoRequestListQueryData | undefined, - updateFn: (convo: ChatBskyConvoDefs.ConvoView) => ChatBskyConvoDefs.ConvoView, + updateFn: ( + convo: chat.bsky.convo.defs.ConvoView, + ) => chat.bsky.convo.defs.ConvoView, ): ConvoRequestListQueryData | undefined { if (!old) return old @@ -62,7 +60,10 @@ export function optimisticUpdate( pages: old.pages.map(page => ({ ...page, requests: page.requests.map((item): ConvoRequestItem => { - if (ChatBskyConvoDefs.isConvoView(item) && item.id === chatId) { + if ( + bsky.isType(chat.bsky.convo.defs.convoView, item) && + item.id === chatId + ) { return { ...updateFn(item), $type: 'chat.bsky.convo.defs#convoView', @@ -85,7 +86,9 @@ export function optimisticDelete( pages: old.pages.map(page => ({ ...page, requests: page.requests.filter( - item => !ChatBskyConvoDefs.isConvoView(item) || item.id !== chatId, + item => + !bsky.isType(chat.bsky.convo.defs.convoView, item) || + item.id !== chatId, ), })), } @@ -101,7 +104,7 @@ export function markAllRead( pages: old.pages.map(page => ({ ...page, requests: page.requests.map((item): ConvoRequestItem => { - if (ChatBskyConvoDefs.isConvoView(item)) { + if (bsky.isType(chat.bsky.convo.defs.convoView, item)) { return { ...item, $type: 'chat.bsky.convo.defs#convoView', @@ -126,7 +129,7 @@ export function optimisticDeleteJoinRequest( ...page, requests: page.requests.filter( item => - !ChatBskyGroupDefs.isJoinRequestConvoView(item) || + !bsky.isType(chat.bsky.group.defs.joinRequestConvoView, item) || item.convoId !== convoId, ), })), @@ -138,7 +141,7 @@ export function* findAllProfilesInQueryData( did: string, ) { const queryDatas = queryClient.getQueriesData< - InfiniteData + InfiniteData >({ queryKey: [RQKEY_ROOT], }) @@ -147,13 +150,15 @@ export function* findAllProfilesInQueryData( for (const page of queryData.pages) { for (const item of page.requests) { - if (ChatBskyConvoDefs.isConvoView(item)) { + if (bsky.isType(chat.bsky.convo.defs.convoView, item)) { for (const member of item.members) { if (member.did === did) { yield member } } - } else if (ChatBskyGroupDefs.isJoinRequestConvoView(item)) { + } else if ( + bsky.isType(chat.bsky.group.defs.joinRequestConvoView, item) + ) { if (item.owner.did === did) { yield item.owner } diff --git a/src/state/queries/messages/list-conversations.tsx b/src/state/queries/messages/list-conversations.tsx index 6d81a96ecc..82e9a37c09 100644 --- a/src/state/queries/messages/list-conversations.tsx +++ b/src/state/queries/messages/list-conversations.tsx @@ -1,9 +1,4 @@ import {useCallback, useEffect, useMemo} from 'react' -import { - type ChatBskyActorDefs, - ChatBskyConvoDefs, - type ChatBskyConvoListConvos, -} from '@atproto/api' import { type InfiniteData, type Query, @@ -68,7 +63,7 @@ export const RQKEY_PARTIAL = ( * filters client-side or convos leak into lists that should exclude them. */ export function convoMatchesQueryKey( - convo: ChatBskyConvoDefs.ConvoView, + convo: chat.bsky.convo.defs.ConvoView, queryKey: QueryKey, ): boolean { const [, status, readState, kind, lockStatus] = queryKey as ReturnType< @@ -76,7 +71,7 @@ export function convoMatchesQueryKey( > if (status !== 'all' && status !== convo.status) return false if (readState === 'unread' && convo.unreadCount === 0) return false - if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) { + if (bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) { if (kind === 'direct') return false if (lockStatus && convo.kind.lockStatus !== lockStatus) return false } else { @@ -94,7 +89,7 @@ export function convoMatchesQueryKey( * longer matches (e.g. unreadCount dropped to 0), mirroring how read/mute * log events update convos in place everywhere. */ -export function convoListQueryPredicate(convo: ChatBskyConvoDefs.ConvoView) { +export function convoListQueryPredicate(convo: chat.bsky.convo.defs.ConvoView) { return (query: Query): boolean => { const data = query.state.data as ConvoListQueryData | undefined if (data && getConvoFromQueryData(convo.id, data)) return true @@ -196,10 +191,10 @@ export function ListConvosProviderInner({ function mutateMembers( convoId: string, fn: ( - members: ChatBskyActorDefs.ProfileViewBasic[], - ) => ChatBskyActorDefs.ProfileViewBasic[], + members: chat.bsky.actor.defs.ProfileViewBasic[], + ) => chat.bsky.actor.defs.ProfileViewBasic[], ) { - queryClient.setQueryData( + queryClient.setQueryData( listConvoMembersQueryKey(convoId), old => { if (!old) return // query doesn't exist yet, skip @@ -211,8 +206,8 @@ export function ListConvosProviderInner({ function updateConvoInAllLists( convoId: string, fn: ( - convo: ChatBskyConvoDefs.ConvoView, - ) => ChatBskyConvoDefs.ConvoView, + convo: chat.bsky.convo.defs.ConvoView, + ) => chat.bsky.convo.defs.ConvoView, ) { queryClient.setQueriesData( {queryKey: [RQKEY_ROOT]}, @@ -227,10 +222,10 @@ export function ListConvosProviderInner({ function mutateConvoView( convoId: string, fn: ( - convo: ChatBskyConvoDefs.ConvoView, - ) => ChatBskyConvoDefs.ConvoView, + convo: chat.bsky.convo.defs.ConvoView, + ) => chat.bsky.convo.defs.ConvoView, ) { - queryClient.setQueryData( + queryClient.setQueryData( CONVO_KEY(convoId), old => (old ? fn(old) : old), ) @@ -251,7 +246,7 @@ export function ListConvosProviderInner({ function handleMemberAdded( convoId: string, did: string, - relatedProfiles: ChatBskyActorDefs.ProfileViewBasic[], + relatedProfiles: chat.bsky.actor.defs.ProfileViewBasic[], rev: string, ) { const newMember = relatedProfiles.find(r => r.did === did) @@ -261,7 +256,7 @@ export function ListConvosProviderInner({ const alreadyKnownMember = queryClient .getQueryData< - ChatBskyActorDefs.ProfileViewBasic[] + chat.bsky.actor.defs.ProfileViewBasic[] >(listConvoMembersQueryKey(convoId)) ?.some(m => m.did === did) ?? false mutateMembers(convoId, list => @@ -285,7 +280,7 @@ export function ListConvosProviderInner({ const alreadyRemovedMember = queryClient .getQueryData< - ChatBskyActorDefs.ProfileViewBasic[] + chat.bsky.actor.defs.ProfileViewBasic[] >(listConvoMembersQueryKey(convoId)) ?.some(m => m.did === did) === false mutateMembers(convoId, list => list.filter(m => m.did !== did)) @@ -298,24 +293,36 @@ export function ListConvosProviderInner({ } for (const log of events.logs) { - if (ChatBskyConvoDefs.isLogBeginConvo(log)) { + if (bsky.isType(chat.bsky.convo.defs.logBeginConvo, log)) { debouncedRefetch() - } else if (ChatBskyConvoDefs.isLogLeaveConvo(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logLeaveConvo, log)) { deleteConvoFromAllLists(log.convoId) // The viewer is no longer in this convo (they left on another // device, or were removed - removed members receive a // logLeaveConvo, not a logRemoveMember). Refetch any cached join // link preview so its viewer state reflects the lost membership. void invalidateJoinLinkPreviewsForConvo(queryClient, log.convoId) - } else if (ChatBskyConvoDefs.isLogDeleteMessage(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logDeleteMessage, log)) { updateConvoInAllLists( log.convoId, withRevGuard(log.rev, convo => { if ( - (ChatBskyConvoDefs.isDeletedMessageView(log.message) || - ChatBskyConvoDefs.isMessageView(log.message)) && - (ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage) || - ChatBskyConvoDefs.isMessageView(convo.lastMessage)) + (bsky.isType( + chat.bsky.convo.defs.deletedMessageView, + log.message, + ) || + bsky.isType( + chat.bsky.convo.defs.messageView, + log.message, + )) && + (bsky.isType( + chat.bsky.convo.defs.deletedMessageView, + convo.lastMessage, + ) || + bsky.isType( + chat.bsky.convo.defs.messageView, + convo.lastMessage, + )) ) { return log.message.id === convo.lastMessage.id ? { @@ -329,9 +336,9 @@ export function ListConvosProviderInner({ } }), ) - } else if (ChatBskyConvoDefs.isLogCreateMessage(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logCreateMessage, log)) { // Store in a new var to avoid TS errors due to closures. - const logRef: ChatBskyConvoDefs.LogCreateMessage = log + const logRef: chat.bsky.convo.defs.LogCreateMessage = log // Get all matching queries const queries = queryClient.getQueriesData({ @@ -339,7 +346,7 @@ export function ListConvosProviderInner({ }) // Check if convo exists in any query - let foundConvo: ChatBskyConvoDefs.ConvoView | null = null + let foundConvo: chat.bsky.convo.defs.ConvoView | null = null for (const [_key, query] of queries) { if (!query) continue const convo = getConvoFromQueryData(logRef.convoId, query) @@ -382,15 +389,23 @@ export function ListConvosProviderInner({ lastMessage: logRef.message, unreadCount: foundConvo.id !== currentConvoId - ? (ChatBskyConvoDefs.isMessageView(logRef.message) || - ChatBskyConvoDefs.isDeletedMessageView(logRef.message)) && + ? (bsky.isType( + chat.bsky.convo.defs.messageView, + logRef.message, + ) || + bsky.isType( + chat.bsky.convo.defs.deletedMessageView, + logRef.message, + )) && logRef.message.sender.did !== currentAccount?.did ? foundConvo.unreadCount + 1 : foundConvo.unreadCount : 0, } - function filterConvoFromPage(convo: ChatBskyConvoDefs.ConvoView[]) { + function filterConvoFromPage( + convo: chat.bsky.convo.defs.ConvoView[], + ) { return convo.filter(c => c.id !== logRef.convoId) } @@ -453,7 +468,7 @@ export function ListConvosProviderInner({ old => moveConvoToTopInRequests(updatedConvo, old), ) } - } else if (ChatBskyConvoDefs.isLogReadMessage(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logReadMessage, log)) { updateConvoInAllLists( log.convoId, withRevGuard(log.rev, convo => ({ @@ -462,7 +477,7 @@ export function ListConvosProviderInner({ rev: log.rev, })), ) - } else if (ChatBskyConvoDefs.isLogReadConvo(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logReadConvo, log)) { updateConvoInAllLists( log.convoId, withRevGuard(log.rev, convo => ({ @@ -471,12 +486,12 @@ export function ListConvosProviderInner({ rev: log.rev, })), ) - } else if (ChatBskyConvoDefs.isLogAcceptConvo(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logAcceptConvo, log)) { const requestQueries = queryClient.getQueriesData({ queryKey: RQKEY_PARTIAL('request'), }) - let foundConvo: ChatBskyConvoDefs.ConvoView | null = null + let foundConvo: chat.bsky.convo.defs.ConvoView | null = null for (const [_key, data] of requestQueries) { if (!data) continue foundConvo = getConvoFromQueryData(log.convoId, data) @@ -492,7 +507,7 @@ export function ListConvosProviderInner({ if (log.rev <= foundConvo.rev) { continue } - const acceptedConvo: ChatBskyConvoDefs.ConvoView = { + const acceptedConvo: chat.bsky.convo.defs.ConvoView = { ...foundConvo, status: 'accepted', rev: log.rev, @@ -553,7 +568,7 @@ export function ListConvosProviderInner({ } }, ) - } else if (ChatBskyConvoDefs.isLogMuteConvo(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logMuteConvo, log)) { mutateConvoView( log.convoId, withRevGuard(log.rev, convo => ({ @@ -562,7 +577,7 @@ export function ListConvosProviderInner({ rev: log.rev, })), ) - } else if (ChatBskyConvoDefs.isLogUnmuteConvo(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logUnmuteConvo, log)) { mutateConvoView( log.convoId, withRevGuard(log.rev, convo => ({ @@ -571,11 +586,11 @@ export function ListConvosProviderInner({ rev: log.rev, })), ) - } else if (ChatBskyConvoDefs.isLogLockConvo(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logLockConvo, log)) { mutateConvoView( log.convoId, withRevGuard(log.rev, convo => { - if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) { + if (bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) { return { ...convo, kind: {...convo.kind, lockStatus: 'locked'}, @@ -590,11 +605,11 @@ export function ListConvosProviderInner({ void queryClient.invalidateQueries({ queryKey: CONVO_KEY(log.convoId), }) - } else if (ChatBskyConvoDefs.isLogUnlockConvo(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logUnlockConvo, log)) { mutateConvoView( log.convoId, withRevGuard(log.rev, convo => { - if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) { + if (bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) { return { ...convo, kind: { @@ -609,11 +624,13 @@ export function ListConvosProviderInner({ return {...convo, rev: log.rev} }), ) - } else if (ChatBskyConvoDefs.isLogLockConvoPermanently(log)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.logLockConvoPermanently, log) + ) { mutateConvoView( log.convoId, withRevGuard(log.rev, convo => { - if (ChatBskyConvoDefs.isGroupConvo(convo.kind)) { + if (bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) { return { ...convo, kind: {...convo.kind, lockStatus: 'locked-permanently'}, @@ -624,20 +641,20 @@ export function ListConvosProviderInner({ }), ) } else if ( - ChatBskyConvoDefs.isLogCreateJoinLink(log) || - ChatBskyConvoDefs.isLogEditJoinLink(log) || - ChatBskyConvoDefs.isLogEnableJoinLink(log) || - ChatBskyConvoDefs.isLogDisableJoinLink(log) + bsky.isType(chat.bsky.convo.defs.logCreateJoinLink, log) || + bsky.isType(chat.bsky.convo.defs.logEditJoinLink, log) || + bsky.isType(chat.bsky.convo.defs.logEnableJoinLink, log) || + bsky.isType(chat.bsky.convo.defs.logDisableJoinLink, log) ) { // Join link data not included in the log event, trigger refetch to get it debouncedRefetch() - } else if (ChatBskyConvoDefs.isLogEditGroup(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logEditGroup, log)) { // Updated group details (name etc.) aren't included in the log // event, so refetch to pick them up. debouncedRefetch() } else if ( - ChatBskyConvoDefs.isLogApproveJoinRequest(log) || - ChatBskyConvoDefs.isLogRejectJoinRequest(log) + bsky.isType(chat.bsky.convo.defs.logApproveJoinRequest, log) || + bsky.isType(chat.bsky.convo.defs.logRejectJoinRequest, log) ) { // Route through mutateConvoView (not updateConvoInAllLists) so the // single-convo cache updates too, keeping the in-convo requests @@ -648,7 +665,9 @@ export function ListConvosProviderInner({ applyJoinRequestCountDelta(convo, log.rev, -1), ), ) - } else if (ChatBskyConvoDefs.isLogIncomingJoinRequest(log)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.logIncomingJoinRequest, log) + ) { // Route through mutateConvoView (not updateConvoInAllLists) so the // single-convo cache updates too, letting the in-convo requests // banner appear live. @@ -658,14 +677,16 @@ export function ListConvosProviderInner({ applyJoinRequestCountDelta(convo, log.rev, 1), ), ) - } else if (ChatBskyConvoDefs.isLogReadJoinRequests(log)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.logReadJoinRequests, log) + ) { // The owner marked join requests as read (possibly on another // device). Zero the unread count but keep the total, mirroring the // useMarkJoinRequestsRead mutation. mutateConvoView( log.convoId, withRevGuard(log.rev, convo => { - if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) { + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) { return {...convo, rev: log.rev} } return { @@ -675,11 +696,18 @@ export function ListConvosProviderInner({ } }), ) - } else if (ChatBskyConvoDefs.isLogOutgoingJoinRequest(log)) { + } else if ( + bsky.isType(chat.bsky.convo.defs.logOutgoingJoinRequest, log) + ) { // Viewer isn't in the chat yet, but the inbox surfaces outgoing // requests, so refetch to pick up the new entry. debouncedRefetch() - } else if (ChatBskyConvoDefs.isLogWithdrawIncomingJoinRequest(log)) { + } else if ( + bsky.isType( + chat.bsky.convo.defs.logWithdrawIncomingJoinRequest, + log, + ) + ) { // A requester rescinded their request to a group the viewer owns. // Mirror of isLogIncomingJoinRequest: decrement the counts. mutateConvoView( @@ -688,14 +716,19 @@ export function ListConvosProviderInner({ applyJoinRequestCountDelta(convo, log.rev, -1), ), ) - } else if (ChatBskyConvoDefs.isLogWithdrawOutgoingJoinRequest(log)) { + } else if ( + bsky.isType( + chat.bsky.convo.defs.logWithdrawOutgoingJoinRequest, + log, + ) + ) { // The viewer rescinded their own outgoing join request (possibly on // another device). Remove it from the requests inbox cache. queryClient.setQueriesData( {queryKey: [REQUESTS_RQKEY_ROOT]}, old => optimisticDeleteJoinRequest(log.convoId, old), ) - } else if (ChatBskyConvoDefs.isLogAddReaction(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logAddReaction, log)) { updateConvoInAllLists( log.convoId, withRevGuard(log.rev, convo => { @@ -709,22 +742,26 @@ export function ListConvosProviderInner({ return { ...convo, members: [...convo.members, ...relatedProfilesSansMembers], + /* + * `logAddReaction.message` is the wider message union (it can + * carry a deleted message too), while the reaction view's + * `message` slot is only the live `messageView` - the server + * never pairs a reaction with a deleted message, so the arm is + * asserted rather than re-narrowed. + */ lastReaction: { $type: 'chat.bsky.convo.defs#messageAndReactionView', reaction: log.reaction, message: log.message, - }, + } as chat.bsky.convo.defs.ConvoView['lastReaction'], rev: log.rev, } }), ) - } else if (ChatBskyConvoDefs.isLogAddMember(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logAddMember, log)) { const data = log.message.data if ( - bsky.dangerousIsType( - data, - ChatBskyConvoDefs.isSystemMessageDataAddMember, - ) + bsky.isType(chat.bsky.convo.defs.systemMessageDataAddMember, data) ) { handleMemberAdded( log.convoId, @@ -738,12 +775,12 @@ export function ListConvosProviderInner({ queryKey: CONVO_KEY(log.convoId), }) debouncedRefetch() - } else if (ChatBskyConvoDefs.isLogRemoveMember(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logRemoveMember, log)) { const data = log.message.data if ( - bsky.dangerousIsType( + bsky.isType( + chat.bsky.convo.defs.systemMessageDataRemoveMember, data, - ChatBskyConvoDefs.isSystemMessageDataRemoveMember, ) ) { handleMemberRemoved(log.convoId, data.member.did, log.rev) @@ -753,12 +790,12 @@ export function ListConvosProviderInner({ queryKey: CONVO_KEY(log.convoId), }) debouncedRefetch() - } else if (ChatBskyConvoDefs.isLogMemberJoin(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logMemberJoin, log)) { const data = log.message.data if ( - bsky.dangerousIsType( + bsky.isType( + chat.bsky.convo.defs.systemMessageDataMemberJoin, data, - ChatBskyConvoDefs.isSystemMessageDataMemberJoin, ) ) { handleMemberAdded( @@ -772,12 +809,12 @@ export function ListConvosProviderInner({ queryKey: CONVO_KEY(log.convoId), }) debouncedRefetch() - } else if (ChatBskyConvoDefs.isLogMemberLeave(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logMemberLeave, log)) { const data = log.message.data if ( - bsky.dangerousIsType( + bsky.isType( + chat.bsky.convo.defs.systemMessageDataMemberLeave, data, - ChatBskyConvoDefs.isSystemMessageDataMemberLeave, ) ) { handleMemberRemoved(log.convoId, data.member.did, log.rev) @@ -786,7 +823,7 @@ export function ListConvosProviderInner({ queryKey: CONVO_KEY(log.convoId), }) debouncedRefetch() - } else if (ChatBskyConvoDefs.isLogRemoveReaction(log)) { + } else if (bsky.isType(chat.bsky.convo.defs.logRemoveReaction, log)) { queryClient.setQueriesData( {queryKey: [RQKEY_ROOT]}, (old?: ConvoListQueryData) => @@ -797,10 +834,14 @@ export function ListConvosProviderInner({ if ( // if the convo is the same log.convoId === convo.id && - ChatBskyConvoDefs.isMessageAndReactionView( + bsky.isType( + chat.bsky.convo.defs.messageAndReactionView, convo.lastReaction, ) && - ChatBskyConvoDefs.isMessageView(log.message) && + bsky.isType( + chat.bsky.convo.defs.messageView, + log.message, + ) && // ...and the message is the same convo.lastReaction.message.id === log.message.id && // ...and the reaction is the same @@ -882,7 +923,7 @@ export function useUnreadMessageCount(): { export type ConvoListQueryData = { pageParams: Array - pages: Array + pages: Array } export function useOnMarkAsRead() { @@ -919,8 +960,8 @@ export function useOnMarkAsRead() { */ function withRevGuard( rev: string, - fn: (convo: ChatBskyConvoDefs.ConvoView) => ChatBskyConvoDefs.ConvoView, -): (convo: ChatBskyConvoDefs.ConvoView) => ChatBskyConvoDefs.ConvoView { + fn: (convo: chat.bsky.convo.defs.ConvoView) => chat.bsky.convo.defs.ConvoView, +): (convo: chat.bsky.convo.defs.ConvoView) => chat.bsky.convo.defs.ConvoView { return convo => (rev <= convo.rev ? convo : fn(convo)) } @@ -928,8 +969,8 @@ function optimisticUpdate( chatId: string, old?: ConvoListQueryData, updateFn?: ( - convo: ChatBskyConvoDefs.ConvoView, - ) => ChatBskyConvoDefs.ConvoView, + convo: chat.bsky.convo.defs.ConvoView, + ) => chat.bsky.convo.defs.ConvoView, ) { if (!old || !updateFn) return old @@ -945,12 +986,12 @@ function optimisticUpdate( } function applyJoinRequestCountDelta( - convo: ChatBskyConvoDefs.ConvoView, + convo: chat.bsky.convo.defs.ConvoView, rev: string, delta: 1 | -1, -): ChatBskyConvoDefs.ConvoView { +): chat.bsky.convo.defs.ConvoView { // Join requests are only meaningful for group convos. - if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) { + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) { return {...convo, rev} } // Bump the total and unread counts together. Both are clamped at 0 and @@ -971,7 +1012,7 @@ function applyJoinRequestCountDelta( } function moveConvoToTopInRequests( - updatedConvo: ChatBskyConvoDefs.ConvoView, + updatedConvo: chat.bsky.convo.defs.ConvoView, old: ConvoRequestListQueryData | undefined, ): ConvoRequestListQueryData | undefined { if (!old) return old @@ -985,7 +1026,8 @@ function moveConvoToTopInRequests( pages: old.pages.map((page, i) => { const filtered = page.requests.filter( item => - !ChatBskyConvoDefs.isConvoView(item) || item.id !== updatedConvo.id, + !bsky.isType(chat.bsky.convo.defs.convoView, item) || + item.id !== updatedConvo.id, ) if (i === 0) { return { @@ -999,13 +1041,13 @@ function moveConvoToTopInRequests( } function removeMemberFromConvoView( - convo: ChatBskyConvoDefs.ConvoView, + convo: chat.bsky.convo.defs.ConvoView, did: string, rev: string, alreadyRemovedMember: boolean, -): ChatBskyConvoDefs.ConvoView { +): chat.bsky.convo.defs.ConvoView { // Member add/remove/join/leave events are only meaningful for group convos. - if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) return convo + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) return convo const nextMembers = convo.members.filter(m => m.did !== did) return { ...convo, @@ -1021,13 +1063,13 @@ function removeMemberFromConvoView( } function addMemberToConvoView( - convo: ChatBskyConvoDefs.ConvoView, - member: ChatBskyActorDefs.ProfileViewBasic, + convo: chat.bsky.convo.defs.ConvoView, + member: chat.bsky.actor.defs.ProfileViewBasic, rev: string, alreadyKnownMember: boolean, -): ChatBskyConvoDefs.ConvoView { +): chat.bsky.convo.defs.ConvoView { // Member add/remove/join/leave events are only meaningful for group convos. - if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) return convo + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) return convo const alreadyInCuratedList = convo.members.some(m => m.did === member.did) const nextMembers = alreadyInCuratedList ? convo.members @@ -1073,7 +1115,7 @@ export function* findAllProfilesInQueryData( did: string, ) { const queryDatas = queryClient.getQueriesData< - InfiniteData + InfiniteData >({ queryKey: [RQKEY_ROOT], }) diff --git a/src/state/queries/messages/list-convo-members.ts b/src/state/queries/messages/list-convo-members.ts index 386d82ca82..007650eb00 100644 --- a/src/state/queries/messages/list-convo-members.ts +++ b/src/state/queries/messages/list-convo-members.ts @@ -1,4 +1,3 @@ -import {type ChatBskyActorDefs} from '@atproto/api' import {type QueryClient, useQuery} from '@tanstack/react-query' import {STALE} from '#/state/queries' @@ -18,7 +17,7 @@ export function useListConvoMembersQuery({ placeholderData, }: { convoId: string - placeholderData?: ChatBskyActorDefs.ProfileViewBasic[] + placeholderData?: chat.bsky.actor.defs.ProfileViewBasic[] }) { const client = useChatClient() @@ -32,7 +31,7 @@ export function useListConvoMembersQuery({ * `members` with the exported profile type also keeps the hook's result * type unchanged for consumers. */ - const members: ChatBskyActorDefs.ProfileViewBasic[] = [] + const members: chat.bsky.actor.defs.ProfileViewBasic[] = [] let cursor: string | undefined do { @@ -55,9 +54,9 @@ export function useListConvoMembersQuery({ export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< - ChatBskyActorDefs.ProfileViewBasic[] + chat.bsky.actor.defs.ProfileViewBasic[] >({ queryKey: [RQKEY_ROOT], }) diff --git a/src/state/queries/messages/list-join-requests.ts b/src/state/queries/messages/list-join-requests.ts index bbb82eeca5..b722fa06f7 100644 --- a/src/state/queries/messages/list-join-requests.ts +++ b/src/state/queries/messages/list-join-requests.ts @@ -1,11 +1,11 @@ import {useEffect} from 'react' -import {ChatBskyConvoDefs} from '@atproto/api' import {useInfiniteQuery, useQueryClient} from '@tanstack/react-query' import {useMessagesEventBus} from '#/state/messages/events' import {createQueryKey} from '#/state/queries/util' import {useChatClient} from '#/state/session' import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {STALE} from '..' export const JOIN_REQUESTS_THRESHOLD = 20 @@ -35,9 +35,9 @@ export function useListJoinRequestsQuery({ if (event.type !== 'logs') return for (const log of event.logs) { if ( - ChatBskyConvoDefs.isLogIncomingJoinRequest(log) || - ChatBskyConvoDefs.isLogApproveJoinRequest(log) || - ChatBskyConvoDefs.isLogRejectJoinRequest(log) + bsky.isType(chat.bsky.convo.defs.logIncomingJoinRequest, log) || + bsky.isType(chat.bsky.convo.defs.logApproveJoinRequest, log) || + bsky.isType(chat.bsky.convo.defs.logRejectJoinRequest, log) ) { void queryClient.invalidateQueries({ queryKey: createListJoinRequestsQueryKey({convoId}), diff --git a/src/state/queries/messages/lock-conversation.ts b/src/state/queries/messages/lock-conversation.ts index 13242d59db..2057317fab 100644 --- a/src/state/queries/messages/lock-conversation.ts +++ b/src/state/queries/messages/lock-conversation.ts @@ -1,8 +1,8 @@ -import {ChatBskyConvoDefs, type ChatBskyConvoLockConvo} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' import {useChatClient} from '#/state/session' import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import { rollbackConvoOptimistic, updateConvoOptimistic, @@ -15,7 +15,7 @@ export function useLockConvo( onError, }: { onSuccess?: ( - data: ChatBskyConvoLockConvo.OutputSchema, + data: chat.bsky.convo.lockConvo.$OutputBody, variables: {lock: boolean; silent?: boolean}, ) => void onError?: ( @@ -39,7 +39,8 @@ export function useLockConvo( onMutate: ({lock}) => { if (!convoId) return return updateConvoOptimistic(queryClient, convoId, prev => { - if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) return undefined + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind)) + return undefined return { ...prev, kind: { diff --git a/src/state/queries/messages/mark-join-request-read.ts b/src/state/queries/messages/mark-join-request-read.ts index 3dfd995af0..ecc928074b 100644 --- a/src/state/queries/messages/mark-join-request-read.ts +++ b/src/state/queries/messages/mark-join-request-read.ts @@ -1,9 +1,9 @@ -import {ChatBskyConvoDefs} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' import {useChatClient} from '#/state/session' import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {RQKEY as CONVO_KEY} from './conversation' import { type ConvoListQueryData, @@ -22,13 +22,15 @@ export function useMarkJoinRequestsRead(convoId: string | undefined) { onMutate: () => { if (!convoId) return - const prevConvo = queryClient.getQueryData( - CONVO_KEY(convoId), - ) - queryClient.setQueryData( + const prevConvo = + queryClient.getQueryData( + CONVO_KEY(convoId), + ) + queryClient.setQueryData( CONVO_KEY(convoId), old => { - if (!old || !ChatBskyConvoDefs.isGroupConvo(old.kind)) return old + if (!old || !bsky.isType(chat.bsky.convo.defs.groupConvo, old.kind)) + return old return { ...old, kind: {...old.kind, unreadJoinRequestCount: 0}, @@ -50,7 +52,7 @@ export function useMarkJoinRequestsRead(convoId: string | undefined) { convos: page.convos.map(convo => { if ( convo.id !== convoId || - !ChatBskyConvoDefs.isGroupConvo(convo.kind) + !bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind) ) { return convo } diff --git a/src/state/queries/messages/mute-conversation.ts b/src/state/queries/messages/mute-conversation.ts index 2e9e911b5e..26cfa2e3b0 100644 --- a/src/state/queries/messages/mute-conversation.ts +++ b/src/state/queries/messages/mute-conversation.ts @@ -1,4 +1,3 @@ -import {type ChatBskyConvoMuteConvo} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' import {useChatClient} from '#/state/session' @@ -14,7 +13,7 @@ export function useMuteConvo( onSuccess, onError, }: { - onSuccess?: (data: ChatBskyConvoMuteConvo.OutputSchema) => void + onSuccess?: (data: chat.bsky.convo.muteConvo.$OutputBody) => void onError?: (error: Error) => void }, ) { diff --git a/src/state/queries/messages/remove-from-group.ts b/src/state/queries/messages/remove-from-group.ts index 10437ad7e9..6abbd4ebc3 100644 --- a/src/state/queries/messages/remove-from-group.ts +++ b/src/state/queries/messages/remove-from-group.ts @@ -1,9 +1,3 @@ -import { - type ChatBskyActorDefs, - ChatBskyConvoDefs, - type ChatBskyConvoListConvos, - type ChatBskyGroupRemoveMembers, -} from '@atproto/api' import {type DidString} from '@atproto/syntax' import { type InfiniteData, @@ -14,6 +8,7 @@ import { import {logger} from '#/logger' import {useChatClient} from '#/state/session' import {chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {RQKEY as CONVO_KEY} from './conversation' import {RQKEY_ROOT as CONVO_LIST_KEY} from './list-conversations' import {listConvoMembersQueryKey} from './list-convo-members' @@ -24,7 +19,7 @@ export function useRemoveFromGroupChat( onSuccess, onError, }: { - onSuccess?: (data: ChatBskyGroupRemoveMembers.OutputSchema) => void + onSuccess?: (data: chat.bsky.group.removeMembers.$OutputBody) => void onError?: (error: Error) => void }, ) { @@ -43,23 +38,24 @@ export function useRemoveFromGroupChat( onMutate: ({members}) => { if (!convoId) return - const prevConvo = queryClient.getQueryData( - CONVO_KEY(convoId), - ) + const prevConvo = + queryClient.getQueryData( + CONVO_KEY(convoId), + ) const prevListEntries = queryClient.getQueriesData< - InfiniteData + InfiniteData >({queryKey: [CONVO_LIST_KEY]}) const prevMemberList = queryClient.getQueryData< - ChatBskyActorDefs.ProfileViewBasic[] + chat.bsky.actor.defs.ProfileViewBasic[] >(listConvoMembersQueryKey(convoId)) - queryClient.setQueryData( + queryClient.setQueryData( CONVO_KEY(convoId), prev => { if (!prev) return const nextMembers = prev.members.filter(m => !members.includes(m.did)) const removed = prev.members.length - nextMembers.length - if (!ChatBskyConvoDefs.isGroupConvo(prev.kind)) { + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, prev.kind)) { return {...prev, members: nextMembers} } return { @@ -74,7 +70,7 @@ export function useRemoveFromGroupChat( ) queryClient.setQueriesData< - InfiniteData + InfiniteData >({queryKey: [CONVO_LIST_KEY]}, prev => { if (!prev?.pages) return return { @@ -87,7 +83,7 @@ export function useRemoveFromGroupChat( m => !members.includes(m.did), ) const removed = convo.members.length - nextMembers.length - if (!ChatBskyConvoDefs.isGroupConvo(convo.kind)) { + if (!bsky.isType(chat.bsky.convo.defs.groupConvo, convo.kind)) { return {...convo, members: nextMembers} } return { @@ -103,7 +99,7 @@ export function useRemoveFromGroupChat( } }) - queryClient.setQueryData( + queryClient.setQueryData( listConvoMembersQueryKey(convoId), prev => { if (!prev) return diff --git a/src/state/queries/messages/request-join-group-chat.ts b/src/state/queries/messages/request-join-group-chat.ts index b0a1922007..32b8043b8f 100644 --- a/src/state/queries/messages/request-join-group-chat.ts +++ b/src/state/queries/messages/request-join-group-chat.ts @@ -1,4 +1,3 @@ -import {type ChatBskyGroupRequestJoin} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' @@ -10,7 +9,7 @@ export function useRequestJoinGroupChat({ onSuccess, onError, }: { - onSuccess?: (data: ChatBskyGroupRequestJoin.OutputSchema) => void + onSuccess?: (data: chat.bsky.group.requestJoin.$OutputBody) => void onError?: (error: Error) => void } = {}) { const client = useChatClient() diff --git a/src/state/queries/messages/update-all-read.ts b/src/state/queries/messages/update-all-read.ts index afcb062d16..55d8dfc417 100644 --- a/src/state/queries/messages/update-all-read.ts +++ b/src/state/queries/messages/update-all-read.ts @@ -1,4 +1,3 @@ -import {type ChatBskyConvoGetUnreadCounts} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' @@ -94,10 +93,12 @@ export function useUpdateAllRead( // zero out the badge count query that actually drives the unread badge, // since it's a separate server query that the list caches don't feed const prevUnreadCountsQueries = - queryClient.getQueriesData({ - queryKey: UNREAD_COUNTS_PARTIAL_KEY, - }) - queryClient.setQueriesData( + queryClient.getQueriesData( + { + queryKey: UNREAD_COUNTS_PARTIAL_KEY, + }, + ) + queryClient.setQueriesData( {queryKey: UNREAD_COUNTS_PARTIAL_KEY}, old => { if (!old) return old diff --git a/src/state/queries/messages/utils/convo-cache.ts b/src/state/queries/messages/utils/convo-cache.ts index 2c9e2b3f96..1157aee79c 100644 --- a/src/state/queries/messages/utils/convo-cache.ts +++ b/src/state/queries/messages/utils/convo-cache.ts @@ -1,24 +1,21 @@ -import { - type ChatBskyConvoDefs, - type ChatBskyConvoListConvos, -} from '@atproto/api' import { type InfiniteData, type QueryClient, type QueryKey, } from '@tanstack/react-query' +import {type chat} from '#/lexicons' import {RQKEY as CONVO_KEY} from '../conversation' import {RQKEY_ROOT as CONVO_LIST_KEY} from '../list-conversations' type ConvoUpdater = ( - prev: ChatBskyConvoDefs.ConvoView, -) => ChatBskyConvoDefs.ConvoView | undefined + prev: chat.bsky.convo.defs.ConvoView, +) => chat.bsky.convo.defs.ConvoView | undefined export type ConvoCacheSnapshot = { - prevConvo: ChatBskyConvoDefs.ConvoView | undefined + prevConvo: chat.bsky.convo.defs.ConvoView | undefined prevListEntries: Array< - [QueryKey, InfiniteData | undefined] + [QueryKey, InfiniteData | undefined] > } @@ -34,14 +31,14 @@ export function updateConvoOptimistic( convoId: string, updater: ConvoUpdater, ): ConvoCacheSnapshot { - const prevConvo = queryClient.getQueryData( + const prevConvo = queryClient.getQueryData( CONVO_KEY(convoId), ) const prevListEntries = queryClient.getQueriesData< - InfiniteData + InfiniteData >({queryKey: [CONVO_LIST_KEY]}) - queryClient.setQueryData( + queryClient.setQueryData( CONVO_KEY(convoId), prev => { if (!prev) return @@ -51,7 +48,7 @@ export function updateConvoOptimistic( ) queryClient.setQueriesData< - InfiniteData + InfiniteData >({queryKey: [CONVO_LIST_KEY]}, prev => { if (!prev?.pages) return return { diff --git a/src/state/queries/messages/withdraw-join-group-chat.ts b/src/state/queries/messages/withdraw-join-group-chat.ts index 736128ee25..10de51a1d1 100644 --- a/src/state/queries/messages/withdraw-join-group-chat.ts +++ b/src/state/queries/messages/withdraw-join-group-chat.ts @@ -1,4 +1,3 @@ -import {type ChatBskyGroupWithdrawJoinRequest} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' @@ -14,7 +13,7 @@ export function useWithdrawJoinGroupChatRequest({ onSuccess, onError, }: { - onSuccess?: (data: ChatBskyGroupWithdrawJoinRequest.OutputSchema) => void + onSuccess?: (data: chat.bsky.group.withdrawJoinRequest.$OutputBody) => void onError?: (error: Error) => void } = {}) { const client = useChatClient() diff --git a/src/state/queries/my-blocked-accounts.ts b/src/state/queries/my-blocked-accounts.ts index 2cbe1dea29..32f9d27833 100644 --- a/src/state/queries/my-blocked-accounts.ts +++ b/src/state/queries/my-blocked-accounts.ts @@ -1,4 +1,3 @@ -import {type AppBskyActorDefs} from '@atproto/api' import { type InfiniteData, type QueryClient, @@ -37,7 +36,7 @@ export function useMyBlockedAccountsQuery() { export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ diff --git a/src/state/queries/my-lists.ts b/src/state/queries/my-lists.ts index d4538104dd..d529b1e2ad 100644 --- a/src/state/queries/my-lists.ts +++ b/src/state/queries/my-lists.ts @@ -1,4 +1,3 @@ -import {type AppBskyGraphDefs} from '@atproto/api' import {type DidString} from '@atproto/syntax' import {type QueryClient, useQuery} from '@tanstack/react-query' @@ -19,11 +18,11 @@ export const RQKEY = (filter: MyListsFilter) => [RQKEY_ROOT, filter] export function useMyListsQuery(filter: MyListsFilter) { const {currentAccount} = useSession() const client = useAppviewClient() - return useQuery({ + return useQuery({ staleTime: STALE.MINUTES.ONE, queryKey: RQKEY(filter), async queryFn() { - let lists: AppBskyGraphDefs.ListView[] = [] + let lists: app.bsky.graph.defs.ListView[] = [] const promises = [ accumulate(cursor => client diff --git a/src/state/queries/my-muted-accounts.ts b/src/state/queries/my-muted-accounts.ts index cd1c6bc6ba..06f1ec1a85 100644 --- a/src/state/queries/my-muted-accounts.ts +++ b/src/state/queries/my-muted-accounts.ts @@ -1,4 +1,3 @@ -import {type AppBskyActorDefs} from '@atproto/api' import { type InfiniteData, type QueryClient, @@ -37,7 +36,7 @@ export function useMyMutedAccountsQuery() { export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ diff --git a/src/state/queries/notifications/__tests__/util.test.ts b/src/state/queries/notifications/__tests__/util.test.ts index 3122770a40..37940a46da 100644 --- a/src/state/queries/notifications/__tests__/util.test.ts +++ b/src/state/queries/notifications/__tests__/util.test.ts @@ -1,14 +1,18 @@ -import {type AppBskyNotificationListNotifications} from '@atproto/api' +import {type DidString} from '@atproto/syntax' import {describe, expect, it, jest} from '@jest/globals' +import {type Notification} from '../types' import {groupNotifications} from '../util' jest.mock('#/state/queries/profile', () => ({precacheProfile: jest.fn()})) -type Notification = AppBskyNotificationListNotifications.Notification - +/* + * Fixture builder. The generated view brands `did`/`uri`/`cid`/`indexedAt`, and + * these are plain test strings, so the whole literal is asserted once - the + * grouping logic under test only compares them as strings. + */ function makeFollowNotification( - did: string, + did: DidString, starterPackUri?: string, ): Notification { return { diff --git a/src/state/queries/notifications/feed.ts b/src/state/queries/notifications/feed.ts index 4dc23db47e..b44abe44a2 100644 --- a/src/state/queries/notifications/feed.ts +++ b/src/state/queries/notifications/feed.ts @@ -17,7 +17,8 @@ */ import {useCallback, useEffect, useMemo, useRef} from 'react' -import {AppBskyFeedDefs, AppBskyFeedPost, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {moderatePost} from '@bsky.app/sdk/moderation' import { type InfiniteData, type QueryClient, @@ -26,12 +27,12 @@ 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' import {useThreadgateHiddenReplyUris} from '#/state/threadgate-hidden-replies' -import type * as bsky from '#/types/bsky' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import { didOrHandleUriMatches, embedViewRecordToPostView, @@ -195,7 +196,9 @@ export function useNotificationFeedQuery(opts: { * a `$type` field on the `subject`. But if the nested * `record` is a post, we know it's a post view. */ - if (AppBskyFeedPost.isRecord(item.subject?.record)) { + if ( + bsky.isType(app.bsky.feed.post, item.subject?.record) + ) { const mod = moderatePost(item.subject, moderationOpts!) if (mod.ui('contentList').filter) { return false @@ -272,7 +275,7 @@ export function useNotificationFeedQuery(opts: { export function* findAllPostsInQueryData( queryClient: QueryClient, uri: string, -): Generator { +): Generator { const atUri = new AtUri(uri) const queryDatas = queryClient.getQueriesData>({ @@ -291,7 +294,7 @@ export function* findAllPostsInQueryData( } } - if (AppBskyFeedDefs.isPostView(item.subject)) { + if (bsky.isType(app.bsky.feed.defs.postView, item.subject)) { const quotedPost = getEmbeddedPost(item.subject?.embed) if (quotedPost && didOrHandleUriMatches(atUri, quotedPost)) { yield embedViewRecordToPostView(quotedPost) @@ -329,7 +332,7 @@ export function* findAllProfilesInQueryData( ) { yield item.subject.author } - if (AppBskyFeedDefs.isPostView(item.subject)) { + if (bsky.isType(app.bsky.feed.defs.postView, item.subject)) { const quotedPost = getEmbeddedPost(item.subject?.embed) if (quotedPost?.author.did === did) { yield quotedPost.author diff --git a/src/state/queries/notifications/settings.ts b/src/state/queries/notifications/settings.ts index 2cadb519dc..8628d508e0 100644 --- a/src/state/queries/notifications/settings.ts +++ b/src/state/queries/notifications/settings.ts @@ -1,7 +1,3 @@ -import { - type AppBskyNotificationDefs, - type ChatBskyNotificationDefs, -} from '@atproto/api' import {t} from '@lingui/core/macro' import { type QueryClient, @@ -24,18 +20,18 @@ const RQKEY_CHAT = [RQKEY_ROOT, 'chat'] // fetched and cached separately. This combined type names every preference for // the generic settings dialog, but it is never the shape of a query response. export type NotificationSettingsPreferences = Omit< - AppBskyNotificationDefs.Preferences, + app.bsky.notification.defs.Preferences, 'chat' > & - Partial> + Partial> export type AppNotificationSettingsPreferences = Omit< - AppBskyNotificationDefs.Preferences, + app.bsky.notification.defs.Preferences, 'chat' > export type ChatNotificationSettingsPreferences = Pick< - ChatBskyNotificationDefs.Preferences, + chat.bsky.notification.defs.Preferences, 'chat' | 'chatRequest' > @@ -45,9 +41,9 @@ export type NotificationSettingsPreferenceName = Exclude< > export type NotificationSettingsPreference = - | AppBskyNotificationDefs.Preference - | AppBskyNotificationDefs.FilterablePreference - | ChatBskyNotificationDefs.ChatPreference + | app.bsky.notification.defs.Preference + | app.bsky.notification.defs.FilterablePreference + | chat.bsky.notification.defs.ChatPreference export function isChatPreferenceName( name: NotificationSettingsPreferenceName, @@ -58,7 +54,7 @@ export function isChatPreferenceName( type NotificationSettingsUpdate = Partial type AppNotificationSettingsUpdate = Partial< - Omit + Omit > type ChatNotificationSettingsUpdate = @@ -159,15 +155,15 @@ function optimisticUpdateNotificationSettings( } function appPreferencesWithoutChat( - preferences: AppBskyNotificationDefs.Preferences, -): Omit { + preferences: app.bsky.notification.defs.Preferences, +): Omit { const {chat: _ignoredChat, ...appPreferences} = preferences return appPreferences } function chatPreferencesForSettings( - preferences: ChatBskyNotificationDefs.Preferences, -): Pick { + preferences: chat.bsky.notification.defs.Preferences, +): Pick { return { chat: preferences.chat, chatRequest: preferences.chatRequest, diff --git a/src/state/queries/notifications/types.ts b/src/state/queries/notifications/types.ts index 1b66dd1759..a08e8bdd35 100644 --- a/src/state/queries/notifications/types.ts +++ b/src/state/queries/notifications/types.ts @@ -1,8 +1,6 @@ -import { - type AppBskyFeedDefs, - type AppBskyGraphDefs, - type AppBskyNotificationListNotifications, -} from '@atproto/api' +import {type app} from '#/lexicons' + +export type Notification = app.bsky.notification.listNotifications.Notification export type NotificationType = | StarterPackNotificationType @@ -11,11 +9,11 @@ export type NotificationType = export type FeedNotification = | (FeedNotificationBase & { type: StarterPackNotificationType - subject?: AppBskyGraphDefs.StarterPackViewBasic + subject?: app.bsky.graph.defs.StarterPackViewBasic }) | (FeedNotificationBase & { type: OtherNotificationType - subject?: AppBskyFeedDefs.PostView + subject?: app.bsky.feed.defs.PostView }) export interface FeedPage { @@ -54,8 +52,10 @@ type OtherNotificationType = type FeedNotificationBase = { _reactKey: string - notification: AppBskyNotificationListNotifications.Notification - additional?: AppBskyNotificationListNotifications.Notification[] + notification: Notification + additional?: Notification[] subjectUri?: string - subject?: AppBskyFeedDefs.PostView | AppBskyGraphDefs.StarterPackViewBasic + subject?: + | app.bsky.feed.defs.PostView + | app.bsky.graph.defs.StarterPackViewBasic } diff --git a/src/state/queries/notifications/util.ts b/src/state/queries/notifications/util.ts index 929517d9fb..83a028423b 100644 --- a/src/state/queries/notifications/util.ts +++ b/src/state/queries/notifications/util.ts @@ -1,26 +1,21 @@ -import { - type AppBskyFeedDefs, - AppBskyFeedLike, - AppBskyFeedPost, - AppBskyFeedRepost, - type AppBskyGraphDefs, - AppBskyGraphStarterpack, - type AppBskyNotificationListNotifications, -} from '@atproto/api' import {type Client} from '@atproto/lex' import {type AtUriString} from '@atproto/syntax' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import { + hasMutedWord, + moderateNotification, + 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' import { type FeedNotification, type FeedPage, + type Notification, type NotificationType, } from './types' @@ -117,7 +112,7 @@ export async function fetchPage({ // = export function shouldFilterNotif( - notif: AppBskyNotificationListNotifications.Notification, + notif: Notification, moderationOpts: ModerationOpts | undefined, ): boolean { const containsImperative = !!notif.author.labels?.some(labelIsHideableOffense) @@ -129,10 +124,7 @@ export function shouldFilterNotif( } if ( notif.reason === 'subscribed-post' && - bsky.dangerousIsType( - notif.record, - AppBskyFeedPost.isRecord, - ) && + bsky.isType(app.bsky.feed.post, notif.record) && hasMutedWord({ mutedWords: moderationOpts.prefs.mutedWords, text: notif.record.text, @@ -150,9 +142,7 @@ export function shouldFilterNotif( return moderateNotification(notif, moderationOpts).ui('contentList').filter } -export function groupNotifications( - notifs: AppBskyNotificationListNotifications.Notification[], -): FeedNotification[] { +export function groupNotifications(notifs: Notification[]): FeedNotification[] { const groupedNotifs: FeedNotification[] = [] for (const notif of notifs) { const ts = +new Date(notif.indexedAt) @@ -211,8 +201,8 @@ async function fetchSubjects( client: Client, groupedNotifs: FeedNotification[], ): Promise<{ - posts: Map - starterPacks: Map + posts: Map + starterPacks: Map }> { const postUris = new Set() const packUris = new Set() @@ -243,15 +233,15 @@ async function fetchSubjects( .then(data => data.starterPacks), ), ) - const postsMap = new Map() - const packsMap = new Map() + const postsMap = new Map() + const packsMap = new Map() for (const post of postsChunks.flat()) { - if (AppBskyFeedPost.isRecord(post.record)) { + if (bsky.isType(app.bsky.feed.post, post.record)) { postsMap.set(post.uri, post) } } for (const pack of packsChunks.flat()) { - if (AppBskyGraphStarterpack.isRecord(pack.record)) { + if (bsky.isType(app.bsky.graph.starterpack, pack.record)) { packsMap.set(pack.uri, pack) } } @@ -261,9 +251,7 @@ async function fetchSubjects( } } -function toKnownType( - notif: AppBskyNotificationListNotifications.Notification, -): NotificationType { +function toKnownType(notif: Notification): NotificationType { if (notif.reason === 'like') { if (notif.reasonSubject?.includes('feed.generator')) { return 'feedgen-like' @@ -291,7 +279,7 @@ function toKnownType( function getSubjectUri( type: NotificationType, - notif: AppBskyNotificationListNotifications.Notification, + notif: Notification, ): string | undefined { if ( type === 'reply' || @@ -307,14 +295,8 @@ function getSubjectUri( type === 'repost-via-repost' ) { if ( - bsky.dangerousIsType( - notif.record, - AppBskyFeedRepost.isRecord, - ) || - bsky.dangerousIsType( - notif.record, - AppBskyFeedLike.isRecord, - ) + bsky.isType(app.bsky.feed.repost, notif.record) || + bsky.isType(app.bsky.feed.like, notif.record) ) { return typeof notif.record.subject?.uri === 'string' ? notif.record.subject?.uri diff --git a/src/state/queries/nuxs/index.ts b/src/state/queries/nuxs/index.ts index f2802f0406..dc69b1fcde 100644 --- a/src/state/queries/nuxs/index.ts +++ b/src/state/queries/nuxs/index.ts @@ -8,7 +8,6 @@ import { usePreferencesQuery, } from '#/state/queries/preferences' import {usePdsClient} from '#/state/session' -import {type app} from '#/lexicons' export {Nux} from '#/state/queries/nuxs/definitions' @@ -108,10 +107,7 @@ export function useSaveNux() { * `serializeAppNux` still returns the legacy `Nux`, whose strings are * unbranded; it is validated against the same schema the action expects. */ - await pdsClient.call( - upsertNux, - serializeAppNux(nux) as app.bsky.actor.defs.Nux, - ) + await pdsClient.call(upsertNux, serializeAppNux(nux)) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, diff --git a/src/state/queries/nuxs/types.ts b/src/state/queries/nuxs/types.ts index 475bdd9429..db4bf5a7ae 100644 --- a/src/state/queries/nuxs/types.ts +++ b/src/state/queries/nuxs/types.ts @@ -1,7 +1,7 @@ -import {type AppBskyActorDefs} from '@atproto/api' +import {type app} from '#/lexicons' export type Data = Record | undefined export type BaseNux< - T extends Pick & {data: Data}, -> = Pick & T + T extends Pick & {data: Data}, +> = Pick & T diff --git a/src/state/queries/nuxs/util.ts b/src/state/queries/nuxs/util.ts index dea35a9166..973b46f0dc 100644 --- a/src/state/queries/nuxs/util.ts +++ b/src/state/queries/nuxs/util.ts @@ -1,4 +1,4 @@ -import {type AppBskyActorDefs, nuxSchema} from '@atproto/api' +import {nuxSchema} from '@bsky.app/sdk/utils' import { type AppNux, @@ -6,8 +6,9 @@ import { nuxNames, NuxSchemas, } from '#/state/queries/nuxs/definitions' +import {type app} from '#/lexicons' -export function parseAppNux(nux: AppBskyActorDefs.Nux): AppNux | undefined { +export function parseAppNux(nux: app.bsky.actor.defs.Nux): AppNux | undefined { if (!nuxNames.has(nux.id as Nux)) return if (!nuxSchema.safeParse(nux).success) return @@ -32,11 +33,11 @@ export function parseAppNux(nux: AppBskyActorDefs.Nux): AppNux | undefined { } as AppNux } -export function serializeAppNux(nux: AppNux): AppBskyActorDefs.Nux { +export function serializeAppNux(nux: AppNux): app.bsky.actor.defs.Nux { const {data, ...rest} = nux const schema = NuxSchemas[nux.id] - const result: AppBskyActorDefs.Nux = { + const result: app.bsky.actor.defs.Nux = { ...rest, data: undefined, } diff --git a/src/state/queries/pinned-post.ts b/src/state/queries/pinned-post.ts index cd3d0cb4a3..e53cefcbfe 100644 --- a/src/state/queries/pinned-post.ts +++ b/src/state/queries/pinned-post.ts @@ -55,10 +55,10 @@ export function usePinnedPostMutation() { : undefined return existing }, - checkCommitted: res => + checkCommitted: profile => pinCurrentPost - ? res.data.pinnedPost?.uri === postUri - : !res.data.pinnedPost, + ? profile.pinnedPost?.uri === postUri + : !profile.pinnedPost, }) if (pinCurrentPost) { diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 44bc8adf02..e8f5b5ee4a 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -1,14 +1,9 @@ import {useCallback, useEffect, useMemo, useRef} from 'react' import {AppState} from 'react-native' -import { - type AppBskyActorDefs, - AppBskyFeedDefs, - type AppBskyFeedPost, - AtUri, -} from '@atproto/api' import {type Client} from '@atproto/lex' -import {type AtIdentifierString, type AtUriString} from '@atproto/syntax' +import {type AtIdentifierString, AtUri, type AtUriString} from '@atproto/syntax' import { + moderatePost, type ModerationDecision, type ModerationPrefs, } from '@bsky.app/sdk/moderation' @@ -32,13 +27,14 @@ 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' import {useAppviewClient, useSession} from '#/state/session' import * as userActionHistory from '#/state/userActionHistory' import {KnownError} from '#/view/com/posts/PostFeedErrorMessage' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {useFeedTuners} from '../preferences/feed-tuners' import {useModerationOpts} from '../preferences/moderation-opts' import {usePreferencesQuery} from './preferences' @@ -82,11 +78,11 @@ export function RQKEY(feedDesc: FeedDescriptor, params?: FeedParams) { export interface FeedPostSliceItem { _reactKey: string - uri: string - post: AppBskyFeedDefs.PostView - record: AppBskyFeedPost.Record + uri: AtUriString + post: app.bsky.feed.defs.PostView + record: app.bsky.feed.post.Main moderation: ModerationDecision - parentAuthor?: AppBskyActorDefs.ProfileViewBasic + parentAuthor?: app.bsky.actor.defs.ProfileViewBasic isParentBlocked?: boolean isParentNotFound?: boolean } @@ -101,8 +97,8 @@ export interface FeedPostSlice { reqId: string | undefined feedPostUri: string reason?: - | AppBskyFeedDefs.ReasonRepost - | AppBskyFeedDefs.ReasonPin + | app.bsky.feed.defs.ReasonRepost + | app.bsky.feed.defs.ReasonPin | ReasonFeedSource | {[k: string]: unknown; $type: string} } @@ -110,7 +106,7 @@ export interface FeedPostSlice { export interface FeedPageUnselected { api: FeedAPI cursor: string | undefined - feed: AppBskyFeedDefs.FeedViewPost[] + feed: app.bsky.feed.defs.FeedViewPost[] fetchedAt: number } @@ -515,7 +511,7 @@ function createApi({ export function* findAllPostsInQueryData( queryClient: QueryClient, uri: string, -): Generator { +): Generator { const atUri = new AtUri(uri) const queryDatas = queryClient.getQueriesData< @@ -538,7 +534,7 @@ export function* findAllPostsInQueryData( yield embedViewRecordToPostView(quotedPost) } - if (AppBskyFeedDefs.isPostView(item.reply?.parent)) { + if (bsky.isType(app.bsky.feed.defs.postView, item.reply?.parent)) { if (didOrHandleUriMatches(atUri, item.reply.parent)) { yield item.reply.parent } @@ -552,7 +548,7 @@ export function* findAllPostsInQueryData( } } - if (AppBskyFeedDefs.isPostView(item.reply?.root)) { + if (bsky.isType(app.bsky.feed.defs.postView, item.reply?.root)) { if (didOrHandleUriMatches(atUri, item.reply.root)) { yield item.reply.root } @@ -570,7 +566,7 @@ export function* findAllPostsInQueryData( export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ @@ -590,13 +586,13 @@ export function* findAllProfilesInQueryData( yield quotedPost.author } if ( - AppBskyFeedDefs.isPostView(item.reply?.parent) && + bsky.isType(app.bsky.feed.defs.postView, item.reply?.parent) && item.reply?.parent?.author.did === did ) { yield item.reply.parent.author } if ( - AppBskyFeedDefs.isPostView(item.reply?.root) && + bsky.isType(app.bsky.feed.defs.postView, item.reply?.root) && item.reply?.root?.author.did === did ) { yield item.reply.root.author @@ -607,7 +603,7 @@ export function* findAllProfilesInQueryData( } function assertSomePostsPassModeration( - feed: AppBskyFeedDefs.FeedViewPost[], + feed: app.bsky.feed.defs.FeedViewPost[], moderationPrefs: ModerationPrefs, ) { // no posts in this feed diff --git a/src/state/queries/post-liked-by.ts b/src/state/queries/post-liked-by.ts index cc18a46426..0964d0be24 100644 --- a/src/state/queries/post-liked-by.ts +++ b/src/state/queries/post-liked-by.ts @@ -1,4 +1,3 @@ -import {type AppBskyActorDefs} from '@atproto/api' import {type AtUriString} from '@atproto/syntax' import { type InfiniteData, @@ -83,7 +82,7 @@ export function useLikedBySampleQuery({uri}: {uri: string | undefined}) { export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ diff --git a/src/state/queries/post-quotes.ts b/src/state/queries/post-quotes.ts index 77b5dabcf8..c0bdb70ef9 100644 --- a/src/state/queries/post-quotes.ts +++ b/src/state/queries/post-quotes.ts @@ -1,10 +1,4 @@ -import { - type AppBskyActorDefs, - AppBskyEmbedRecord, - type AppBskyFeedDefs, - AtUri, -} from '@atproto/api' -import {type AtUriString} from '@atproto/syntax' +import {AtUri, type AtUriString} from '@atproto/syntax' import { type InfiniteData, type QueryClient, @@ -14,6 +8,7 @@ import { import {useAppviewClient} from '#/state/session' import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import { didOrHandleUriMatches, embedViewRecordToPostView, @@ -54,8 +49,16 @@ export function usePostQuotesQuery(resolvedUri: string | undefined) { return { ...page, posts: page.posts.filter(post => { - if (post.embed && AppBskyEmbedRecord.isView(post.embed)) { - if (AppBskyEmbedRecord.isViewDetached(post.embed.record)) { + if ( + post.embed && + bsky.isType(app.bsky.embed.record.view, post.embed) + ) { + if ( + bsky.isType( + app.bsky.embed.record.viewDetached, + post.embed.record, + ) + ) { return false } } @@ -71,7 +74,7 @@ export function usePostQuotesQuery(resolvedUri: string | undefined) { export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ @@ -98,7 +101,7 @@ export function* findAllProfilesInQueryData( export function* findAllPostsInQueryData( queryClient: QueryClient, uri: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ diff --git a/src/state/queries/post-reposted-by.ts b/src/state/queries/post-reposted-by.ts index d2c5e62385..03a0f65126 100644 --- a/src/state/queries/post-reposted-by.ts +++ b/src/state/queries/post-reposted-by.ts @@ -1,4 +1,3 @@ -import {type AppBskyActorDefs} from '@atproto/api' import {type AtUriString} from '@atproto/syntax' import { type InfiniteData, @@ -44,7 +43,7 @@ export function usePostRepostedByQuery(resolvedUri: string | undefined) { export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ diff --git a/src/state/queries/post.ts b/src/state/queries/post.ts index 0a0664d13b..73191c83ee 100644 --- a/src/state/queries/post.ts +++ b/src/state/queries/post.ts @@ -1,7 +1,6 @@ import {useCallback} from 'react' -import {type AppBskyActorDefs, type AppBskyFeedDefs, AtUri} from '@atproto/api' import {type Client} from '@atproto/lex' -import {type AtUriString, type HandleString} from '@atproto/syntax' +import {AtUri, type AtUriString, type HandleString} from '@atproto/syntax' import {deleteLike, deletePost, deleteRepost, like, repost} from '@bsky.app/sdk' import { type QueryClient, @@ -26,7 +25,7 @@ export const RQKEY = (postUri: string) => [RQKEY_ROOT, postUri] export function usePostQuery(uri: string | undefined) { const client = useAppviewClient() - return useQuery({ + return useQuery({ queryKey: RQKEY(uri || ''), queryFn: async () => { if (!uri) throw new Error('[unreachable] No URI provided') @@ -46,14 +45,13 @@ export function usePostQuery(uri: string | undefined) { * Read one post by AT-URI, resolving a handle authority first when the URI * carries one. * - * The appview still answers with `@atproto/api`-shaped views for the callers of - * these hooks, so the generated view is asserted across at this single + * The appview response is asserted to the generated view at this single * boundary rather than at every consumer. */ async function fetchPost( client: Client, uri: string, -): Promise { +): Promise { const urip = new AtUri(uri) if (!urip.host.startsWith('did:')) { @@ -72,7 +70,7 @@ async function fetchPost( export function precachePost( queryClient: QueryClient, uri: string, - post: AppBskyFeedDefs.PostView, + post: app.bsky.feed.defs.PostView, ) { queryClient.setQueryData(RQKEY(uri), post) } @@ -110,7 +108,7 @@ export function useGetPosts() { uris: uris as AtUriString[], }) // See the note on `fetchPost` about the view shapes. - return data.posts as AppBskyFeedDefs.PostView[] + return data.posts }, }) }, @@ -119,7 +117,7 @@ export function useGetPosts() { } export function usePostLikeMutationQueue( - post: Shadow, + post: Shadow, viaRepost: {uri: string; cid: string} | undefined, feedDescriptor: string | undefined, logContext: Metrics['post:like']['logContext'], @@ -183,7 +181,7 @@ export function usePostLikeMutationQueue( function usePostLikeMutation( feedDescriptor: string | undefined, logContext: Metrics['post:like']['logContext'], - post: Shadow, + post: Shadow, ) { const {currentAccount} = useSession() const queryClient = useQueryClient() @@ -191,12 +189,12 @@ function usePostLikeMutation( const pdsClient = usePdsClient() const ax = useAnalytics() return useMutation< - {uri: string}, // responds with the uri of the like + {uri: AtUriString}, // responds with the uri of the like Error, {uri: string; cid: string; via?: {uri: string; cid: string}} // the post's uri and cid, and the repost uri/cid if present >({ mutationFn: ({uri, cid, via}) => { - let ownProfile: AppBskyActorDefs.ProfileViewDetailed | undefined + let ownProfile: app.bsky.actor.defs.ProfileViewDetailed | undefined if (currentAccount) { ownProfile = findProfileQueryData(queryClient, currentAccount.did) } @@ -231,7 +229,7 @@ function usePostLikeMutation( function usePostUnlikeMutation( feedDescriptor: string | undefined, logContext: Metrics['post:unlike']['logContext'], - post: Shadow, + post: Shadow, ) { const pdsClient = usePdsClient() const ax = useAnalytics() @@ -249,7 +247,7 @@ function usePostUnlikeMutation( } export function usePostRepostMutationQueue( - post: Shadow, + post: Shadow, viaRepost: {uri: string; cid: string} | undefined, feedDescriptor: string | undefined, logContext: Metrics['post:repost']['logContext'], @@ -315,12 +313,12 @@ export function usePostRepostMutationQueue( function usePostRepostMutation( feedDescriptor: string | undefined, logContext: Metrics['post:repost']['logContext'], - post: Shadow, + post: Shadow, ) { const pdsClient = usePdsClient() const ax = useAnalytics() return useMutation< - {uri: string}, // responds with the uri of the repost + {uri: AtUriString}, // responds with the uri of the repost Error, {uri: string; cid: string; via?: {uri: string; cid: string}} // the post's uri and cid, and the repost uri/cid if present >({ @@ -343,7 +341,7 @@ function usePostRepostMutation( function usePostUnrepostMutation( feedDescriptor: string | undefined, logContext: Metrics['post:unrepost']['logContext'], - post: Shadow, + post: Shadow, ) { const pdsClient = usePdsClient() const ax = useAnalytics() @@ -374,7 +372,7 @@ export function usePostDeleteMutation() { } export function useThreadMuteMutationQueue( - post: Shadow, + post: Shadow, rootUri: string, ) { const threadMuteMutation = useThreadMuteMutation() diff --git a/src/state/queries/postgate/index.ts b/src/state/queries/postgate/index.ts index e213fa73d5..08dff1a45c 100644 --- a/src/state/queries/postgate/index.ts +++ b/src/state/queries/postgate/index.ts @@ -1,11 +1,6 @@ import {useRef} from 'react' -import { - AppBskyEmbedRecord, - AppBskyEmbedRecordWithMedia, - type AppBskyFeedDefs, -} from '@atproto/api' import {type Client} from '@atproto/lex' -import {AtUri, type HandleString} from '@atproto/syntax' +import {AtUri, type AtUriString, type HandleString} from '@atproto/syntax' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {networkRetry, retry} from '#/lib/async/retry' @@ -173,7 +168,7 @@ export function useToggleQuoteDetachmentMutation() { const pdsClient = usePdsClient() const queryClient = useQueryClient() const getPosts = useGetPosts() - const prevEmbed = useRef(undefined) + const prevEmbed = useRef(undefined) return useMutation({ mutationFn: async ({ @@ -181,8 +176,8 @@ export function useToggleQuoteDetachmentMutation() { quoteUri, action, }: { - post: AppBskyFeedDefs.PostView - quoteUri: string + post: app.bsky.feed.defs.PostView + quoteUri: AtUriString action: 'detach' | 'reattach' }) => { // cache here since post shadow mutates original object @@ -247,8 +242,8 @@ export function useToggleQuoteDetachmentMutation() { if (action === 'detach' && prevEmbed.current) { // detach failed, add the embed back if ( - AppBskyEmbedRecord.isView(prevEmbed.current) || - AppBskyEmbedRecordWithMedia.isView(prevEmbed.current) + bsky.isType(app.bsky.embed.record.view, prevEmbed.current) || + bsky.isType(app.bsky.embed.recordWithMedia.view, prevEmbed.current) ) { updatePostShadow(queryClient, post.uri, { embed: prevEmbed.current, diff --git a/src/state/queries/postgate/util.ts b/src/state/queries/postgate/util.ts index 51c84419d0..b9a3218b2c 100644 --- a/src/state/queries/postgate/util.ts +++ b/src/state/queries/postgate/util.ts @@ -1,13 +1,8 @@ -import { - type $Typed, - AppBskyEmbedRecord, - AppBskyEmbedRecordWithMedia, - type AppBskyFeedDefs, - AtUri, -} from '@atproto/api' -import {type AtUriString, toDatetimeString} from '@atproto/syntax' +import {type $Typed} from '@atproto/lex' +import {AtUri, type AtUriString, toDatetimeString} from '@atproto/syntax' -import {type app} from '#/lexicons' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' export const POSTGATE_COLLECTION = 'app.bsky.feed.postgate' @@ -63,9 +58,9 @@ export function mergePostgateRecords( export function createEmbedViewDetachedRecord({ uri, }: { - uri: string -}): $Typed { - const record: $Typed = { + uri: AtUriString +}): $Typed { + const record: $Typed = { $type: 'app.bsky.embed.record#viewDetached', uri, detached: true, @@ -83,24 +78,27 @@ export function createMaybeDetachedQuoteEmbed({ detached, }: | { - post: AppBskyFeedDefs.PostView - quote: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView + quote: app.bsky.feed.defs.PostView quoteUri: undefined detached: false } | { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView quote: undefined - quoteUri: string + quoteUri: AtUriString detached: true - }): AppBskyEmbedRecord.View | AppBskyEmbedRecordWithMedia.View | undefined { - if (AppBskyEmbedRecord.isView(post.embed)) { + }): + | app.bsky.embed.record.View + | app.bsky.embed.recordWithMedia.View + | undefined { + if (bsky.isType(app.bsky.embed.record.view, post.embed)) { if (detached) { return createEmbedViewDetachedRecord({uri: quoteUri}) } else { return createEmbedRecordView({post: quote}) } - } else if (AppBskyEmbedRecordWithMedia.isView(post.embed)) { + } else if (bsky.isType(app.bsky.embed.recordWithMedia.view, post.embed)) { if (detached) { return { ...post.embed, @@ -113,8 +111,8 @@ export function createMaybeDetachedQuoteEmbed({ } export function createEmbedViewRecordFromPost( - post: AppBskyFeedDefs.PostView, -): $Typed { + post: app.bsky.feed.defs.PostView, +): $Typed { return { $type: 'app.bsky.embed.record#viewRecord', uri: post.uri, @@ -134,8 +132,8 @@ export function createEmbedViewRecordFromPost( export function createEmbedRecordView({ post, }: { - post: AppBskyFeedDefs.PostView -}): AppBskyEmbedRecord.View { + post: app.bsky.feed.defs.PostView +}): app.bsky.embed.record.View { return { $type: 'app.bsky.embed.record#view', record: createEmbedViewRecordFromPost(post), @@ -146,10 +144,10 @@ export function createEmbedRecordWithMediaView({ post, quote, }: { - post: AppBskyFeedDefs.PostView - quote: AppBskyFeedDefs.PostView -}): AppBskyEmbedRecordWithMedia.View | undefined { - if (!AppBskyEmbedRecordWithMedia.isView(post.embed)) return + post: app.bsky.feed.defs.PostView + quote: app.bsky.feed.defs.PostView +}): app.bsky.embed.recordWithMedia.View | undefined { + if (!bsky.isType(app.bsky.embed.recordWithMedia.view, post.embed)) return return { ...(post.embed || {}), record: { @@ -163,11 +161,11 @@ export function getMaybeDetachedQuoteEmbed({ post, }: { viewerDid: string - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView }) { - if (AppBskyEmbedRecord.isView(post.embed)) { + if (bsky.isType(app.bsky.embed.record.view, post.embed)) { // detached - if (AppBskyEmbedRecord.isViewDetached(post.embed.record)) { + if (bsky.isType(app.bsky.embed.record.viewDetached, post.embed.record)) { const urip = new AtUri(post.embed.record.uri) return { embed: post.embed, @@ -178,7 +176,7 @@ export function getMaybeDetachedQuoteEmbed({ } // post - if (AppBskyEmbedRecord.isViewRecord(post.embed.record)) { + if (bsky.isType(app.bsky.embed.record.viewRecord, post.embed.record)) { const urip = new AtUri(post.embed.record.uri) return { embed: post.embed, @@ -187,9 +185,11 @@ export function getMaybeDetachedQuoteEmbed({ isDetached: false, } } - } else if (AppBskyEmbedRecordWithMedia.isView(post.embed)) { + } else if (bsky.isType(app.bsky.embed.recordWithMedia.view, post.embed)) { // detached - if (AppBskyEmbedRecord.isViewDetached(post.embed.record.record)) { + if ( + bsky.isType(app.bsky.embed.record.viewDetached, post.embed.record.record) + ) { const urip = new AtUri(post.embed.record.record.uri) return { embed: post.embed, @@ -200,7 +200,9 @@ export function getMaybeDetachedQuoteEmbed({ } // post - if (AppBskyEmbedRecord.isViewRecord(post.embed.record.record)) { + if ( + bsky.isType(app.bsky.embed.record.viewRecord, post.embed.record.record) + ) { const urip = new AtUri(post.embed.record.record.uri) return { embed: post.embed, diff --git a/src/state/queries/preferences/index.ts b/src/state/queries/preferences/index.ts index 33f7d2f8d1..f3bb37d1a0 100644 --- a/src/state/queries/preferences/index.ts +++ b/src/state/queries/preferences/index.ts @@ -1,5 +1,4 @@ import {useCallback} from 'react' -import {type AppBskyActorDefs} from '@atproto/api' import {type DidString} from '@atproto/syntax' import { addSavedFeeds, @@ -278,7 +277,7 @@ export function useOverwriteSavedFeedsMutation() { const queryClient = useQueryClient() const client = usePdsClient() - return useMutation({ + return useMutation({ mutationFn: async savedFeeds => { await client.call(overwriteSavedFeeds, savedFeeds) // triggers a refetch @@ -296,7 +295,7 @@ export function useAddSavedFeedsMutation() { return useMutation< void, unknown, - Pick[] + Pick[] >({ mutationFn: async savedFeeds => { await client.call(addSavedFeeds, savedFeeds) @@ -312,7 +311,7 @@ export function useRemoveFeedMutation() { const queryClient = useQueryClient() const client = usePdsClient() - return useMutation>({ + return useMutation>({ mutationFn: async savedFeed => { await client.call(removeSavedFeeds, [savedFeed.id]) // triggers a refetch @@ -332,8 +331,8 @@ export function useReplaceForYouWithDiscoverFeedMutation() { forYouFeedConfig, discoverFeedConfig, }: { - forYouFeedConfig: AppBskyActorDefs.SavedFeed | undefined - discoverFeedConfig: AppBskyActorDefs.SavedFeed | undefined + forYouFeedConfig: app.bsky.actor.defs.SavedFeed | undefined + discoverFeedConfig: app.bsky.actor.defs.SavedFeed | undefined }) => { if (forYouFeedConfig) { await client.call(removeSavedFeeds, [forYouFeedConfig.id]) @@ -366,7 +365,7 @@ export function useUpdateSavedFeedsMutation() { const queryClient = useQueryClient() const client = usePdsClient() - return useMutation({ + return useMutation({ mutationFn: async feeds => { await client.call(updateSavedFeeds, feeds) @@ -477,7 +476,7 @@ export function useSetActiveProgressGuideMutation() { return useMutation({ mutationFn: async ( - guide: AppBskyActorDefs.BskyAppProgressGuide | undefined, + guide: app.bsky.actor.defs.BskyAppProgressGuide | undefined, ) => { await client.call(setActiveProgressGuide, guide) // triggers a refetch @@ -508,7 +507,7 @@ export function useSetVerificationPrefsMutation() { const queryClient = useQueryClient() const client = usePdsClient() - return useMutation({ + return useMutation({ mutationFn: async prefs => { await client.call(setVerificationPrefs, prefs) if (prefs.hideBadges) { diff --git a/src/state/queries/preferences/useThreadPreferences.ts b/src/state/queries/preferences/useThreadPreferences.ts index 629d1c61b8..2c3c850ffe 100644 --- a/src/state/queries/preferences/useThreadPreferences.ts +++ b/src/state/queries/preferences/useThreadPreferences.ts @@ -1,5 +1,4 @@ import {useCallback, useMemo, useRef, useState} from 'react' -import {type AppBskyUnspeccedGetPostThreadV2} from '@atproto/api' import {useFocusEffect} from '@react-navigation/native' import debounce from 'lodash.debounce' @@ -10,10 +9,11 @@ import { } from '#/state/queries/preferences' import {type ThreadViewPreferences} from '#/state/queries/preferences/types' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import {type Literal} from '#/types/utils' export type ThreadSortOption = Literal< - AppBskyUnspeccedGetPostThreadV2.QueryParams['sort'], + app.bsky.unspecced.getPostThreadV2.$Params['sort'], string > export type ThreadViewOption = 'linear' | 'tree' diff --git a/src/state/queries/profile-feedgens.ts b/src/state/queries/profile-feedgens.ts index 7a78c0a6c6..79309b1328 100644 --- a/src/state/queries/profile-feedgens.ts +++ b/src/state/queries/profile-feedgens.ts @@ -1,11 +1,11 @@ import {type DidString} from '@atproto/syntax' +import {moderateFeedGenerator} from '@bsky.app/sdk/moderation' import { type InfiniteData, type QueryKey, 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-followers.ts b/src/state/queries/profile-followers.ts index 5022ae9576..5e79b5719c 100644 --- a/src/state/queries/profile-followers.ts +++ b/src/state/queries/profile-followers.ts @@ -1,4 +1,3 @@ -import {type AppBskyActorDefs} from '@atproto/api' import {type DidString} from '@atproto/syntax' import { type InfiniteData, @@ -67,7 +66,7 @@ export function useProfileFollowersQuery( export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ diff --git a/src/state/queries/profile-follows.ts b/src/state/queries/profile-follows.ts index 1cd857af16..340184ef2c 100644 --- a/src/state/queries/profile-follows.ts +++ b/src/state/queries/profile-follows.ts @@ -1,4 +1,3 @@ -import {type AppBskyActorDefs} from '@atproto/api' import {type DidString} from '@atproto/syntax' import { type InfiniteData, @@ -72,7 +71,7 @@ export function useProfileFollowsQuery( export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ diff --git a/src/state/queries/profile-lists.ts b/src/state/queries/profile-lists.ts index 3d795def0d..8f1087c245 100644 --- a/src/state/queries/profile-lists.ts +++ b/src/state/queries/profile-lists.ts @@ -1,11 +1,11 @@ import {type DidString} from '@atproto/syntax' +import {moderateUserList} from '@bsky.app/sdk/moderation' import { type InfiniteData, type QueryKey, 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/profile.ts b/src/state/queries/profile.ts index d673ba3230..faf4dbc3fe 100644 --- a/src/state/queries/profile.ts +++ b/src/state/queries/profile.ts @@ -1,15 +1,8 @@ import {useCallback} from 'react' -import { - type AppBskyActorDefs, - type AppBskyActorGetProfile, - type AppBskyActorGetProfiles, - type AppBskyGraphGetFollows, - AtUri, - type Un$Typed, -} from '@atproto/api' -import {type Client} from '@atproto/lex' +import {type Client, type Un$Typed} from '@atproto/lex' import { type AtIdentifierString, + AtUri, type AtUriString, type DidString, toDatetimeString, @@ -82,7 +75,7 @@ export function useProfileQuery({ }) { const client = useAppviewClient() const {getUnstableProfile} = useUnstableProfileViewCache() - return useQuery({ + return useQuery({ // WARNING // this staleTime is load-bearing // if you remove it, the UI infinite-loops @@ -97,7 +90,7 @@ export function useProfileQuery({ }, placeholderData: () => { if (!did) return - return getUnstableProfile(did) as AppBskyActorDefs.ProfileViewDetailed + return getUnstableProfile(did) as app.bsky.actor.defs.ProfileViewDetailed }, enabled: !!did, }) @@ -145,7 +138,7 @@ export function usePrefetchProfileQuery() { } interface ProfileUpdateParams { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed updates: | Un$Typed | (( @@ -153,7 +146,7 @@ interface ProfileUpdateParams { ) => Un$Typed) newUserAvatar?: ImageMeta | undefined | null newUserBanner?: ImageMeta | undefined | null - checkCommitted?: (res: AppBskyActorGetProfile.Response) => boolean + checkCommitted?: (profile: app.bsky.actor.getProfile.$OutputBody) => boolean } export function useProfileUpdateMutation() { const queryClient = useQueryClient() @@ -213,21 +206,21 @@ export function useProfileUpdateMutation() { appviewClient, profile.did, checkCommitted || - (res => { + (profile => { if (typeof newUserAvatar !== 'undefined') { - if (newUserAvatar === null && res.data.avatar) { + if (newUserAvatar === null && profile.avatar) { // url hasn't cleared yet return false - } else if (res.data.avatar === profile.avatar) { + } else if (profile.avatar === profile.avatar) { // url hasn't changed yet return false } } if (typeof newUserBanner !== 'undefined') { - if (newUserBanner === null && res.data.banner) { + if (newUserBanner === null && profile.banner) { // url hasn't cleared yet return false - } else if (res.data.banner === profile.banner) { + } else if (profile.banner === profile.banner) { // url hasn't changed yet return false } @@ -236,8 +229,8 @@ export function useProfileUpdateMutation() { return true } return ( - res.data.displayName === updates.displayName && - res.data.description === updates.description + profile.displayName === updates.displayName && + profile.description === updates.description ) }), ) @@ -303,7 +296,7 @@ export function useProfileFollowMutationQueue( // Optimistically update profile follows cache for avatar displays if (currentAccount?.did) { type FollowsQueryData = - InfiniteData + InfiniteData queryClient.setQueryData( PROFILE_FOLLOWS_RQKEY(currentAccount.did), old => { @@ -320,7 +313,7 @@ export function useProfileFollowMutationQueue( { ...old.pages[0], follows: [ - profile as AppBskyActorDefs.ProfileView, + profile as app.bsky.actor.defs.ProfileView, ...old.pages[0].follows, ], }, @@ -344,7 +337,7 @@ export function useProfileFollowMutationQueue( if (finalFollowingUri) { void client .call(app.bsky.graph.getSuggestedFollowsByActor, { - actor: did as AtIdentifierString, + actor: did, }) .then(res => { const dids = res.suggestions @@ -388,9 +381,9 @@ function useProfileFollowMutation( const queryClient = useQueryClient() const {captureAction} = useProgressGuideControls() - return useMutation<{uri: string; cid: string}, Error, {did: string}>({ + return useMutation<{uri: AtUriString; cid: string}, Error, {did: string}>({ mutationFn: async ({did}) => { - let ownProfile: AppBskyActorDefs.ProfileViewDetailed | undefined + let ownProfile: app.bsky.actor.defs.ProfileViewDetailed | undefined if (currentAccount) { ownProfile = findProfileQueryData(queryClient, currentAccount.did) } @@ -432,7 +425,7 @@ export function useProfileMuteMutationQueue( ) { const ax = useAnalytics() const queryClient = useQueryClient() - const did = profile.did as DidString + const did = profile.did const initialMuted = profile.viewer?.muted const muteMutation = useProfileMuteMutation() const unmuteMutation = useProfileUnmuteMutation() @@ -490,7 +483,7 @@ export function useProfileMuteRepostsMutationQueue( ) { const ax = useAnalytics() const queryClient = useQueryClient() - const did = profile.did as DidString + const did = profile.did const initialMutedOnlyReposts = !!profile.viewer?.mutedOnlyReposts const muteRepostsMutation = useProfileMuteRepostsMutation() const unmuteMutation = useProfileUnmuteMutation() @@ -644,7 +637,7 @@ function useProfileBlockMutation() { const {currentAccount} = useSession() const pdsClient = usePdsClient() const queryClient = useQueryClient() - return useMutation<{uri: string; cid: string}, Error, {did: string}>({ + return useMutation<{uri: AtUriString; cid: string}, Error, {did: string}>({ mutationFn: async ({did}) => { if (!currentAccount) { throw new Error('Not signed in') @@ -686,32 +679,25 @@ function useProfileUnblockMutation() { async function whenAppViewReady( client: Client, actor: string, - fn: (res: AppBskyActorGetProfile.Response) => boolean, + fn: (profile: app.bsky.actor.getProfile.$OutputBody) => boolean, ) { await until( 5, // 5 tries 1e3, // 1s delay between tries fn, - /* - * `checkCommitted` callbacks are still written against the legacy - * `Response` envelope, so wrap the lex output until those consumers move. - */ - async () => ({ - success: true, - headers: {}, - data: await client.call(app.bsky.actor.getProfile, { + async () => + await client.call(app.bsky.actor.getProfile, { actor: actor as AtIdentifierString, }), - }), ) } export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const profileQueryDatas = - queryClient.getQueriesData({ + queryClient.getQueriesData({ queryKey: [RQKEY_ROOT], }) for (const [_queryKey, queryData] of profileQueryDatas) { @@ -723,7 +709,7 @@ export function* findAllProfilesInQueryData( } } const profilesQueryDatas = - queryClient.getQueriesData({ + queryClient.getQueriesData({ queryKey: [profilesQueryKeyRoot], }) for (const [_queryKey, queryData] of profilesQueryDatas) { @@ -741,8 +727,8 @@ export function* findAllProfilesInQueryData( export function findProfileQueryData( queryClient: QueryClient, did: string, -): AppBskyActorDefs.ProfileViewDetailed | undefined { - return queryClient.getQueryData( +): app.bsky.actor.defs.ProfileViewDetailed | undefined { + return queryClient.getQueryData( RQKEY(did), ) } diff --git a/src/state/queries/resolve-uri.ts b/src/state/queries/resolve-uri.ts index 9ca0aab36d..ba2e9f933c 100644 --- a/src/state/queries/resolve-uri.ts +++ b/src/state/queries/resolve-uri.ts @@ -1,6 +1,5 @@ -import {AtUri} from '@atproto/api' import {type Client} from '@atproto/lex' -import {type HandleString} from '@atproto/syntax' +import {AtUri, type HandleString} from '@atproto/syntax' import {type QueryClient, queryOptions, useQuery} from '@tanstack/react-query' import {STALE} from '#/state/queries' diff --git a/src/state/queries/search-posts-params.ts b/src/state/queries/search-posts-params.ts index c2769b2295..6ed9ffea46 100644 --- a/src/state/queries/search-posts-params.ts +++ b/src/state/queries/search-posts-params.ts @@ -3,13 +3,11 @@ * a free-text query. Kept free of React Native imports so it can be unit * tested in isolation (the search-posts query hook re-exports these). */ - -import {type AppBskyFeedSearchPostsV2} from '@atproto/api' - import { filtersToApiParams, type SearchFilters, } from '#/screens/Search/searchParams' +import {type app} from '#/lexicons' const DATE_RE = /^\d{4}-\d{2}-\d{2}/ @@ -201,12 +199,31 @@ function mergeList(a?: string[], b?: string[]): string[] | undefined { * operator. v2 renames v1's singular operators to plural arrays, and `lang` to * `language`. */ +/** + * The filter subset of the `searchPostsV2` params, with the format-constrained + * strings (uri, at-identifier, language, datetime) left unbranded: every value + * here is parsed from user input, so the brand is asserted by the caller rather + * than re-validated here. `q`, `limit`, `cursor` and `sort` are the caller's. + */ +type SearchPostsV2FilterParams = { + [K in Exclude< + keyof app.bsky.feed.searchPostsV2.$Params, + 'query' | 'limit' | 'cursor' | 'sort' + >]?: app.bsky.feed.searchPostsV2.$Params[K] extends + | readonly (infer _E)[] + | undefined + ? string[] + : app.bsky.feed.searchPostsV2.$Params[K] extends string | undefined + ? string + : app.bsky.feed.searchPostsV2.$Params[K] +} + export function buildSearchPostsV2Filters( embedded: Omit, filters?: SearchFilters, -): AppBskyFeedSearchPostsV2.QueryParams { +): SearchPostsV2FilterParams { const apiFilters = filters ? filtersToApiParams(filters) : {} - const params: AppBskyFeedSearchPostsV2.QueryParams = {} + const params: SearchPostsV2FilterParams = {} const authors = mergeList( embedded.author ? [embedded.author] : undefined, @@ -269,7 +286,7 @@ export function buildSearchPostsV2Filters( } /** - * Consistent with timestamp parsing in @atproto/api. Only the date is used; the + * Only the date is used; the * time is appended here since the lexicon expects a datetime value. */ const parseTimestamp = (value: string | undefined): string | undefined => { diff --git a/src/state/queries/search-posts-v2.ts b/src/state/queries/search-posts-v2.ts index 9716383654..e9a1178009 100644 --- a/src/state/queries/search-posts-v2.ts +++ b/src/state/queries/search-posts-v2.ts @@ -1,5 +1,6 @@ import {useCallback, useMemo, useRef} from 'react' -import {type AppBskyFeedDefs, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {moderatePost} from '@bsky.app/sdk/moderation' import { type InfiniteData, type QueryClient, @@ -7,7 +8,6 @@ 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' @@ -180,7 +180,7 @@ export function useSearchPostsV2Query({ export function* findAllPostsInQueryData( queryClient: QueryClient, uri: string, -): Generator { +): Generator { const queryDatas = queryClient.getQueriesData< InfiniteData >({ diff --git a/src/state/queries/starter-packs.ts b/src/state/queries/starter-packs.ts index 0c781bcfea..3a30b30caa 100644 --- a/src/state/queries/starter-packs.ts +++ b/src/state/queries/starter-packs.ts @@ -1,9 +1,4 @@ -import { - AppBskyFeedDefs, - AppBskyGraphDefs, - AppBskyGraphStarterpack, -} from '@atproto/api' -import {type Client, type LexValue} from '@atproto/lex' +import {type Client} from '@atproto/lex' import {AtUri, type AtUriString, toDatetimeString} from '@atproto/syntax' import {RichText} from '@bsky.app/sdk/richtext' import { @@ -57,7 +52,7 @@ export function useStarterPackQuery({ }) { const client = useAppviewClient() - return useQuery({ + return useQuery({ queryKey: RQKEY(uri ? {uri} : {did, rkey}), queryFn: async () => { if (!uri) { @@ -92,7 +87,7 @@ interface UseCreateStarterPackMutationParams { name: string description?: string profiles: bsky.profile.AnyProfileView[] - feeds?: AppBskyFeedDefs.GeneratorView[] + feeds?: app.bsky.feed.defs.GeneratorView[] } export function useCreateStarterPackMutation({ @@ -138,7 +133,7 @@ export function useCreateStarterPackMutation({ descriptionFacets, // `createStarterPackList` returns a plain string uri list: listRes?.uri as AtUriString, - feeds: feeds?.map(f => ({uri: f.uri as AtUriString})), + feeds: feeds?.map(f => ({uri: f.uri})), createdAt: toDatetimeString(new Date()), }) }, @@ -173,8 +168,8 @@ export function useEditStarterPackMutation({ void, Error, UseCreateStarterPackMutationParams & { - currentStarterPack: AppBskyGraphDefs.StarterPackView - currentListItems: AppBskyGraphDefs.ListItemView[] + currentStarterPack: app.bsky.graph.defs.StarterPackView + currentListItems: app.bsky.graph.defs.ListItemView[] } >({ mutationFn: async ({ @@ -262,7 +257,7 @@ export function useEditStarterPackMutation({ * types the raw `putRecord` body as a `LexValue`, which the legacy * view interface does not structurally satisfy, hence the cast. */ - feeds: feeds as unknown as LexValue, + feeds: feeds, createdAt: currentStarterPack.record.createdAt, updatedAt: new Date().toISOString(), }, @@ -373,33 +368,33 @@ async function whenAppViewReady( export function precacheStarterPack( queryClient: QueryClient, starterPack: - | AppBskyGraphDefs.StarterPackViewBasic - | AppBskyGraphDefs.StarterPackView, + | app.bsky.graph.defs.StarterPackViewBasic + | app.bsky.graph.defs.StarterPackView, ) { - if (!AppBskyGraphStarterpack.isRecord(starterPack.record)) { + if (!bsky.isType(app.bsky.graph.starterpack, starterPack.record)) { return } - let starterPackView: AppBskyGraphDefs.StarterPackView | undefined - if (AppBskyGraphDefs.isStarterPackView(starterPack)) { + let starterPackView: app.bsky.graph.defs.StarterPackView | undefined + if (bsky.isType(app.bsky.graph.defs.starterPackView, starterPack)) { starterPackView = starterPack } else if ( - AppBskyGraphDefs.isStarterPackViewBasic(starterPack) && - bsky.validate(starterPack.record, AppBskyGraphStarterpack.validateRecord) + bsky.isType(app.bsky.graph.defs.starterPackViewBasic, starterPack) && + bsky.matches(app.bsky.graph.starterpack, starterPack.record) ) { - let feeds: AppBskyFeedDefs.GeneratorView[] | undefined + let feeds: app.bsky.feed.defs.GeneratorView[] | undefined if (starterPack.record.feeds) { feeds = [] for (const feed of starterPack.record.feeds) { // note: types are wrong? claims to be `FeedItem`, but we actually // get un$typed `GeneratorView` objects here -sfn - if (bsky.validate(feed, AppBskyFeedDefs.validateGeneratorView)) { + if (bsky.matches(app.bsky.feed.defs.generatorView, feed)) { feeds.push(feed) } } } - const listView: AppBskyGraphDefs.ListViewBasic = { + const listView: app.bsky.graph.defs.ListViewBasic = { uri: starterPack.record.list, // This will be populated once the data from server is fetched cid: '', diff --git a/src/state/queries/suggested-follows.ts b/src/state/queries/suggested-follows.ts index 12f726baea..19f8cc11c4 100644 --- a/src/state/queries/suggested-follows.ts +++ b/src/state/queries/suggested-follows.ts @@ -1,5 +1,4 @@ import {useCallback, useMemo} from 'react' -import {type AppBskyActorDefs} from '@atproto/api' import {type DidString} from '@atproto/syntax' import { type InfiniteData, @@ -103,7 +102,7 @@ export function useSuggestedFollowsByActorWithDismiss({ export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { yield* findAllProfilesInSuggestedFollowsQueryData(queryClient, did) yield* findAllProfilesInSuggestedFollowsByActorQueryData(queryClient, did) } diff --git a/src/state/queries/threadgate/index.ts b/src/state/queries/threadgate/index.ts index bd1a0f6b8c..c9b1740c0b 100644 --- a/src/state/queries/threadgate/index.ts +++ b/src/state/queries/threadgate/index.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type Client} from '@atproto/lex' import {AtUri, type HandleString} from '@atproto/syntax' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' @@ -67,7 +66,7 @@ export function useThreadgateViewQuery({ initialData, }: { postUri?: string - initialData?: AppBskyFeedDefs.ThreadgateView + initialData?: app.bsky.feed.defs.ThreadgateView } = {}) { const getPost = useGetPost() @@ -248,7 +247,7 @@ export function useSetThreadgateAllowMutation() { }) }, async onSuccess(_, {postUri, allow}) { - const data = await retry( + const data = await retry( 5, // 5 tries _e => true, async () => { diff --git a/src/state/queries/threadgate/util.ts b/src/state/queries/threadgate/util.ts index 275611df1c..4e64835f4d 100644 --- a/src/state/queries/threadgate/util.ts +++ b/src/state/queries/threadgate/util.ts @@ -1,4 +1,3 @@ -import {type AppBskyFeedDefs} from '@atproto/api' import {type AtUriString, toDatetimeString} from '@atproto/syntax' import {type ThreadgateAllowUISetting} from '#/state/queries/threadgate/types' @@ -12,7 +11,7 @@ import * as bsky from '#/types/bsky' * through `com.atproto.repo.putRecord`, whose body is typed as a lex `LexMap`. */ export function threadgateViewToAllowUISetting( - threadgateView: AppBskyFeedDefs.ThreadgateView | undefined, + threadgateView: app.bsky.feed.defs.ThreadgateView | undefined, ): ThreadgateAllowUISetting[] { // Validate the record for clarity, since backwards compat code is a little confusing const threadgate = diff --git a/src/state/queries/usePostThread/const.ts b/src/state/queries/usePostThread/const.ts index 9b74361307..df7332cf25 100644 --- a/src/state/queries/usePostThread/const.ts +++ b/src/state/queries/usePostThread/const.ts @@ -1,27 +1,26 @@ // eslint-disable-next-line @typescript-eslint/no-unused-vars -import {type AppBskyUnspeccedGetPostThreadV2} from '@atproto/api' /** - * See the `below` param on {@link AppBskyUnspeccedGetPostThreadV2.QueryParams} + * See the `below` param on {@link app.bsky.unspecced.getPostThreadV2.$Params} */ export const LINEAR_VIEW_BELOW = 10 /** - * See the `branchingFactor` param on {@link AppBskyUnspeccedGetPostThreadV2.QueryParams} + * See the `branchingFactor` param on {@link app.bsky.unspecced.getPostThreadV2.$Params} */ export const LINEAR_VIEW_BF = 1 /** - * See the `below` param on {@link AppBskyUnspeccedGetPostThreadV2.QueryParams} + * See the `below` param on {@link app.bsky.unspecced.getPostThreadV2.$Params} */ export const TREE_VIEW_BELOW = 4 /** - * See the `branchingFactor` param on {@link AppBskyUnspeccedGetPostThreadV2.QueryParams} + * See the `branchingFactor` param on {@link app.bsky.unspecced.getPostThreadV2.$Params} */ export const TREE_VIEW_BF = undefined /** - * See the `below` param on {@link AppBskyUnspeccedGetPostThreadV2.QueryParams} + * See the `below` param on {@link app.bsky.unspecced.getPostThreadV2.$Params} */ export const TREE_VIEW_BELOW_DESKTOP = 6 diff --git a/src/state/queries/usePostThread/queryCache.ts b/src/state/queries/usePostThread/queryCache.ts index 73a8575c6e..afc7465ceb 100644 --- a/src/state/queries/usePostThread/queryCache.ts +++ b/src/state/queries/usePostThread/queryCache.ts @@ -1,13 +1,6 @@ import {useCallback} from 'react' -import { - type $Typed, - type AppBskyActorDefs, - type AppBskyFeedDefs, - AppBskyUnspeccedDefs, - type AppBskyUnspeccedGetPostThreadOtherV2, - type AppBskyUnspeccedGetPostThreadV2, - AtUri, -} from '@atproto/api' +import {type $Typed} from '@atproto/lex' +import {AtUri} from '@atproto/syntax' import {type QueryClient, useQueryClient} from '@tanstack/react-query' import { @@ -36,6 +29,8 @@ import { embedViewRecordToPostView, getEmbeddedPost, } from '#/state/queries/util' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' export function createCacheMutator({ queryClient, @@ -51,18 +46,18 @@ export function createCacheMutator({ return { insertReplies( parentUri: string, - replies: AppBskyUnspeccedGetPostThreadV2.ThreadItem[], + replies: app.bsky.unspecced.getPostThreadV2.ThreadItem[], ) { /* * Main thread query mutator. */ - queryClient.setQueryData( + queryClient.setQueryData( postThreadQueryKey, data => { if (!data) return return { ...data, - thread: mutator([ + thread: mutator([ ...data.thread, ]), } @@ -72,15 +67,15 @@ export function createCacheMutator({ /* * Additional replies query mutator. */ - queryClient.setQueryData( + queryClient.setQueryData( postThreadOtherQueryKey, data => { if (!data) return return { ...data, - thread: mutator([ - ...data.thread, - ]), + thread: mutator( + [...data.thread], + ), } }, ) @@ -89,7 +84,10 @@ export function createCacheMutator({ for (let i = 0; i < thread.length; i++) { const parent = thread[i] - if (!AppBskyUnspeccedDefs.isThreadItemPost(parent.value)) continue + if ( + !bsky.isType(app.bsky.unspecced.defs.threadItemPost, parent.value) + ) + continue if (parent.uri !== parentUri) continue /* @@ -124,7 +122,8 @@ export function createCacheMutator({ const isParentRoot = parent.depth === 0 const isParentBelowRoot = parent.depth > 0 const optimisticReply = replies.at(0) - const opIsReplier = AppBskyUnspeccedDefs.isThreadItemPost( + const opIsReplier = bsky.isType( + app.bsky.unspecced.defs.threadItemPost, optimisticReply?.value, ) ? opDid === optimisticReply.value.post.author.did @@ -172,8 +171,8 @@ export function createCacheMutator({ * Unused atm, post shadow does the trick, but it would be nice to clean up * the whole sub-tree on deletes. */ - deletePost(post: AppBskyUnspeccedGetPostThreadV2.ThreadItem) { - queryClient.setQueryData( + deletePost(post: app.bsky.unspecced.getPostThreadV2.ThreadItem) { + queryClient.setQueryData( postThreadQueryKey, queryData => { if (!queryData) return @@ -182,7 +181,10 @@ export function createCacheMutator({ for (let i = 0; i < thread.length; i++) { const existingPost = thread[i] - if (!AppBskyUnspeccedDefs.isThreadItemPost(post.value)) continue + if ( + !bsky.isType(app.bsky.unspecced.defs.threadItemPost, post.value) + ) + continue if (existingPost.uri === post.uri) { const branch = getBranch(thread, i, existingPost.depth) @@ -204,7 +206,7 @@ export function createCacheMutator({ export function getThreadPlaceholder( queryClient: QueryClient, uri: string, -): $Typed | void { +): $Typed | void { let partial for (let item of getThreadPlaceholderCandidates(queryClient, uri)) { /* @@ -231,8 +233,8 @@ export function* getThreadPlaceholderCandidates( uri: string, ): Generator< $Typed< - Omit & { - value: $Typed + Omit & { + value: $Typed } >, void @@ -276,10 +278,10 @@ export function* getThreadPlaceholderCandidates( export function* findAllPostsInQueryData( queryClient: QueryClient, uri: string, -): Generator { +): Generator { const atUri = new AtUri(uri) const queryDatas = - queryClient.getQueriesData({ + queryClient.getQueriesData({ queryKey: [postThreadQueryKeyRoot], }) @@ -289,7 +291,7 @@ export function* findAllPostsInQueryData( const {thread} = queryData for (const item of thread) { - if (AppBskyUnspeccedDefs.isThreadItemPost(item.value)) { + if (bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value)) { if (didOrHandleUriMatches(atUri, item.value.post)) { yield item.value.post } @@ -306,9 +308,9 @@ export function* findAllPostsInQueryData( export function* findAllProfilesInQueryData( queryClient: QueryClient, did: string, -): Generator { +): Generator { const queryDatas = - queryClient.getQueriesData({ + queryClient.getQueriesData({ queryKey: [postThreadQueryKeyRoot], }) @@ -318,7 +320,7 @@ export function* findAllProfilesInQueryData( const {thread} = queryData for (const item of thread) { - if (AppBskyUnspeccedDefs.isThreadItemPost(item.value)) { + if (bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value)) { if (item.value.post.author.did === did) { yield item.value.post.author } @@ -337,14 +339,15 @@ export function useUpdatePostThreadThreadgateQueryCache() { const context = usePostThreadContext() return useCallback( - (threadgate: AppBskyFeedDefs.ThreadgateView) => { + (threadgate: app.bsky.feed.defs.ThreadgateView) => { if (!context) return function mutator(thread: ApiThreadItem[]): T[] { for (let i = 0; i < thread.length; i++) { const item = thread[i] - if (!AppBskyUnspeccedDefs.isThreadItemPost(item.value)) continue + if (!bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value)) + continue if (item.depth === 0) { thread.splice(i, 1, { @@ -363,13 +366,13 @@ export function useUpdatePostThreadThreadgateQueryCache() { return thread as T[] } - qc.setQueryData( + qc.setQueryData( context.postThreadQueryKey, data => { if (!data) return return { ...data, - thread: mutator([ + thread: mutator([ ...data.thread, ]), } diff --git a/src/state/queries/usePostThread/traversal.ts b/src/state/queries/usePostThread/traversal.ts index 2aca5dfa96..107697f76f 100644 --- a/src/state/queries/usePostThread/traversal.ts +++ b/src/state/queries/usePostThread/traversal.ts @@ -1,4 +1,3 @@ -import {AppBskyUnspeccedDefs} from '@atproto/api' import {type ModerationOpts} from '@bsky.app/sdk/moderation' import { @@ -15,6 +14,8 @@ import { storeTraversalMetadata, } from '#/state/queries/usePostThread/utils' import * as views from '#/state/queries/usePostThread/views' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' export function sortAndAnnotateThreadItems( thread: ApiThreadItem[], @@ -46,7 +47,7 @@ export function sortAndAnnotateThreadItems( let parentMetadata: TraversalMetadata | undefined let metadata: TraversalMetadata | undefined - if (AppBskyUnspeccedDefs.isThreadItemPost(item.value)) { + if (bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value)) { parentMetadata = metadatas.get( getPostRecord(item.value.post).reply?.parent?.uri || '', ) @@ -65,13 +66,24 @@ export function sortAndAnnotateThreadItems( * _up_ from there. */ } else if (item.depth === 0) { - if (AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(item.value)) { + if ( + bsky.isType( + app.bsky.unspecced.defs.threadItemNoUnauthenticated, + item.value, + ) + ) { threadItems.push(views.threadPostNoUnauthenticated(item)) - } else if (AppBskyUnspeccedDefs.isThreadItemNotFound(item.value)) { + } else if ( + bsky.isType(app.bsky.unspecced.defs.threadItemNotFound, item.value) + ) { threadItems.push(views.threadPostNotFound(item)) - } else if (AppBskyUnspeccedDefs.isThreadItemBlocked(item.value)) { + } else if ( + bsky.isType(app.bsky.unspecced.defs.threadItemBlocked, item.value) + ) { threadItems.push(views.threadPostBlocked(item)) - } else if (AppBskyUnspeccedDefs.isThreadItemPost(item.value)) { + } else if ( + bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value) + ) { const post = views.threadPost({ uri: item.uri, depth: item.depth, @@ -85,7 +97,10 @@ export function sortAndAnnotateThreadItems( const parent = thread[pi] if ( - AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(parent.value) + bsky.isType( + app.bsky.unspecced.defs.threadItemNoUnauthenticated, + parent.value, + ) ) { const post = views.threadPostNoUnauthenticated(parent) post.ui = getThreadPostNoUnauthenticatedUI({ @@ -97,13 +112,22 @@ export function sortAndAnnotateThreadItems( threadItems.unshift(post) // for now, break parent traversal at first no-unauthed break parentTraversal - } else if (AppBskyUnspeccedDefs.isThreadItemNotFound(parent.value)) { + } else if ( + bsky.isType( + app.bsky.unspecced.defs.threadItemNotFound, + parent.value, + ) + ) { threadItems.unshift(views.threadPostNotFound(parent)) break parentTraversal - } else if (AppBskyUnspeccedDefs.isThreadItemBlocked(parent.value)) { + } else if ( + bsky.isType(app.bsky.unspecced.defs.threadItemBlocked, parent.value) + ) { threadItems.unshift(views.threadPostBlocked(parent)) break parentTraversal - } else if (AppBskyUnspeccedDefs.isThreadItemPost(parent.value)) { + } else if ( + bsky.isType(app.bsky.unspecced.defs.threadItemPost, parent.value) + ) { threadItems.unshift( views.threadPost({ uri: parent.uri, @@ -123,16 +147,21 @@ export function sortAndAnnotateThreadItems( * we could. */ const shouldBreak = - AppBskyUnspeccedDefs.isThreadItemNoUnauthenticated(item.value) || - AppBskyUnspeccedDefs.isThreadItemNotFound(item.value) || - AppBskyUnspeccedDefs.isThreadItemBlocked(item.value) + bsky.isType( + app.bsky.unspecced.defs.threadItemNoUnauthenticated, + item.value, + ) || + bsky.isType(app.bsky.unspecced.defs.threadItemNotFound, item.value) || + bsky.isType(app.bsky.unspecced.defs.threadItemBlocked, item.value) if (shouldBreak) { const branch = getBranch(thread, i, item.depth) // could insert tombstone i = branch.end continue traversal - } else if (AppBskyUnspeccedDefs.isThreadItemPost(item.value)) { + } else if ( + bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value) + ) { if (parentMetadata) { /* * Set this value before incrementing the `repliesSeenCounter` later @@ -180,7 +209,9 @@ export function sortAndAnnotateThreadItems( for (let ci = startIndex; ci <= branch.end; ci++) { const child = thread[ci] - if (AppBskyUnspeccedDefs.isThreadItemPost(child.value)) { + if ( + bsky.isType(app.bsky.unspecced.defs.threadItemPost, child.value) + ) { const childParentMetadata = metadatas.get( getPostRecord(child.value.post).reply?.parent?.uri || '', ) diff --git a/src/state/queries/usePostThread/types.ts b/src/state/queries/usePostThread/types.ts index 561490c328..2c2548ff50 100644 --- a/src/state/queries/usePostThread/types.ts +++ b/src/state/queries/usePostThread/types.ts @@ -1,16 +1,10 @@ -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, - type AppBskyUnspeccedDefs, - type AppBskyUnspeccedGetPostThreadOtherV2, - type AppBskyUnspeccedGetPostThreadV2, -} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' +import {type app} from '#/lexicons' + export type ApiThreadItem = - | AppBskyUnspeccedGetPostThreadV2.ThreadItem - | AppBskyUnspeccedGetPostThreadOtherV2.ThreadItem + | app.bsky.unspecced.getPostThreadV2.ThreadItem + | app.bsky.unspecced.getPostThreadOtherV2.ThreadItem export const postThreadQueryKeyRoot = 'post-thread-v2' as const @@ -18,13 +12,13 @@ export const createPostThreadQueryKey = (props: PostThreadParams) => [postThreadQueryKeyRoot, props] as const export const createPostThreadOtherQueryKey = ( - props: Omit & { + props: Omit & { anchor?: string }, ) => [postThreadQueryKeyRoot, 'other', props] as const export type PostThreadParams = Pick< - AppBskyUnspeccedGetPostThreadV2.QueryParams, + app.bsky.unspecced.getPostThreadV2.$Params, 'sort' > & { anchor?: string @@ -33,9 +27,9 @@ export type PostThreadParams = Pick< export type UsePostThreadQueryResult = { hasOtherReplies: boolean - thread: AppBskyUnspeccedGetPostThreadV2.ThreadItem[] - threadgate?: Omit & { - record: AppBskyFeedThreadgate.Record + thread: app.bsky.unspecced.getPostThreadV2.ThreadItem[] + threadgate?: Omit & { + record: app.bsky.feed.threadgate.Main } } @@ -45,9 +39,9 @@ export type ThreadItem = key: string uri: string depth: number - value: Omit & { - post: Omit & { - record: AppBskyFeedPost.Record + value: Omit & { + post: Omit & { + record: app.bsky.feed.post.Main } } isBlurred: boolean @@ -67,7 +61,7 @@ export type ThreadItem = key: string uri: string depth: number - value: AppBskyUnspeccedDefs.ThreadItemNoUnauthenticated + value: app.bsky.unspecced.defs.ThreadItemNoUnauthenticated ui: { showParentReplyLine: boolean showChildReplyLine: boolean @@ -78,14 +72,14 @@ export type ThreadItem = key: string uri: string depth: number - value: AppBskyUnspeccedDefs.ThreadItemNotFound + value: app.bsky.unspecced.defs.ThreadItemNotFound } | { type: 'threadPostBlocked' key: string uri: string depth: number - value: AppBskyUnspeccedDefs.ThreadItemBlocked + value: app.bsky.unspecced.defs.ThreadItemBlocked } | { type: 'replyComposer' diff --git a/src/state/queries/usePostThread/utils.ts b/src/state/queries/usePostThread/utils.ts index be3be36b85..dde80db181 100644 --- a/src/state/queries/usePostThread/utils.ts +++ b/src/state/queries/usePostThread/utils.ts @@ -1,38 +1,24 @@ -import { - type AppBskyFeedDefs, - AppBskyFeedPost, - AppBskyFeedThreadgate, - AppBskyUnspeccedDefs, - type AppBskyUnspeccedGetPostThreadV2, - AtUri, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import { type ApiThreadItem, type ThreadItem, type TraversalMetadata, } from '#/state/queries/usePostThread/types' +import {app} from '#/lexicons' import {isDevMode} from '#/storage/hooks/dev-mode' import * as bsky from '#/types/bsky' export function getThreadgateRecord( - view: AppBskyUnspeccedGetPostThreadV2.OutputSchema['threadgate'], + view: app.bsky.unspecced.getPostThreadV2.$OutputBody['threadgate'], ) { - return bsky.dangerousIsType( - view?.record, - AppBskyFeedThreadgate.isRecord, - ) + return bsky.isType(app.bsky.feed.threadgate, view?.record) ? view?.record : undefined } -export function getRootPostAtUri(post: AppBskyFeedDefs.PostView) { - if ( - bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) - ) { +export function getRootPostAtUri(post: app.bsky.feed.defs.PostView) { + if (bsky.isType(app.bsky.feed.post, post.record)) { /** * If the record has no `reply` field, it is a root post. */ @@ -45,8 +31,8 @@ export function getRootPostAtUri(post: AppBskyFeedDefs.PostView) { } } -export function getPostRecord(post: AppBskyFeedDefs.PostView) { - return post.record as AppBskyFeedPost.Record +export function getPostRecord(post: app.bsky.feed.defs.PostView) { + return post.record as app.bsky.feed.post.Main } export function getTraversalMetadata({ @@ -60,7 +46,7 @@ export function getTraversalMetadata({ nextItem?: ApiThreadItem parentMetadata?: TraversalMetadata }): TraversalMetadata { - if (!AppBskyUnspeccedDefs.isThreadItemPost(item.value)) { + if (!bsky.isType(app.bsky.unspecced.defs.threadItemPost, item.value)) { throw new Error(`Expected thread item to be a post`) } const repliesCount = item.value.post.replyCount || 0 diff --git a/src/state/queries/usePostThread/views.ts b/src/state/queries/usePostThread/views.ts index af23ee2959..d03f49219c 100644 --- a/src/state/queries/usePostThread/views.ts +++ b/src/state/queries/usePostThread/views.ts @@ -1,20 +1,14 @@ -import { - type $Typed, - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyUnspeccedDefs, - type AppBskyUnspeccedGetPostThreadV2, - AtUri, -} from '@atproto/api' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {type $Typed} from '@atproto/lex' +import {AtUri} from '@atproto/syntax' +import {moderatePost, type ModerationOpts} from '@bsky.app/sdk/moderation' -import {moderatePost} from '#/lib/moderation/subjects' import {makeProfileLink} from '#/lib/routes/links' import { type ApiThreadItem, type ThreadItem, type TraversalMetadata, } from '#/state/queries/usePostThread/types' +import {type app} from '#/lexicons' export function threadPostNoUnauthenticated({ uri, @@ -26,7 +20,7 @@ export function threadPostNoUnauthenticated({ key: uri, uri, depth, - value: value as AppBskyUnspeccedDefs.ThreadItemNoUnauthenticated, + value: value as app.bsky.unspecced.defs.ThreadItemNoUnauthenticated, // @ts-ignore populated by the traversal ui: {}, } @@ -42,7 +36,7 @@ export function threadPostNotFound({ key: uri, uri, depth, - value: value as AppBskyUnspeccedDefs.ThreadItemNotFound, + value: value as app.bsky.unspecced.defs.ThreadItemNotFound, } } @@ -56,7 +50,7 @@ export function threadPostBlocked({ key: uri, uri, depth, - value: value as AppBskyUnspeccedDefs.ThreadItemBlocked, + value: value as app.bsky.unspecced.defs.ThreadItemBlocked, } } @@ -69,7 +63,7 @@ export function threadPost({ }: { uri: string depth: number - value: $Typed + value: $Typed moderationOpts: ModerationOpts threadgateHiddenReplies: Set }): Extract { @@ -91,8 +85,8 @@ export function threadPost({ * Do not spread anything here, load bearing for post shadow strict * equality reference checks. */ - post: value.post as Omit & { - record: AppBskyFeedPost.Record + post: value.post as Omit & { + record: app.bsky.feed.post.Main }, }, isBlurred, @@ -161,10 +155,10 @@ export function skeleton({ } export function postViewToThreadPlaceholder( - post: AppBskyFeedDefs.PostView, + post: app.bsky.feed.defs.PostView, ): $Typed< - Omit & { - value: $Typed + Omit & { + value: $Typed } > { return { diff --git a/src/state/queries/util.ts b/src/state/queries/util.ts index 7ea54745c0..a3fe12aec7 100644 --- a/src/state/queries/util.ts +++ b/src/state/queries/util.ts @@ -1,17 +1,11 @@ -import { - type AppBskyActorDefs, - AppBskyEmbedRecord, - AppBskyEmbedRecordWithMedia, - type AppBskyFeedDefs, - AppBskyFeedPost, - type AtUri, -} from '@atproto/api' +import {type AtUri} from '@atproto/syntax' import { type InfiniteData, type QueryClient, type QueryKey, } from '@tanstack/react-query' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' export type StructuredQueryKey> = readonly [ @@ -93,7 +87,7 @@ export async function truncateAndInvalidate( // of the currentUri that is being checked. export function didOrHandleUriMatches( atUri: AtUri, - record: {uri: string; author: AppBskyActorDefs.ProfileViewBasic}, + record: {uri: string; author: app.bsky.actor.defs.ProfileViewBasic}, ) { if (atUri.host.startsWith('did:')) { return atUri.href === record.uri @@ -104,26 +98,19 @@ export function didOrHandleUriMatches( export function getEmbeddedPost( v: unknown, -): AppBskyEmbedRecord.ViewRecord | undefined { - if ( - bsky.dangerousIsType(v, AppBskyEmbedRecord.isView) - ) { +): app.bsky.embed.record.ViewRecord | undefined { + if (bsky.isType(app.bsky.embed.record.view, v)) { if ( - AppBskyEmbedRecord.isViewRecord(v.record) && - AppBskyFeedPost.isRecord(v.record.value) + bsky.isType(app.bsky.embed.record.viewRecord, v.record) && + bsky.isType(app.bsky.feed.post, v.record.value) ) { return v.record } } - if ( - bsky.dangerousIsType( - v, - AppBskyEmbedRecordWithMedia.isView, - ) - ) { + if (bsky.isType(app.bsky.embed.recordWithMedia.view, v)) { if ( - AppBskyEmbedRecord.isViewRecord(v.record.record) && - AppBskyFeedPost.isRecord(v.record.record.value) + bsky.isType(app.bsky.embed.record.viewRecord, v.record.record) && + bsky.isType(app.bsky.feed.post, v.record.record.value) ) { return v.record.record } @@ -131,8 +118,8 @@ export function getEmbeddedPost( } export function embedViewRecordToPostView( - v: AppBskyEmbedRecord.ViewRecord, -): AppBskyFeedDefs.PostView { + v: app.bsky.embed.record.ViewRecord, +): app.bsky.feed.defs.PostView { return { uri: v.uri, cid: v.cid, diff --git a/src/state/queries/verification/useUpdateProfileVerificationCache.ts b/src/state/queries/verification/useUpdateProfileVerificationCache.ts index 08da1244c5..6079457e2e 100644 --- a/src/state/queries/verification/useUpdateProfileVerificationCache.ts +++ b/src/state/queries/verification/useUpdateProfileVerificationCache.ts @@ -1,5 +1,4 @@ import {useCallback} from 'react' -import {type DidString} from '@atproto/syntax' import {useQueryClient} from '@tanstack/react-query' import {logger} from '#/logger' @@ -25,7 +24,7 @@ export function useUpdateProfileVerificationCache() { return } const updated = await client.call(app.bsky.actor.getProfile, { - actor: profile.did as DidString, + actor: profile.did, }) updateProfileShadow(qc, profile.did, { verification: updated.verification, diff --git a/src/state/queries/verification/useVerificationCreateMutation.tsx b/src/state/queries/verification/useVerificationCreateMutation.tsx index 4f1a318f48..cd52a62a1f 100644 --- a/src/state/queries/verification/useVerificationCreateMutation.tsx +++ b/src/state/queries/verification/useVerificationCreateMutation.tsx @@ -1,9 +1,4 @@ -import { - type AtIdentifierString, - type DidString, - type HandleString, - toDatetimeString, -} from '@atproto/syntax' +import {toDatetimeString} from '@atproto/syntax' import {useMutation} from '@tanstack/react-query' import {until} from '#/lib/async/until' @@ -28,9 +23,9 @@ export function useVerificationCreateMutation() { const {uri} = await pdsClient.create(app.bsky.graph.verification, { // the profile view is still legacy-typed, so its strings are unbranded - subject: profile.did as DidString, + subject: profile.did, createdAt: toDatetimeString(new Date()), - handle: profile.handle as HandleString, + handle: profile.handle, displayName: profile.displayName || '', }) @@ -48,7 +43,7 @@ export function useVerificationCreateMutation() { }, () => { return appviewClient.call(app.bsky.actor.getProfile, { - actor: (profile.did ?? '') as AtIdentifierString, + actor: profile.did ?? '', }) }, ) diff --git a/src/state/queries/verification/useVerificationsRemoveMutation.tsx b/src/state/queries/verification/useVerificationsRemoveMutation.tsx index 657c50c909..0de675f0d2 100644 --- a/src/state/queries/verification/useVerificationsRemoveMutation.tsx +++ b/src/state/queries/verification/useVerificationsRemoveMutation.tsx @@ -1,5 +1,4 @@ -import {type AppBskyActorDefs} from '@atproto/api' -import {type AtIdentifierString, AtUri} from '@atproto/syntax' +import {AtUri} from '@atproto/syntax' import {useMutation} from '@tanstack/react-query' import {until} from '#/lib/async/until' @@ -22,7 +21,7 @@ export function useVerificationsRemoveMutation() { verifications, }: { profile: bsky.profile.AnyProfileView - verifications: AppBskyActorDefs.VerificationView[] + verifications: app.bsky.actor.defs.VerificationView[] }) { if (!currentAccount) { throw new Error('User not logged in') @@ -52,7 +51,7 @@ export function useVerificationsRemoveMutation() { () => { return appviewClient.call(app.bsky.actor.getProfile, { // the profile view is still legacy-typed, so its did is unbranded - actor: (profile.did ?? '') as AtIdentifierString, + actor: profile.did ?? '', }) }, ) diff --git a/src/state/session/moderation.ts b/src/state/session/moderation.ts index 691aad4c81..c54da1be46 100644 --- a/src/state/session/moderation.ts +++ b/src/state/session/moderation.ts @@ -1,6 +1,6 @@ -import {BSKY_LABELER_DID} from '@atproto/api' import {type Client} from '@atproto/lex' import {type DidString} from '@atproto/syntax' +import {api} from '@bsky.app/sdk' import {IS_TEST_USER} from '#/lib/constants' import {com} from '#/lexicons' @@ -53,7 +53,7 @@ export function applyLabelersToClient( subscribedDids: string[], ) { client.setLabelers( - subscribedDids.filter(did => did !== BSKY_LABELER_DID) as DidString[], + subscribedDids.filter(did => did !== api.moderation.did) as DidString[], ) } @@ -94,7 +94,7 @@ export function configureModerationForAccount( } function switchToBskyAppLabeler() { - configureGlobalAppLabelers([BSKY_LABELER_DID]) + configureGlobalAppLabelers([api.moderation.did]) } /** Resolve and install the test environment's moderation authority. */ diff --git a/src/state/shell/composer/index.tsx b/src/state/shell/composer/index.tsx index 087a3b348a..12e734bf6c 100644 --- a/src/state/shell/composer/index.tsx +++ b/src/state/shell/composer/index.tsx @@ -1,9 +1,4 @@ import {createContext, useContext, useMemo, useState} from 'react' -import { - type AppBskyActorDefs, - type AppBskyFeedDefs, - type AppBskyUnspeccedGetPostThreadV2, -} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -18,21 +13,22 @@ import { RQKEY_LINK_ROOT, } from '#/state/queries/resolve-link' import * as Toast from '#/components/Toast' +import {type app} from '#/lexicons' export interface ComposerOptsPostRef { uri: string cid: string text: string langs?: string[] - author: AppBskyActorDefs.ProfileViewBasic - embed?: AppBskyFeedDefs.PostView['embed'] + author: app.bsky.actor.defs.ProfileViewBasic + embed?: app.bsky.feed.defs.PostView['embed'] moderation?: ModerationDecision } export type OnPostSuccessData = | { replyToUri?: string - posts: AppBskyUnspeccedGetPostThreadV2.ThreadItem[] + posts: app.bsky.unspecced.getPostThreadV2.ThreadItem[] } | undefined @@ -48,7 +44,7 @@ export interface ComposerOpts { replyTo?: ComposerOptsPostRef onPost?: (postUri: string | undefined) => void onPostSuccess?: (data: OnPostSuccessData) => void - quote?: AppBskyFeedDefs.PostView + quote?: app.bsky.feed.defs.PostView mention?: string // handle of user to mention text?: string imageUris?: {uri: string; width: number; height: number; altText?: string}[] diff --git a/src/state/threadgate-hidden-replies.tsx b/src/state/threadgate-hidden-replies.tsx index 698b450ef2..8bdc462e2e 100644 --- a/src/state/threadgate-hidden-replies.tsx +++ b/src/state/threadgate-hidden-replies.tsx @@ -1,5 +1,6 @@ import {createContext, useCallback, useContext, useMemo, useState} from 'react' -import {type AppBskyFeedThreadgate} from '@atproto/api' + +import {type app} from '#/lexicons' type StateContext = { uris: Set @@ -74,7 +75,7 @@ export function useThreadgateHiddenReplyUrisAPI() { export function useMergedThreadgateHiddenReplies({ threadgateRecord, }: { - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main }) { const {uris, recentlyUnhiddenUris} = useThreadgateHiddenReplyUris() return useMemo(() => { @@ -89,7 +90,7 @@ export function useMergedThreadgateHiddenReplies({ export function useMergeThreadgateHiddenReplies() { const {uris, recentlyUnhiddenUris} = useThreadgateHiddenReplyUris() return useCallback( - (threadgate?: AppBskyFeedThreadgate.Record) => { + (threadgate?: app.bsky.feed.threadgate.Main) => { const set = new Set([...(threadgate?.hiddenReplies || []), ...uris]) for (const uri of recentlyUnhiddenUris) { set.delete(uri) diff --git a/src/state/unstable-post-source.tsx b/src/state/unstable-post-source.tsx index c5b7d1c2bf..e374cbbeae 100644 --- a/src/state/unstable-post-source.tsx +++ b/src/state/unstable-post-source.tsx @@ -1,8 +1,9 @@ import {useEffect, useId, useState} from 'react' -import {type AppBskyFeedDefs, AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {Logger} from '#/logger' import {type FeedSourceInfo} from '#/state/queries/feed' +import {type app} from '#/lexicons' /** * Separate logger for better debugging @@ -10,7 +11,7 @@ import {type FeedSourceInfo} from '#/state/queries/feed' const logger = Logger.create(Logger.Context.PostSource) export type PostSource = { - post: AppBskyFeedDefs.FeedViewPost + post: app.bsky.feed.defs.FeedViewPost feedSourceInfo?: FeedSourceInfo } diff --git a/src/types/bsky/__tests__/index.test.ts b/src/types/bsky/__tests__/index.test.ts index b0b4466b2c..ce7c98acab 100644 --- a/src/types/bsky/__tests__/index.test.ts +++ b/src/types/bsky/__tests__/index.test.ts @@ -1,5 +1,3 @@ -import {AppBskyFeedPost} from '@atproto/api' - import {app} from '#/lexicons' import * as bsky from '#/types/bsky' @@ -167,42 +165,3 @@ describe('types/bsky lexicon schema helpers (#/lexicons)', () => { }) }) }) - -describe('types/bsky validator helpers (@atproto/api)', () => { - it('dangerousIsType accepts the right $type without validating the body', () => { - expect( - bsky.dangerousIsType( - validPost, - AppBskyFeedPost.isRecord, - ), - ).toBe(true) - // Right $type, invalid body - still passes the fast guard. - expect( - bsky.dangerousIsType( - invalidPost, - AppBskyFeedPost.isRecord, - ), - ).toBe(true) - expect( - bsky.dangerousIsType( - wrongType, - AppBskyFeedPost.isRecord, - ), - ).toBe(false) - }) - - it('validate fully validates the body', () => { - expect( - bsky.validate( - validPost, - AppBskyFeedPost.validateRecord, - ), - ).toBe(true) - expect( - bsky.validate( - invalidPost, - AppBskyFeedPost.validateRecord, - ), - ).toBe(false) - }) -}) diff --git a/src/types/bsky/__tests__/post.test.ts b/src/types/bsky/__tests__/post.test.ts index 6db1d23878..a19d9dda27 100644 --- a/src/types/bsky/__tests__/post.test.ts +++ b/src/types/bsky/__tests__/post.test.ts @@ -1,8 +1,3 @@ -import { - type $Typed as $TypedApi, - type AppBskyEmbedRecord, - type AppBskyFeedDefs, -} from '@atproto/api' import {type $Typed} from '@atproto/lex' import {type app} from '#/lexicons' @@ -109,38 +104,25 @@ const starterPackViewBasic = { const asEmbed = (v: unknown) => v as app.bsky.feed.defs.PostView['embed'] /* - * Type-level assertions for the dual-world widening. These are compile-time - * only: each widened arm must accept both the `#/lexicons` view and the - * `@atproto/api` view, and `parseEmbed` must accept a `PostView.embed` from - * either world, because both worlds have live producers. + * Type-level assertions for the embed union. Compile-time only: each arm must + * accept the `#/lexicons` view of its def, and `parseEmbed` must accept a + * `PostView.embed`. */ type Assignable = From extends To ? true : false type Expect = T -type _PostArmAcceptsNewWorld = Expect< +type _PostArmAcceptsView = Expect< Assignable< {type: 'post'; view: $Typed}, EmbedType<'post'> > > -type _PostArmAcceptsOldWorld = Expect< - Assignable< - {type: 'post'; view: $TypedApi}, - EmbedType<'post'> - > -> -type _ParseEmbedAcceptsNewWorld = Expect< +type _ParseEmbedAcceptsPostViewEmbed = Expect< Assignable< app.bsky.feed.defs.PostView['embed'], Parameters[0] > > -type _ParseEmbedAcceptsOldWorld = Expect< - Assignable< - AppBskyFeedDefs.PostView['embed'], - Parameters[0] - > -> type _ParseEmbedReturnsEmbed = Expect< Assignable, Embed> > diff --git a/src/types/bsky/__tests__/starterPack.test.ts b/src/types/bsky/__tests__/starterPack.test.ts index b973766f2e..161de2a867 100644 --- a/src/types/bsky/__tests__/starterPack.test.ts +++ b/src/types/bsky/__tests__/starterPack.test.ts @@ -1,5 +1,3 @@ -import {type AppBskyGraphDefs} from '@atproto/api' - import {type app} from '#/lexicons' import { type AnyStarterPackView, @@ -30,25 +28,19 @@ const fullView = { } /* - * Type-level assertions for the dual-world widening: the alias must accept a - * starter pack view from either world, because both have live producers. These - * are compile-time only - a failure surfaces as a typecheck error. + * Type-level assertions for the view alias: it must accept both the basic and + * the full starter pack view. Compile-time only - a failure surfaces as a + * typecheck error. */ type Assignable = From extends To ? true : false type Expect = T -type _AcceptsNewBasicView = Expect< +type _AcceptsBasicView = Expect< Assignable > -type _AcceptsNewFullView = Expect< +type _AcceptsFullView = Expect< Assignable > -type _AcceptsOldBasicView = Expect< - Assignable -> -type _AcceptsOldFullView = Expect< - Assignable -> describe('types/bsky/starterPack guards', () => { describe('isBasicView', () => { diff --git a/src/types/bsky/index.ts b/src/types/bsky/index.ts index 1d498bc130..278c1c1079 100644 --- a/src/types/bsky/index.ts +++ b/src/types/bsky/index.ts @@ -10,71 +10,15 @@ import { type ValidateOptions, type ValidationResult, } from '@atproto/lex' -import {type ValidationResult as LegacyValidationResult} from '@atproto/lexicon' export * as post from '#/types/bsky/post' export * as profile from '#/types/bsky/profile' export * as starterPack from '#/types/bsky/starterPack' -/** - * Fast type checking without full schema validation, for use with data we - * trust, or for non-critical path use cases. Why? Our SDK's `is*` identity - * utils do not assert the type of the entire object, only the `$type` string. - * - * For full validation of the object schema, use the `validate` export from - * this file. - * - * Usage: - * ```ts - * import * as bsky from '#/types/bsky' - * - * if (bsky.dangerousIsType(item, AppBskyFeedPost.isRecord)) { - * // `item` has type `$Typed` here - * } - * ``` - */ -export function dangerousIsType( - record: unknown, - identity: (v: V) => v is V & {$type: NonNullable}, -): record is R { - return identity(record) -} - -/** - * Fully validates the object schema, which has a performance cost. - * - * For faster checks with data we trust, like that from our app view, use the - * `dangerousIsType` export from this same file. - * - * Usage: - * ```ts - * import * as bsky from '#/types/bsky' - * - * if (bsky.validate(item, AppBskyFeedPost.validateRecord)) { - * // `item` has type `$Typed` here - * } - * ``` - */ -export function validate( - record: unknown, - validator: (v: unknown) => LegacyValidationResult, -): record is R { - return validator(record).success -} - /* - * Two families of helpers live in this file, distinguished by the validation - * surface they consume. - * - * `dangerousIsType` and `validate` (above) operate on the standalone - * `is*`/`validate*` functions exported by '@atproto/api' - the record and the - * validator are passed separately, e.g. - * `dangerousIsType(v, AppBskyFeedPost.isRecord)`. - * - * `isType`, `matches`, `parse`, and `safeParse` (below) operate on the - * generated lexicon schema objects from '#/lexicons'. That codegen attaches - * the validation surface directly to each schema, so these helpers take the - * schema object itself: + * These helpers operate on the generated lexicon schema objects from + * '#/lexicons'. The codegen attaches the validation surface directly to each + * schema, so these helpers take the schema object itself: * * ```ts * import {app} from '#/lexicons' @@ -104,9 +48,8 @@ type TypedSchema = RecordSchema | TypedObjectSchema * string; it does NOT assert the rest of the object matches the schema. An * invalid record with the right `$type` will pass. * - * This is the '#/lexicons' equivalent of {@link dangerousIsType}. For full - * validation of the object schema, use {@link matches}, {@link parse}, or - * {@link safeParse} from this same file. + * For full validation of the object schema, use {@link matches}, {@link parse}, + * or {@link safeParse} from this same file. * * Usage: * ```ts @@ -126,10 +69,9 @@ export function isType( * Deliberately NOT delegating to the schema's `isTypeOf`: the generated * `TypedObjectSchema.isTypeOf` treats a MISSING `$type` as a match * (maybe-typed semantics), which would let any plain object satisfy any - * def-schema check and break `$type`-discriminated unions. The old - * `dangerousIsType` required a present, matching `$type`, and so do we. - * The nullish/object guard also mirrors the old `is$typed` behavior of - * returning false (not throwing) for null/undefined input. + * def-schema check and break `$type`-discriminated unions. We require a + * present, matching `$type`. The nullish/object guard also returns false + * (not throws) for null/undefined input. */ return ( value != null && @@ -142,9 +84,8 @@ export function isType( * Fully validates the object against the schema (strict, no coercion), which * has a performance cost, and narrows the value on success. * - * This is the '#/lexicons' equivalent of {@link validate}. For faster checks - * with data we trust, like that from our app view, use {@link isType} from this - * same file. + * For faster checks with data we trust, like that from our app view, use + * {@link isType} from this same file. * * Usage: * ```ts @@ -166,7 +107,7 @@ export function matches( /** * Fully validates and parses the value against the schema, returning the typed - * value or throwing a `LexValidationError` on failure. Parsing may apply + * value or throwing an `LexValidationError` on failure. Parsing may apply * schema transformations such as default values. * * Prefer {@link safeParse} where you want to branch on failure without a diff --git a/src/types/bsky/post.ts b/src/types/bsky/post.ts index 2c8e3f4d87..ed0d8a9d83 100644 --- a/src/types/bsky/post.ts +++ b/src/types/bsky/post.ts @@ -1,110 +1,56 @@ -import { - type $Typed as $TypedApi, - type AppBskyEmbedExternal, - type AppBskyEmbedGallery, - type AppBskyEmbedImages, - type AppBskyEmbedRecord, - type AppBskyEmbedVideo, - type AppBskyFeedDefs, - type AppBskyGraphDefs, - type AppBskyLabelerDefs, -} from '@atproto/api' import {type $Typed} from '@atproto/lex' import {app} from '#/lexicons' import {isType} from '#/types/bsky' -/* - * Each `view` slot below accepts both the generated `#/lexicons` view and the - * `@atproto/api` view of the same def, because both worlds have live producers: - * `parseEmbed` narrows with the `#/lexicons` schemas and so returns new-world - * views, while call sites that build an `Embed` by hand still pass views - * produced through the `@atproto/api` agent. - * - * The two worlds share the same `$type` strings, so the guards in this file - * narrow a value from either producer; only the static type differs. - * - * TODO: remove the @atproto/api arms once all producers emit #/lexicons views - */ export type Embed = | { type: 'post' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'post_not_found' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'post_blocked' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'post_detached' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'feed' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'list' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'labeler' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'starter_pack' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'images' - /* - * Only the `@atproto/api` view, unlike the other arms: the ImageEmbed - * consumer reads `view.images` directly, and the `#/lexicons` view is - * assignable to this slot, so `parseEmbed`'s new-world value flows in - * while the consumer keeps a single structural shape to read from. - */ - view: $TypedApi + view: $Typed } | { type: 'gallery' - /* - * Old-world only for the same reason as the `images` arm above: the - * consumer narrows `view.items` with `AppBskyEmbedGallery.isViewImage`, - * which cannot narrow the new view's `Unknown$TypedObject` arm. - */ - view: $TypedApi + view: $Typed } | { type: 'link' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'video' - view: - | $Typed - | $TypedApi + view: $Typed } | { type: 'post_with_media' @@ -169,15 +115,7 @@ export function parseEmbedRecordView({ } } -export function parseEmbed( - /* - * Accepts a `PostView.embed` from either world; the `#/lexicons` guards below - * narrow on `$type`, which is world-independent. - */ - embed: - | app.bsky.feed.defs.PostView['embed'] - | AppBskyFeedDefs.PostView['embed'], -): Embed { +export function parseEmbed(embed: app.bsky.feed.defs.PostView['embed']): Embed { if (isType(app.bsky.embed.images.view, embed)) { return { type: 'images', diff --git a/src/types/bsky/profile.ts b/src/types/bsky/profile.ts index 4231a6d0fe..edf1aa1c08 100644 --- a/src/types/bsky/profile.ts +++ b/src/types/bsky/profile.ts @@ -1,23 +1,10 @@ -import {type AppBskyActorDefs, type ChatBskyActorDefs} from '@atproto/api' - import {type app, type chat} from '#/lexicons' /** - * Matches any profile view exported by our SDK, in either world. - * - * Both the generated `#/lexicons` views and the `@atproto/api` views are - * accepted because both are live producers: queries migrated to the lexicon - * client emit the former, unmigrated ones emit the latter, and consumers of - * this alias take profiles from both. - * - * TODO: remove the @atproto/api arms once all producers emit #/lexicons views + * Matches any profile view exported by our SDK. */ export type AnyProfileView = | 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 diff --git a/src/types/bsky/starterPack.ts b/src/types/bsky/starterPack.ts index 23df0e4631..21cb4634df 100644 --- a/src/types/bsky/starterPack.ts +++ b/src/types/bsky/starterPack.ts @@ -1,23 +1,14 @@ -import {type AppBskyGraphDefs} from '@atproto/api' - import {app} from '#/lexicons' /* - * `$type`-only guards for starter pack views. They compare against the - * `#/lexicons` schema's `$type` string rather than delegating to the schema's - * `isTypeOf` (which treats a missing `$type` as a match), matching the - * present-and-equal semantics of the old `@atproto/api` - * `AppBskyGraphDefs.isStarterPackView*` helpers. - * - * The `$type` string is identical in both worlds, so a single check narrows a - * value from either producer; the narrowed type is the union of both worlds' - * views for the same reason {@link AnyStarterPackView} is. + * The generated `$type`-only guards. These match on a present, matching + * `$type` against the `#/lexicons` schema's `$type` string rather than + * delegating to the schema's `isTypeOf` (which treats a missing `$type` as a + * match). */ export function isBasicView( v: unknown, -): v is - | app.bsky.graph.defs.StarterPackViewBasic - | AppBskyGraphDefs.StarterPackViewBasic { +): v is app.bsky.graph.defs.StarterPackViewBasic { return ( v != null && typeof v === 'object' && @@ -26,9 +17,7 @@ export function isBasicView( ) } -export function isView( - v: unknown, -): v is app.bsky.graph.defs.StarterPackView | AppBskyGraphDefs.StarterPackView { +export function isView(v: unknown): v is app.bsky.graph.defs.StarterPackView { return ( v != null && typeof v === 'object' && @@ -37,16 +26,8 @@ export function isView( } /** - * Matches any starter pack view exported by our SDK, in either world. - * - * Both the generated `#/lexicons` views and the `@atproto/api` views are - * accepted because both are live producers: queries migrated to the lexicon - * client emit the former, unmigrated ones emit the latter. - * - * TODO: remove the @atproto/api arms once all producers emit #/lexicons views + * Matches any starter pack view exported by our SDK. */ export type AnyStarterPackView = | app.bsky.graph.defs.StarterPackViewBasic | app.bsky.graph.defs.StarterPackView - | AppBskyGraphDefs.StarterPackViewBasic - | AppBskyGraphDefs.StarterPackView diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 8108c41cd1..7a2a2f5484 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -44,14 +44,8 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context' import {scheduleOnUI} from 'react-native-worklets' import * as FileSystem from 'expo-file-system' import {type ImagePickerAsset} from 'expo-image-picker' -import { - AppBskyDraftCreateDraft, - AppBskyUnspeccedDefs, - AtUri, - ChatBskyGroupDefs, -} from '@atproto/api' -import {type Client} from '@atproto/lex' -import {type AtUriString} from '@atproto/syntax' +import {type Client, type UriString} from '@atproto/lex' +import {AtUri, type AtUriString} from '@atproto/syntax' import {type RichText} from '@bsky.app/sdk/richtext' import {plural} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro' @@ -78,6 +72,7 @@ import {useCallOnce} from '#/lib/once' import {type NavigationProp} from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' import {colors} from '#/lib/styles' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useDialogStateControlContext} from '#/state/dialogs' import {emitPostCreated} from '#/state/events' @@ -149,7 +144,8 @@ import { IS_WEB_SAFARI, } from '#/env' import {type Gif} from '#/features/gifPicker/types' -import {app} from '#/lexicons' +import {app, chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {BottomSheetPortalProvider} from '../../../../modules/bottom-sheet' import { draftToComposerPosts, @@ -771,7 +767,9 @@ export const ComposePost = ({ const getDraftSaveError = useCallback( (e: unknown): string => { - if (e instanceof AppBskyDraftCreateDraft.DraftLimitReachedError) { + if ( + matchXrpcError(e, app.bsky.draft.createDraft) === 'DraftLimitReached' + ) { return l`You've reached the maximum number of drafts` } return l`Failed to save draft` @@ -1010,7 +1008,7 @@ export const ComposePost = ({ const hasUnavailableChatInvite = linkQueries.some( q => q.data?.type === 'chat-invite' && - !ChatBskyGroupDefs.isJoinLinkPreviewView(q.data.view), + !bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, q.data.view), ) const canPost = @@ -1140,7 +1138,7 @@ export const ComposePost = ({ } if ( !res.thread.every(p => - AppBskyUnspeccedDefs.isThreadItemPost(p.value), + bsky.isType(app.bsky.unspecced.defs.threadItemPost, p.value), ) ) { throw new Error(`composer: app view returned non-post items`) @@ -1212,7 +1210,7 @@ export const ComposePost = ({ const resolved = q.data if ( resolved?.type === 'chat-invite' && - ChatBskyGroupDefs.isJoinLinkPreviewView(resolved.view) + bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, resolved.view) ) { ax.metric('groupchat:inviteLink:shared', { convoId: resolved.view.convoId, @@ -1251,7 +1249,7 @@ export const ComposePost = ({ void whenAppViewReady(client, initQuote.uri, res => { const anchor = res.thread.at(0) if ( - AppBskyUnspeccedDefs.isThreadItemPost(anchor?.value) && + bsky.isType(app.bsky.unspecced.defs.threadItemPost, anchor?.value) && anchor.value.post.quoteCount !== initQuote.quoteCount ) { onPost?.(postUri) @@ -1681,7 +1679,7 @@ let ComposerPost = memo(function ComposerPost({ const onNewLink = useCallback( (uri: string) => { - dispatchPost({type: 'embed_add_uri', uri}) + dispatchPost({type: 'embed_add_uri', uri: uri as UriString}) }, [dispatchPost], ) diff --git a/src/view/com/composer/ComposerReplyTo.tsx b/src/view/com/composer/ComposerReplyTo.tsx index 00a108bb54..175cda7d30 100644 --- a/src/view/com/composer/ComposerReplyTo.tsx +++ b/src/view/com/composer/ComposerReplyTo.tsx @@ -1,13 +1,6 @@ import {useCallback, useMemo, useState} from 'react' import {LayoutAnimation, Pressable, View} from 'react-native' import {Image} from 'expo-image' -import { - AppBskyEmbedGallery, - AppBskyEmbedImages, - AppBskyEmbedRecord, - AppBskyEmbedRecordWithMedia, - AppBskyFeedPost, -} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -20,6 +13,8 @@ import {atoms as a, useTheme, utils, web} from '#/alf' import {QuoteEmbed} from '#/components/Post/Embed' import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {parseEmbed} from '#/types/bsky/post' export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { @@ -39,15 +34,15 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { const quoteEmbed = useMemo(() => { if ( - AppBskyEmbedRecord.isView(embed) && - AppBskyEmbedRecord.isViewRecord(embed.record) && - AppBskyFeedPost.isRecord(embed.record.value) + bsky.isType(app.bsky.embed.record.view, embed) && + bsky.isType(app.bsky.embed.record.viewRecord, embed.record) && + bsky.isType(app.bsky.feed.post, embed.record.value) ) { return embed } else if ( - AppBskyEmbedRecordWithMedia.isView(embed) && - AppBskyEmbedRecord.isViewRecord(embed.record.record) && - AppBskyFeedPost.isRecord(embed.record.record.value) + bsky.isType(app.bsky.embed.recordWithMedia.view, embed) && + bsky.isType(app.bsky.embed.record.viewRecord, embed.record.record) && + bsky.isType(app.bsky.feed.post, embed.record.record.value) ) { return embed.record } @@ -61,20 +56,20 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { : null const {images, totalNumber} = useMemo(() => { - if (AppBskyEmbedImages.isView(embed)) { + if (bsky.isType(app.bsky.embed.images.view, embed)) { return {images: embed.images, totalNumber: embed.images.length} - } else if (AppBskyEmbedGallery.isView(embed)) { + } else if (bsky.isType(app.bsky.embed.gallery.view, embed)) { return { images: galleryItemsToImages(embed.items), totalNumber: embed.items.length, } - } else if (AppBskyEmbedRecordWithMedia.isView(embed)) { - if (AppBskyEmbedImages.isView(embed.media)) { + } else if (bsky.isType(app.bsky.embed.recordWithMedia.view, embed)) { + if (bsky.isType(app.bsky.embed.images.view, embed.media)) { return { images: embed.media.images, totalNumber: embed.media.images.length, } - } else if (AppBskyEmbedGallery.isView(embed.media)) { + } else if (bsky.isType(app.bsky.embed.gallery.view, embed.media)) { return { images: galleryItemsToImages(embed.media.items), totalNumber: embed.media.items.length, @@ -145,12 +140,12 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) { } function galleryItemsToImages( - items: AppBskyEmbedGallery.View['items'], -): AppBskyEmbedImages.ViewImage[] { + items: app.bsky.embed.gallery.View['items'], +): app.bsky.embed.images.ViewImage[] { // The reply-to thumbnail only renders up to 4 tiles; slicing here keeps // the existing layout switch valid for galleries up to 10 items. return items - .filter(AppBskyEmbedGallery.isViewImage) + .filter(item => bsky.isType(app.bsky.embed.gallery.viewImage, item)) .slice(0, 4) .map(item => ({ thumb: item.thumbnail, @@ -164,7 +159,7 @@ function ComposerReplyToImages({ images, totalNumber, }: { - images: AppBskyEmbedImages.ViewImage[] + images: app.bsky.embed.images.ViewImage[] totalNumber: number }) { const t = useTheme() diff --git a/src/view/com/composer/ExternalEmbed.tsx b/src/view/com/composer/ExternalEmbed.tsx index 0b14f16ed3..b39e1393f3 100644 --- a/src/view/com/composer/ExternalEmbed.tsx +++ b/src/view/com/composer/ExternalEmbed.tsx @@ -1,5 +1,6 @@ import {useMemo} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' +import {type UriString} from '@atproto/lex' import {cleanError} from '#/lib/strings/errors' import { @@ -32,9 +33,9 @@ export const ExternalEmbedGif = ({ () => data && { title: data.title ?? data.uri, - uri: data.uri, + uri: data.uri as UriString, description: data.description ?? '', - thumb: data.thumb?.source.path, + thumb: data.thumb?.source.path as UriString | undefined, }, [data], ) @@ -80,7 +81,7 @@ export const ExternalEmbedLink = ({ hasQuote, onRemove, }: { - uri: string + uri: UriString hasQuote: boolean onRemove: () => void }) => { @@ -100,7 +101,8 @@ export const ExternalEmbedLink = ({ description: data.view?.external?.description || data.description, // prefer opengraph data to atproto record-derived image - thumb: data.thumb?.source.path || data.view?.external?.thumb, + thumb: (data.thumb?.source.path || + data.view?.external?.thumb) as UriString | undefined, }} /> ) @@ -111,7 +113,7 @@ export const ExternalEmbedLink = ({ title: data.title || uri, uri, description: data.description, - thumb: data.thumb?.source.path, + thumb: data.thumb?.source.path as UriString | undefined, }} hideAlt /> diff --git a/src/view/com/composer/drafts/state/api.ts b/src/view/com/composer/drafts/state/api.ts index 34fa4f6db4..2260e04bd0 100644 --- a/src/view/com/composer/drafts/state/api.ts +++ b/src/view/com/composer/drafts/state/api.ts @@ -1,7 +1,8 @@ /** * Type converters for Draft API - convert between ComposerState and server Draft types. */ -import {AppBskyDraftDefs, AtUri} from '@atproto/api' +import {type UriString} from '@atproto/lex' +import {AtUri} from '@atproto/syntax' import {RichText} from '@bsky.app/sdk/richtext' import {nanoid} from 'nanoid/non-secure' @@ -22,6 +23,8 @@ import {type VideoState} from '#/view/com/composer/state/video' import {type AnalyticsContextType} from '#/analytics' import {getDeviceId} from '#/analytics/identifiers' import {type Gif} from '#/features/gifPicker/types' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {logger} from './logger' import {type DraftPostDisplay, type DraftSummary} from './schema' import * as storage from './storage' @@ -63,18 +66,18 @@ export async function composerStateToDraft( clients: LinkResolvers, state: ComposerState, ): Promise<{ - draft: AppBskyDraftDefs.Draft + draft: app.bsky.draft.defs.Draft localRefPaths: Map }> { const localRefPaths = new Map() - const posts: AppBskyDraftDefs.DraftPost[] = await Promise.all( + const posts: app.bsky.draft.defs.DraftPost[] = await Promise.all( state.thread.posts.map(post => { return postDraftToServerPost(clients, post, localRefPaths) }), ) - const draft: AppBskyDraftDefs.Draft = { + const draft: app.bsky.draft.defs.Draft = { $type: 'app.bsky.draft.defs#draft', deviceId: getDeviceId(), deviceName: getDeviceName().slice(0, 100), // max length of 100 in lex @@ -99,8 +102,8 @@ async function postDraftToServerPost( clients: LinkResolvers, post: PostDraft, localRefPaths: Map, -): Promise { - const draftPost: AppBskyDraftDefs.DraftPost = { +): Promise { + const draftPost: app.bsky.draft.defs.DraftPost = { $type: 'app.bsky.draft.defs#draftPost', text: post.richtext.text, } @@ -176,7 +179,7 @@ async function postDraftToServerPost( function serializeImages( images: ComposerImage[], localRefPaths: Map, -): AppBskyDraftDefs.DraftEmbedGalleryItems { +): app.bsky.draft.defs.DraftEmbedGalleryItems { return images.map(image => { const sourcePath = image.transformed?.path || image.source.path // Reuse existing localRefPath if present (editing draft), otherwise generate new @@ -208,7 +211,7 @@ function serializeImages( async function serializeVideo( videoState: VideoState, localRefPaths: Map, -): Promise { +): Promise { // Only save videos that have been compressed (have a video file) if (!videoState.video) { return undefined @@ -221,7 +224,7 @@ async function serializeVideo( localRefPaths.set(localRefPath, videoState.video.uri) // Read caption file contents as text - const captions: AppBskyDraftDefs.DraftEmbedCaption[] = [] + const captions: app.bsky.draft.defs.DraftEmbedCaption[] = [] for (const caption of videoState.captions) { if (caption.lang) { const content = await caption.file.text() @@ -252,7 +255,7 @@ function serializeGif(gifMedia: { type: 'gif' gif: Gif alt: string -}): AppBskyDraftDefs.DraftEmbedExternal | undefined { +}): app.bsky.draft.defs.DraftEmbedExternal | undefined { const gif = gifMedia.gif const gifFormat = gif.media_formats.gif || gif.media_formats.tinygif @@ -273,7 +276,7 @@ function serializeGif(gifMedia: { return { $type: 'app.bsky.draft.defs#draftEmbedExternal', - uri: url.toString(), + uri: url.toString() as UriString, } } @@ -282,7 +285,7 @@ function serializeGif(gifMedia: { * both the `embedImages` and `embedGallery` paths in draftToComposerPosts. */ async function restoreDraftImages( - draftImages: AppBskyDraftDefs.DraftEmbedImage[], + draftImages: app.bsky.draft.defs.DraftEmbedImage[], loadedMedia: Map, ): Promise { const imagePromises = draftImages.map(async img => { @@ -338,7 +341,7 @@ export function draftViewToSummary({ view, analytics, }: { - view: AppBskyDraftDefs.DraftView + view: app.bsky.draft.defs.DraftView analytics: AnalyticsContextType }): DraftSummary { const meta = { @@ -378,7 +381,7 @@ export function draftViewToSummary({ // Process gallery if (post.embedGallery) { for (const item of post.embedGallery.items) { - if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue + if (!bsky.isType(app.bsky.draft.defs.draftEmbedImage, item)) continue meta.mediaCount++ meta.hasMedia = true const exists = storage.mediaExists(item.localRef.path) @@ -494,7 +497,7 @@ function parseGifFromUrl( * by initiating video processing for each entry. */ export async function draftToComposerPosts( - draft: AppBskyDraftDefs.Draft, + draft: app.bsky.draft.defs.Draft, loadedMedia: Map, ): Promise<{posts: PostDraft[]; restoredVideos: Map}> { const restoredVideos = new Map() @@ -523,8 +526,8 @@ export async function draftToComposerPosts( ) } if (post.embedGallery && post.embedGallery.items.length > 0) { - const galleryImages = post.embedGallery.items.filter( - AppBskyDraftDefs.isDraftEmbedImage, + const galleryImages = post.embedGallery.items.filter(item => + bsky.isType(app.bsky.draft.defs.draftEmbedImage, item), ) restoredImages.push( ...(await restoreDraftImages(galleryImages, loadedMedia)), @@ -605,7 +608,7 @@ export async function draftToComposerPosts( const record = post.embedRecords[0] const urip = new AtUri(record.record.uri) const url = `https://bsky.app/profile/${urip.host}/post/${urip.rkey}` - embed.quote = {type: 'link', uri: url} + embed.quote = {type: 'link', uri: url as UriString} } // Restore link embed (only if not a GIF) @@ -644,7 +647,7 @@ export async function draftToComposerPosts( * Convert server threadgate rules back to UI settings. */ export function threadgateToUISettings( - threadgateAllow?: AppBskyDraftDefs.Draft['threadgateAllow'], + threadgateAllow?: app.bsky.draft.defs.Draft['threadgateAllow'], ): Array<{type: string; list?: string}> { if (!threadgateAllow) { return [] @@ -678,7 +681,9 @@ export function threadgateToUISettings( * Extract all localRef paths from a draft. * Used to identify which media files belong to a draft for cleanup. */ -export function extractLocalRefs(draft: AppBskyDraftDefs.Draft): Set { +export function extractLocalRefs( + draft: app.bsky.draft.defs.Draft, +): Set { const refs = new Set() for (const post of draft.posts) { if (post.embedImages) { @@ -688,7 +693,7 @@ export function extractLocalRefs(draft: AppBskyDraftDefs.Draft): Set { } if (post.embedGallery) { for (const item of post.embedGallery.items) { - if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue + if (!bsky.isType(app.bsky.draft.defs.draftEmbedImage, item)) continue refs.add(item.localRef.path) } } diff --git a/src/view/com/composer/drafts/state/queries.ts b/src/view/com/composer/drafts/state/queries.ts index a620f0ec0f..7f4840c7bf 100644 --- a/src/view/com/composer/drafts/state/queries.ts +++ b/src/view/com/composer/drafts/state/queries.ts @@ -1,4 +1,3 @@ -import {AppBskyDraftDefs} from '@atproto/api' import { useInfiniteQuery, useMutation, @@ -12,6 +11,7 @@ import {type ComposerState} from '#/view/com/composer/state/composer' import {useAnalytics} from '#/analytics' import {getDeviceId} from '#/analytics/identifiers' import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {composerStateToDraft, draftViewToSummary} from './api' import {logger} from './logger' import * as storage from './storage' @@ -52,7 +52,9 @@ export function useDraftsQuery() { * Load a draft's local media for editing. * Takes the full Draft object (from DraftSummary) to avoid re-fetching. */ -export async function loadDraftMedia(draft: AppBskyDraftDefs.Draft): Promise<{ +export async function loadDraftMedia( + draft: app.bsky.draft.defs.Draft, +): Promise<{ loadedMedia: Map }> { // Load local media files @@ -81,7 +83,7 @@ export async function loadDraftMedia(draft: AppBskyDraftDefs.Draft): Promise<{ // Load gallery if (post.embedGallery) { for (const item of post.embedGallery.items) { - if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue + if (!bsky.isType(app.bsky.draft.defs.draftEmbedImage, item)) continue try { const url = await storage.loadMediaFromLocal(item.localRef.path) loadedMedia.set(item.localRef.path, url) @@ -142,11 +144,10 @@ export function useSaveDraftMutation() { composerState, ) /* - * `composerStateToDraft` builds the draft against the `@atproto/api` - * types, whose string fields are unbranded, so it is asserted once here - * to the vendored input type. + * `composerStateToDraft` builds the draft with unbranded string fields, + * so it is asserted once here to the generated input type. */ - const draft = apiDraft as unknown as app.bsky.draft.defs.Draft + const draft = apiDraft logger.debug('saving draft', { existingDraftId, @@ -242,7 +243,7 @@ export function useDeleteDraftMutation() { draftId, }: { draftId: string - draft: AppBskyDraftDefs.Draft + draft: app.bsky.draft.defs.Draft }) => { // Delete from server first - if this fails, we keep local media for retry await client.call(app.bsky.draft.deleteDraft, {id: draftId}) @@ -257,7 +258,8 @@ export function useDeleteDraftMutation() { } if (post.embedGallery) { for (const item of post.embedGallery.items) { - if (!AppBskyDraftDefs.isDraftEmbedImage(item)) continue + if (!bsky.isType(app.bsky.draft.defs.draftEmbedImage, item)) + continue await storage.deleteMediaFromLocal(item.localRef.path) } } diff --git a/src/view/com/composer/drafts/state/schema.ts b/src/view/com/composer/drafts/state/schema.ts index 9f88aa07da..25bba23152 100644 --- a/src/view/com/composer/drafts/state/schema.ts +++ b/src/view/com/composer/drafts/state/schema.ts @@ -1,8 +1,8 @@ +import {type app} from '#/lexicons' /** * Types for draft display and local media tracking. - * Server draft types come from @atproto/api. + * Server draft types come from the generated `#/lexicons` modules. */ -import {type AppBskyDraftDefs} from '@atproto/api' /** * Reference to locally cached media file for display @@ -55,7 +55,7 @@ export type DraftSummary = { /** ISO timestamp of last update */ updatedAt: string /** The full draft data from the server */ - draft: AppBskyDraftDefs.Draft + draft: app.bsky.draft.defs.Draft /** All posts in the draft for full display */ posts: DraftPostDisplay[] /** Metadata about the draft for display purposes */ diff --git a/src/view/com/composer/state/composer.ts b/src/view/com/composer/state/composer.ts index 34e6b69508..228f9a62ce 100644 --- a/src/view/com/composer/state/composer.ts +++ b/src/view/com/composer/state/composer.ts @@ -1,5 +1,5 @@ import {type ImagePickerAsset} from 'expo-image-picker' -import {type AppBskyActorDefs, type AppBskyDraftDefs} from '@atproto/api' +import {type UriString} from '@atproto/lex' import {type AtUriString, toDatetimeString} from '@atproto/syntax' import {RichText} from '@bsky.app/sdk/richtext' import {nanoid} from 'nanoid/non-secure' @@ -56,7 +56,12 @@ type GifMedia = { type Link = { type: 'link' - uri: string + /* + * A URL the user typed or pasted, validated by the link resolver rather than + * at construction, so it carries lex's `uri` brand for the record and view + * slots it flows into. + */ + uri: UriString } // This structure doesn't exactly correspond to the data model. @@ -91,7 +96,7 @@ export type PostAction = } | {type: 'embed_remove_video'} | {type: 'embed_update_video'; videoAction: VideoAction} - | {type: 'embed_add_uri'; uri: string} + | {type: 'embed_add_uri'; uri: UriString} | {type: 'embed_remove_quote'} | {type: 'embed_remove_link'} | {type: 'embed_add_gif'; gif: Gif} @@ -141,8 +146,8 @@ export type ComposerAction = type: 'restore_from_draft' draftId: string posts: PostDraft[] - threadgateAllow: AppBskyDraftDefs.Draft['threadgateAllow'] - postgateEmbeddingRules: AppBskyDraftDefs.Draft['postgateEmbeddingRules'] + threadgateAllow: app.bsky.draft.defs.Draft['threadgateAllow'] + postgateEmbeddingRules: app.bsky.draft.defs.Draft['postgateEmbeddingRules'] /** Map of localRefPath -> loaded media path/URL */ loadedMedia: Map @@ -152,7 +157,7 @@ export type ComposerAction = | { type: 'clear' initInteractionSettings: - | AppBskyActorDefs.PostInteractionSettingsPref + | app.bsky.actor.defs.PostInteractionSettingsPref | undefined } | { @@ -320,14 +325,13 @@ export function composerReducer( * Draft records are still typed against the legacy client, so the * stored rules arrive unbranded. Wave B migrates the draft types. */ - embeddingRules: - postgateEmbeddingRules as app.bsky.feed.postgate.Main['embeddingRules'], + embeddingRules: postgateEmbeddingRules, }), threadgate: threadgateRecordToAllowUISetting({ $type: 'app.bsky.feed.threadgate', post: '' as AtUriString, createdAt: toDatetimeString(new Date()), - allow: threadgateAllow as app.bsky.feed.threadgate.Main['allow'], + allow: threadgateAllow, }), }, } @@ -632,7 +636,7 @@ export function createComposerState({ initImageUris: ComposerOpts['imageUris'] initQuoteUri: string | undefined initInteractionSettings: - | AppBskyActorDefs.PostInteractionSettingsPref + | app.bsky.actor.defs.PostInteractionSettingsPref | undefined }): ComposerState { let media: ImagesMedia | GalleryMedia | undefined @@ -646,7 +650,7 @@ export function createComposerState({ if (path) { quote = { type: 'link', - uri: toBskyAppUrl(path), + uri: toBskyAppUrl(path) as UriString, } } } @@ -700,7 +704,7 @@ export function createComposerState({ if (suggestedExtUri) { link = { type: 'link', - uri: suggestedExtUri, + uri: suggestedExtUri as UriString, } } const suggestedPostUri = suggestLinkCardUri( @@ -717,7 +721,7 @@ export function createComposerState({ if (!quote) { quote = { type: 'link', - uri: suggestedPostUri, + uri: suggestedPostUri as UriString, } } } @@ -750,15 +754,13 @@ export function createComposerState({ * Preferences are still typed against the legacy client, so the stored * rules arrive unbranded. Wave B migrates `getPreferences`. */ - embeddingRules: (initInteractionSettings?.postgateEmbeddingRules || - []) as app.bsky.feed.postgate.Main['embeddingRules'], + embeddingRules: initInteractionSettings?.postgateEmbeddingRules || [], }), threadgate: threadgateRecordToAllowUISetting({ $type: 'app.bsky.feed.threadgate', post: '' as AtUriString, createdAt: toDatetimeString(new Date()), - allow: - initInteractionSettings?.threadgateAllowRules as app.bsky.feed.threadgate.Main['allow'], + allow: initInteractionSettings?.threadgateAllowRules, }), }, } diff --git a/src/view/com/composer/text-input/mobile/Autocomplete.tsx b/src/view/com/composer/text-input/mobile/Autocomplete.tsx index 9621ebd82c..c2f80165a1 100644 --- a/src/view/com/composer/text-input/mobile/Autocomplete.tsx +++ b/src/view/com/composer/text-input/mobile/Autocomplete.tsx @@ -1,6 +1,5 @@ import {View} from 'react-native' import Animated, {FadeInDown, FadeOut} from 'react-native-reanimated' -import {type AppBskyActorDefs} from '@atproto/api' import {Trans} from '@lingui/react/macro' import {PressableScale} from '#/lib/custom-animations/PressableScale' @@ -11,6 +10,7 @@ import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, platform, useTheme} from '#/alf' import {ProfileBadges} from '#/components/ProfileBadges' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' export function Autocomplete({ prefix, @@ -70,7 +70,7 @@ function AutocompleteProfileCard({ totalItems, onPress, }: { - profile: AppBskyActorDefs.ProfileViewBasic + profile: app.bsky.actor.defs.ProfileViewBasic itemIndex: number totalItems: number onPress: () => void diff --git a/src/view/com/composer/text-input/web/Autocomplete.tsx b/src/view/com/composer/text-input/web/Autocomplete.tsx index 267d4a4829..945ce59adc 100644 --- a/src/view/com/composer/text-input/web/Autocomplete.tsx +++ b/src/view/com/composer/text-input/web/Autocomplete.tsx @@ -1,6 +1,5 @@ import {forwardRef, useEffect, useImperativeHandle, useState} from 'react' import {Pressable, View} from 'react-native' -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' @@ -16,6 +15,7 @@ import {type ActorAutocompleteFn} from '#/state/queries/actor-autocomplete' import {atoms as a, useTheme} from '#/alf' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' interface MentionListRef { onKeyDown: (props: SuggestionKeyDownProps) => boolean @@ -205,7 +205,7 @@ function AutocompleteProfileCard({ onHover, moderationOpts, }: { - profile: AppBskyActorDefs.ProfileViewBasic + profile: app.bsky.actor.defs.ProfileViewBasic isSelected: boolean onPress: () => void onHover: () => void diff --git a/src/view/com/composer/text-input/web/LinkDecorator.ts b/src/view/com/composer/text-input/web/LinkDecorator.ts index 4843f0ddfc..bcb81b2cf5 100644 --- a/src/view/com/composer/text-input/web/LinkDecorator.ts +++ b/src/view/com/composer/text-input/web/LinkDecorator.ts @@ -14,7 +14,7 @@ * the facet-set. */ -import {URL_REGEX} from '@atproto/api' +import {URL_REGEX} from '@bsky.app/sdk/richtext' import {Mark} from '@tiptap/core' import {type Node as ProsemirrorNode} from '@tiptap/pm/model' import {Plugin, PluginKey} from '@tiptap/pm/state' diff --git a/src/view/com/composer/text-input/web/TagDecorator.ts b/src/view/com/composer/text-input/web/TagDecorator.ts index 8f1142b86c..34f6396abb 100644 --- a/src/view/com/composer/text-input/web/TagDecorator.ts +++ b/src/view/com/composer/text-input/web/TagDecorator.ts @@ -18,7 +18,7 @@ import { CASHTAG_REGEX, TAG_REGEX, TRAILING_PUNCTUATION_REGEX, -} from '@atproto/api' +} from '@bsky.app/sdk/richtext' import {Mark} from '@tiptap/core' import {type Node as ProsemirrorNode} from '@tiptap/pm/model' import {Plugin, PluginKey} from '@tiptap/pm/state' diff --git a/src/view/com/feeds/FeedPage.tsx b/src/view/com/feeds/FeedPage.tsx index 331c62e0d1..10572f9cba 100644 --- a/src/view/com/feeds/FeedPage.tsx +++ b/src/view/com/feeds/FeedPage.tsx @@ -7,7 +7,6 @@ import { useState, } from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {type NavigationProp, useNavigation} from '@react-navigation/native' @@ -38,6 +37,7 @@ import {useHeaderOffset} from '#/components/hooks/useHeaderOffset' import {EditBig_Stroke2_Corner2_Rounded as EditBigIcon} from '#/components/icons/EditBig' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' +import {app} from '#/lexicons' const POLL_FREQ = 60e3 // 60sec @@ -59,7 +59,7 @@ export function FeedPage({ isPageAdjacent: boolean renderEmptyState: () => JSX.Element renderEndOfFeed?: () => JSX.Element - savedFeedConfig?: AppBskyActorDefs.SavedFeed + savedFeedConfig?: app.bsky.actor.defs.SavedFeed feedInfo: FeedSourceInfo }) { const ax = useAnalytics() @@ -77,7 +77,7 @@ export function FeedPage({ const isVideoFeed = useMemo(() => { const isBskyVideoFeed = VIDEO_FEED_URIS.includes(feedInfo.uri) const feedIsVideoMode = - feedInfo.contentMode === AppBskyFeedDefs.CONTENTMODEVIDEO + feedInfo.contentMode === app.bsky.feed.defs.contentModeVideo.value const _isVideoFeed = isBskyVideoFeed || feedIsVideoMode return IS_NATIVE && _isVideoFeed }, [feedInfo]) diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx index ee62392556..ecc11c5864 100644 --- a/src/view/com/feeds/FeedSourceCard.tsx +++ b/src/view/com/feeds/FeedSourceCard.tsx @@ -1,10 +1,6 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' -import { - type $Typed, - AppBskyFeedDefs, - type AppBskyGraphDefs, - AtUri, -} from '@atproto/api' +import {type $Typed} from '@atproto/lex' +import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' @@ -22,13 +18,15 @@ import {atoms as a, useTheme} from '#/alf' import {Link} from '#/components/Link' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {MissingFeed} from './MissingFeed' type FeedSourceCardProps = { feedUri: string feedData?: - | $Typed - | $Typed + | $Typed + | $Typed style?: StyleProp showSaveBtn?: boolean showDescription?: boolean @@ -46,7 +44,7 @@ export function FeedSourceCard({ }: FeedSourceCardProps) { if (feedData) { let feed: FeedSourceInfo - if (AppBskyFeedDefs.isGeneratorView(feedData)) { + if (bsky.isType(app.bsky.feed.defs.generatorView, feedData)) { feed = hydrateFeedGenerator(feedData) } else { feed = hydrateList(feedData) diff --git a/src/view/com/feeds/MissingFeed.tsx b/src/view/com/feeds/MissingFeed.tsx index bc2af31077..bd0f4f5948 100644 --- a/src/view/com/feeds/MissingFeed.tsx +++ b/src/view/com/feeds/MissingFeed.tsx @@ -1,5 +1,5 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/view/com/lists/ListMembers.tsx b/src/view/com/lists/ListMembers.tsx index 04876eefb2..2f24dfec09 100644 --- a/src/view/com/lists/ListMembers.tsx +++ b/src/view/com/lists/ListMembers.tsx @@ -6,7 +6,6 @@ import { View, type ViewStyle, } from 'react-native' -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -26,6 +25,7 @@ import {useDialogControl} from '#/components/Dialog' import {UserAddRemoveListsDialog} from '#/components/dialogs/lists/UserAddRemoveListsDialog' import {ListFooter} from '#/components/Lists' import * as ProfileCard from '#/components/ProfileCard' +import {type app} from '#/lexicons' import type * as bsky from '#/types/bsky' const LOADING_ITEM = {kind: 'loading', _reactKey: '__loading__'} as const @@ -43,7 +43,7 @@ type Item = | typeof LOAD_MORE_ERROR_ITEM | { kind: 'list_item' - listItem: AppBskyGraphDefs.ListItemView + listItem: app.bsky.graph.defs.ListItemView } export function ListMembers({ diff --git a/src/view/com/lists/MyLists.tsx b/src/view/com/lists/MyLists.tsx index 0ec9828102..e1f5a607fd 100644 --- a/src/view/com/lists/MyLists.tsx +++ b/src/view/com/lists/MyLists.tsx @@ -7,7 +7,6 @@ import { View, type ViewStyle, } from 'react-native' -import {type AppBskyGraphDefs as GraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -21,6 +20,7 @@ import {atoms as a, useTheme} from '#/alf' import {BulletList_Stroke1_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' import * as ListCard from '#/components/ListCard' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' import {ErrorMessage} from '../util/error/ErrorMessage' import {List} from '../util/List' @@ -38,7 +38,10 @@ export function MyLists({ filter: MyListsFilter inline?: boolean style?: StyleProp - renderItem?: (list: GraphDefs.ListView, index: number) => JSX.Element + renderItem?: ( + list: app.bsky.graph.defs.ListView, + index: number, + ) => JSX.Element testID?: string }) { const pal = usePalette('default') diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx index a10e4b47ac..e60640c9d6 100644 --- a/src/view/com/notifications/NotificationFeedItem.tsx +++ b/src/view/com/notifications/NotificationFeedItem.tsx @@ -8,18 +8,10 @@ import { TouchableOpacity, View, } from 'react-native' -import { - type AppBskyActorDefs, - type AppBskyFeedDefs, - AppBskyFeedPost, - type AppBskyGraphDefs, - AppBskyGraphFollow, - AppBskyGraphStarterpack, - AtUri, -} from '@atproto/api' import {TID} from '@atproto/common-web' -import {type DidString} from '@atproto/syntax' +import {AtUri, type DidString} from '@atproto/syntax' import { + moderateProfile, type ModerationDecision, type ModerationOpts, } from '@bsky.app/sdk/moderation' @@ -30,7 +22,6 @@ 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' @@ -76,13 +67,13 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' -import {chat} from '#/lexicons' +import {app, chat} from '#/lexicons' import * as bsky from '#/types/bsky' const MAX_AUTHORS = 5 interface Author { - profile: AppBskyActorDefs.ProfileView + profile: app.bsky.actor.defs.ProfileView href: string moderation: ModerationDecision } @@ -195,10 +186,7 @@ let NotificationFeedItem = ({ if (item.type !== 'follow') return false if ( item.notification.author.viewer?.following && - bsky.dangerousIsType( - item.notification.record, - AppBskyGraphFollow.isRecord, - ) + bsky.isType(app.bsky.graph.follow, item.notification.record) ) { let followingTimestamp try { @@ -734,7 +722,7 @@ export {NotificationFeedItem} function FollowedViaStarterPack({ starterPack, }: { - starterPack: AppBskyGraphDefs.StarterPackViewBasic + starterPack: app.bsky.graph.defs.StarterPackViewBasic }) { const t = useTheme() const link = useStarterPackLink({view: starterPack}) @@ -768,12 +756,9 @@ function FollowedViaStarterPack({ } function getStarterPackName( - starterPack: AppBskyGraphDefs.StarterPackViewBasic, + starterPack: app.bsky.graph.defs.StarterPackViewBasic, ) { - return bsky.dangerousIsType( - starterPack.record, - AppBskyGraphStarterpack.isRecord, - ) + return bsky.isType(app.bsky.graph.starterpack, starterPack.record) ? starterPack.record.name : undefined } @@ -807,7 +792,11 @@ function ExpandListPressable({ } } -function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { +function FollowBackButton({ + profile, +}: { + profile: app.bsky.actor.defs.ProfileView +}) { const {t: l} = useLingui() const {currentAccount, hasSession} = useSession() const profileShadow = useProfileShadow(profile) @@ -913,7 +902,7 @@ function FollowBackButton({profile}: {profile: AppBskyActorDefs.ProfileView}) { ) } -function SayHelloBtn({profile}: {profile: AppBskyActorDefs.ProfileView}) { +function SayHelloBtn({profile}: {profile: app.bsky.actor.defs.ProfileView}) { const {t: l} = useLingui() const client = useChatClient() const {currentAccount} = useSession() @@ -1147,15 +1136,9 @@ function ExpandedAuthorProfileCard({ ) } -function AdditionalPostText({post}: {post?: AppBskyFeedDefs.PostView}) { +function AdditionalPostText({post}: {post?: app.bsky.feed.defs.PostView}) { const t = useTheme() - if ( - post && - bsky.dangerousIsType( - post?.record, - AppBskyFeedPost.isRecord, - ) - ) { + if (post && bsky.isType(app.bsky.feed.post, post?.record)) { const text = post.record.text return ( diff --git a/src/view/com/post-thread/PostLikedBy.tsx b/src/view/com/post-thread/PostLikedBy.tsx index 48669d99ce..8f297d0428 100644 --- a/src/view/com/post-thread/PostLikedBy.tsx +++ b/src/view/com/post-thread/PostLikedBy.tsx @@ -1,5 +1,4 @@ import {useCallback, useMemo, useState} from 'react' -import {type AppBskyFeedGetLikes as GetLikes} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -11,8 +10,15 @@ import {useResolveUriQuery} from '#/state/queries/resolve-uri' import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' import {List} from '#/view/com/util/List' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {type app} from '#/lexicons' -function renderItem({item, index}: {item: GetLikes.Like; index: number}) { +function renderItem({ + item, + index, +}: { + item: app.bsky.feed.getLikes.Like + index: number +}) { return ( page.posts.map(post => { if ( - !bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) || + !bsky.isType(app.bsky.feed.post, post.record) || !moderationOpts ) { return null diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx index 4c2db9eda0..06bdda13df 100644 --- a/src/view/com/post-thread/PostRepostedBy.tsx +++ b/src/view/com/post-thread/PostRepostedBy.tsx @@ -1,5 +1,4 @@ import {useCallback, useMemo, useState} from 'react' -import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -11,12 +10,13 @@ import {useResolveUriQuery} from '#/state/queries/resolve-uri' import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard' import {List} from '#/view/com/util/List' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' +import {type app} from '#/lexicons' function renderItem({ item, index, }: { - item: ActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number }) { return ( @@ -28,7 +28,7 @@ function renderItem({ ) } -function keyExtractor(item: ActorDefs.ProfileView) { +function keyExtractor(item: app.bsky.actor.defs.ProfileView) { return item.did } diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index 98f6cfb9a0..f9369ce0d4 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -1,16 +1,14 @@ import {useCallback, useMemo, useState} from 'react' import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native' -import {type AppBskyFeedDefs, AppBskyFeedPost, AtUri} from '@atproto/api' -import {type ModerationDecision} from '@bsky.app/sdk/moderation' +import {AtUri} from '@atproto/syntax' +import {moderatePost, 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' import { POST_TOMBSTONE, type Shadow, @@ -36,6 +34,7 @@ import {TranslatedPost} from '#/components/Post/Translated' import {PostControls} from '#/components/PostControls' import {RichText} from '#/components/RichText' import {SubtleHover} from '#/components/SubtleHover' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' export function Post({ @@ -45,18 +44,16 @@ export function Post({ style, onBeforePress, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView showReplyLine?: boolean hideTopBorder?: boolean style?: StyleProp onBeforePress?: () => void }) { const moderationOpts = useModerationOpts() - const record = useMemo( + const record = useMemo( () => - bsky.validate(post.record, AppBskyFeedPost.validateRecord) - ? post.record - : undefined, + bsky.matches(app.bsky.feed.post, post.record) ? post.record : undefined, [post], ) const postShadowed = usePostShadow(post) @@ -65,7 +62,7 @@ export function Post({ record ? new RichTextAPI({ text: record.text, - facets: asSdkFacets(record.facets), + facets: record.facets, }) : undefined, [record], @@ -106,8 +103,8 @@ function PostInner({ style, onBeforePress: outerOnBeforePress, }: { - post: Shadow - record: AppBskyFeedPost.Record + post: Shadow + record: app.bsky.feed.post.Main richText: RichTextAPI moderation: ModerationDecision showReplyLine?: boolean diff --git a/src/view/com/posts/PostFeed.tsx b/src/view/com/posts/PostFeed.tsx index 2ccf3872dc..dbda8d0c41 100644 --- a/src/view/com/posts/PostFeed.tsx +++ b/src/view/com/posts/PostFeed.tsx @@ -18,14 +18,6 @@ import { View, type ViewStyle, } from 'react-native' -import { - type AppBskyActorDefs, - AppBskyEmbedExternal, - AppBskyEmbedGallery, - AppBskyEmbedImages, - AppBskyEmbedVideo, - type AppBskyFeedDefs, -} from '@atproto/api' import {type RichText as RichTextType} from '@bsky.app/sdk/richtext' import {useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' @@ -82,6 +74,8 @@ import { isStatusValidForViewers, useLiveNowConfig, } from '#/features/liveNow' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {ComposerPrompt} from '../feeds/ComposerPrompt' import {DiscoverFallbackHeader} from './DiscoverFallbackHeader' import {FeedShutdownMsg} from './FeedShutdownMsg' @@ -256,7 +250,7 @@ let PostFeed = ({ desktopFixedHeightOffset?: number ListHeaderComponent?: () => React.ReactElement extraData?: Record - savedFeedConfig?: AppBskyActorDefs.SavedFeed + savedFeedConfig?: app.bsky.actor.defs.SavedFeed initialNumToRender?: number isVideoFeed?: boolean lastFetchDate?: () => number @@ -290,7 +284,7 @@ let PostFeed = ({ () => new Set(), ) const onPressShowLess = useCallback( - (interaction: AppBskyFeedDefs.Interaction) => { + (interaction: app.bsky.feed.defs.Interaction) => { if (interaction.item) { const uri = interaction.item setHasPressedShowLessUris(prev => new Set([...prev, uri])) @@ -492,7 +486,7 @@ let PostFeed = ({ ) if ( item && - AppBskyEmbedVideo.isView(item.post.embed) && + bsky.isType(app.bsky.embed.video.view, item.post.embed) && !blockedOrMutedAuthors.includes(item.post.author.did) ) { videos.push({ @@ -1030,13 +1024,13 @@ let PostFeed = ({ // Events that should fire exactly once for every new post, regardless of // its position within a slice or video grid row. - const onPostSeen = (post: AppBskyFeedDefs.PostView) => { + const onPostSeen = (post: app.bsky.feed.defs.PostView) => { if (seenPerPostUrisRef.current.has(post.uri)) return seenPerPostUrisRef.current.add(post.uri) // Standard site embed view tracking if ( - AppBskyEmbedExternal.isView(post.embed) && + bsky.isType(app.bsky.embed.external.view, post.embed) && isStandardSiteEmbed(post.embed.external) ) { ax.metric('embed:standardSite:view', {url: post.embed.external.uri}) @@ -1044,13 +1038,21 @@ let PostFeed = ({ // Photo embed impression tracking if ( - AppBskyEmbedImages.isView(post.embed) || - AppBskyEmbedGallery.isView(post.embed) + bsky.isType(app.bsky.embed.images.view, post.embed) || + bsky.isType(app.bsky.embed.gallery.view, post.embed) ) { - const totalImages = AppBskyEmbedGallery.isView(post.embed) - ? post.embed.items.filter(AppBskyEmbedGallery.isViewImage).length + const totalImages = bsky.isType( + app.bsky.embed.gallery.view, + post.embed, + ) + ? post.embed.items.filter(item => + bsky.isType(app.bsky.embed.gallery.viewImage, item), + ).length : post.embed.images.length - const useExpandedLayout = AppBskyEmbedGallery.isView(post.embed) + const useExpandedLayout = bsky.isType( + app.bsky.embed.gallery.view, + post.embed, + ) ? totalImages > 4 : ax.features.enabled(ax.features.PostGalleryEmbedEnable) const layout = diff --git a/src/view/com/posts/PostFeedErrorMessage.tsx b/src/view/com/posts/PostFeedErrorMessage.tsx index 0298ce228b..12aea32a81 100644 --- a/src/view/com/posts/PostFeedErrorMessage.tsx +++ b/src/view/com/posts/PostFeedErrorMessage.tsx @@ -1,10 +1,6 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' -import { - type AppBskyActorDefs, - AppBskyFeedGetAuthorFeed, - AtUri, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {msg as msgLingui} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -13,11 +9,13 @@ import {useNavigation} from '@react-navigation/native' import {usePalette} from '#/lib/hooks/usePalette' import {type NavigationProp} from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' +import {getErrorName, getErrorStatus} from '#/lib/xrpc-error' import {logger} from '#/logger' import {type FeedDescriptor} from '#/state/queries/post-feed' import {useRemoveFeedMutation} from '#/state/queries/preferences' import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' import * as Prompt from '#/components/Prompt' +import {type app} from '#/lexicons' import {EmptyState} from '../util/EmptyState' import {ErrorMessage} from '../util/error/ErrorMessage' import {Button} from '../util/forms/Button' @@ -45,7 +43,7 @@ export function PostFeedErrorMessage({ feedDesc: FeedDescriptor error?: Error onPressTryAgain: () => void - savedFeedConfig?: AppBskyActorDefs.SavedFeed + savedFeedConfig?: app.bsky.actor.defs.SavedFeed }) { const {_: _l} = useLingui() const knownError = useMemo( @@ -96,7 +94,7 @@ function FeedgenErrorMessage({ feedDesc: FeedDescriptor knownError: KnownError rawError?: Error - savedFeedConfig?: AppBskyActorDefs.SavedFeed + savedFeedConfig?: app.bsky.actor.defs.SavedFeed }) { const pal = usePalette('default') const {_: _l} = useLingui() @@ -241,15 +239,21 @@ function detectKnownError( if (!error) { return undefined } + /* + * Both names are declared by `app.bsky.feed.getAuthorFeed` AND + * `.getActorLikes`, and this helper takes an error from an arbitrary feed + * descriptor, so the source method is ambiguous - hence the untyped + * `getErrorName` check rather than `matchXrpcError`. + */ if ( - error instanceof AppBskyFeedGetAuthorFeed.BlockedActorError || - error instanceof AppBskyFeedGetAuthorFeed.BlockedByActorError + getErrorName(error) === 'BlockedActor' || + getErrorName(error) === 'BlockedByActor' ) { return KnownError.Block } // check status codes - if (error?.status === 429) { + if (getErrorStatus(error) === 429) { return KnownError.FeedTooManyRequests } diff --git a/src/view/com/posts/PostFeedItem.tsx b/src/view/com/posts/PostFeedItem.tsx index aa3fd06976..cb43274741 100644 --- a/src/view/com/posts/PostFeedItem.tsx +++ b/src/view/com/posts/PostFeedItem.tsx @@ -1,12 +1,6 @@ import {memo, useCallback, useMemo, useState} from 'react' import {StyleSheet, View} from 'react-native' -import { - type AppBskyActorDefs, - AppBskyFeedDefs, - AppBskyFeedPost, - AppBskyFeedThreadgate, - AtUri, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {RichText as RichTextAPI} from '@bsky.app/sdk/richtext' import {useQueryClient} from '@tanstack/react-query' @@ -17,7 +11,6 @@ import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {usePalette} from '#/lib/hooks/usePalette' import {makeProfileLink} from '#/lib/routes/links' import {countLines} from '#/lib/strings/helpers' -import {asSdkFacets} from '#/lib/strings/rich-text-helpers' import { POST_TOMBSTONE, type Shadow, @@ -54,19 +47,20 @@ import {RichText} from '#/components/RichText' import {SubtleHover} from '#/components/SubtleHover' import {useAnalytics} from '#/analytics' import {useActorStatus} from '#/features/liveNow' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' import {PostFeedReason} from './PostFeedReason' interface FeedItemProps { - record: AppBskyFeedPost.Record + record: app.bsky.feed.post.Main reason: - | AppBskyFeedDefs.ReasonRepost - | AppBskyFeedDefs.ReasonPin + | app.bsky.feed.defs.ReasonRepost + | app.bsky.feed.defs.ReasonPin | ReasonFeedSource | {[k: string]: unknown; $type: string} | undefined moderation: ModerationDecision - parentAuthor: AppBskyActorDefs.ProfileViewBasic | undefined + parentAuthor: app.bsky.actor.defs.ProfileViewBasic | undefined showReplyTo: boolean isThreadChild?: boolean isThreadLastChild?: boolean @@ -96,16 +90,16 @@ export function PostFeedItem({ rootPost, onShowLess, }: FeedItemProps & { - post: AppBskyFeedDefs.PostView - rootPost: AppBskyFeedDefs.PostView - onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void + post: app.bsky.feed.defs.PostView + rootPost: app.bsky.feed.defs.PostView + onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void }): React.ReactNode { const postShadowed = usePostShadow(post) const richText = useMemo( () => new RichTextAPI({ text: record.text, - facets: asSdkFacets(record.facets), + facets: record.facets, }), [record], ) @@ -160,9 +154,9 @@ let FeedItemInner = ({ onShowLess, }: FeedItemProps & { richText: RichTextAPI - post: Shadow - rootPost: AppBskyFeedDefs.PostView - onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void + post: Shadow + rootPost: app.bsky.feed.defs.PostView + onShowLess?: (interaction: app.bsky.feed.defs.Interaction) => void }): React.ReactNode => { const ax = useAnalytics() const queryClient = useQueryClient() @@ -258,7 +252,14 @@ let FeedItemInner = ({ feedSourceInfo, post: { post, - reason: AppBskyFeedDefs.isReasonRepost(reason) ? reason : undefined, + /* + * `isType` requires a present `$type` at runtime but narrows to the + * schema's input type, whose `$type` is optional, so the `$Typed` arm + * of `FeedViewPost['reason']` needs the assertion back. + */ + reason: bsky.isType(app.bsky.feed.defs.reasonRepost, reason) + ? (reason as app.bsky.feed.defs.FeedViewPost['reason']) + : undefined, feedContext, reqId, }, @@ -282,9 +283,9 @@ let FeedItemInner = ({ * If `post[0]` in this slice is the actual root post (not an orphan thread), * then we may have a threadgate record to reference */ - const threadgateRecord = bsky.dangerousIsType( + const threadgateRecord = bsky.isType( + app.bsky.feed.threadgate, rootPost.threadgate?.record, - AppBskyFeedThreadgate.isRecord, ) ? rootPost.threadgate.record : undefined @@ -292,7 +293,11 @@ let FeedItemInner = ({ const {isActive: live} = useActorStatus(post.author) const viaRepost = useMemo(() => { - if (AppBskyFeedDefs.isReasonRepost(reason) && reason.uri && reason.cid) { + if ( + bsky.isType(app.bsky.feed.defs.reasonRepost, reason) && + reason.uri && + reason.cid + ) { return { uri: reason.uri, cid: reason.cid, @@ -305,10 +310,7 @@ let FeedItemInner = ({ }) const additionalPostAlerts: AppModerationCause[] = useMemo(() => { const isPostHiddenByThreadgate = threadgateHiddenReplies.has(post.uri) - const rootPostUri = bsky.dangerousIsType( - post.record, - AppBskyFeedPost.isRecord, - ) + const rootPostUri = bsky.isType(app.bsky.feed.post, post.record) ? post.record?.reply?.root?.uri || post.uri : undefined const isControlledByViewer = @@ -465,10 +467,10 @@ let PostContent = ({ }: { moderation: ModerationDecision richText: RichTextAPI - postEmbed: AppBskyFeedDefs.PostView['embed'] - postAuthor: AppBskyFeedDefs.PostView['author'] + postEmbed: app.bsky.feed.defs.PostView['embed'] + postAuthor: app.bsky.feed.defs.PostView['author'] onOpenEmbed: () => void - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView additionalPostAlerts?: AppModerationCause[] feedDescriptor?: string }): React.ReactNode => { @@ -476,11 +478,9 @@ let PostContent = ({ () => countLines(richText.text) >= MAX_POST_LINES, ) - const record = useMemo( + const record = useMemo( () => - bsky.validate(post.record, AppBskyFeedPost.validateRecord) - ? post.record - : undefined, + bsky.matches(app.bsky.feed.post, post.record) ? post.record : undefined, [post], ) diff --git a/src/view/com/posts/PostFeedReason.tsx b/src/view/com/posts/PostFeedReason.tsx index 57ac3b825c..9e1a043946 100644 --- a/src/view/com/posts/PostFeedReason.tsx +++ b/src/view/com/posts/PostFeedReason.tsx @@ -1,5 +1,4 @@ import {StyleSheet, View} from 'react-native' -import {AppBskyFeedDefs} from '@atproto/api' import {type ModerationDecision} from '@bsky.app/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -15,6 +14,8 @@ import {Repost_Stroke2_Corner3_Rounded as RepostIcon} from '#/components/icons/R import {Link} from '#/components/Link' import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {Text} from '#/components/Typography' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {FeedNameText} from '../util/FeedInfoText' export function PostFeedReason({ @@ -24,8 +25,8 @@ export function PostFeedReason({ }: { reason: | ReasonFeedSource - | AppBskyFeedDefs.ReasonRepost - | AppBskyFeedDefs.ReasonPin + | app.bsky.feed.defs.ReasonRepost + | app.bsky.feed.defs.ReasonPin | {[k: string]: unknown; $type: string} moderation?: ModerationDecision onOpenReposter?: () => void @@ -64,7 +65,7 @@ export function PostFeedReason({ ) } - if (AppBskyFeedDefs.isReasonRepost(reason)) { + if (bsky.isType(app.bsky.feed.defs.reasonRepost, reason)) { const isOwner = reason.by.did === currentAccount?.did const reposter = createSanitizedDisplayName( reason.by, @@ -103,7 +104,7 @@ export function PostFeedReason({ ) } - if (AppBskyFeedDefs.isReasonPin(reason)) { + if (bsky.isType(app.bsky.feed.defs.reasonPin, reason)) { return ( + ({item, index}: {item: app.bsky.actor.defs.ProfileView; index: number}) => renderItem({item, index, contextProfileDid: resolvedDid}), [resolvedDid], ) @@ -160,7 +160,7 @@ export function ProfileFollowers({name}: {name: string}) { seenItemsRef.current.clear() }, [resolvedDid]) const onItemSeen = useCallback( - (item: ActorDefs.ProfileView) => { + (item: app.bsky.actor.defs.ProfileView) => { if (seenItemsRef.current.has(item.did)) { return } diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx index b72161187c..86755904db 100644 --- a/src/view/com/profile/ProfileFollows.tsx +++ b/src/view/com/profile/ProfileFollows.tsx @@ -1,5 +1,4 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react' -import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -15,6 +14,7 @@ import {PeopleRemove2_Stroke1_Corner0_Rounded as PeopleRemoveIcon} from '#/compo import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {type app} from '#/lexicons' import {List} from '../util/List' import {ProfileCardWithFollowBtn} from './ProfileCard' @@ -23,7 +23,7 @@ function renderItem({ index, contextProfileDid, }: { - item: ActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number contextProfileDid: string | undefined }) { @@ -38,7 +38,7 @@ function renderItem({ ) } -function keyExtractor(item: ActorDefs.ProfileView) { +function keyExtractor(item: app.bsky.actor.defs.ProfileView) { return item.did } @@ -143,7 +143,7 @@ export function ProfileFollows({name}: {name: string}) { }, [isFetchingNextPage, hasNextPage, error, fetchNextPage]) const renderItemWithContext = useCallback( - ({item, index}: {item: ActorDefs.ProfileView; index: number}) => + ({item, index}: {item: app.bsky.actor.defs.ProfileView; index: number}) => renderItem({item, index, contextProfileDid: resolvedDid}), [resolvedDid], ) @@ -165,7 +165,7 @@ export function ProfileFollows({name}: {name: string}) { seenItemsRef.current.clear() }, [resolvedDid]) const onItemSeen = useCallback( - (item: ActorDefs.ProfileView) => { + (item: app.bsky.actor.defs.ProfileView) => { if (seenItemsRef.current.has(item.did)) { return } diff --git a/src/view/com/profile/ProfileMenu.tsx b/src/view/com/profile/ProfileMenu.tsx index 20446dd30c..11d69f2277 100644 --- a/src/view/com/profile/ProfileMenu.tsx +++ b/src/view/com/profile/ProfileMenu.tsx @@ -1,5 +1,4 @@ import {memo, useCallback, useMemo} from 'react' -import {type AppBskyActorDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' @@ -66,12 +65,13 @@ import {GoLiveDialog} from '#/features/liveNow/components/GoLiveDialog' import {GoLiveDisabledDialog} from '#/features/liveNow/components/GoLiveDisabledDialog' import {Dot} from '#/features/nuxs/components/Dot' import {Gradient} from '#/features/nuxs/components/Gradient' +import {type app} from '#/lexicons' import {useDevMode} from '#/storage/hooks/dev-mode' let ProfileMenu = ({ profile, }: { - profile: Shadow + profile: Shadow }): React.ReactNode => { const t = useTheme() const ax = useAnalytics() diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx index 24ec5aa2b3..70b4791230 100644 --- a/src/view/com/profile/ProfileSubpageHeader.tsx +++ b/src/view/com/profile/ProfileSubpageHeader.tsx @@ -1,7 +1,6 @@ import {useCallback} from 'react' import {Pressable, View} from 'react-native' import Animated, {useAnimatedRef} from 'react-native-reanimated' -import {type AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -20,6 +19,7 @@ import {UserAvatar, type UserAvatarType} from '#/view/com/util/UserAvatar' import {StarterPackMultiPathLarge as StarterPackIcon} from '#/components/icons/StarterPack' import * as Layout from '#/components/Layout' import {useLightboxControls} from '#/components/Lightbox/state' +import {type app} from '#/lexicons' export function ProfileSubpageHeader({ isLoading, @@ -37,7 +37,7 @@ export function ProfileSubpageHeader({ title: string | undefined avatar: string | undefined isOwner: boolean | undefined - purpose: AppBskyGraphDefs.ListPurpose | undefined + purpose: app.bsky.graph.defs.ListPurpose | undefined creator: | { did: string diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index f50e14b18f..2c83017c96 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -1,6 +1,5 @@ import {memo, useCallback} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -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' @@ -21,11 +20,12 @@ import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {Text} from '#/components/Typography' import {IS_ANDROID} from '#/env' import {useActorStatus} from '#/features/liveNow' +import {type app} from '#/lexicons' import {TimeElapsed} from './TimeElapsed' import {PreviewableUserAvatar} from './UserAvatar' interface PostMetaOpts { - author: AppBskyActorDefs.ProfileViewBasic + author: app.bsky.actor.defs.ProfileViewBasic moderation: ModerationDecision | undefined postHref: string timestamp: string diff --git a/src/view/com/util/UserInfoText.tsx b/src/view/com/util/UserInfoText.tsx index 028b85d38c..e70c512881 100644 --- a/src/view/com/util/UserInfoText.tsx +++ b/src/view/com/util/UserInfoText.tsx @@ -1,5 +1,4 @@ import {type StyleProp, type TextStyle} from 'react-native' -import {type AppBskyActorGetProfile} from '@atproto/api' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeDisplayName} from '#/lib/strings/display-names' @@ -9,6 +8,7 @@ import {useProfileQuery} from '#/state/queries/profile' import {atoms as a} from '#/alf' import {InlineLinkText} from '#/components/Link' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' import {LoadingPlaceholder} from './LoadingPlaceholder' export function UserInfoText({ @@ -19,7 +19,7 @@ export function UserInfoText({ style, }: { did: string - attr?: keyof AppBskyActorGetProfile.OutputSchema + attr?: keyof app.bsky.actor.getProfile.$OutputBody loading?: string failed?: string prefix?: string diff --git a/src/view/screens/DebugMod.tsx b/src/view/screens/DebugMod.tsx index ad0124eb49..c58edac3c9 100644 --- a/src/view/screens/DebugMod.tsx +++ b/src/view/screens/DebugMod.tsx @@ -1,17 +1,12 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' import {useSharedValue} from 'react-native-reanimated' -import { - type AppBskyActorDefs, - type AppBskyFeedDefs, - type AppBskyFeedPost, - type ComAtprotoLabelDefs, - mock, -} from '@atproto/api' import { interpretLabelValueDefinition, type LabelPreference, LABELS, + moderatePost, + moderateProfile, type ModerationBehavior, type ModerationDecision, type ModerationOpts, @@ -20,7 +15,6 @@ 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, @@ -54,6 +48,7 @@ import { import * as Layout from '#/components/Layout' import * as ProfileCard from '#/components/ProfileCard' import {H1, H3, P, Text} from '#/components/Typography' +import {type app, type com} from '#/lexicons' import {ScreenHider} from '../../components/moderation/ScreenHider' import {NotificationFeedItem} from '../com/notifications/NotificationFeedItem' import {PagerHeaderProvider} from '../com/pager/PagerHeaderContext' @@ -63,6 +58,211 @@ const LABEL_VALUES: (keyof typeof LABELS)[] = Object.keys( LABELS, ) as (keyof typeof LABELS)[] +const FAKE_CID = 'bafyreiclp443lavogvhj3d2ob2cxbfuscni2k5jk7bebjzg7khl3esabwq' + +/* + * Local test-data builders for this dev-only moderation debug screen. These + * replace the `mock` object the old api package used to export (the SDK does + * not ship one). Each builder returns a plain `#/lexicons` object literal with + * the same field values the old `mock` builders produced. Branded string slots + * (`did`/`at-uri`/`cid`/`lang`) are cast, since this is trusted mock data. + */ +const mock = { + post({ + text, + facets, + reply, + embed, + }: { + text: string + facets?: app.bsky.feed.post.Main['facets'] + reply?: app.bsky.feed.post.Main['reply'] + embed?: app.bsky.feed.post.Main['embed'] + }): app.bsky.feed.post.Main { + return { + $type: 'app.bsky.feed.post', + text, + facets, + reply, + embed, + langs: ['en'], + createdAt: + new Date().toISOString() as app.bsky.feed.post.Main['createdAt'], + } + }, + postView({ + record, + author, + embed, + replyCount, + repostCount, + likeCount, + viewer, + labels, + }: { + record: app.bsky.feed.post.Main + author: app.bsky.actor.defs.ProfileViewBasic + embed?: app.bsky.feed.defs.PostView['embed'] + replyCount?: number + repostCount?: number + likeCount?: number + viewer?: app.bsky.feed.defs.ViewerState + labels?: com.atproto.label.defs.Label[] + }): app.bsky.feed.defs.PostView { + return { + $type: 'app.bsky.feed.defs#postView', + uri: `at://${author.did}/app.bsky.feed.post/fake`, + cid: FAKE_CID, + author, + record, + embed, + replyCount, + repostCount, + likeCount, + indexedAt: + new Date().toISOString() as app.bsky.feed.defs.PostView['indexedAt'], + viewer, + labels, + } + }, + embedRecordView({ + record, + author, + labels, + }: { + record: app.bsky.feed.post.Main + author: app.bsky.actor.defs.ProfileViewBasic + labels?: com.atproto.label.defs.Label[] + }): app.bsky.embed.record.View { + return { + $type: 'app.bsky.embed.record#view', + record: { + $type: 'app.bsky.embed.record#viewRecord', + uri: `at://${author.did}/app.bsky.feed.post/fake`, + cid: FAKE_CID, + author, + value: record, + labels, + indexedAt: + new Date().toISOString() as app.bsky.embed.record.ViewRecord['indexedAt'], + }, + } + }, + profileViewBasic({ + handle, + displayName, + description, + viewer, + labels, + }: { + handle: string + displayName?: string + description?: string + viewer?: app.bsky.actor.defs.ViewerState + labels?: com.atproto.label.defs.Label[] + }): app.bsky.actor.defs.ProfileViewBasic & {description?: string} { + return { + did: `did:web:${handle}`, + handle: handle as app.bsky.actor.defs.ProfileViewBasic['handle'], + displayName, + description, + viewer, + labels, + } + }, + actorViewerState({ + muted, + mutedByList, + blockedBy, + blocking, + blockingByList, + following, + followedBy, + }: { + muted?: boolean + mutedByList?: app.bsky.graph.defs.ListViewBasic + blockedBy?: boolean + blocking?: string + blockingByList?: app.bsky.graph.defs.ListViewBasic + following?: string + followedBy?: string + }): app.bsky.actor.defs.ViewerState { + return { + muted, + mutedByList, + blockedBy, + blocking: blocking as app.bsky.actor.defs.ViewerState['blocking'], + blockingByList, + following: following as app.bsky.actor.defs.ViewerState['following'], + followedBy: followedBy as app.bsky.actor.defs.ViewerState['followedBy'], + } + }, + replyNotification({ + author, + record, + labels, + }: { + record: app.bsky.feed.post.Main + author: app.bsky.actor.defs.ProfileViewBasic + labels?: com.atproto.label.defs.Label[] + }): app.bsky.notification.listNotifications.Notification { + return { + uri: `at://${author.did}/app.bsky.feed.post/fake`, + cid: FAKE_CID, + author: author as app.bsky.actor.defs.ProfileView, + reason: 'reply', + reasonSubject: `at://${author.did}/app.bsky.feed.post/fake-parent`, + record, + isRead: false, + indexedAt: + new Date().toISOString() as app.bsky.notification.listNotifications.Notification['indexedAt'], + labels, + } + }, + followNotification({ + author, + subjectDid, + labels, + }: { + author: app.bsky.actor.defs.ProfileViewBasic + subjectDid: string + labels?: com.atproto.label.defs.Label[] + }): app.bsky.notification.listNotifications.Notification { + return { + uri: `at://${author.did}/app.bsky.graph.follow/fake`, + cid: FAKE_CID, + author: author as app.bsky.actor.defs.ProfileView, + reason: 'follow', + record: { + $type: 'app.bsky.graph.follow', + createdAt: new Date().toISOString(), + subject: subjectDid, + }, + isRead: false, + indexedAt: + new Date().toISOString() as app.bsky.notification.listNotifications.Notification['indexedAt'], + labels, + } + }, + label({ + val, + uri, + src, + }: { + val: string + uri: string + src?: string + }): com.atproto.label.defs.Label { + return { + src: (src || + 'did:plc:fake-labeler') as com.atproto.label.defs.Label['src'], + uri: uri as com.atproto.label.defs.Label['uri'], + val, + cts: new Date().toISOString() as com.atproto.label.defs.Label['cts'], + } + }, +} + export const DebugModScreen = ({}: NativeStackScreenProps< CommonNavigatorParams, 'DebugMod' @@ -74,7 +274,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps< const [target, setTarget] = useState(['account']) const [visibility, setVisiblity] = useState(['warn']) const [customLabelDef, setCustomLabelDef] = - useState({ + useState({ identifier: 'custom', blurs: 'content', severity: 'alert', @@ -141,7 +341,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps< blockingByList: undefined, }), }) - mockedProfile.did = did + mockedProfile.did = did as app.bsky.actor.defs.ProfileViewBasic['did'] mockedProfile.avatar = 'https://bsky.social/about/images/favicon-32x32.png' // @ts-expect-error ProfileViewBasic is close enough -esb mockedProfile.banner = @@ -165,36 +365,35 @@ export const DebugModScreen = ({}: NativeStackScreenProps< }), ] : undefined, - embed: - target[0] === 'embed' - ? mock.embedRecordView({ - record: mock.post({ - text: 'Embed', - }), - labels: - scenario[0] === 'label' && target[0] === 'embed' - ? [ - mock.label({ - src: isSelfLabel ? did : undefined, - val: label[0], - uri: `at://${did}/app.bsky.feed.post/fake`, - }), - ] - : undefined, - author: profile, - }) - : { - $type: 'app.bsky.embed.images#view', - images: [ - { - thumb: - 'https://bsky.social/about/images/social-card-default-gradient.png', - fullsize: - 'https://bsky.social/about/images/social-card-default-gradient.png', - alt: '', - }, - ], - }, + embed: (target[0] === 'embed' + ? mock.embedRecordView({ + record: mock.post({ + text: 'Embed', + }), + labels: + scenario[0] === 'label' && target[0] === 'embed' + ? [ + mock.label({ + src: isSelfLabel ? did : undefined, + val: label[0], + uri: `at://${did}/app.bsky.feed.post/fake`, + }), + ] + : undefined, + author: profile, + }) + : { + $type: 'app.bsky.embed.images#view', + images: [ + { + thumb: + 'https://bsky.social/about/images/social-card-default-gradient.png', + fullsize: + 'https://bsky.social/about/images/social-card-default-gradient.png', + alt: '', + }, + ], + }) as app.bsky.feed.defs.PostView['embed'], }) }, [scenario, label, target, profile, isSelfLabel, did]) @@ -227,7 +426,7 @@ export const DebugModScreen = ({}: NativeStackScreenProps< }) const [item] = groupNotifications([notif]) item.subject = mock.postView({ - record: notif.record as AppBskyFeedPost.Record, + record: notif.record as app.bsky.feed.post.Main, author: profile, labels: notif.labels, }) @@ -634,9 +833,9 @@ function CustomLabelForm({ def, setDef, }: { - def: ComAtprotoLabelDefs.LabelValueDefinition + def: com.atproto.label.defs.LabelValueDefinition setDef: React.Dispatch< - React.SetStateAction + React.SetStateAction > }) { const t = useTheme() @@ -838,7 +1037,7 @@ function MockPostFeedItem({ post, moderation, }: { - post: AppBskyFeedDefs.PostView + post: app.bsky.feed.defs.PostView moderation: ModerationDecision }) { const t = useTheme() @@ -852,7 +1051,7 @@ function MockPostFeedItem({ return ( @@ -91,7 +91,7 @@ type FlatlistSlice = type: 'popularFeed' key: string feedUri: string - feed: AppBskyFeedDefs.GeneratorView + feed: app.bsky.feed.defs.GeneratorView } | { type: 'popularFeedsLoadingMore' diff --git a/src/view/screens/Lists.tsx b/src/view/screens/Lists.tsx index 54f3568fc0..fe9827ac88 100644 --- a/src/view/screens/Lists.tsx +++ b/src/view/screens/Lists.tsx @@ -1,5 +1,5 @@ import {useCallback} from 'react' -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/view/screens/ModerationBlockedAccounts.tsx b/src/view/screens/ModerationBlockedAccounts.tsx index 258a3c9b5f..da3463233c 100644 --- a/src/view/screens/ModerationBlockedAccounts.tsx +++ b/src/view/screens/ModerationBlockedAccounts.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {Trans} from '@lingui/react/macro' import {type NativeStackScreenProps} from '@react-navigation/native-stack' @@ -16,6 +15,7 @@ import * as Layout from '#/components/Layout' import {ListFooter} from '#/components/Lists' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' type Props = NativeStackScreenProps< CommonNavigatorParams, @@ -68,7 +68,7 @@ export function ModerationBlockedAccounts({}: Props) { item, index, }: { - item: ActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number }) => { if (!moderationOpts) return null @@ -113,7 +113,7 @@ export function ModerationBlockedAccounts({}: Props) { ) : ( item.did} + keyExtractor={(item: app.bsky.actor.defs.ProfileView) => item.did} refreshing={isPTRing} onRefresh={onRefresh} onEndReached={onEndReached} diff --git a/src/view/screens/ModerationModlists.tsx b/src/view/screens/ModerationModlists.tsx index 4ef555a6c2..aa8adbacc6 100644 --- a/src/view/screens/ModerationModlists.tsx +++ b/src/view/screens/ModerationModlists.tsx @@ -1,5 +1,5 @@ import {useCallback} from 'react' -import {AtUri} from '@atproto/api' +import {AtUri} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/view/screens/ModerationMutedAccounts.tsx b/src/view/screens/ModerationMutedAccounts.tsx index 122464d301..cfdc4a7a80 100644 --- a/src/view/screens/ModerationMutedAccounts.tsx +++ b/src/view/screens/ModerationMutedAccounts.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' import {Trans} from '@lingui/react/macro' import {type NativeStackScreenProps} from '@react-navigation/native-stack' @@ -16,6 +15,7 @@ import * as Layout from '#/components/Layout' import {ListFooter} from '#/components/Lists' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' type Props = NativeStackScreenProps< CommonNavigatorParams, @@ -68,7 +68,7 @@ export function ModerationMutedAccounts({}: Props) { item, index, }: { - item: ActorDefs.ProfileView + item: app.bsky.actor.defs.ProfileView index: number }) => { if (!moderationOpts) return null diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 9c4db67b01..09eafda0f7 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -2,8 +2,7 @@ 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} from '@atproto/api' -import {type ModerationOpts} from '@bsky.app/sdk/moderation' +import {moderateProfile, 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' @@ -14,7 +13,6 @@ 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, @@ -52,6 +50,7 @@ import {VideoClip_Stroke1_Corner0_Rounded as VideoIcon} from '#/components/icons import * as Layout from '#/components/Layout' import {ScreenHider} from '#/components/moderation/ScreenHider' import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks' +import {type app} from '#/lexicons' import {navigate} from '#/Navigation' interface SectionRef { @@ -166,7 +165,7 @@ function ProfileScreenLoaded({ moderationOpts, hideBackButton, }: { - profile: AppBskyActorDefs.ProfileViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed moderationOpts: ModerationOpts hideBackButton: boolean isPlaceholderProfile: boolean diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index d777b09e86..a7ce796865 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -1,6 +1,5 @@ import {useCallback, useMemo, useState} from 'react' import {StyleSheet, View} from 'react-native' -import {type AppBskyActorDefs} from '@atproto/api' import {plural} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation, useNavigationState} from '@react-navigation/native' @@ -86,6 +85,7 @@ import {useAgeAssurance} from '#/ageAssurance' import {useAnalytics} from '#/analytics' import {type Events} from '#/analytics/metrics/types' import {useActorStatus} from '#/features/liveNow' +import {type app} from '#/lexicons' import {router} from '#/routes' import {PlatformInfo} from '../../../../modules/expo-bluesky-swiss-army' @@ -239,7 +239,7 @@ function SwitchMenuItems({ accounts: | { account: SessionAccount - profile?: AppBskyActorDefs.ProfileViewDetailed + profile?: app.bsky.actor.defs.ProfileViewDetailed }[] | undefined signOutPromptControl: DialogControlProps @@ -350,7 +350,7 @@ function SwitchMenuItem({ profile, }: { account: SessionAccount - profile: AppBskyActorDefs.ProfileViewDetailed | undefined + profile: app.bsky.actor.defs.ProfileViewDetailed | undefined }) { const {t: l} = useLingui() const {onPressSwitchAccount, pendingDid} = useAccountSwitcher() diff --git a/webpack.config.js b/webpack.config.js index 889a2cb83f..dace4e8fe5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -45,7 +45,7 @@ function patchSourceMapFilter(rules, pathPattern) { module.exports = async function (env, argv) { env.babel = { - dangerouslyAddModulePathsToTranspile: ['@bsky.app/expo', '@atproto/api'], + dangerouslyAddModulePathsToTranspile: ['@bsky.app/expo'], } let config = await createExpoWebpackConfigAsync(env, argv) /*