diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f07079b5c0..e63de19c73 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -22,7 +22,14 @@ jobs: fail-fast: false matrix: job: - [lint, prettier, 'typecheck:ios', 'typecheck:android', 'typecheck:web'] + [ + lint, + prettier, + 'lexicons:verify', + 'typecheck:ios', + 'typecheck:android', + 'typecheck:web', + ] steps: - name: ⬇️ Check out Git repository uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 diff --git a/.gitignore b/.gitignore index 8a38445859..a9d64c266c 100644 --- a/.gitignore +++ b/.gitignore @@ -117,6 +117,9 @@ src/locale/locales/**/messages.js src/locale/locales/**/messages.mjs src/locale/locales/**/messages.ts +# generated lexicon schemas (pnpm lexicons:generate) +src/lexicons/ + # local builds *.apk *.aab 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/.oxlintrc.json b/.oxlintrc.json index 63739f1f48..907b47af39 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -60,8 +60,10 @@ "bskyweb/**", "bskyembed/**", "bskyogcard/**", + "lint-rules/**", "src/locale/locales/_build/**", "src/locale/locales/**/*.js", + "src/lexicons/**", "*.e2e.ts", "*.e2e.tsx", "eslint.config.mjs", @@ -238,7 +240,6 @@ } } ], - "bsky-internal/use-exact-imports": "error", "bsky-internal/use-prefixed-imports": "error", "bsky-internal/lingui-msg-rule": "error", "react/display-name": "error", @@ -259,7 +260,6 @@ "react/no-unsafe": "off", "react/react-in-jsx-scope": "off", "react/hook-use-state": "warn", - "react-native/no-inline-styles": "off", "react-native-a11y/has-accessibility-hint": "error", "react-native-a11y/has-accessibility-props": "error", "react-native-a11y/has-valid-accessibility-actions": "error", @@ -274,7 +274,7 @@ "react-native-a11y/has-valid-accessibility-ignores-invert-colors": "error", "react-native-a11y/has-valid-accessibility-live-region": "error", "react-native-a11y/has-valid-important-for-accessibility": "error", - "react-compiler/react-compiler": "warn", + "react/react-compiler": "warn", "simple-import-sort/imports": [ "error", { @@ -374,9 +374,7 @@ }, "jsPlugins": [ "eslint-plugin-bsky-internal", - "eslint-plugin-react-native", "eslint-plugin-react-native-a11y", - "eslint-plugin-react-compiler", "eslint-plugin-simple-import-sort" ], "env": { @@ -404,4 +402,4 @@ } } ] -} \ No newline at end of file +} diff --git a/.prettierignore b/.prettierignore index afc71dbac1..be1658124a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -18,6 +18,7 @@ android ios src/locale/locales +src/lexicons lib/react-compiler-runtime bskyweb/static coverage diff --git a/__tests__/lib/string.test.ts b/__tests__/lib/string.test.ts index 8a56423796..cbe0d889de 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/sdk/richtext' import {i18n} from '@lingui/core' import {parseEmbedPlayerFromUrl} from '#/lib/strings/embed-player' diff --git a/babel.config.js b/babel.config.js index c29a1d2e44..90abe51c4d 100644 --- a/babel.config.js +++ b/babel.config.js @@ -35,7 +35,13 @@ module.exports = function (api) { // cannot use `env` field because it will put them after // the `react-native-worklets/plugin` plugin ...(api.env('test') - ? ['@babel/plugin-transform-class-static-block'] + ? [ + '@babel/plugin-transform-class-static-block', + // Compile `import()` to require so jest (which runs without + // `--experimental-vm-modules`) can execute lazily-loaded modules + // like `@ipld/dag-cbor` via its moduleNameMapper. + '@babel/plugin-transform-dynamic-import', + ] : []), ...(api.env('production') ? ['transform-remove-console'] : []), diff --git a/bskyogcard/src/types/bsky/starterPack.ts b/bskyogcard/src/types/bsky/starterPack.ts index 0064e16bc6..08bbe6c3b3 100644 --- a/bskyogcard/src/types/bsky/starterPack.ts +++ b/bskyogcard/src/types/bsky/starterPack.ts @@ -7,5 +7,4 @@ export const isView = AppBskyGraphDefs.isStarterPackView * Matches any starter pack view exported by our SDK */ export type AnyStarterPackView = - | AppBskyGraphDefs.StarterPackViewBasic - | AppBskyGraphDefs.StarterPackView + AppBskyGraphDefs.StarterPackViewBasic | AppBskyGraphDefs.StarterPackView diff --git a/docs/deploy-ota.md b/docs/deploy-ota.md index d582402672..52a319bf38 100644 --- a/docs/deploy-ota.md +++ b/docs/deploy-ota.md @@ -44,7 +44,7 @@ Double check yourself by ensuring that the `version` in `package.json` matches what's in the latest release. This new OTA branch should follow the format `1.X.0-ota-1`. If one or more OTAs -have already been deployed for this release, incremement the branch name e.g. +have already been deployed for this release, increment the branch name e.g. `1.x.0-ota-2`. ### 4. Add commits to the OTA branch diff --git a/docs/img/ota_action.png b/docs/img/ota_action.png index 56911400aa..0d14bb0bc8 100644 Binary files a/docs/img/ota_action.png and b/docs/img/ota_action.png differ diff --git a/eslint/use-exact-imports.js b/eslint/use-exact-imports.js deleted file mode 100644 index 6ba2df5d6c..0000000000 --- a/eslint/use-exact-imports.js +++ /dev/null @@ -1,32 +0,0 @@ -const BANNED_IMPORTS = [ - '@fortawesome/free-regular-svg-icons', - '@fortawesome/free-solid-svg-icons', -] - -/** @type {import('eslint').Rule.RuleModule} */ -module.exports = { - meta: { - type: 'suggestion', - docs: { - description: 'Prevent importing entire icon packages', - }, - schema: [], - }, - create(context) { - return { - ImportDeclaration(node) { - const source = node.source - if (typeof source.value !== 'string') { - return - } - if (BANNED_IMPORTS.includes(source.value)) { - context.report({ - node, - message: - 'Import the specific thing you want instead of the entire package', - }) - } - }, - } - }, -} diff --git a/lexicons.json b/lexicons.json new file mode 100644 index 0000000000..5fa2fdb0bb --- /dev/null +++ b/lexicons.json @@ -0,0 +1,1332 @@ +{ + "version": 1, + "lexicons": [ + "app.bsky.actor.defs", + "app.bsky.actor.getPreferences", + "app.bsky.actor.getProfile", + "app.bsky.actor.getProfiles", + "app.bsky.actor.getSuggestions", + "app.bsky.actor.profile", + "app.bsky.actor.putPreferences", + "app.bsky.actor.searchActors", + "app.bsky.actor.searchActorsTypeahead", + "app.bsky.actor.status", + "app.bsky.ageassurance.begin", + "app.bsky.ageassurance.defs", + "app.bsky.ageassurance.getConfig", + "app.bsky.ageassurance.getState", + "app.bsky.bookmark.createBookmark", + "app.bsky.bookmark.defs", + "app.bsky.bookmark.deleteBookmark", + "app.bsky.bookmark.getBookmarks", + "app.bsky.contact.defs", + "app.bsky.contact.dismissMatch", + "app.bsky.contact.getMatches", + "app.bsky.contact.getSyncStatus", + "app.bsky.contact.importContacts", + "app.bsky.contact.removeData", + "app.bsky.contact.sendNotification", + "app.bsky.contact.startPhoneVerification", + "app.bsky.contact.verifyPhone", + "app.bsky.draft.createDraft", + "app.bsky.draft.defs", + "app.bsky.draft.deleteDraft", + "app.bsky.draft.getDrafts", + "app.bsky.draft.updateDraft", + "app.bsky.embed.defs", + "app.bsky.embed.external", + "app.bsky.embed.gallery", + "app.bsky.embed.getEmbedExternalView", + "app.bsky.embed.images", + "app.bsky.embed.record", + "app.bsky.embed.recordWithMedia", + "app.bsky.embed.video", + "app.bsky.feed.defs", + "app.bsky.feed.describeFeedGenerator", + "app.bsky.feed.generator", + "app.bsky.feed.getActorFeeds", + "app.bsky.feed.getActorLikes", + "app.bsky.feed.getAuthorFeed", + "app.bsky.feed.getFeed", + "app.bsky.feed.getFeedGenerator", + "app.bsky.feed.getFeedGenerators", + "app.bsky.feed.getFeedSkeleton", + "app.bsky.feed.getLikes", + "app.bsky.feed.getListFeed", + "app.bsky.feed.getPostThread", + "app.bsky.feed.getPosts", + "app.bsky.feed.getQuotes", + "app.bsky.feed.getRepostedBy", + "app.bsky.feed.getSuggestedFeeds", + "app.bsky.feed.getTimeline", + "app.bsky.feed.like", + "app.bsky.feed.post", + "app.bsky.feed.postgate", + "app.bsky.feed.repost", + "app.bsky.feed.searchPosts", + "app.bsky.feed.searchPostsV2", + "app.bsky.feed.sendInteractions", + "app.bsky.feed.threadgate", + "app.bsky.graph.block", + "app.bsky.graph.defs", + "app.bsky.graph.follow", + "app.bsky.graph.getActorStarterPacks", + "app.bsky.graph.getBlocks", + "app.bsky.graph.getFollowers", + "app.bsky.graph.getFollows", + "app.bsky.graph.getKnownFollowers", + "app.bsky.graph.getList", + "app.bsky.graph.getListBlocks", + "app.bsky.graph.getListMutes", + "app.bsky.graph.getLists", + "app.bsky.graph.getListsWithMembership", + "app.bsky.graph.getMutes", + "app.bsky.graph.getRelationships", + "app.bsky.graph.getStarterPack", + "app.bsky.graph.getStarterPacks", + "app.bsky.graph.getStarterPacksWithMembership", + "app.bsky.graph.getSuggestedFollowsByActor", + "app.bsky.graph.list", + "app.bsky.graph.listblock", + "app.bsky.graph.listitem", + "app.bsky.graph.muteActor", + "app.bsky.graph.muteActorList", + "app.bsky.graph.muteThread", + "app.bsky.graph.searchStarterPacks", + "app.bsky.graph.searchStarterPacksV2", + "app.bsky.graph.starterpack", + "app.bsky.graph.unmuteActor", + "app.bsky.graph.unmuteActorList", + "app.bsky.graph.unmuteThread", + "app.bsky.graph.verification", + "app.bsky.labeler.defs", + "app.bsky.labeler.getServices", + "app.bsky.labeler.service", + "app.bsky.notification.declaration", + "app.bsky.notification.defs", + "app.bsky.notification.getPreferences", + "app.bsky.notification.getUnreadCount", + "app.bsky.notification.listActivitySubscriptions", + "app.bsky.notification.listNotifications", + "app.bsky.notification.putActivitySubscription", + "app.bsky.notification.putPreferences", + "app.bsky.notification.putPreferencesV2", + "app.bsky.notification.registerPush", + "app.bsky.notification.unregisterPush", + "app.bsky.notification.updateSeen", + "app.bsky.richtext.facet", + "app.bsky.video.defs", + "app.bsky.video.getJobStatus", + "app.bsky.video.getUploadLimits", + "app.bsky.video.uploadVideo", + "chat.bsky.actor.declaration", + "chat.bsky.actor.defs", + "chat.bsky.actor.deleteAccount", + "chat.bsky.actor.exportAccountData", + "chat.bsky.actor.getStatus", + "chat.bsky.convo.acceptConvo", + "chat.bsky.convo.addReaction", + "chat.bsky.convo.defs", + "chat.bsky.convo.deleteMessageForSelf", + "chat.bsky.convo.getConvo", + "chat.bsky.convo.getConvoAvailability", + "chat.bsky.convo.getConvoForMembers", + "chat.bsky.convo.getConvoMembers", + "chat.bsky.convo.getLog", + "chat.bsky.convo.getMessages", + "chat.bsky.convo.getUnreadCounts", + "chat.bsky.convo.leaveConvo", + "chat.bsky.convo.listConvoRequests", + "chat.bsky.convo.listConvos", + "chat.bsky.convo.lockConvo", + "chat.bsky.convo.muteConvo", + "chat.bsky.convo.removeReaction", + "chat.bsky.convo.sendMessage", + "chat.bsky.convo.sendMessageBatch", + "chat.bsky.convo.unlockConvo", + "chat.bsky.convo.unmuteConvo", + "chat.bsky.convo.updateAllRead", + "chat.bsky.convo.updateRead", + "chat.bsky.embed.joinLink", + "chat.bsky.group.addMembers", + "chat.bsky.group.approveJoinRequest", + "chat.bsky.group.createGroup", + "chat.bsky.group.createJoinLink", + "chat.bsky.group.defs", + "chat.bsky.group.disableJoinLink", + "chat.bsky.group.editGroup", + "chat.bsky.group.editJoinLink", + "chat.bsky.group.enableJoinLink", + "chat.bsky.group.getJoinLinkPreviews", + "chat.bsky.group.listJoinRequests", + "chat.bsky.group.listMutualGroups", + "chat.bsky.group.rejectJoinRequest", + "chat.bsky.group.removeMembers", + "chat.bsky.group.requestJoin", + "chat.bsky.group.updateJoinRequestsRead", + "chat.bsky.group.withdrawJoinRequest", + "chat.bsky.moderation.defs", + "chat.bsky.moderation.getActorMetadata", + "chat.bsky.moderation.getConvo", + "chat.bsky.moderation.getConvoMembers", + "chat.bsky.moderation.getConvos", + "chat.bsky.moderation.getMessageContext", + "chat.bsky.moderation.subscribeModEvents", + "chat.bsky.moderation.updateActorAccess", + "chat.bsky.notification.defs", + "chat.bsky.notification.getPreferences", + "chat.bsky.notification.putPreferences", + "com.atproto.admin.defs", + "com.atproto.admin.deleteAccount", + "com.atproto.admin.disableAccountInvites", + "com.atproto.admin.disableInviteCodes", + "com.atproto.admin.enableAccountInvites", + "com.atproto.admin.getAccountInfo", + "com.atproto.admin.getAccountInfos", + "com.atproto.admin.getInviteCodes", + "com.atproto.admin.getSubjectStatus", + "com.atproto.admin.searchAccounts", + "com.atproto.admin.sendEmail", + "com.atproto.admin.updateAccountEmail", + "com.atproto.admin.updateAccountHandle", + "com.atproto.admin.updateAccountPassword", + "com.atproto.admin.updateAccountSigningKey", + "com.atproto.admin.updateSubjectStatus", + "com.atproto.identity.defs", + "com.atproto.identity.getRecommendedDidCredentials", + "com.atproto.identity.refreshIdentity", + "com.atproto.identity.requestPlcOperationSignature", + "com.atproto.identity.resolveDid", + "com.atproto.identity.resolveHandle", + "com.atproto.identity.resolveIdentity", + "com.atproto.identity.signPlcOperation", + "com.atproto.identity.submitPlcOperation", + "com.atproto.identity.updateHandle", + "com.atproto.label.defs", + "com.atproto.label.queryLabels", + "com.atproto.label.subscribeLabels", + "com.atproto.lexicon.resolveLexicon", + "com.atproto.lexicon.schema", + "com.atproto.moderation.createReport", + "com.atproto.moderation.defs", + "com.atproto.repo.applyWrites", + "com.atproto.repo.createRecord", + "com.atproto.repo.defs", + "com.atproto.repo.deleteRecord", + "com.atproto.repo.describeRepo", + "com.atproto.repo.getRecord", + "com.atproto.repo.importRepo", + "com.atproto.repo.listMissingBlobs", + "com.atproto.repo.listRecords", + "com.atproto.repo.putRecord", + "com.atproto.repo.strongRef", + "com.atproto.repo.uploadBlob", + "com.atproto.server.activateAccount", + "com.atproto.server.checkAccountStatus", + "com.atproto.server.confirmEmail", + "com.atproto.server.createAccount", + "com.atproto.server.createAppPassword", + "com.atproto.server.createInviteCode", + "com.atproto.server.createInviteCodes", + "com.atproto.server.createSession", + "com.atproto.server.deactivateAccount", + "com.atproto.server.defs", + "com.atproto.server.deleteAccount", + "com.atproto.server.deleteSession", + "com.atproto.server.describeServer", + "com.atproto.server.getAccountInviteCodes", + "com.atproto.server.getServiceAuth", + "com.atproto.server.getSession", + "com.atproto.server.listAppPasswords", + "com.atproto.server.refreshSession", + "com.atproto.server.requestAccountDelete", + "com.atproto.server.requestEmailConfirmation", + "com.atproto.server.requestEmailUpdate", + "com.atproto.server.requestPasswordReset", + "com.atproto.server.reserveSigningKey", + "com.atproto.server.resetPassword", + "com.atproto.server.revokeAppPassword", + "com.atproto.server.updateEmail", + "com.atproto.sync.defs", + "com.atproto.sync.getBlob", + "com.atproto.sync.getBlocks", + "com.atproto.sync.getCheckout", + "com.atproto.sync.getHead", + "com.atproto.sync.getHostStatus", + "com.atproto.sync.getLatestCommit", + "com.atproto.sync.getRecord", + "com.atproto.sync.getRepo", + "com.atproto.sync.getRepoStatus", + "com.atproto.sync.listBlobs", + "com.atproto.sync.listHosts", + "com.atproto.sync.listRepos", + "com.atproto.sync.listReposByCollection", + "com.atproto.sync.notifyOfUpdate", + "com.atproto.sync.requestCrawl", + "com.atproto.sync.subscribeRepos", + "com.germnetwork.declaration", + "tools.ozone.report.defs" + ], + "resolutions": { + "app.bsky.actor.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.defs", + "cid": "bafyreiaxduxpwdpjgvve3klfs4flkwjwfqiurszw4o6jvjarpqqmeqwiza" + }, + "app.bsky.actor.getPreferences": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.getPreferences", + "cid": "bafyreiftuoh6n5npkyelejxbzettltmgl46xm3qot5nkn7lm365ej2ahii" + }, + "app.bsky.actor.getProfile": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.getProfile", + "cid": "bafyreigrtosreva7e5m7bwbbfsmw77gkdnieizgxwpobw5iobuck3j54xa" + }, + "app.bsky.actor.getProfiles": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.getProfiles", + "cid": "bafyreihphrndnt2cvnv3nk442f6kohdnpagk4xip74f2jn36uoal25vhta" + }, + "app.bsky.actor.getSuggestions": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.getSuggestions", + "cid": "bafyreig7dhef4df3n72bj6pvklzija26yk5szxdzlixm5x72bingowmf3m" + }, + "app.bsky.actor.profile": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.profile", + "cid": "bafyreia6umzg3a6d7mjbow4p57tviey45muohklhgsvjoamcctoiusr4pe" + }, + "app.bsky.actor.putPreferences": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.putPreferences", + "cid": "bafyreietfhhwbwb3ltcffsonhw5p62z233frhe6edls67su5a3evqrykju" + }, + "app.bsky.actor.searchActors": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.searchActors", + "cid": "bafyreidfkzp3tcbxvqo3aty2prhnaiqoxxbjhrapaqmko2gtnymtgxyqdi" + }, + "app.bsky.actor.searchActorsTypeahead": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.searchActorsTypeahead", + "cid": "bafyreigydtxvpnrndvk7iws4uypd6adqzohcdafeuqajiqmmbcqq5vatmu" + }, + "app.bsky.actor.status": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.actor.status", + "cid": "bafyreifdg4b64wohpwkh5lydc6tckvol2rspnpni6dec6recy2rhvlnz4a" + }, + "app.bsky.ageassurance.begin": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.ageassurance.begin", + "cid": "bafyreieqogxqzohb5fdtw5jiqckscgipkbrtvluoodscm45ajw2ba26zpm" + }, + "app.bsky.ageassurance.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.ageassurance.defs", + "cid": "bafyreig2g6pata4a2yjc7aiqoyybi3jzu76trduxlswte2gzhqdx252sie" + }, + "app.bsky.ageassurance.getConfig": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.ageassurance.getConfig", + "cid": "bafyreifpfw7n3y6vqhiyqaqiyrmxm3lypkamyfk7dk6jzho7ujjujou6xq" + }, + "app.bsky.ageassurance.getState": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.ageassurance.getState", + "cid": "bafyreialq63q3obxzsrzbwx25stjmtha3frlifzbgkvtthr535oal3467y" + }, + "app.bsky.bookmark.createBookmark": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.bookmark.createBookmark", + "cid": "bafyreiegg2yu2jl5kfswirsylpn56ro2pj7lkfsufvrnv7xik7xdw27kfi" + }, + "app.bsky.bookmark.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.bookmark.defs", + "cid": "bafyreifi3xao6wlbsnrzt36xnrfcvl5pil7jw7owrgqpmppfzy52dpy7e4" + }, + "app.bsky.bookmark.deleteBookmark": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.bookmark.deleteBookmark", + "cid": "bafyreig6rcgoqayi4fwgx5k5va3cnfsjykvc3fes7rmcfpszdd745hvatu" + }, + "app.bsky.bookmark.getBookmarks": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.bookmark.getBookmarks", + "cid": "bafyreicnazdriby4i3uu3nelt5ur4o3575ivjuw5pxlfj46tvfmglpny2e" + }, + "app.bsky.contact.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.contact.defs", + "cid": "bafyreidsqrf3mf6g3zphne4t2swm6hbgd3ojk2nzlmwj6ezsvq62o3abgu" + }, + "app.bsky.contact.dismissMatch": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.contact.dismissMatch", + "cid": "bafyreihtnfxggnq2n6ijlvrmtmpspwz3pzceprcfzqpa6iwjftpoknkory" + }, + "app.bsky.contact.getMatches": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.contact.getMatches", + "cid": "bafyreihk7c54sgsxyh3bhtwd3cf5kdzjh4lauwwpbogau3zm65acfc3b2u" + }, + "app.bsky.contact.getSyncStatus": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.contact.getSyncStatus", + "cid": "bafyreicozb7ibbr4h6xeh23jdjpfs6thxvodudypurdwqajwdnnj7lrioi" + }, + "app.bsky.contact.importContacts": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.contact.importContacts", + "cid": "bafyreihzcos4jxddkpsk3isoxpgmw2x4rqbvp4b3f475cxwnfdzngnl7by" + }, + "app.bsky.contact.removeData": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.contact.removeData", + "cid": "bafyreidtbulghr5cv27rzcato5esrgigddaafde2kgkz6hnivaimclynla" + }, + "app.bsky.contact.sendNotification": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.contact.sendNotification", + "cid": "bafyreietl2pnpkuxeip5ftk7xvvrkuzdu4ndsn2nldt4wf2vbteyj5v6ju" + }, + "app.bsky.contact.startPhoneVerification": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.contact.startPhoneVerification", + "cid": "bafyreif6jrd6gkonvwlvfkv2wyx5tnk4ayuv2xihcao7kyzdms42s34bhe" + }, + "app.bsky.contact.verifyPhone": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.contact.verifyPhone", + "cid": "bafyreiapgsesqmksilfc6nxndzglbxquxtmk3gwiikie44rangg2drplti" + }, + "app.bsky.draft.createDraft": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.draft.createDraft", + "cid": "bafyreid43zfsfg4wk4p3mf3gihwhpqh4d5rmpw7l5n3zks3kl53bsrsaba" + }, + "app.bsky.draft.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.draft.defs", + "cid": "bafyreiewy3wgajj4al6xifjc2e5gpg4yyj4laf2gl7pe3liekulfzxnlcu" + }, + "app.bsky.draft.deleteDraft": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.draft.deleteDraft", + "cid": "bafyreicxw6nrrrikakfdaue3x45vfp4w62mncscdkpivpeom2orx2ae3su" + }, + "app.bsky.draft.getDrafts": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.draft.getDrafts", + "cid": "bafyreiewac2qwebwwtm4mtvk3aobtsyhbqug3hplhftjlbbn6bvuuxyumy" + }, + "app.bsky.draft.updateDraft": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.draft.updateDraft", + "cid": "bafyreicjy2aatoptybrtogj3nckokmkjpbmelvkej5bkfvxfcs5enu2fhu" + }, + "app.bsky.embed.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.embed.defs", + "cid": "bafyreia42uud4qil67wknywzbxfyxc3b7woewsii54cakq2ould3ldetei" + }, + "app.bsky.embed.external": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.embed.external", + "cid": "bafyreigwach36azu4xg3jvcmb6fe53umfln3wkuj5srqsvcjaypa6rw534" + }, + "app.bsky.embed.gallery": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.embed.gallery", + "cid": "bafyreia4tixag4sadkirfo63qfma6fz3ifbqanify4e6ce6swkl5lwjnfq" + }, + "app.bsky.embed.getEmbedExternalView": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.embed.getEmbedExternalView", + "cid": "bafyreicnv2umobvrfrlo6tqw7umcsxv77owcwit3vld7l46fphpy5uuzky" + }, + "app.bsky.embed.images": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.embed.images", + "cid": "bafyreifw77mkqkzlvm5csjwutkrnaivsjvlhg2qxdlwwabqpahlyyjrpwi" + }, + "app.bsky.embed.record": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.embed.record", + "cid": "bafyreia7yq4c6rqkz32fjf6gj5rcvxsirguducn545s3dvls7qutnn2ali" + }, + "app.bsky.embed.recordWithMedia": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.embed.recordWithMedia", + "cid": "bafyreidszvpeqf7copdmowm4ln4eyhahlqxktlvootjjjdmtkwy6s62jya" + }, + "app.bsky.embed.video": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.embed.video", + "cid": "bafyreie3nug4ezpwodl6yrpgv5edkazzn22t7ea4yaeuun4rctyekkngai" + }, + "app.bsky.feed.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.defs", + "cid": "bafyreibg2zvz4n25vtd5yfqlodqra72s4h4lz7vwdexoutv3523wbw6jca" + }, + "app.bsky.feed.describeFeedGenerator": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.describeFeedGenerator", + "cid": "bafyreigzxk3um5hbi7gbuh5bhmob75bxlfh5bonfsl7h5dmt7woxdhxq54" + }, + "app.bsky.feed.generator": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.generator", + "cid": "bafyreig2rppxkltsbe3523gavulrgprwjkyyb7mvmor22fdkoas6hrc2ju" + }, + "app.bsky.feed.getActorFeeds": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getActorFeeds", + "cid": "bafyreihqvune35emnr3vby6ldv2tzy57cfcgst2qy5icwpeay4mdvughfq" + }, + "app.bsky.feed.getActorLikes": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getActorLikes", + "cid": "bafyreiaxgdm2fv6bjzh2hdixgc3teprs7z46ja273kzevjnsp3hi224xoy" + }, + "app.bsky.feed.getAuthorFeed": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getAuthorFeed", + "cid": "bafyreiha5ca2lkguxvh22smjuehn2evxhyqk7isuiyqy6b23raljriv5a4" + }, + "app.bsky.feed.getFeed": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getFeed", + "cid": "bafyreihnhxqdfxojwo4xnedj55kskjki3opd2oiz3pfa5nm2ijr5janlq4" + }, + "app.bsky.feed.getFeedGenerator": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getFeedGenerator", + "cid": "bafyreibdzaan7vboli3ywi7wqzkibc2puukhfpg5rcdgizxlr7fbxx5gcy" + }, + "app.bsky.feed.getFeedGenerators": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getFeedGenerators", + "cid": "bafyreiervdq422lralzyssoewrfstcmqxsqimtmdskkqeflllszjnq32em" + }, + "app.bsky.feed.getFeedSkeleton": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getFeedSkeleton", + "cid": "bafyreidmn4nltqygzpd3vzobfkrkxcypbfirremfhzuluhlavwp5ht637m" + }, + "app.bsky.feed.getLikes": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getLikes", + "cid": "bafyreib6ylalknwxizbtbwsdmja4rhjswwczut5ivjdavfgew27m7knqou" + }, + "app.bsky.feed.getListFeed": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getListFeed", + "cid": "bafyreifvox2w2c6ybgioi6ofgp2i6pz44guwvo76w26miv4hpy7fgiwvlq" + }, + "app.bsky.feed.getPostThread": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getPostThread", + "cid": "bafyreif37upfzfu7t4rv3pqzz3swu2oc4nrxdxfr3xqjsrrmgyckneitta" + }, + "app.bsky.feed.getPosts": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getPosts", + "cid": "bafyreihy4tanaizkynghab46n3qztsspvoaqtbrkzinvxi4igeylg5ghji" + }, + "app.bsky.feed.getQuotes": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getQuotes", + "cid": "bafyreiftjmrnfpwtri5v47e7yfdgjgpfqds6kx3duf3t2x7vn7qdtbttfy" + }, + "app.bsky.feed.getRepostedBy": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getRepostedBy", + "cid": "bafyreif4v4aucwv4xvwnub6w2npmioqpa6nn4t5dsbqophoyfnimhsuweq" + }, + "app.bsky.feed.getSuggestedFeeds": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getSuggestedFeeds", + "cid": "bafyreif4v4aldmcoc2nkyanu6t3ks4jazqsiffq5gp54tuyn5o6rk2ff5e" + }, + "app.bsky.feed.getTimeline": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.getTimeline", + "cid": "bafyreihumvisufuf6t7ibkyhg4psri37pj535mhmyr4t6mn7ogvgcxpul4" + }, + "app.bsky.feed.like": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.like", + "cid": "bafyreiasvjidnd3sazbx46rzgipfkpejq4mgfuzwv2xrdaesp64t4dl2ry" + }, + "app.bsky.feed.post": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.post", + "cid": "bafyreia7rgxu7467phwpcgzaw62l5bzmxufkpikrc5zm4q3xhjmuhq4pti" + }, + "app.bsky.feed.postgate": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.postgate", + "cid": "bafyreiai5efexyluyptv5tbl6kqbqlnneczqzexcqnxmitmulyjfaftgva" + }, + "app.bsky.feed.repost": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.repost", + "cid": "bafyreihks43jtwanviarzctwx2vvcyloivuxo2kdi7ex24gog5dfn4s5au" + }, + "app.bsky.feed.searchPosts": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.searchPosts", + "cid": "bafyreif3xl6jj27tktxmxr3gtmf74wuxj6y6xnoycs7cclvifh4ncsi6mi" + }, + "app.bsky.feed.searchPostsV2": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.searchPostsV2", + "cid": "bafyreiemtjvwfo6xuab6vkwbji46d7wms5xdzad6vygjymmcqr326quwqu" + }, + "app.bsky.feed.sendInteractions": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.sendInteractions", + "cid": "bafyreicaoiskxpn2lmbzxuvpzawbsqxoewko3drbsxms222obabj2dkque" + }, + "app.bsky.feed.threadgate": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.feed.threadgate", + "cid": "bafyreiht77wd6duduz4yqp62m6dwma5dy7gdihps4g2nd73acfzqlglvdi" + }, + "app.bsky.graph.block": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.block", + "cid": "bafyreifalnveckyi2375ek5b5uyg4ef23mktcdktmgiwwsb3rygdls7owm" + }, + "app.bsky.graph.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.defs", + "cid": "bafyreifcipomli7yggtl46xufgxlnrw7se6xmsdxmzgfcz2tiu76ljatxm" + }, + "app.bsky.graph.follow": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.follow", + "cid": "bafyreiav7u7jchqtsxr2yy3gsazfqqziw5tcrfniuprrmoariruj3cft5u" + }, + "app.bsky.graph.getActorStarterPacks": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getActorStarterPacks", + "cid": "bafyreihm57pc7dmeig4lxcv3x53z53y27obkkxnopimax7dbtqx4enqf7i" + }, + "app.bsky.graph.getBlocks": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getBlocks", + "cid": "bafyreic3yp4tuxthww5sidvv27gmg3ha3argl3moconhmcccwvooobggve" + }, + "app.bsky.graph.getFollowers": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getFollowers", + "cid": "bafyreiaboxa4aux2y5jxut5kw4iqlnctlswklofvgkhxl2wn7rkbjjrzru" + }, + "app.bsky.graph.getFollows": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getFollows", + "cid": "bafyreicun6mwmjgrdmrtmnfqv3ws37g5okcmo3uiaf2qwopk3bhtj5rqae" + }, + "app.bsky.graph.getKnownFollowers": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getKnownFollowers", + "cid": "bafyreia3bjqeaj5uk2zlkhk5pipefehy2mlrdp3hoqtgu3np3tvbg6xjuq" + }, + "app.bsky.graph.getList": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getList", + "cid": "bafyreigplofixg524nkirazfh3loe36lvqqeg6jfqvrggzztspkefwm4re" + }, + "app.bsky.graph.getListBlocks": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getListBlocks", + "cid": "bafyreiagod73i7ecuzpyxwlibrwimo7u4ew75nzc2b3ajdocc5sg6eszpy" + }, + "app.bsky.graph.getListMutes": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getListMutes", + "cid": "bafyreictd7aefsbizbn66bmhfbyvxbirbuz4uiqcp4o5tb3eiyun42wm4a" + }, + "app.bsky.graph.getLists": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getLists", + "cid": "bafyreiffrqdk7vdses2qzrmmfotwyvncuhvq7anaw64x7wmlz5cix5nihy" + }, + "app.bsky.graph.getListsWithMembership": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getListsWithMembership", + "cid": "bafyreifgvypq6ahfxtkbdmudh7bb7vib6c7kvkit6ks3gjoagor747cxfy" + }, + "app.bsky.graph.getMutes": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getMutes", + "cid": "bafyreibnvq62dmchuulpzvvl6w2x6z4mofnqdrfbsvc5ynoo6cazr7wove" + }, + "app.bsky.graph.getRelationships": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getRelationships", + "cid": "bafyreiabszfuqanet2tbyyx7plxmknfegygnfyaydhwyialt4cbo24k5dq" + }, + "app.bsky.graph.getStarterPack": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getStarterPack", + "cid": "bafyreia77gh56vqe3ws26ntm7pffj43uhvl6l66vpb7uhxrhpih6rvne6m" + }, + "app.bsky.graph.getStarterPacks": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getStarterPacks", + "cid": "bafyreighxbnsgugwwjnu37nzoca7qtpipvf2l5ijz2z74ghdfrgppqfoaa" + }, + "app.bsky.graph.getStarterPacksWithMembership": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getStarterPacksWithMembership", + "cid": "bafyreigpeb22avssmmfqiveh3hem6qnxrduyyhkxc3ixpfgehmhkskgssy" + }, + "app.bsky.graph.getSuggestedFollowsByActor": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.getSuggestedFollowsByActor", + "cid": "bafyreih4zuul6xlijankc73dx7bax3gbeva2dlfnzabk335y7iaptxbmr4" + }, + "app.bsky.graph.list": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.list", + "cid": "bafyreihqckm5nkbamel2pljhk5lbjfqfmczmb7a5eglpic7ontdx3r4sey" + }, + "app.bsky.graph.listblock": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.listblock", + "cid": "bafyreieomtmr6mpvgnbbgwfkdqlyatwymvu4vdnhldpqetzczlj4eywxqq" + }, + "app.bsky.graph.listitem": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.listitem", + "cid": "bafyreiegh2vyadmieea52lnt574lfcddprhpuz5moqhqiij56egfvyf62m" + }, + "app.bsky.graph.muteActor": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.muteActor", + "cid": "bafyreiebgeqbzitzytuvuu23nxgiyxjdh3iawcxonsiu6t2aubqd6ugt6m" + }, + "app.bsky.graph.muteActorList": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.muteActorList", + "cid": "bafyreihadi2x64bpzncb2xfo5ww4mvajftf4qd3nxmsjud7assloxzfm44" + }, + "app.bsky.graph.muteThread": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.muteThread", + "cid": "bafyreib6ppci3qzye6wktkm4byxtb5mnl2vg22fm7oawcdvof2tfogx4dy" + }, + "app.bsky.graph.searchStarterPacks": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.searchStarterPacks", + "cid": "bafyreia446ip6mbnwpml6hlvxab7jtsud7zczde3u6zmnsa3op4bpxu7um" + }, + "app.bsky.graph.searchStarterPacksV2": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.searchStarterPacksV2", + "cid": "bafyreiftevu66nkth5uprlwhoxtxloz4aqym25hu3wj2bal2vcuupa6uky" + }, + "app.bsky.graph.starterpack": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.starterpack", + "cid": "bafyreiejgf46gibbkpmjtgt5yyiir3wyxgrtvnfxtcdeyxrdrdnsvwgjc4" + }, + "app.bsky.graph.unmuteActor": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.unmuteActor", + "cid": "bafyreihqc6o54652e56jmvzpaf7ix4liws4bmnd2bklc2qkuqjozfcvka4" + }, + "app.bsky.graph.unmuteActorList": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.unmuteActorList", + "cid": "bafyreiamqj5gh6x36dzdk6ve7kfjvzotqjfcg2nplgch7woshqrubbe2ee" + }, + "app.bsky.graph.unmuteThread": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.unmuteThread", + "cid": "bafyreiahpkqj4v7b3ujk45ufdcvynu2dxjtunzi2oipu7kzdupybmysgzu" + }, + "app.bsky.graph.verification": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.graph.verification", + "cid": "bafyreidn7abfzt5p4gcujys2w4seigbhcfnscos6fmh4hnce2gdormbwpa" + }, + "app.bsky.labeler.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.labeler.defs", + "cid": "bafyreicxx5i36v5dbqk5vvfzhnta5gajrvc544mnepux4wksrkid7mw3q4" + }, + "app.bsky.labeler.getServices": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.labeler.getServices", + "cid": "bafyreife7p4pjij5jvmngdlxfq4cjzqe2cx7himnkf7zgxsykcxg6klpl4" + }, + "app.bsky.labeler.service": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.labeler.service", + "cid": "bafyreihr336i3ys6rc4ezhk2wwpeyufnp2ma2n6dxljd2ogksi4sjo6aqe" + }, + "app.bsky.notification.declaration": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.notification.declaration", + "cid": "bafyreibjnupqb7xke5eeeehpcm2gecacduwd4ebifcovgigzdpo42jq3kq" + }, + "app.bsky.notification.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.notification.defs", + "cid": "bafyreifzktxnol5rafjoznauk5j7ub2gsxxm7qjskg3vqmcgn6krrsz55u" + }, + "app.bsky.notification.getPreferences": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.notification.getPreferences", + "cid": "bafyreibaz7cyl7ytpextx7bv6oaguadj5bdvpf4hksw6t737zkfw5y3rue" + }, + "app.bsky.notification.getUnreadCount": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.notification.getUnreadCount", + "cid": "bafyreih2vxe7vy3hcp7fwka7cwenxbkx2d7nd2s3whffh23dn3wxt65s7m" + }, + "app.bsky.notification.listActivitySubscriptions": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.notification.listActivitySubscriptions", + "cid": "bafyreien2hslzbpoypvz3shv6tphkmdtccejieg5lricwl6stdjomiu3zi" + }, + "app.bsky.notification.listNotifications": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.notification.listNotifications", + "cid": "bafyreigqqfk3gvslu36fvmx3fc3yi4ibmsmbfvty55rq4vw7cvfhecpxge" + }, + "app.bsky.notification.putActivitySubscription": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.notification.putActivitySubscription", + "cid": "bafyreifvbgf53yncsrjpzwa5xwnlax7vyesflkg6fmxb2367vfa6fv7kwu" + }, + "app.bsky.notification.putPreferences": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.notification.putPreferences", + "cid": "bafyreihbqjdbhra45czd5m5665btugrie7bwsiwzehutjdwygyy5dsq4fi" + }, + "app.bsky.notification.putPreferencesV2": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.notification.putPreferencesV2", + "cid": "bafyreiefun5klfddbnd7gfodnlcbn7ing45qo6lbmsahau7qgw4zlgv42i" + }, + "app.bsky.notification.registerPush": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.notification.registerPush", + "cid": "bafyreiayl6ngk3ckmaorocgbwxf2ruqgficcbkm2xvoja2crnyul66u3hy" + }, + "app.bsky.notification.unregisterPush": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.notification.unregisterPush", + "cid": "bafyreiboilq7jye3s7isayixm6iqjbsxrfzizaj727obtdda6qzh56at6y" + }, + "app.bsky.notification.updateSeen": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.notification.updateSeen", + "cid": "bafyreigzds576ji7wzi6alzn4wegtfzk2iqagwtugbw3byb2v3xvqho4hi" + }, + "app.bsky.richtext.facet": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.richtext.facet", + "cid": "bafyreidg56eo7zynf6ihz4xb627vwoqf5idnevkmwp7sxc4tijg6xngbu4" + }, + "app.bsky.video.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.video.defs", + "cid": "bafyreicorzkcw5zp4zjsiepyakpfn3vvtpwsbqwzsy43d2y6i3t5rp3meu" + }, + "app.bsky.video.getJobStatus": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.video.getJobStatus", + "cid": "bafyreia5xkrbastzzbpodjm242orufiswrz5aasjqrqtuvb6lmmfwixmqy" + }, + "app.bsky.video.getUploadLimits": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.video.getUploadLimits", + "cid": "bafyreidlw3fozroigsbrz73jtqdoc7dkdpxi4oie3df7nb3vnycy2kkz2y" + }, + "app.bsky.video.uploadVideo": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/app.bsky.video.uploadVideo", + "cid": "bafyreibvsdivxr54ooykqhsy3i6n6f7223hbfnnnzs4h6xrzr5kvvztuvy" + }, + "chat.bsky.actor.declaration": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.actor.declaration", + "cid": "bafyreih4ivey4vqreb2ebujlt7iranqyizxkv5kpm3ksdlgkinkgcu53pu" + }, + "chat.bsky.actor.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.actor.defs", + "cid": "bafyreigiv5fo5iufd3ahma6tgtozm35xibvx276nhjsjwuvsvxh3zj7u7q" + }, + "chat.bsky.actor.deleteAccount": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.actor.deleteAccount", + "cid": "bafyreierykzslwmkkugpyio3yiaxguttd5his5i4ow7re7wzcbxfogerrq" + }, + "chat.bsky.actor.exportAccountData": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.actor.exportAccountData", + "cid": "bafyreigwllvtmdetzfvgrzwhtzjilnj3gfxaj45yd32oe436etzp7yghai" + }, + "chat.bsky.actor.getStatus": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.actor.getStatus", + "cid": "bafyreib7f6wo5xsnfz37ywggtvqbcuxigf325q6pezshyhzyp2n5pdlzhu" + }, + "chat.bsky.convo.acceptConvo": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.acceptConvo", + "cid": "bafyreih2ob7rsibl3h3so356rcdo2fop3dosxkna75w2u45qflbtw2r7qm" + }, + "chat.bsky.convo.addReaction": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.addReaction", + "cid": "bafyreia2odzz7wk5vfdjxchttbr44ojcxks72ytdmeybpub6zqt3l327vi" + }, + "chat.bsky.convo.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.defs", + "cid": "bafyreibpb3r5pqc3yrjslx7ntfl5vvgdlgqanuwbcvq3q7sfhayi75x7uu" + }, + "chat.bsky.convo.deleteMessageForSelf": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.deleteMessageForSelf", + "cid": "bafyreicsfbucwa3y5jdtzv6focgl5dao2lhmaa22jljllwjp6rz4noxowe" + }, + "chat.bsky.convo.getConvo": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.getConvo", + "cid": "bafyreidjox5oa467rn7x6xjuko23w5ekypjbkdryia4hz5s7isswhxrilu" + }, + "chat.bsky.convo.getConvoAvailability": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.getConvoAvailability", + "cid": "bafyreidnzzp2b7w4pedgnspgwlzl7oc4uuwxlbh3asteu6s4rgcuagnzl4" + }, + "chat.bsky.convo.getConvoForMembers": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.getConvoForMembers", + "cid": "bafyreicheufh7jkrstr6hgpggylildyh3k5tihbh5747dumqe5nabp24fi" + }, + "chat.bsky.convo.getConvoMembers": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.getConvoMembers", + "cid": "bafyreigup3axpdj3r3p4s3x7qgarrysft3vognwzgetl4bn3n2bgocxl4m" + }, + "chat.bsky.convo.getLog": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.getLog", + "cid": "bafyreicc4wwkmh5rskpzyriwad4lte64rk2w7vnzzzofy6tnhbisxsint4" + }, + "chat.bsky.convo.getMessages": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.getMessages", + "cid": "bafyreif2fjad4zrbjwhbuk3ny7yfnv6sjyr6llrveh2dr4i2aovwe77yia" + }, + "chat.bsky.convo.getUnreadCounts": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.getUnreadCounts", + "cid": "bafyreibuu7abhy74gxfprerxpiimjit43fhcrrewe43zhili7rmr4crrsi" + }, + "chat.bsky.convo.leaveConvo": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.leaveConvo", + "cid": "bafyreigdjs5t2jod6zojwnj5giqucsad257vhocykv6fftkj3qkqzqlqua" + }, + "chat.bsky.convo.listConvoRequests": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.listConvoRequests", + "cid": "bafyreieoce2dsrg6czlm7tkfi7glxjvqh6wwvfq777s5zxi6hyzqjeqo3e" + }, + "chat.bsky.convo.listConvos": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.listConvos", + "cid": "bafyreifxh52sx6i7vrlj2xi5h3yd56dmawkplcfdsfiu4npou6rutz4pge" + }, + "chat.bsky.convo.lockConvo": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.lockConvo", + "cid": "bafyreidnrgfxtyg233wfvasho34p34k2tiekrpiticcl4sau7k2masydby" + }, + "chat.bsky.convo.muteConvo": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.muteConvo", + "cid": "bafyreihpyxtotf6ltd6w6ayoirar7obxoqlijljov6yrb7r5qztq3ag74e" + }, + "chat.bsky.convo.removeReaction": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.removeReaction", + "cid": "bafyreiagvcoqprw5jew2m4uoeg6i32wu6ld2ltgjgb2d2s36k4o6i7arb4" + }, + "chat.bsky.convo.sendMessage": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.sendMessage", + "cid": "bafyreih7yu45wtptd7qjg6iomrugxucfw7zkkz7ijjj7o3bddee3od2q3m" + }, + "chat.bsky.convo.sendMessageBatch": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.sendMessageBatch", + "cid": "bafyreifvinecphums77xvuuspqgjkgfa7cpre3zavtrkh5tffmi643q2rq" + }, + "chat.bsky.convo.unlockConvo": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.unlockConvo", + "cid": "bafyreiaind2trvtuo2bgqupx4acxx5vvfhvvo4xwencwcf2trnrithwkya" + }, + "chat.bsky.convo.unmuteConvo": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.unmuteConvo", + "cid": "bafyreifbip4w4q4dnm2bvhtaxr4ut3yrzck4xkdu2ifnkoggyqigsuasbe" + }, + "chat.bsky.convo.updateAllRead": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.updateAllRead", + "cid": "bafyreiewx6wmllc4nujgtakvu4jiapnrjtbqyxvugohuu7xfy3bhrn4wq4" + }, + "chat.bsky.convo.updateRead": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.convo.updateRead", + "cid": "bafyreihba5bopb5vnlqqvtavipybqgemp6itwrz4xysnp6yi6iow5ntnv4" + }, + "chat.bsky.embed.joinLink": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.embed.joinLink", + "cid": "bafyreib3z65a2myai4hc55vxa7woue4bmelo2a4dlgnawps746w3l2y72q" + }, + "chat.bsky.group.addMembers": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.addMembers", + "cid": "bafyreiftelalu5yc3zctgch7req6syovskusqgma5vyyd4elas5rs47tvy" + }, + "chat.bsky.group.approveJoinRequest": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.approveJoinRequest", + "cid": "bafyreicykc372f7ebk7c3wrco75wgox3poyop7w3duovvn2piqzsvhzthq" + }, + "chat.bsky.group.createGroup": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.createGroup", + "cid": "bafyreidhalpchhospn2hwox54navotl4svw4pmt33aynj22gp52ofoxzau" + }, + "chat.bsky.group.createJoinLink": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.createJoinLink", + "cid": "bafyreicxjzsyjrqbkwd7vztmaxstgulx5naln55gsppix27pjnoz5okv7i" + }, + "chat.bsky.group.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.defs", + "cid": "bafyreiexck6gwns5nesq44mqgsikwrjkgplz33cemxaq365xwdcxeoqjje" + }, + "chat.bsky.group.disableJoinLink": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.disableJoinLink", + "cid": "bafyreiholtxhmuqku64fkv6rjl2rn3jyicutkcuieoviys3zdzx6lh43n4" + }, + "chat.bsky.group.editGroup": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.editGroup", + "cid": "bafyreifwwj2mzridii6sm6wlc5sxgu7g6k67bijvylus4jnbvfcup7ecfe" + }, + "chat.bsky.group.editJoinLink": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.editJoinLink", + "cid": "bafyreiafmbnein4xpbr76jzofk53ut6u5opeuwyorilkhpe3u2kzgavlde" + }, + "chat.bsky.group.enableJoinLink": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.enableJoinLink", + "cid": "bafyreib45q6ccuobjlws7sysjojabo6gvww6wlgy7dif5ejkoasb23l7su" + }, + "chat.bsky.group.getJoinLinkPreviews": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.getJoinLinkPreviews", + "cid": "bafyreigrglu52cdsrc7sxxflwoxr6eg6paw4zsd7ioy7kyue2kqfdjyqn4" + }, + "chat.bsky.group.listJoinRequests": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.listJoinRequests", + "cid": "bafyreigoj66wx3sx7nbav4ywxckk7ggjklxvy2w5tmmnrapib7nfpgh3ci" + }, + "chat.bsky.group.listMutualGroups": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.listMutualGroups", + "cid": "bafyreihp23gsqmaie6uzouxfqi6zwhr3wqglqjblbcawkp6asirzmqq4oi" + }, + "chat.bsky.group.rejectJoinRequest": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.rejectJoinRequest", + "cid": "bafyreiaztaygpnfglcldsdbh5dunovp2nz3kenetahycpymoxxrxkafiha" + }, + "chat.bsky.group.removeMembers": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.removeMembers", + "cid": "bafyreidfkumfvrtsseaavuhs2lfblcsmrjbkeaeojh2u576jze5xrfcasy" + }, + "chat.bsky.group.requestJoin": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.requestJoin", + "cid": "bafyreiatzqdp7zlnbqmbti6xt3orag7yqyuqr6tqzwzzp52gq7hlu3it6q" + }, + "chat.bsky.group.updateJoinRequestsRead": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.updateJoinRequestsRead", + "cid": "bafyreieod4o3cdfkhev6tia63q6yrdk3pgwvrrpyxggswxpp6pzwjj2jyi" + }, + "chat.bsky.group.withdrawJoinRequest": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.group.withdrawJoinRequest", + "cid": "bafyreicdn25bcyya4for6vtdhhkjaeyfbkldgaxqtqoh5hfwljbrxszezy" + }, + "chat.bsky.moderation.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.moderation.defs", + "cid": "bafyreiee3r5j2psnahumolqrbr6gj6adqt3ov3xflbeibnyfzlwwc3e5km" + }, + "chat.bsky.moderation.getActorMetadata": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.moderation.getActorMetadata", + "cid": "bafyreifo2qp6ozex6phd5dd2sewpyltlnzijjqmfl5xpujxk3mkx6b4pze" + }, + "chat.bsky.moderation.getConvo": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.moderation.getConvo", + "cid": "bafyreie6ds2blosf5zeg7eralqp5jmbbvsv4befevms3nyqoihblzxwzsy" + }, + "chat.bsky.moderation.getConvoMembers": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.moderation.getConvoMembers", + "cid": "bafyreicm4fvldxqk5zdwopjjrsukl7wpib2ixqgkfc3nrszorx46i4np34" + }, + "chat.bsky.moderation.getConvos": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.moderation.getConvos", + "cid": "bafyreif5k5jqz4546omvosnx2sey4apq4nebiizwo3xit6q2knf2tteoey" + }, + "chat.bsky.moderation.getMessageContext": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.moderation.getMessageContext", + "cid": "bafyreih436hnrsenvuaelqk5bz4esvjku2kz3blwh7lk5fmmjfzn5uxaei" + }, + "chat.bsky.moderation.subscribeModEvents": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.moderation.subscribeModEvents", + "cid": "bafyreifb7xeucadmhsbzezibkn4hxyezzajfydjfyy3tjmwm7bwjmiam3u" + }, + "chat.bsky.moderation.updateActorAccess": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.moderation.updateActorAccess", + "cid": "bafyreif3kv6qzqf533kjoodzu6s2cf3qypyafsntemeir6uuac4dnnzlwe" + }, + "chat.bsky.notification.defs": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.notification.defs", + "cid": "bafyreifv3xy2nhjxvr4uqfheezyp3bo2eczvihqto4kp4mmkirnpkmmt4u" + }, + "chat.bsky.notification.getPreferences": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.notification.getPreferences", + "cid": "bafyreicfjpp5ldakcwyo7wmq6e5kpwtizn5owdd7cux4pbif6s2u6dbxne" + }, + "chat.bsky.notification.putPreferences": { + "uri": "at://did:plc:4v4y5r3lwsbtmsxhile2ljac/com.atproto.lexicon.schema/chat.bsky.notification.putPreferences", + "cid": "bafyreiatnvy5wssiuzrjtdtv3sd2yarrd3xiaijpeuvsnopq3neh52bfay" + }, + "com.atproto.admin.defs": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.defs", + "cid": "bafyreicuagseedq4ko6lx6ublh6agdyeaox4pkvbaq7yj54s2d66yjk2h4" + }, + "com.atproto.admin.deleteAccount": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.deleteAccount", + "cid": "bafyreigzxdce4ztwo2n5xxahr6fwd4bozc4budnaepl26mbejpa3q2ess4" + }, + "com.atproto.admin.disableAccountInvites": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.disableAccountInvites", + "cid": "bafyreihi35g5ngllvyykzlq3fdfqs4xjiuao7nxg37ealsdtqcdquvj2ce" + }, + "com.atproto.admin.disableInviteCodes": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.disableInviteCodes", + "cid": "bafyreiatqxhcmu23qc2nkttjf2e7gjsdhbh6x5nrqambamo5fcwq523eey" + }, + "com.atproto.admin.enableAccountInvites": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.enableAccountInvites", + "cid": "bafyreid5xvqhkbpywbk5eaumneqzdcxtkj5lh45hzmcbed4exzvqk37p2i" + }, + "com.atproto.admin.getAccountInfo": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.getAccountInfo", + "cid": "bafyreifcp3jeg332nfwatctxwjiokqv72tyuppb2mzwjxih7qdtr2glou4" + }, + "com.atproto.admin.getAccountInfos": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.getAccountInfos", + "cid": "bafyreib7kdehrlwtxbpdkdk3hxswvjdgw63yadellwf4n32qofh5pbf2zy" + }, + "com.atproto.admin.getInviteCodes": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.getInviteCodes", + "cid": "bafyreiec5v6odvpqm3qfcxmuq5nihskij7xmb5nij32vve426z6s7tii24" + }, + "com.atproto.admin.getSubjectStatus": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.getSubjectStatus", + "cid": "bafyreicx3n74uhu3jtolm6vvtxx4xy3vo57iu4boxn7chocuokx3q7fazm" + }, + "com.atproto.admin.searchAccounts": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.searchAccounts", + "cid": "bafyreigqi7z4ic2ljca6zvmtzha4gdnsa25gsn4pw2upqymrh6lelkzqjy" + }, + "com.atproto.admin.sendEmail": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.sendEmail", + "cid": "bafyreig7vffo7s4diefuoylvl6crosfo5mcc74hlnpkd24fb3xs2wyewbi" + }, + "com.atproto.admin.updateAccountEmail": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.updateAccountEmail", + "cid": "bafyreidlzhtftznouwxruqumookeupzk4iytlvm5giwziuv4m7wzi76lf4" + }, + "com.atproto.admin.updateAccountHandle": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.updateAccountHandle", + "cid": "bafyreidsj323nackffydrz3buggrubayxwnhxu2v2sf3saoiao44im6dnm" + }, + "com.atproto.admin.updateAccountPassword": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.updateAccountPassword", + "cid": "bafyreieptac2cku6iazjjbgn6oy2ivaleofleprdhiysprato336fapghq" + }, + "com.atproto.admin.updateAccountSigningKey": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.updateAccountSigningKey", + "cid": "bafyreibzf73rasuo5rhzjjugexi5l7tpyahcmmjg2rl2bweoxyb3672cca" + }, + "com.atproto.admin.updateSubjectStatus": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.admin.updateSubjectStatus", + "cid": "bafyreieirresnt6qdfshzsaaikwpstoql5q5n7vlbfadkmjwzuweeojuyu" + }, + "com.atproto.identity.defs": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.identity.defs", + "cid": "bafyreiftikhen7lwpyay4e4sycmmflexkroqjwwpfuqtmog3vjhs2qnz3m" + }, + "com.atproto.identity.getRecommendedDidCredentials": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.identity.getRecommendedDidCredentials", + "cid": "bafyreidkfpbromp7kjmpbzkbcamycj3722sxvcm47pabju744ytk2ljhay" + }, + "com.atproto.identity.refreshIdentity": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.identity.refreshIdentity", + "cid": "bafyreiabglay3heljv6azsioza4lbbq7ocfnllbugb4ep4cmoofvhbz74q" + }, + "com.atproto.identity.requestPlcOperationSignature": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.identity.requestPlcOperationSignature", + "cid": "bafyreiefkbiptbpxy4ijubutlp2lbjzlkdqbbvt77uguxwg2lns7ba2anm" + }, + "com.atproto.identity.resolveDid": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.identity.resolveDid", + "cid": "bafyreicy6cgn4agn5jnteswy624asmf74k34do54sptozrcmyrlglvnufu" + }, + "com.atproto.identity.resolveHandle": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.identity.resolveHandle", + "cid": "bafyreigckmqtt3jrtzd7tvigjatnz6ajqyafs26h5pwcudwag2anedxnmu" + }, + "com.atproto.identity.resolveIdentity": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.identity.resolveIdentity", + "cid": "bafyreibc3gr2wcoogckav25quul7dz24jnq7oevwvrve4fhanmyypujpai" + }, + "com.atproto.identity.signPlcOperation": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.identity.signPlcOperation", + "cid": "bafyreicjgnk7lmloumjsgwpk6fbnplcu7or23edto4vgdow2quxq2okjha" + }, + "com.atproto.identity.submitPlcOperation": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.identity.submitPlcOperation", + "cid": "bafyreigu67ehin43bdmkt5oyrwsttvzon3lsmp4fkfpzlyptpfnx2hozau" + }, + "com.atproto.identity.updateHandle": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.identity.updateHandle", + "cid": "bafyreibzqwkofzc7g5j7vatyyzolgnarm3zd2g6swegkc3ivz4xivm2buu" + }, + "com.atproto.label.defs": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.label.defs", + "cid": "bafyreidp2wpcbzl2qiob2uwoc7lhntojx3tjcr553mmahw2f5cumtm3wpm" + }, + "com.atproto.label.queryLabels": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.label.queryLabels", + "cid": "bafyreig424opkctuzynvqvbwcoqafbqzqu6tl2kilwif7ivnwcdmafbhni" + }, + "com.atproto.label.subscribeLabels": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.label.subscribeLabels", + "cid": "bafyreigeyxddcuwebpvscyy2l7s3urxsbdtzpjrljncjo4az62woytutx4" + }, + "com.atproto.lexicon.resolveLexicon": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.lexicon.resolveLexicon", + "cid": "bafyreihyejjwx2cpbv3ikfojbq57jmtmilhjjsamqwfeiwkg2vanxxaz6m" + }, + "com.atproto.lexicon.schema": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.lexicon.schema", + "cid": "bafyreigtmtzisxiuao4kcswgnbud7wmpkcnsdb6h2kenhbnaolsx7bpiq4" + }, + "com.atproto.moderation.createReport": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.moderation.createReport", + "cid": "bafyreib5kl3klngngi47k7nx2g2bmrg2roj52q3dobvuf73kr3exvn73n4" + }, + "com.atproto.moderation.defs": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.moderation.defs", + "cid": "bafyreideawy4rlpgces2oebk5q4kpurbonhb5qtl4pes7dvxsc5osaiksy" + }, + "com.atproto.repo.applyWrites": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.applyWrites", + "cid": "bafyreidwfwif7k5uncwqxhpxpxhvdkgedbvp6kqy6r4nozii433lwlajba" + }, + "com.atproto.repo.createRecord": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.createRecord", + "cid": "bafyreihyvpmy2l4ou2v5etx25j5lbqty6tvna7gsxdqge76rbb7gltulei" + }, + "com.atproto.repo.defs": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.defs", + "cid": "bafyreigmp5xfpkhnmwr3l5o626ued2gc2ouioykj2tpgqjqqajvbvm3wlm" + }, + "com.atproto.repo.deleteRecord": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.deleteRecord", + "cid": "bafyreibwdxbyhuuljujl7g4ylwdso2x2ov6yv6nk3ext3sr7kjvvubkula" + }, + "com.atproto.repo.describeRepo": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.describeRepo", + "cid": "bafyreif5ho6rtknjmiqjclrfogiqyzxnix2fzpwxyftfebplkj4q23pnrm" + }, + "com.atproto.repo.getRecord": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.getRecord", + "cid": "bafyreihdnm3v25uaptt5vjvnpfsfgb6fbhxew2ye3l6q4ndm7xi5wpc4vy" + }, + "com.atproto.repo.importRepo": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.importRepo", + "cid": "bafyreihymzggr6n33y3ile7hiy5zekxkgqc6ypkoby5og7tr6buqiasabu" + }, + "com.atproto.repo.listMissingBlobs": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.listMissingBlobs", + "cid": "bafyreiayrrhk77vn3az63mor2bepw2qnkskr5awxvpzb5xtgwtzr5z6v3u" + }, + "com.atproto.repo.listRecords": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.listRecords", + "cid": "bafyreihv666jp6lxncj6amcgsu5qrvwq4rwmtc7xbxzndxqdwxamlsatee" + }, + "com.atproto.repo.putRecord": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.putRecord", + "cid": "bafyreigevzo2cypxpaj5re2u35so36df4rkgx6oh4zpi4zwicfvzqfdqpa" + }, + "com.atproto.repo.strongRef": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.strongRef", + "cid": "bafyreifrkdbnkvfjujntdaeigolnrjj3srrs53tfixjhmacclps72qlov4" + }, + "com.atproto.repo.uploadBlob": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.repo.uploadBlob", + "cid": "bafyreifj5rusrt7rd763rqcnyh7srwukrdn427m7qrhwyb5hiayyyq4lxe" + }, + "com.atproto.server.activateAccount": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.activateAccount", + "cid": "bafyreih4z7qtht643ddxszglbworpyttngm4iahyne5vp5n6puvysckc2u" + }, + "com.atproto.server.checkAccountStatus": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.checkAccountStatus", + "cid": "bafyreiggejdmd3ezjwphxo6jgcaf4cnwwc2jxq5so7gvet3suozhb2a52q" + }, + "com.atproto.server.confirmEmail": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.confirmEmail", + "cid": "bafyreigsd23jlgjwtbbvv2pueoiuji3qini232nwzhjiex6tj7ehc57cdy" + }, + "com.atproto.server.createAccount": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.createAccount", + "cid": "bafyreie6o2sq47ukysnnnwxcb7eocpyyazetp5n3m4u3n5f7v5xr7qn62u" + }, + "com.atproto.server.createAppPassword": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.createAppPassword", + "cid": "bafyreickddwu7udrphtmvpcajiittidvobrror7tztxkshnnhcxyexylbu" + }, + "com.atproto.server.createInviteCode": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.createInviteCode", + "cid": "bafyreicce55wknsshpysqc2torzhkuo6v555piprncrkw7glptinbssbli" + }, + "com.atproto.server.createInviteCodes": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.createInviteCodes", + "cid": "bafyreidpwhayukm2ytzncekfinio4dehp5y6ksmwpvrxwl4fhdnqh62ewa" + }, + "com.atproto.server.createSession": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.createSession", + "cid": "bafyreieftxrfcsudzvfk5l73r2cukzwa6axzkfjs2l7dxtieyqnkdngwsq" + }, + "com.atproto.server.deactivateAccount": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.deactivateAccount", + "cid": "bafyreicewuflgo55i4yr7wblmnfcfsrqehy6zrq5jcqap2igkniantarwu" + }, + "com.atproto.server.defs": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.defs", + "cid": "bafyreie6rctvespvjvdsway3keyu5yy25eqordhq44jmkxbf6roirlidwm" + }, + "com.atproto.server.deleteAccount": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.deleteAccount", + "cid": "bafyreie44rxc4ax3u2hg54636cgjedydzvy2q2cnifsayo3kxlpockkhzu" + }, + "com.atproto.server.deleteSession": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.deleteSession", + "cid": "bafyreiave4iopcsa62d5hm42ctf4n6z2ajkdqhz5evq53hmw26hghoz35q" + }, + "com.atproto.server.describeServer": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.describeServer", + "cid": "bafyreidjve4qbhebxzppq23ogsto6luddd63mmn7a57rmml6qi3yf7n3ee" + }, + "com.atproto.server.getAccountInviteCodes": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.getAccountInviteCodes", + "cid": "bafyreibeczlag36upzn67uqsheo4st47z7nbpvfrdgko777633saur7jui" + }, + "com.atproto.server.getServiceAuth": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.getServiceAuth", + "cid": "bafyreigjuxsvldkifmwzdrzvziq2ldomzoqizsv5umwgumjv77ld56wxyi" + }, + "com.atproto.server.getSession": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.getSession", + "cid": "bafyreihjh3lkkbdznrs5jfnt6mhxtnaitviqqma6zmoltt3weknwxlc5ae" + }, + "com.atproto.server.listAppPasswords": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.listAppPasswords", + "cid": "bafyreic7ipzaep5tjfw6pzvcl5goidfhkvquzvwyfftqnlgnc6eu2sjqjy" + }, + "com.atproto.server.refreshSession": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.refreshSession", + "cid": "bafyreie5cdpsl3ibp473xuycfxakryp5svq4gzdcwnfdzwm5xkxyzux2ly" + }, + "com.atproto.server.requestAccountDelete": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.requestAccountDelete", + "cid": "bafyreietqywku2g5whu4hehlopx3mwwrnkhupuw3udhp76ovdsqrhbhzgm" + }, + "com.atproto.server.requestEmailConfirmation": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.requestEmailConfirmation", + "cid": "bafyreidwpigpyhtbka5bgc3qkyr6ifp2j22bwdpjuitb2b3sjy5fae6cj4" + }, + "com.atproto.server.requestEmailUpdate": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.requestEmailUpdate", + "cid": "bafyreibd4flizyrui5mlyv5icokqhgocw2txvgjpmxkjrd6ok43jpjbleq" + }, + "com.atproto.server.requestPasswordReset": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.requestPasswordReset", + "cid": "bafyreihcycgtiyv5ah2nnpbbez3qleo2y2a3cf5uvz34tcaliicke2osfa" + }, + "com.atproto.server.reserveSigningKey": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.reserveSigningKey", + "cid": "bafyreig4anpzivr7yutnqobuqdtta6ls6i4xbarupyud5bwztmmpnvcr2u" + }, + "com.atproto.server.resetPassword": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.resetPassword", + "cid": "bafyreighhpnpsmoauobk4gqxpa5qtirodft25gcxnfxme7ic5zpv73tu6u" + }, + "com.atproto.server.revokeAppPassword": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.revokeAppPassword", + "cid": "bafyreif7fouud77wtwkbbxrgz2ixc7pofd7klamsrxotvs4cw63dzcqqvi" + }, + "com.atproto.server.updateEmail": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.server.updateEmail", + "cid": "bafyreia57t6r2crv7hinanwkcfyhqfcbt3qnfqponrj3j5owwlepnbxb7u" + }, + "com.atproto.sync.defs": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.defs", + "cid": "bafyreia6vzfbj2nfmr7kn7fwy6lnugozgimpdcg2f3vovgsa4fk6fsfhse" + }, + "com.atproto.sync.getBlob": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.getBlob", + "cid": "bafyreigievymssvpqn7v5gb6q7hrlepcyzy7xko5rnfrk2ywcjcfbd3574" + }, + "com.atproto.sync.getBlocks": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.getBlocks", + "cid": "bafyreie5jvieoangaotlziavns65njosj5pklfqv6u5psgtkhdgmk25lpa" + }, + "com.atproto.sync.getCheckout": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.getCheckout", + "cid": "bafyreiba6hvjo2chopkgwsy5n2sumywx2qhqvqycd5a6tqfsnbf5nodkzm" + }, + "com.atproto.sync.getHead": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.getHead", + "cid": "bafyreiecq2pcytdy56ucv22a7snlt5lgacmfvemioj5jjyco6uo7t7akwi" + }, + "com.atproto.sync.getHostStatus": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.getHostStatus", + "cid": "bafyreiazetpylsjgp5qfnu6qfb6ahthgjw7spbcl7hpyiz3srl7wnebdsq" + }, + "com.atproto.sync.getLatestCommit": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.getLatestCommit", + "cid": "bafyreifewzvfus7z6rnmtv74lpulhldvikqmhzepwqdlaqmhllgyg4oejq" + }, + "com.atproto.sync.getRecord": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.getRecord", + "cid": "bafyreiermob6nqcfc6hlhmvuo7jv3imo7hfy3og34oom2p3qe6tldajoru" + }, + "com.atproto.sync.getRepo": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.getRepo", + "cid": "bafyreieyt5x6wf4pgcn56tb2724uplmwcqjivkubmi25km3xrvroz6dw5q" + }, + "com.atproto.sync.getRepoStatus": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.getRepoStatus", + "cid": "bafyreidv752rhe2abwz2rp2akok2gnopmsk4wlwrwsf22uwhfzst6nh4ry" + }, + "com.atproto.sync.listBlobs": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.listBlobs", + "cid": "bafyreidjsbhbe5rstmx5tjbbjjrznqr2ehlqkdklvlnhlcndhdjycnuc4a" + }, + "com.atproto.sync.listHosts": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.listHosts", + "cid": "bafyreigxo77zttzvqzmhhvttfs7kom5npwjxkapmnmpirjyg7oui5tpwbe" + }, + "com.atproto.sync.listRepos": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.listRepos", + "cid": "bafyreic6q46ptlr4kv4eonzw3isiqegom5ldr32onhrv2kyrmppgakss2i" + }, + "com.atproto.sync.listReposByCollection": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.listReposByCollection", + "cid": "bafyreicl2azqcl4poa5cqyp3ghlomqkzswmu4uqff6cpwk2c7bjrhth75y" + }, + "com.atproto.sync.notifyOfUpdate": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.notifyOfUpdate", + "cid": "bafyreigohysvh6tjme6ihyygbrnjckr7e3rgmsoes6tx7xxhszulbek6gm" + }, + "com.atproto.sync.requestCrawl": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.requestCrawl", + "cid": "bafyreiepdcevsxqy6matus3wn2n3htnyihgc3etubne2ay3idq7h523hte" + }, + "com.atproto.sync.subscribeRepos": { + "uri": "at://did:plc:6msi3pj7krzih5qxqtryxlzw/com.atproto.lexicon.schema/com.atproto.sync.subscribeRepos", + "cid": "bafyreid5fdkjulxpnbfqwi4hjehx7ibzzj2h5ln7nachurdzqcpihytbuu" + }, + "com.germnetwork.declaration": { + "uri": "at://did:plc:qyqmmncrm6qx33kpy7vqndik/com.atproto.lexicon.schema/com.germnetwork.declaration", + "cid": "bafyreickqsspgkujwucg3low5or64ywjryf44eua6xtszd6zsl2yteumoi" + }, + "tools.ozone.report.defs": { + "uri": "at://did:plc:33dt5kftu3jq2h5h4jjlqezt/com.atproto.lexicon.schema/tools.ozone.report.defs", + "cid": "bafyreic3l2rmh2ugirt3jz372wcvy333m7t2ynlyzj2k54oshijs6lxdfu" + } + } +} diff --git a/lexicons/app/bsky/actor/contentVisibilityDeclaration.json b/lexicons/app/bsky/actor/contentVisibilityDeclaration.json new file mode 100644 index 0000000000..ec7d627e43 --- /dev/null +++ b/lexicons/app/bsky/actor/contentVisibilityDeclaration.json @@ -0,0 +1,21 @@ +{ + "lexicon": 1, + "id": "app.bsky.actor.contentVisibilityDeclaration", + "defs": { + "main": { + "type": "record", + "description": "A declaration of an account's preferences for appearing in content discovery surfaces.", + "key": "literal:self", + "record": { + "type": "object", + "required": ["hideFromAlgorithmicRecommendations"], + "properties": { + "hideFromAlgorithmicRecommendations": { + "type": "boolean", + "description": "Whether the account requests that its posts be hidden from algorithmic recommendations. Consumers must treat a missing record as false." + } + } + } + } + } +} diff --git a/lexicons/app/bsky/actor/defs.json b/lexicons/app/bsky/actor/defs.json new file mode 100644 index 0000000000..2e6403ff51 --- /dev/null +++ b/lexicons/app/bsky/actor/defs.json @@ -0,0 +1,994 @@ +{ + "id": "app.bsky.actor.defs", + "defs": { + "nux": { + "type": "object", + "required": [ + "id", + "completed" + ], + "properties": { + "id": { + "type": "string", + "maxLength": 100 + }, + "data": { + "type": "string", + "maxLength": 3000, + "description": "Arbitrary data for the NUX. The structure is defined by the NUX itself. Limited to 300 characters.", + "maxGraphemes": 300 + }, + "completed": { + "type": "boolean", + "default": false + }, + "expiresAt": { + "type": "string", + "format": "datetime", + "description": "The date and time at which the NUX will expire and should be considered completed." + } + }, + "description": "A new user experiences (NUX) storage object" + }, + "mutedWord": { + "type": "object", + "required": [ + "value", + "targets" + ], + "properties": { + "id": { + "type": "string" + }, + "value": { + "type": "string", + "maxLength": 10000, + "description": "The muted word itself.", + "maxGraphemes": 1000 + }, + "targets": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#mutedWordTarget", + "type": "ref" + }, + "description": "The intended targets of the muted word." + }, + "expiresAt": { + "type": "string", + "format": "datetime", + "description": "The date and time at which the muted word will expire and no longer be applied." + }, + "actorTarget": { + "type": "string", + "default": "all", + "description": "Groups of users to apply the muted word to. If undefined, applies to all users.", + "knownValues": [ + "all", + "exclude-following" + ] + } + }, + "description": "A word that the account owner has muted." + }, + "savedFeed": { + "type": "object", + "required": [ + "id", + "type", + "value", + "pinned" + ], + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "knownValues": [ + "feed", + "list", + "timeline" + ] + }, + "value": { + "type": "string" + }, + "pinned": { + "type": "boolean" + } + } + }, + "statusView": { + "type": "object", + "required": [ + "status", + "record" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "embed": { + "refs": [ + "app.bsky.embed.external#view" + ], + "type": "union", + "description": "An optional embed associated with the status." + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "record": { + "type": "unknown" + }, + "status": { + "type": "string", + "description": "The status for the account.", + "knownValues": [ + "app.bsky.actor.status#live" + ] + }, + "isActive": { + "type": "boolean", + "description": "True if the status is not expired, false if it is expired. Only present if expiration was set." + }, + "expiresAt": { + "type": "string", + "format": "datetime", + "description": "The date when this status will expire. The application might choose to no longer return the status after expiration." + }, + "isDisabled": { + "type": "boolean", + "description": "True if the user's go-live access has been disabled by a moderator, false otherwise." + } + } + }, + "preferences": { + "type": "array", + "items": { + "refs": [ + "#adultContentPref", + "#contentLabelPref", + "#savedFeedsPref", + "#savedFeedsPrefV2", + "#personalDetailsPref", + "#declaredAgePref", + "#feedViewPref", + "#threadViewPref", + "#interestsPref", + "#mutedWordsPref", + "#hiddenPostsPref", + "#bskyAppStatePref", + "#labelersPref", + "#postInteractionSettingsPref", + "#verificationPrefs", + "#liveEventPreferences" + ], + "type": "union" + } + }, + "profileView": { + "type": "object", + "required": [ + "did", + "handle" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "debug": { + "type": "unknown", + "description": "Debug information for internal development" + }, + "avatar": { + "type": "string", + "format": "uri" + }, + "handle": { + "type": "string", + "format": "handle" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "status": { + "ref": "#statusView", + "type": "ref" + }, + "viewer": { + "ref": "#viewerState", + "type": "ref" + }, + "pronouns": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "indexedAt": { + "type": "string", + "format": "datetime" + }, + "associated": { + "ref": "#profileAssociated", + "type": "ref" + }, + "description": { + "type": "string", + "maxLength": 2560, + "maxGraphemes": 256 + }, + "displayName": { + "type": "string", + "maxLength": 640, + "maxGraphemes": 64 + }, + "verification": { + "ref": "#verificationState", + "type": "ref" + } + } + }, + "viewerState": { + "type": "object", + "properties": { + "muted": { + "type": "boolean", + "description": "Whether the account is fully muted, directly or via a mutelist. False when the mute is scoped to specific kinds; see mutedOnlyReposts and mutedOnlyQuoteposts." + }, + "blocking": { + "type": "string", + "format": "at-uri" + }, + "blockedBy": { + "type": "boolean" + }, + "following": { + "type": "string", + "format": "at-uri" + }, + "followedBy": { + "type": "string", + "format": "at-uri" + }, + "mutedByList": { + "ref": "app.bsky.graph.defs#listViewBasic", + "type": "ref" + }, + "blockingByList": { + "ref": "app.bsky.graph.defs#listViewBasic", + "type": "ref" + }, + "knownFollowers": { + "ref": "#knownFollowers", + "type": "ref", + "description": "This property is present only in selected cases, as an optimization." + }, + "mutedOnlyReposts": { + "type": "boolean", + "description": "Whether the account's reposts are muted. Scoped mutes are exclusive with muted: this can be true while muted is false. If muted is true, this will be false." + }, + "mutedOnlyQuoteposts": { + "type": "boolean", + "description": "Whether the account's quote posts are muted. Scoped mutes are exclusive with muted: this can be true while muted is false. If muted is true, this will be false." + }, + "activitySubscription": { + "ref": "app.bsky.notification.defs#activitySubscription", + "type": "ref", + "description": "This property is present only in selected cases, as an optimization." + } + }, + "description": "Metadata about the requesting account's relationship with the subject account. Only has meaningful content for authed requests." + }, + "feedViewPref": { + "type": "object", + "required": [ + "feed" + ], + "properties": { + "feed": { + "type": "string", + "description": "The URI of the feed, or an identifier which describes the feed." + }, + "hideReplies": { + "type": "boolean", + "description": "Hide replies in the feed." + }, + "hideReposts": { + "type": "boolean", + "description": "Hide reposts in the feed." + }, + "hideQuotePosts": { + "type": "boolean", + "description": "Hide quote posts in the feed." + }, + "hideRepliesByLikeCount": { + "type": "integer", + "description": "Hide replies in the feed if they do not have this number of likes." + }, + "hideRepliesByUnfollowed": { + "type": "boolean", + "default": true, + "description": "Hide replies in the feed if they are not by followed users." + } + } + }, + "labelersPref": { + "type": "object", + "required": [ + "labelers" + ], + "properties": { + "labelers": { + "type": "array", + "items": { + "ref": "#labelerPrefItem", + "type": "ref" + } + } + } + }, + "interestsPref": { + "type": "object", + "required": [ + "tags" + ], + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string", + "maxLength": 640, + "maxGraphemes": 64 + }, + "maxLength": 100, + "description": "A list of tags which describe the account owner's interests gathered during onboarding." + } + } + }, + "knownFollowers": { + "type": "object", + "required": [ + "count", + "followers" + ], + "properties": { + "count": { + "type": "integer" + }, + "followers": { + "type": "array", + "items": { + "ref": "#profileViewBasic", + "type": "ref" + }, + "maxLength": 5, + "minLength": 0 + } + }, + "description": "The subject's followers whom you also follow" + }, + "mutedWordsPref": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#mutedWord", + "type": "ref" + }, + "description": "A list of words the account owner has muted." + } + } + }, + "savedFeedsPref": { + "type": "object", + "required": [ + "pinned", + "saved" + ], + "properties": { + "saved": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + } + }, + "pinned": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + } + }, + "timelineIndex": { + "type": "integer" + } + } + }, + "threadViewPref": { + "type": "object", + "properties": { + "sort": { + "type": "string", + "description": "Sorting mode for threads.", + "knownValues": [ + "oldest", + "newest", + "most-likes", + "random", + "hotness" + ] + } + } + }, + "declaredAgePref": { + "type": "object", + "properties": { + "isOverAge13": { + "type": "boolean", + "description": "Indicates if the user has declared that they are over 13 years of age." + }, + "isOverAge16": { + "type": "boolean", + "description": "Indicates if the user has declared that they are over 16 years of age." + }, + "isOverAge18": { + "type": "boolean", + "description": "Indicates if the user has declared that they are over 18 years of age." + } + }, + "description": "Read-only preference containing value(s) inferred from the user's declared birthdate. Absence of this preference object in the response indicates that the user has not made a declaration." + }, + "hiddenPostsPref": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + }, + "description": "A list of URIs of posts the account owner has hidden." + } + } + }, + "labelerPrefItem": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + } + } + }, + "mutedWordTarget": { + "type": "string", + "maxLength": 640, + "knownValues": [ + "content", + "tag" + ], + "maxGraphemes": 64 + }, + "adultContentPref": { + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "type": "boolean", + "default": false + } + } + }, + "bskyAppStatePref": { + "type": "object", + "properties": { + "nuxs": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#nux", + "type": "ref" + }, + "maxLength": 100, + "description": "Storage for NUXs the user has encountered." + }, + "isBetaUser": { + "type": "boolean", + "description": "Indicates if the user is participating in the beta features program." + }, + "queuedNudges": { + "type": "array", + "items": { + "type": "string", + "maxLength": 100 + }, + "maxLength": 1000, + "description": "An array of tokens which identify nudges (modals, popups, tours, highlight dots) that should be shown to the user." + }, + "activeProgressGuide": { + "ref": "#bskyAppProgressGuide", + "type": "ref" + } + }, + "description": "A grab bag of state that's specific to the bsky.app program. Third-party apps shouldn't use this." + }, + "contentLabelPref": { + "type": "object", + "required": [ + "label", + "visibility" + ], + "properties": { + "label": { + "type": "string" + }, + "labelerDid": { + "type": "string", + "format": "did", + "description": "Which labeler does this preference apply to? If undefined, applies globally." + }, + "visibility": { + "type": "string", + "knownValues": [ + "ignore", + "show", + "warn", + "hide" + ] + } + } + }, + "profileViewBasic": { + "type": "object", + "required": [ + "did", + "handle" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "debug": { + "type": "unknown", + "description": "Debug information for internal development" + }, + "avatar": { + "type": "string", + "format": "uri" + }, + "handle": { + "type": "string", + "format": "handle" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "status": { + "ref": "#statusView", + "type": "ref" + }, + "viewer": { + "ref": "#viewerState", + "type": "ref" + }, + "pronouns": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "associated": { + "ref": "#profileAssociated", + "type": "ref" + }, + "displayName": { + "type": "string", + "maxLength": 640, + "maxGraphemes": 64 + }, + "verification": { + "ref": "#verificationState", + "type": "ref" + } + } + }, + "savedFeedsPrefV2": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#savedFeed", + "type": "ref" + } + } + } + }, + "verificationView": { + "type": "object", + "required": [ + "issuer", + "uri", + "isValid", + "createdAt" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "The AT-URI of the verification record." + }, + "issuer": { + "type": "string", + "format": "did", + "description": "The user who issued this verification." + }, + "isValid": { + "type": "boolean", + "description": "True if the verification passes validation, otherwise false." + }, + "createdAt": { + "type": "string", + "format": "datetime", + "description": "Timestamp when the verification was created." + }, + "issuerHandle": { + "type": "string", + "format": "handle", + "description": "The handle of the issuer." + }, + "issuerDisplayName": { + "type": "string", + "description": "The display name of the issuer." + } + }, + "description": "An individual verification for an associated subject." + }, + "profileAssociated": { + "type": "object", + "properties": { + "chat": { + "ref": "#profileAssociatedChat", + "type": "ref" + }, + "germ": { + "ref": "#profileAssociatedGerm", + "type": "ref" + }, + "lists": { + "type": "integer" + }, + "labeler": { + "type": "boolean" + }, + "feedgens": { + "type": "integer" + }, + "starterPacks": { + "type": "integer" + }, + "activitySubscription": { + "ref": "#profileAssociatedActivitySubscription", + "type": "ref" + } + } + }, + "verificationPrefs": { + "type": "object", + "required": [], + "properties": { + "hideBadges": { + "type": "boolean", + "default": false, + "description": "Hide the blue check badges for verified accounts and trusted verifiers." + } + }, + "description": "Preferences for how verified accounts appear in the app." + }, + "verificationState": { + "type": "object", + "required": [ + "verifications", + "verifiedStatus", + "trustedVerifierStatus" + ], + "properties": { + "verifications": { + "type": "array", + "items": { + "ref": "#verificationView", + "type": "ref" + }, + "description": "All verifications issued by trusted verifiers on behalf of this user. Verifications by untrusted verifiers are not included." + }, + "verifiedStatus": { + "type": "string", + "description": "The user's status as a verified account.", + "knownValues": [ + "valid", + "invalid", + "none" + ] + }, + "trustedVerifierStatus": { + "type": "string", + "description": "The user's status as a trusted verifier.", + "knownValues": [ + "valid", + "invalid", + "none" + ] + } + }, + "description": "Represents the verification information about the user this object is attached to." + }, + "personalDetailsPref": { + "type": "object", + "properties": { + "birthDate": { + "type": "string", + "format": "datetime", + "description": "The birth date of account owner." + } + } + }, + "profileViewDetailed": { + "type": "object", + "required": [ + "did", + "handle" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "debug": { + "type": "unknown", + "description": "Debug information for internal development" + }, + "avatar": { + "type": "string", + "format": "uri" + }, + "banner": { + "type": "string", + "format": "uri" + }, + "handle": { + "type": "string", + "format": "handle" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "status": { + "ref": "#statusView", + "type": "ref" + }, + "viewer": { + "ref": "#viewerState", + "type": "ref" + }, + "website": { + "type": "string", + "format": "uri" + }, + "pronouns": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "indexedAt": { + "type": "string", + "format": "datetime" + }, + "associated": { + "ref": "#profileAssociated", + "type": "ref" + }, + "pinnedPost": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "postsCount": { + "type": "integer" + }, + "description": { + "type": "string", + "maxLength": 2560, + "maxGraphemes": 256 + }, + "displayName": { + "type": "string", + "maxLength": 640, + "maxGraphemes": 64 + }, + "followsCount": { + "type": "integer" + }, + "verification": { + "ref": "#verificationState", + "type": "ref" + }, + "followersCount": { + "type": "integer" + }, + "joinedViaStarterPack": { + "ref": "app.bsky.graph.defs#starterPackViewBasic", + "type": "ref" + } + } + }, + "bskyAppProgressGuide": { + "type": "object", + "required": [ + "guide" + ], + "properties": { + "guide": { + "type": "string", + "maxLength": 100 + } + }, + "description": "If set, an active progress guide. Once completed, can be set to undefined. Should have unspecced fields tracking progress." + }, + "liveEventPreferences": { + "type": "object", + "properties": { + "hideAllFeeds": { + "type": "boolean", + "default": false, + "description": "Whether to hide all feeds from live events." + }, + "hiddenFeedIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "A list of feed IDs that the user has hidden from live events." + } + }, + "description": "Preferences for live events." + }, + "profileAssociatedChat": { + "type": "object", + "required": [ + "allowIncoming" + ], + "properties": { + "allowIncoming": { + "type": "string", + "knownValues": [ + "all", + "none", + "following" + ] + }, + "allowGroupInvites": { + "type": "string", + "knownValues": [ + "all", + "none", + "following" + ] + } + } + }, + "profileAssociatedGerm": { + "type": "object", + "required": [ + "showButtonTo", + "messageMeUrl" + ], + "properties": { + "messageMeUrl": { + "type": "string", + "format": "uri" + }, + "showButtonTo": { + "type": "string", + "knownValues": [ + "usersIFollow", + "everyone" + ] + } + } + }, + "postInteractionSettingsPref": { + "type": "object", + "required": [], + "properties": { + "threadgateAllowRules": { + "type": "array", + "items": { + "refs": [ + "app.bsky.feed.threadgate#mentionRule", + "app.bsky.feed.threadgate#followerRule", + "app.bsky.feed.threadgate#followingRule", + "app.bsky.feed.threadgate#listRule" + ], + "type": "union" + }, + "maxLength": 5, + "description": "Matches threadgate record. List of rules defining who can reply to this users posts. If value is an empty array, no one can reply. If value is undefined, anyone can reply." + }, + "postgateEmbeddingRules": { + "type": "array", + "items": { + "refs": [ + "app.bsky.feed.postgate#disableRule" + ], + "type": "union" + }, + "maxLength": 5, + "description": "Matches postgate record. List of rules defining who can embed this users posts. If value is an empty array or is undefined, no particular rules apply and anyone can embed." + } + }, + "description": "Default post interaction settings for the account. These values should be applied as default values when creating new posts. These refs should mirror the threadgate and postgate records exactly." + }, + "profileAssociatedActivitySubscription": { + "type": "object", + "required": [ + "allowSubscriptions" + ], + "properties": { + "allowSubscriptions": { + "type": "string", + "knownValues": [ + "followers", + "mutuals", + "none" + ] + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/actor/getPreferences.json b/lexicons/app/bsky/actor/getPreferences.json new file mode 100644 index 0000000000..513d27e6a9 --- /dev/null +++ b/lexicons/app/bsky/actor/getPreferences.json @@ -0,0 +1,30 @@ +{ + "id": "app.bsky.actor.getPreferences", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "preferences" + ], + "properties": { + "preferences": { + "ref": "app.bsky.actor.defs#preferences", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": {} + }, + "description": "Get private preferences attached to the current account. Expected use is synchronization between multiple devices, and import/export during account migration. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/actor/getProfile.json b/lexicons/app/bsky/actor/getProfile.json new file mode 100644 index 0000000000..cbdde68df6 --- /dev/null +++ b/lexicons/app/bsky/actor/getProfile.json @@ -0,0 +1,31 @@ +{ + "id": "app.bsky.actor.getProfile", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "ref": "app.bsky.actor.defs#profileViewDetailed", + "type": "ref" + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier", + "description": "Handle or DID of account to fetch profile of." + } + } + }, + "description": "Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/actor/getProfiles.json b/lexicons/app/bsky/actor/getProfiles.json new file mode 100644 index 0000000000..23f0693417 --- /dev/null +++ b/lexicons/app/bsky/actor/getProfiles.json @@ -0,0 +1,45 @@ +{ + "id": "app.bsky.actor.getProfiles", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "profiles" + ], + "properties": { + "profiles": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileViewDetailed", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actors" + ], + "properties": { + "actors": { + "type": "array", + "items": { + "type": "string", + "format": "at-identifier" + }, + "maxLength": 25 + } + } + }, + "description": "Get detailed profile views of multiple actors." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/actor/getSuggestions.json b/lexicons/app/bsky/actor/getSuggestions.json new file mode 100644 index 0000000000..84afa86b36 --- /dev/null +++ b/lexicons/app/bsky/actor/getSuggestions.json @@ -0,0 +1,54 @@ +{ + "id": "app.bsky.actor.getSuggestions", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "actors" + ], + "properties": { + "recId": { + "type": "integer", + "description": "DEPRECATED: use recIdStr instead." + }, + "actors": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get a list of suggested actors. Expected use is discovery of accounts to follow during new account onboarding." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/actor/profile.json b/lexicons/app/bsky/actor/profile.json new file mode 100644 index 0000000000..256405db65 --- /dev/null +++ b/lexicons/app/bsky/actor/profile.json @@ -0,0 +1,75 @@ +{ + "id": "app.bsky.actor.profile", + "defs": { + "main": { + "key": "literal:self", + "type": "record", + "record": { + "type": "object", + "properties": { + "avatar": { + "type": "blob", + "accept": [ + "image/png", + "image/jpeg" + ], + "maxSize": 1000000, + "description": "Small image to be displayed next to posts from account. AKA, 'profile picture'" + }, + "banner": { + "type": "blob", + "accept": [ + "image/png", + "image/jpeg" + ], + "maxSize": 1000000, + "description": "Larger horizontal image to display behind profile view." + }, + "labels": { + "refs": [ + "com.atproto.label.defs#selfLabels" + ], + "type": "union", + "description": "Self-label values, specific to the Bluesky application, on the overall account." + }, + "website": { + "type": "string", + "format": "uri" + }, + "pronouns": { + "type": "string", + "maxLength": 200, + "description": "Free-form pronouns text.", + "maxGraphemes": 20 + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "pinnedPost": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "description": { + "type": "string", + "maxLength": 2560, + "description": "Free-form profile description text.", + "maxGraphemes": 256 + }, + "displayName": { + "type": "string", + "maxLength": 640, + "maxGraphemes": 64 + }, + "joinedViaStarterPack": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + } + } + }, + "description": "A declaration of a Bluesky account profile." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/actor/putPreferences.json b/lexicons/app/bsky/actor/putPreferences.json new file mode 100644 index 0000000000..824d5a2e80 --- /dev/null +++ b/lexicons/app/bsky/actor/putPreferences.json @@ -0,0 +1,26 @@ +{ + "id": "app.bsky.actor.putPreferences", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "preferences" + ], + "properties": { + "preferences": { + "ref": "app.bsky.actor.defs#preferences", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Set the private preferences attached to the account." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/actor/searchActors.json b/lexicons/app/bsky/actor/searchActors.json new file mode 100644 index 0000000000..1e097e505f --- /dev/null +++ b/lexicons/app/bsky/actor/searchActors.json @@ -0,0 +1,54 @@ +{ + "id": "app.bsky.actor.searchActors", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "actors" + ], + "properties": { + "actors": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "q": { + "type": "string", + "description": "Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended." + }, + "term": { + "type": "string", + "description": "DEPRECATED: use 'q' instead." + }, + "limit": { + "type": "integer", + "default": 25, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Find actors (profiles) matching search criteria. Does not require auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/actor/searchActorsTypeahead.json b/lexicons/app/bsky/actor/searchActorsTypeahead.json new file mode 100644 index 0000000000..9f11920c7e --- /dev/null +++ b/lexicons/app/bsky/actor/searchActorsTypeahead.json @@ -0,0 +1,48 @@ +{ + "id": "app.bsky.actor.searchActorsTypeahead", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "actors" + ], + "properties": { + "actors": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileViewBasic", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "q": { + "type": "string", + "description": "Search query prefix; not a full query string." + }, + "term": { + "type": "string", + "description": "DEPRECATED: use 'q' instead." + }, + "limit": { + "type": "integer", + "default": 10, + "maximum": 100, + "minimum": 1 + } + } + }, + "description": "Find actor suggestions for a prefix search term. Expected use is for auto-completion during text field entry. Does not require auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/actor/status.json b/lexicons/app/bsky/actor/status.json new file mode 100644 index 0000000000..7fd193c4d5 --- /dev/null +++ b/lexicons/app/bsky/actor/status.json @@ -0,0 +1,48 @@ +{ + "id": "app.bsky.actor.status", + "defs": { + "live": { + "type": "token", + "description": "Advertises an account as currently offering live content." + }, + "main": { + "key": "literal:self", + "type": "record", + "record": { + "type": "object", + "required": [ + "status", + "createdAt" + ], + "properties": { + "embed": { + "refs": [ + "app.bsky.embed.external" + ], + "type": "union", + "description": "An optional embed associated with the status." + }, + "status": { + "type": "string", + "description": "The status for the account.", + "knownValues": [ + "app.bsky.actor.status#live" + ] + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "durationMinutes": { + "type": "integer", + "minimum": 1, + "description": "The duration of the status in minutes. Applications can choose to impose minimum and maximum limits." + } + } + }, + "description": "A declaration of a Bluesky account status." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/ageassurance/begin.json b/lexicons/app/bsky/ageassurance/begin.json new file mode 100644 index 0000000000..0354e12190 --- /dev/null +++ b/lexicons/app/bsky/ageassurance/begin.json @@ -0,0 +1,61 @@ +{ + "id": "app.bsky.ageassurance.begin", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "email", + "language", + "countryCode" + ], + "properties": { + "email": { + "type": "string", + "description": "The user's email address to receive Age Assurance instructions." + }, + "language": { + "type": "string", + "description": "The user's preferred language for communication during the Age Assurance process." + }, + "regionCode": { + "type": "string", + "description": "An optional ISO 3166-2 code of the user's region or state within the country." + }, + "countryCode": { + "type": "string", + "description": "An ISO 3166-1 alpha-2 code of the user's location." + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidEmail" + }, + { + "name": "DidTooLong" + }, + { + "name": "InvalidInitiation" + }, + { + "name": "RegionNotSupported" + } + ], + "output": { + "schema": { + "ref": "app.bsky.ageassurance.defs#state", + "type": "ref" + }, + "encoding": "application/json" + }, + "description": "Initiate Age Assurance for an account." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/ageassurance/defs.json b/lexicons/app/bsky/ageassurance/defs.json new file mode 100644 index 0000000000..2589abfd0f --- /dev/null +++ b/lexicons/app/bsky/ageassurance/defs.json @@ -0,0 +1,333 @@ +{ + "id": "app.bsky.ageassurance.defs", + "defs": { + "event": { + "type": "object", + "required": [ + "createdAt", + "status", + "access", + "attemptId", + "countryCode" + ], + "properties": { + "email": { + "type": "string", + "description": "The email used for Age Assurance." + }, + "access": { + "type": "string", + "description": "The access level granted based on Age Assurance data we've processed.", + "knownValues": [ + "unknown", + "none", + "safe", + "full" + ] + }, + "initIp": { + "type": "string", + "description": "The IP address used when initiating the Age Assurance flow." + }, + "initUa": { + "type": "string", + "description": "The user agent used when initiating the Age Assurance flow." + }, + "status": { + "type": "string", + "description": "The status of the Age Assurance process.", + "knownValues": [ + "unknown", + "pending", + "assured", + "blocked" + ] + }, + "attemptId": { + "type": "string", + "description": "The unique identifier for this instance of the Age Assurance flow, in UUID format." + }, + "createdAt": { + "type": "string", + "format": "datetime", + "description": "The date and time of this write operation." + }, + "completeIp": { + "type": "string", + "description": "The IP address used when completing the Age Assurance flow." + }, + "completeUa": { + "type": "string", + "description": "The user agent used when completing the Age Assurance flow." + }, + "regionCode": { + "type": "string", + "description": "The ISO 3166-2 region code provided when beginning the Age Assurance flow." + }, + "countryCode": { + "type": "string", + "description": "The ISO 3166-1 alpha-2 country code provided when beginning the Age Assurance flow." + } + }, + "description": "Object used to store Age Assurance data in stash." + }, + "state": { + "type": "object", + "required": [ + "status", + "access" + ], + "properties": { + "access": { + "ref": "app.bsky.ageassurance.defs#access", + "type": "ref" + }, + "status": { + "ref": "app.bsky.ageassurance.defs#status", + "type": "ref" + }, + "lastInitiatedAt": { + "type": "string", + "format": "datetime", + "description": "The timestamp when this state was last updated." + } + }, + "description": "The user's computed Age Assurance state." + }, + "access": { + "type": "string", + "description": "The access level granted based on Age Assurance data we've processed.", + "knownValues": [ + "unknown", + "none", + "safe", + "full" + ] + }, + "config": { + "type": "object", + "required": [ + "regions" + ], + "properties": { + "regions": { + "type": "array", + "items": { + "ref": "app.bsky.ageassurance.defs#configRegion", + "type": "ref" + }, + "description": "The per-region Age Assurance configuration." + } + }, + "description": "" + }, + "status": { + "type": "string", + "description": "The status of the Age Assurance process.", + "knownValues": [ + "unknown", + "pending", + "assured", + "blocked" + ] + }, + "configRegion": { + "type": "object", + "required": [ + "countryCode", + "minAccessAge", + "rules" + ], + "properties": { + "rules": { + "type": "array", + "items": { + "refs": [ + "#configRegionRuleDefault", + "#configRegionRuleIfDeclaredOverAge", + "#configRegionRuleIfDeclaredUnderAge", + "#configRegionRuleIfAssuredOverAge", + "#configRegionRuleIfAssuredUnderAge", + "#configRegionRuleIfAccountNewerThan", + "#configRegionRuleIfAccountOlderThan" + ], + "type": "union" + }, + "description": "The ordered list of Age Assurance rules that apply to this region. Rules should be applied in order, and the first matching rule determines the access level granted. The rules array should always include a default rule as the last item." + }, + "platforms": { + "type": "array", + "items": { + "type": "string", + "knownValues": [ + "web", + "ios", + "android" + ] + }, + "description": "The platforms this configuration applies to. If omitted, the configuration applies to all platforms." + }, + "regionCode": { + "type": "string", + "description": "The ISO 3166-2 region code this configuration applies to. If omitted, the configuration applies to the entire country." + }, + "countryCode": { + "type": "string", + "description": "The ISO 3166-1 alpha-2 country code this configuration applies to." + }, + "minAccessAge": { + "type": "integer", + "description": "The minimum age (as a whole integer) required to use Bluesky in this region." + }, + "additionalVerificationMethods": { + "type": "array", + "items": { + "type": "string", + "knownValues": [ + "device" + ] + }, + "description": "Verification methods permitted in this region in addition to the third-party (KWS) flow, which is always supported. `device` permits using the native on-device age APIs (e.g. Apple Declared Age Range, Google Play Age Signals)." + } + }, + "description": "The Age Assurance configuration for a specific region." + }, + "stateMetadata": { + "type": "object", + "required": [], + "properties": { + "accountCreatedAt": { + "type": "string", + "format": "datetime", + "description": "The account creation timestamp." + } + }, + "description": "Additional metadata needed to compute Age Assurance state client-side." + }, + "configRegionRuleDefault": { + "type": "object", + "required": [ + "access" + ], + "properties": { + "access": { + "ref": "app.bsky.ageassurance.defs#access", + "type": "ref" + } + }, + "description": "Age Assurance rule that applies by default." + }, + "configRegionRuleIfAssuredOverAge": { + "type": "object", + "required": [ + "age", + "access" + ], + "properties": { + "age": { + "type": "integer", + "description": "The age threshold as a whole integer." + }, + "access": { + "ref": "app.bsky.ageassurance.defs#access", + "type": "ref" + } + }, + "description": "Age Assurance rule that applies if the user has been assured to be equal-to or over a certain age." + }, + "configRegionRuleIfAssuredUnderAge": { + "type": "object", + "required": [ + "age", + "access" + ], + "properties": { + "age": { + "type": "integer", + "description": "The age threshold as a whole integer." + }, + "access": { + "ref": "app.bsky.ageassurance.defs#access", + "type": "ref" + } + }, + "description": "Age Assurance rule that applies if the user has been assured to be under a certain age." + }, + "configRegionRuleIfDeclaredOverAge": { + "type": "object", + "required": [ + "age", + "access" + ], + "properties": { + "age": { + "type": "integer", + "description": "The age threshold as a whole integer." + }, + "access": { + "ref": "app.bsky.ageassurance.defs#access", + "type": "ref" + } + }, + "description": "Age Assurance rule that applies if the user has declared themselves equal-to or over a certain age." + }, + "configRegionRuleIfAccountNewerThan": { + "type": "object", + "required": [ + "date", + "access" + ], + "properties": { + "date": { + "type": "string", + "format": "datetime", + "description": "The date threshold as a datetime string." + }, + "access": { + "ref": "app.bsky.ageassurance.defs#access", + "type": "ref" + } + }, + "description": "Age Assurance rule that applies if the account is equal-to or newer than a certain date." + }, + "configRegionRuleIfAccountOlderThan": { + "type": "object", + "required": [ + "date", + "access" + ], + "properties": { + "date": { + "type": "string", + "format": "datetime", + "description": "The date threshold as a datetime string." + }, + "access": { + "ref": "app.bsky.ageassurance.defs#access", + "type": "ref" + } + }, + "description": "Age Assurance rule that applies if the account is older than a certain date." + }, + "configRegionRuleIfDeclaredUnderAge": { + "type": "object", + "required": [ + "age", + "access" + ], + "properties": { + "age": { + "type": "integer", + "description": "The age threshold as a whole integer." + }, + "access": { + "ref": "app.bsky.ageassurance.defs#access", + "type": "ref" + } + }, + "description": "Age Assurance rule that applies if the user has declared themselves under a certain age." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/ageassurance/getConfig.json b/lexicons/app/bsky/ageassurance/getConfig.json new file mode 100644 index 0000000000..793d5b9519 --- /dev/null +++ b/lexicons/app/bsky/ageassurance/getConfig.json @@ -0,0 +1,18 @@ +{ + "id": "app.bsky.ageassurance.getConfig", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "ref": "app.bsky.ageassurance.defs#config", + "type": "ref" + }, + "encoding": "application/json" + }, + "description": "Returns Age Assurance configuration for use on the client." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/ageassurance/getState.json b/lexicons/app/bsky/ageassurance/getState.json new file mode 100644 index 0000000000..42c7cd0ba9 --- /dev/null +++ b/lexicons/app/bsky/ageassurance/getState.json @@ -0,0 +1,45 @@ +{ + "id": "app.bsky.ageassurance.getState", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "state", + "metadata" + ], + "properties": { + "state": { + "ref": "app.bsky.ageassurance.defs#state", + "type": "ref" + }, + "metadata": { + "ref": "app.bsky.ageassurance.defs#stateMetadata", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "countryCode" + ], + "properties": { + "regionCode": { + "type": "string" + }, + "countryCode": { + "type": "string" + } + } + }, + "description": "Returns server-computed Age Assurance state, if available, and any additional metadata needed to compute Age Assurance state client-side." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/bookmark/createBookmark.json b/lexicons/app/bsky/bookmark/createBookmark.json new file mode 100644 index 0000000000..378d494a2f --- /dev/null +++ b/lexicons/app/bsky/bookmark/createBookmark.json @@ -0,0 +1,37 @@ +{ + "id": "app.bsky.bookmark.createBookmark", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "uri", + "cid" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "UnsupportedCollection", + "description": "The URI to be bookmarked is for an unsupported collection." + } + ], + "description": "Creates a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/bookmark/defs.json b/lexicons/app/bsky/bookmark/defs.json new file mode 100644 index 0000000000..f499e55cd5 --- /dev/null +++ b/lexicons/app/bsky/bookmark/defs.json @@ -0,0 +1,47 @@ +{ + "id": "app.bsky.bookmark.defs", + "defs": { + "bookmark": { + "type": "object", + "required": [ + "subject" + ], + "properties": { + "subject": { + "ref": "com.atproto.repo.strongRef", + "type": "ref", + "description": "A strong ref to the record to be bookmarked. Currently, only `app.bsky.feed.post` records are supported." + } + }, + "description": "Object used to store bookmark data in stash." + }, + "bookmarkView": { + "type": "object", + "required": [ + "subject", + "item" + ], + "properties": { + "item": { + "refs": [ + "app.bsky.feed.defs#blockedPost", + "app.bsky.feed.defs#notFoundPost", + "app.bsky.feed.defs#postView" + ], + "type": "union" + }, + "subject": { + "ref": "com.atproto.repo.strongRef", + "type": "ref", + "description": "A strong ref to the bookmarked record." + }, + "createdAt": { + "type": "string", + "format": "datetime" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/bookmark/deleteBookmark.json b/lexicons/app/bsky/bookmark/deleteBookmark.json new file mode 100644 index 0000000000..52d5c3c2f5 --- /dev/null +++ b/lexicons/app/bsky/bookmark/deleteBookmark.json @@ -0,0 +1,32 @@ +{ + "id": "app.bsky.bookmark.deleteBookmark", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "uri" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "UnsupportedCollection", + "description": "The URI to be bookmarked is for an unsupported collection." + } + ], + "description": "Deletes a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/bookmark/getBookmarks.json b/lexicons/app/bsky/bookmark/getBookmarks.json new file mode 100644 index 0000000000..301914d4c2 --- /dev/null +++ b/lexicons/app/bsky/bookmark/getBookmarks.json @@ -0,0 +1,46 @@ +{ + "id": "app.bsky.bookmark.getBookmarks", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "bookmarks" + ], + "properties": { + "cursor": { + "type": "string" + }, + "bookmarks": { + "type": "array", + "items": { + "ref": "app.bsky.bookmark.defs#bookmarkView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Gets views of records bookmarked by the authenticated user. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/contact/defs.json b/lexicons/app/bsky/contact/defs.json new file mode 100644 index 0000000000..68f0179fb3 --- /dev/null +++ b/lexicons/app/bsky/contact/defs.json @@ -0,0 +1,67 @@ +{ + "id": "app.bsky.contact.defs", + "defs": { + "syncStatus": { + "type": "object", + "required": [ + "syncedAt", + "matchesCount" + ], + "properties": { + "syncedAt": { + "type": "string", + "format": "datetime", + "description": "Last date when contacts where imported." + }, + "matchesCount": { + "type": "integer", + "minimum": 0, + "description": "Number of existing contact matches resulting of the user imports and of their imported contacts having imported the user. Matches stop being counted when the user either follows the matched contact or dismisses the match." + } + } + }, + "notification": { + "type": "object", + "required": [ + "from", + "to" + ], + "properties": { + "to": { + "type": "string", + "format": "did", + "description": "The DID of who this notification should go to." + }, + "from": { + "type": "string", + "format": "did", + "description": "The DID of who this notification comes from." + } + }, + "description": "A stash object to be sent via bsync representing a notification to be created." + }, + "matchAndContactIndex": { + "type": "object", + "required": [ + "match", + "contactIndex" + ], + "properties": { + "match": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref", + "description": "Profile of the matched user." + }, + "contactIndex": { + "type": "integer", + "maximum": 999, + "minimum": 0, + "description": "The index of this match in the import contact input." + } + }, + "description": "Associates a profile with the positional index of the contact import input in the call to `app.bsky.contact.importContacts`, so clients can know which phone caused a particular match." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/contact/dismissMatch.json b/lexicons/app/bsky/contact/dismissMatch.json new file mode 100644 index 0000000000..27cb2b3d7a --- /dev/null +++ b/lexicons/app/bsky/contact/dismissMatch.json @@ -0,0 +1,42 @@ +{ + "id": "app.bsky.contact.dismissMatch", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "subject" + ], + "properties": { + "subject": { + "type": "string", + "format": "did", + "description": "The subject's DID to dismiss the match with." + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidDid" + }, + { + "name": "InternalError" + } + ], + "output": { + "schema": { + "type": "object", + "properties": {} + }, + "encoding": "application/json" + }, + "description": "Removes a match that was found via contact import. It shouldn't appear again if the same contact is re-imported. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/contact/getMatches.json b/lexicons/app/bsky/contact/getMatches.json new file mode 100644 index 0000000000..cce3cac464 --- /dev/null +++ b/lexicons/app/bsky/contact/getMatches.json @@ -0,0 +1,60 @@ +{ + "id": "app.bsky.contact.getMatches", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "InvalidDid" + }, + { + "name": "InvalidLimit" + }, + { + "name": "InvalidCursor" + }, + { + "name": "InternalError" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "matches" + ], + "properties": { + "cursor": { + "type": "string" + }, + "matches": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Returns the matched contacts (contacts that were mutually imported). Excludes dismissed matches. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/contact/getSyncStatus.json b/lexicons/app/bsky/contact/getSyncStatus.json new file mode 100644 index 0000000000..53dc538f78 --- /dev/null +++ b/lexicons/app/bsky/contact/getSyncStatus.json @@ -0,0 +1,36 @@ +{ + "id": "app.bsky.contact.getSyncStatus", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "InvalidDid" + }, + { + "name": "InternalError" + } + ], + "output": { + "schema": { + "type": "object", + "properties": { + "syncStatus": { + "ref": "app.bsky.contact.defs#syncStatus", + "type": "ref", + "description": "If present, indicates the user has imported their contacts. If not present, indicates the user never used the feature or called `app.bsky.contact.removeData` and didn't import again since." + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": {} + }, + "description": "Gets the user's current contact import status. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/contact/importContacts.json b/lexicons/app/bsky/contact/importContacts.json new file mode 100644 index 0000000000..70092e2583 --- /dev/null +++ b/lexicons/app/bsky/contact/importContacts.json @@ -0,0 +1,72 @@ +{ + "id": "app.bsky.contact.importContacts", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "token", + "contacts" + ], + "properties": { + "token": { + "type": "string", + "description": "JWT to authenticate the call. Use the JWT received as a response to the call to `app.bsky.contact.verifyPhone`." + }, + "contacts": { + "type": "array", + "items": { + "type": "string" + }, + "maxLength": 1000, + "minLength": 1, + "description": "List of phone numbers in global E.164 format (e.g., '+12125550123'). Phone numbers that cannot be normalized into a valid phone number will be discarded. Should not repeat the 'phone' input used in `app.bsky.contact.verifyPhone`." + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidDid" + }, + { + "name": "InvalidContacts" + }, + { + "name": "TooManyContacts" + }, + { + "name": "InvalidToken" + }, + { + "name": "InternalError" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "matchesAndContactIndexes" + ], + "properties": { + "matchesAndContactIndexes": { + "type": "array", + "items": { + "ref": "app.bsky.contact.defs#matchAndContactIndex", + "type": "ref" + }, + "description": "The users that matched during import and their indexes on the input contacts, so the client can correlate with its local list." + } + } + }, + "encoding": "application/json" + }, + "description": "Import contacts for securely matching with other users. This follows the protocol explained in https://docs.bsky.app/blog/contact-import-rfc. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/contact/removeData.json b/lexicons/app/bsky/contact/removeData.json new file mode 100644 index 0000000000..de8a938336 --- /dev/null +++ b/lexicons/app/bsky/contact/removeData.json @@ -0,0 +1,33 @@ +{ + "id": "app.bsky.contact.removeData", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "properties": {} + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidDid" + }, + { + "name": "InternalError" + } + ], + "output": { + "schema": { + "type": "object", + "properties": {} + }, + "encoding": "application/json" + }, + "description": "Removes all stored hashes used for contact matching, existing matches, and sync status. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/contact/sendNotification.json b/lexicons/app/bsky/contact/sendNotification.json new file mode 100644 index 0000000000..400120ce6f --- /dev/null +++ b/lexicons/app/bsky/contact/sendNotification.json @@ -0,0 +1,40 @@ +{ + "id": "app.bsky.contact.sendNotification", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "from", + "to" + ], + "properties": { + "to": { + "type": "string", + "format": "did", + "description": "The DID of who this notification should go to." + }, + "from": { + "type": "string", + "format": "did", + "description": "The DID of who this notification comes from." + } + } + }, + "encoding": "application/json" + }, + "output": { + "schema": { + "type": "object", + "properties": {} + }, + "encoding": "application/json" + }, + "description": "System endpoint to send notifications related to contact imports. Requires role authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/contact/startPhoneVerification.json b/lexicons/app/bsky/contact/startPhoneVerification.json new file mode 100644 index 0000000000..d2dcad8769 --- /dev/null +++ b/lexicons/app/bsky/contact/startPhoneVerification.json @@ -0,0 +1,47 @@ +{ + "id": "app.bsky.contact.startPhoneVerification", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "phone" + ], + "properties": { + "phone": { + "type": "string", + "description": "The phone number to receive the code via SMS." + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "RateLimitExceeded" + }, + { + "name": "InvalidDid" + }, + { + "name": "InvalidPhone" + }, + { + "name": "InternalError" + } + ], + "output": { + "schema": { + "type": "object", + "properties": {} + }, + "encoding": "application/json" + }, + "description": "Starts a phone verification flow. The phone passed will receive a code via SMS that should be passed to `app.bsky.contact.verifyPhone`. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/contact/verifyPhone.json b/lexicons/app/bsky/contact/verifyPhone.json new file mode 100644 index 0000000000..20386c25f6 --- /dev/null +++ b/lexicons/app/bsky/contact/verifyPhone.json @@ -0,0 +1,63 @@ +{ + "id": "app.bsky.contact.verifyPhone", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "phone", + "code" + ], + "properties": { + "code": { + "type": "string", + "description": "The code received via SMS as a result of the call to `app.bsky.contact.startPhoneVerification`." + }, + "phone": { + "type": "string", + "description": "The phone number to verify. Should be the same as the one passed to `app.bsky.contact.startPhoneVerification`." + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "RateLimitExceeded" + }, + { + "name": "InvalidDid" + }, + { + "name": "InvalidPhone" + }, + { + "name": "InvalidCode" + }, + { + "name": "InternalError" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "string", + "description": "JWT to be used in a call to `app.bsky.contact.importContacts`. It is only valid for a single call." + } + } + }, + "encoding": "application/json" + }, + "description": "Verifies control over a phone number with a code received via SMS and starts a contact import session. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/draft/createDraft.json b/lexicons/app/bsky/draft/createDraft.json new file mode 100644 index 0000000000..d3c5c830bf --- /dev/null +++ b/lexicons/app/bsky/draft/createDraft.json @@ -0,0 +1,48 @@ +{ + "id": "app.bsky.draft.createDraft", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "draft" + ], + "properties": { + "draft": { + "ref": "app.bsky.draft.defs#draft", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "DraftLimitReached", + "description": "Trying to insert a new draft when the limit was already reached." + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "format": "tid", + "description": "The ID of the created draft." + } + } + }, + "encoding": "application/json" + }, + "description": "Inserts a draft using private storage (stash). An upper limit of drafts might be enforced. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/draft/defs.json b/lexicons/app/bsky/draft/defs.json new file mode 100644 index 0000000000..507432944d --- /dev/null +++ b/lexicons/app/bsky/draft/defs.json @@ -0,0 +1,296 @@ +{ + "id": "app.bsky.draft.defs", + "defs": { + "draft": { + "type": "object", + "required": [ + "posts" + ], + "properties": { + "langs": { + "type": "array", + "items": { + "type": "string", + "format": "language" + }, + "maxLength": 3, + "description": "Indicates human language of posts primary text content." + }, + "posts": { + "type": "array", + "items": { + "ref": "#draftPost", + "type": "ref" + }, + "maxLength": 100, + "minLength": 1, + "description": "Array of draft posts that compose this draft." + }, + "deviceId": { + "type": "string", + "maxLength": 100, + "description": "UUIDv4 identifier of the device that created this draft." + }, + "deviceName": { + "type": "string", + "maxLength": 100, + "description": "The device and/or platform on which the draft was created." + }, + "threadgateAllow": { + "type": "array", + "items": { + "refs": [ + "app.bsky.feed.threadgate#mentionRule", + "app.bsky.feed.threadgate#followerRule", + "app.bsky.feed.threadgate#followingRule", + "app.bsky.feed.threadgate#listRule" + ], + "type": "union" + }, + "maxLength": 5, + "description": "Allow-rules for the threadgate to be created when this draft is published." + }, + "postgateEmbeddingRules": { + "type": "array", + "items": { + "refs": [ + "app.bsky.feed.postgate#disableRule" + ], + "type": "union" + }, + "maxLength": 5, + "description": "Embedding rules for the postgates to be created when this draft is published." + } + }, + "description": "A draft containing an array of draft posts." + }, + "draftPost": { + "type": "object", + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string", + "maxLength": 10000, + "description": "The primary post content. It has a higher limit than post contents to allow storing a larger text that can later be refined into smaller posts.", + "maxGraphemes": 1000 + }, + "labels": { + "refs": [ + "com.atproto.label.defs#selfLabels" + ], + "type": "union", + "description": "Self-label values for this post. Effectively content warnings." + }, + "embedImages": { + "type": "array", + "items": { + "ref": "#draftEmbedImage", + "type": "ref" + }, + "maxLength": 4 + }, + "embedVideos": { + "type": "array", + "items": { + "ref": "#draftEmbedVideo", + "type": "ref" + }, + "maxLength": 1 + }, + "embedGallery": { + "ref": "#draftEmbedGallery", + "type": "ref" + }, + "embedRecords": { + "type": "array", + "items": { + "ref": "#draftEmbedRecord", + "type": "ref" + }, + "maxLength": 1 + }, + "embedExternals": { + "type": "array", + "items": { + "ref": "#draftEmbedExternal", + "type": "ref" + }, + "maxLength": 1 + } + }, + "description": "One of the posts that compose a draft." + }, + "draftView": { + "type": "object", + "required": [ + "id", + "draft", + "createdAt", + "updatedAt" + ], + "properties": { + "id": { + "type": "string", + "format": "tid", + "description": "A TID to be used as a draft identifier." + }, + "draft": { + "ref": "#draft", + "type": "ref" + }, + "createdAt": { + "type": "string", + "format": "datetime", + "description": "The time the draft was created." + }, + "updatedAt": { + "type": "string", + "format": "datetime", + "description": "The time the draft was last updated." + } + }, + "description": "View to present drafts data to users." + }, + "draftWithId": { + "type": "object", + "required": [ + "id", + "draft" + ], + "properties": { + "id": { + "type": "string", + "format": "tid", + "description": "A TID to be used as a draft identifier." + }, + "draft": { + "ref": "#draft", + "type": "ref" + } + }, + "description": "A draft with an identifier, used to store drafts in private storage (stash)." + }, + "draftEmbedImage": { + "type": "object", + "required": [ + "localRef" + ], + "properties": { + "alt": { + "type": "string", + "maxGraphemes": 2000 + }, + "localRef": { + "ref": "#draftEmbedLocalRef", + "type": "ref" + } + } + }, + "draftEmbedVideo": { + "type": "object", + "required": [ + "localRef" + ], + "properties": { + "alt": { + "type": "string", + "maxGraphemes": 2000 + }, + "captions": { + "type": "array", + "items": { + "ref": "#draftEmbedCaption", + "type": "ref" + }, + "maxLength": 20 + }, + "localRef": { + "ref": "#draftEmbedLocalRef", + "type": "ref" + } + } + }, + "draftEmbedRecord": { + "type": "object", + "required": [ + "record" + ], + "properties": { + "record": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + } + } + }, + "draftEmbedCaption": { + "type": "object", + "required": [ + "lang", + "content" + ], + "properties": { + "lang": { + "type": "string", + "format": "language" + }, + "content": { + "type": "string", + "maxLength": 10000 + } + } + }, + "draftEmbedGallery": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "ref": "#draftEmbedGalleryItems", + "type": "ref" + } + } + }, + "draftEmbedExternal": { + "type": "object", + "required": [ + "uri" + ], + "properties": { + "uri": { + "type": "string", + "format": "uri" + } + } + }, + "draftEmbedLocalRef": { + "type": "object", + "required": [ + "path" + ], + "properties": { + "path": { + "type": "string", + "maxLength": 1024, + "minLength": 1, + "description": "Local, on-device ref to file to be embedded. Embeds are currently device-bound for drafts." + } + } + }, + "draftEmbedGalleryItems": { + "type": "array", + "items": { + "refs": [ + "#draftEmbedImage" + ], + "type": "union" + }, + "maxLength": 20, + "description": "The schema-level maxLength of 20 is a future-proof ceiling. Clients should currently enforce a soft limit of 10 items in authoring UIs." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/draft/deleteDraft.json b/lexicons/app/bsky/draft/deleteDraft.json new file mode 100644 index 0000000000..a88f3d48d2 --- /dev/null +++ b/lexicons/app/bsky/draft/deleteDraft.json @@ -0,0 +1,26 @@ +{ + "id": "app.bsky.draft.deleteDraft", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "id" + ], + "properties": { + "id": { + "type": "string", + "format": "tid" + } + } + }, + "encoding": "application/json" + }, + "description": "Deletes a draft by ID. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/draft/getDrafts.json b/lexicons/app/bsky/draft/getDrafts.json new file mode 100644 index 0000000000..aba35d59f8 --- /dev/null +++ b/lexicons/app/bsky/draft/getDrafts.json @@ -0,0 +1,46 @@ +{ + "id": "app.bsky.draft.getDrafts", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "drafts" + ], + "properties": { + "cursor": { + "type": "string" + }, + "drafts": { + "type": "array", + "items": { + "ref": "app.bsky.draft.defs#draftView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Gets views of user drafts. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/draft/updateDraft.json b/lexicons/app/bsky/draft/updateDraft.json new file mode 100644 index 0000000000..a99f331e1b --- /dev/null +++ b/lexicons/app/bsky/draft/updateDraft.json @@ -0,0 +1,26 @@ +{ + "id": "app.bsky.draft.updateDraft", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "draft" + ], + "properties": { + "draft": { + "ref": "app.bsky.draft.defs#draftWithId", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Updates a draft using private storage (stash). If the draft ID points to a non-existing ID, the update will be silently ignored. This is done because updates don't enforce draft limit, so it accepts all writes, but will ignore invalid ones. Requires authentication." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/embed/defs.json b/lexicons/app/bsky/embed/defs.json new file mode 100644 index 0000000000..9aff68864c --- /dev/null +++ b/lexicons/app/bsky/embed/defs.json @@ -0,0 +1,25 @@ +{ + "id": "app.bsky.embed.defs", + "defs": { + "aspectRatio": { + "type": "object", + "required": [ + "width", + "height" + ], + "properties": { + "width": { + "type": "integer", + "minimum": 1 + }, + "height": { + "type": "integer", + "minimum": 1 + } + }, + "description": "width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/embed/external.json b/lexicons/app/bsky/embed/external.json new file mode 100644 index 0000000000..d4bd886490 --- /dev/null +++ b/lexicons/app/bsky/embed/external.json @@ -0,0 +1,210 @@ +{ + "id": "app.bsky.embed.external", + "defs": { + "main": { + "type": "object", + "required": [ + "external" + ], + "properties": { + "external": { + "ref": "#external", + "type": "ref" + } + }, + "description": "A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post)." + }, + "view": { + "type": "object", + "required": [ + "external" + ], + "properties": { + "external": { + "ref": "#viewExternal", + "type": "ref" + } + } + }, + "colorRGB": { + "type": "object", + "required": [ + "r", + "g", + "b" + ], + "properties": { + "b": { + "type": "integer", + "maximum": 255, + "minimum": 0 + }, + "g": { + "type": "integer", + "maximum": 255, + "minimum": 0 + }, + "r": { + "type": "integer", + "maximum": 255, + "minimum": 0 + } + }, + "description": "RGB color definition, inspired by site.standard.theme.color#rgb" + }, + "external": { + "type": "object", + "required": [ + "uri", + "title", + "description" + ], + "properties": { + "uri": { + "type": "string", + "format": "uri" + }, + "thumb": { + "type": "blob", + "accept": [ + "image/*" + ], + "maxSize": 1000000 + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "associatedRefs": { + "type": "array", + "items": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "description": "StrongRefs (uri+cid) of the Atmosphere records that backed this view." + } + } + }, + "viewExternal": { + "type": "object", + "required": [ + "uri", + "title", + "description" + ], + "properties": { + "uri": { + "type": "string", + "format": "uri" + }, + "thumb": { + "type": "string", + "format": "uri" + }, + "title": { + "type": "string" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "source": { + "ref": "#viewExternalSource", + "type": "ref" + }, + "createdAt": { + "type": "string", + "format": "datetime", + "description": "When the external content was created, if available. Example: a publication date, for an article." + }, + "updatedAt": { + "type": "string", + "format": "datetime", + "description": "When the external content was updated, if available." + }, + "description": { + "type": "string" + }, + "readingTime": { + "type": "integer", + "description": "Estimated reading time in minutes, if applicable and available." + }, + "associatedRefs": { + "type": "array", + "items": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "description": "StrongRefs (uri+cid) of the Atmosphere records that backed this view." + }, + "associatedProfiles": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "description": "Profiles of the owners of the Atmosphere records that backed this view." + } + } + }, + "viewExternalSource": { + "type": "object", + "required": [ + "uri", + "title" + ], + "properties": { + "uri": { + "type": "string", + "format": "uri", + "description": "URI of the source, if available. Example: the https:// URL of a site.standard.publication record." + }, + "icon": { + "type": "string", + "format": "uri", + "description": "Fully-qualified URL where an icon representing the source can be fetched. For example, CDN location provided by the App View." + }, + "theme": { + "ref": "#viewExternalSourceTheme", + "type": "ref" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "description": "The source of an external embed, such as a standard.site publication." + }, + "viewExternalSourceTheme": { + "type": "object", + "properties": { + "accentRGB": { + "ref": "#colorRGB", + "type": "ref" + }, + "backgroundRGB": { + "ref": "#colorRGB", + "type": "ref" + }, + "foregroundRGB": { + "ref": "#colorRGB", + "type": "ref" + }, + "accentForegroundRGB": { + "ref": "#colorRGB", + "type": "ref" + } + }, + "description": "The theme colors of an external source, such as a site.standard.publication. These colors may be used when rendering an embed from that source." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/embed/gallery.json b/lexicons/app/bsky/embed/gallery.json new file mode 100644 index 0000000000..39f8a3024e --- /dev/null +++ b/lexicons/app/bsky/embed/gallery.json @@ -0,0 +1,99 @@ +{ + "id": "app.bsky.embed.gallery", + "defs": { + "main": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "refs": [ + "#image" + ], + "type": "union", + "description": "The media items in the gallery. Each item may be of a different type, but all types must be supported by the client." + }, + "maxLength": 20, + "description": "The schema-level maxLength of 20 is a future-proof ceiling. Clients should currently enforce a soft limit of 10 items in authoring UIs." + } + } + }, + "view": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "refs": [ + "#viewImage" + ], + "type": "union" + } + } + } + }, + "image": { + "type": "object", + "required": [ + "image", + "alt", + "aspectRatio" + ], + "properties": { + "alt": { + "type": "string", + "description": "Alt text description of the image, for accessibility." + }, + "image": { + "type": "blob", + "accept": [ + "image/*" + ], + "maxSize": 2000000 + }, + "aspectRatio": { + "ref": "app.bsky.embed.defs#aspectRatio", + "type": "ref" + } + } + }, + "viewImage": { + "type": "object", + "required": [ + "thumbnail", + "fullsize", + "alt", + "aspectRatio" + ], + "properties": { + "alt": { + "type": "string", + "description": "Alt text description of the image, for accessibility." + }, + "fullsize": { + "type": "string", + "format": "uri", + "description": "Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View." + }, + "thumbnail": { + "type": "string", + "format": "uri", + "description": "Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View." + }, + "aspectRatio": { + "ref": "app.bsky.embed.defs#aspectRatio", + "type": "ref" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "description": "An assortment of media embedded in a Bluesky record (eg, a post)." +} diff --git a/lexicons/app/bsky/embed/getEmbedExternalView.json b/lexicons/app/bsky/embed/getEmbedExternalView.json new file mode 100644 index 0000000000..7b60eb3fc9 --- /dev/null +++ b/lexicons/app/bsky/embed/getEmbedExternalView.json @@ -0,0 +1,62 @@ +{ + "id": "app.bsky.embed.getEmbedExternalView", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "properties": { + "view": { + "ref": "app.bsky.embed.external#view", + "type": "ref", + "description": "Hydrated view of the embed. Present only when the resolved records back the requested URL and supply enough information to populate the required `viewExternal` fields. Omitted alongside the rest of the response when no records resolved or validation failed." + }, + "associatedRefs": { + "type": "array", + "items": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "description": "StrongRefs (URI+CID) of the Atmosphere records that backed this view, suitable for embedding into a post's external.associatedRefs." + }, + "associatedRecords": { + "type": "array", + "items": { + "type": "unknown", + "description": "The raw record data of the Atmosphere records that backed this view. This is returned for convenience, to avoid the need for the client to separately fetch the record data for the associatedRefs. Example: the site.standard.document and site.standard.publication records that backed this view." + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "url", + "uris" + ], + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "The canonical web URL the embed represents (typically the URL the user pasted into the composer). Used as the returned view's `uri`. May be used for validation in the future." + }, + "uris": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + }, + "maxLength": 4, + "description": "AT-URIs of any Atmosphere records that can be resolved and used to construct #externalView views. Example: a site.standard.document and optionally its associated site.standard.publication." + } + } + }, + "description": "Resolve one or more AT-URIs into the data needed to render an enhanced external embed. Returns `associatedRefs` (strongRefs to embed into a post's external.associatedRefs), the raw `associatedRecords`, and a hydrated `view`. The response is empty (`{}`) when no records were resolvable, or when validation determined the resolved records don't actually back the requested URL; clients should fall back to their own link-card rendering in that case and skip writing strongRefs to the post." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/embed/images.json b/lexicons/app/bsky/embed/images.json new file mode 100644 index 0000000000..7f9c30c263 --- /dev/null +++ b/lexicons/app/bsky/embed/images.json @@ -0,0 +1,93 @@ +{ + "id": "app.bsky.embed.images", + "defs": { + "main": { + "type": "object", + "required": [ + "images" + ], + "properties": { + "images": { + "type": "array", + "items": { + "ref": "#image", + "type": "ref" + }, + "maxLength": 4 + } + } + }, + "view": { + "type": "object", + "required": [ + "images" + ], + "properties": { + "images": { + "type": "array", + "items": { + "ref": "#viewImage", + "type": "ref" + }, + "maxLength": 4 + } + } + }, + "image": { + "type": "object", + "required": [ + "image", + "alt" + ], + "properties": { + "alt": { + "type": "string", + "description": "Alt text description of the image, for accessibility." + }, + "image": { + "type": "blob", + "accept": [ + "image/*" + ], + "maxSize": 2000000, + "description": "The raw image file. May be up to 2 MB, formerly limited to 1 MB." + }, + "aspectRatio": { + "ref": "app.bsky.embed.defs#aspectRatio", + "type": "ref" + } + } + }, + "viewImage": { + "type": "object", + "required": [ + "thumb", + "fullsize", + "alt" + ], + "properties": { + "alt": { + "type": "string", + "description": "Alt text description of the image, for accessibility." + }, + "thumb": { + "type": "string", + "format": "uri", + "description": "Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View." + }, + "fullsize": { + "type": "string", + "format": "uri", + "description": "Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View." + }, + "aspectRatio": { + "ref": "app.bsky.embed.defs#aspectRatio", + "type": "ref" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "description": "A set of images embedded in a Bluesky record (eg, a post)." +} diff --git a/lexicons/app/bsky/embed/record.json b/lexicons/app/bsky/embed/record.json new file mode 100644 index 0000000000..3aef654207 --- /dev/null +++ b/lexicons/app/bsky/embed/record.json @@ -0,0 +1,162 @@ +{ + "id": "app.bsky.embed.record", + "defs": { + "main": { + "type": "object", + "required": [ + "record" + ], + "properties": { + "record": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + } + } + }, + "view": { + "type": "object", + "required": [ + "record" + ], + "properties": { + "record": { + "refs": [ + "#viewRecord", + "#viewNotFound", + "#viewBlocked", + "#viewDetached", + "app.bsky.feed.defs#generatorView", + "app.bsky.graph.defs#listView", + "app.bsky.labeler.defs#labelerView", + "app.bsky.graph.defs#starterPackViewBasic" + ], + "type": "union" + } + } + }, + "viewRecord": { + "type": "object", + "required": [ + "uri", + "cid", + "author", + "value", + "indexedAt" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "value": { + "type": "unknown", + "description": "The record data itself." + }, + "author": { + "ref": "app.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "embeds": { + "type": "array", + "items": { + "refs": [ + "app.bsky.embed.images#view", + "app.bsky.embed.video#view", + "app.bsky.embed.gallery#view", + "app.bsky.embed.external#view", + "app.bsky.embed.record#view", + "app.bsky.embed.recordWithMedia#view" + ], + "type": "union" + } + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "indexedAt": { + "type": "string", + "format": "datetime" + }, + "likeCount": { + "type": "integer" + }, + "quoteCount": { + "type": "integer" + }, + "replyCount": { + "type": "integer" + }, + "repostCount": { + "type": "integer" + } + } + }, + "viewBlocked": { + "type": "object", + "required": [ + "uri", + "blocked", + "author" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "author": { + "ref": "app.bsky.feed.defs#blockedAuthor", + "type": "ref" + }, + "blocked": { + "type": "boolean", + "const": true + } + } + }, + "viewDetached": { + "type": "object", + "required": [ + "uri", + "detached" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "detached": { + "type": "boolean", + "const": true + } + } + }, + "viewNotFound": { + "type": "object", + "required": [ + "uri", + "notFound" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "notFound": { + "type": "boolean", + "const": true + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "description": "A representation of a record embedded in a Bluesky record (eg, a post). For example, a quote-post, or sharing a feed generator record." +} diff --git a/lexicons/app/bsky/embed/recordWithMedia.json b/lexicons/app/bsky/embed/recordWithMedia.json new file mode 100644 index 0000000000..76da14a364 --- /dev/null +++ b/lexicons/app/bsky/embed/recordWithMedia.json @@ -0,0 +1,52 @@ +{ + "id": "app.bsky.embed.recordWithMedia", + "defs": { + "main": { + "type": "object", + "required": [ + "record", + "media" + ], + "properties": { + "media": { + "refs": [ + "app.bsky.embed.images", + "app.bsky.embed.video", + "app.bsky.embed.gallery", + "app.bsky.embed.external" + ], + "type": "union" + }, + "record": { + "ref": "app.bsky.embed.record", + "type": "ref" + } + } + }, + "view": { + "type": "object", + "required": [ + "record", + "media" + ], + "properties": { + "media": { + "refs": [ + "app.bsky.embed.images#view", + "app.bsky.embed.video#view", + "app.bsky.embed.gallery#view", + "app.bsky.embed.external#view" + ], + "type": "union" + }, + "record": { + "ref": "app.bsky.embed.record#view", + "type": "ref" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "description": "A representation of a record embedded in a Bluesky record (eg, a post), alongside other compatible embeds. For example, a quote post and image, or a quote post and external URL card." +} diff --git a/lexicons/app/bsky/embed/video.json b/lexicons/app/bsky/embed/video.json new file mode 100644 index 0000000000..67278da9c2 --- /dev/null +++ b/lexicons/app/bsky/embed/video.json @@ -0,0 +1,108 @@ +{ + "id": "app.bsky.embed.video", + "defs": { + "main": { + "type": "object", + "required": [ + "video" + ], + "properties": { + "alt": { + "type": "string", + "maxLength": 10000, + "description": "Alt text description of the video, for accessibility.", + "maxGraphemes": 1000 + }, + "video": { + "type": "blob", + "accept": [ + "video/mp4" + ], + "maxSize": 100000000, + "description": "The mp4 video file. May be up to 100mb, formerly limited to 50mb." + }, + "captions": { + "type": "array", + "items": { + "ref": "#caption", + "type": "ref" + }, + "maxLength": 20 + }, + "aspectRatio": { + "ref": "app.bsky.embed.defs#aspectRatio", + "type": "ref" + }, + "presentation": { + "type": "string", + "description": "A hint to the client about how to present the video.", + "knownValues": [ + "default", + "gif" + ] + } + } + }, + "view": { + "type": "object", + "required": [ + "cid", + "playlist" + ], + "properties": { + "alt": { + "type": "string", + "maxLength": 10000, + "maxGraphemes": 1000 + }, + "cid": { + "type": "string", + "format": "cid" + }, + "playlist": { + "type": "string", + "format": "uri" + }, + "thumbnail": { + "type": "string", + "format": "uri" + }, + "aspectRatio": { + "ref": "app.bsky.embed.defs#aspectRatio", + "type": "ref" + }, + "presentation": { + "type": "string", + "description": "A hint to the client about how to present the video.", + "knownValues": [ + "default", + "gif" + ] + } + } + }, + "caption": { + "type": "object", + "required": [ + "lang", + "file" + ], + "properties": { + "file": { + "type": "blob", + "accept": [ + "text/vtt" + ], + "maxSize": 20000 + }, + "lang": { + "type": "string", + "format": "language" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "description": "A video embedded in a Bluesky record (eg, a post)." +} diff --git a/lexicons/app/bsky/feed/defs.json b/lexicons/app/bsky/feed/defs.json new file mode 100644 index 0000000000..436782d4ec --- /dev/null +++ b/lexicons/app/bsky/feed/defs.json @@ -0,0 +1,545 @@ +{ + "id": "app.bsky.feed.defs", + "defs": { + "postView": { + "type": "object", + "required": [ + "uri", + "cid", + "author", + "record", + "indexedAt" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "debug": { + "type": "unknown", + "description": "Debug information for internal development" + }, + "embed": { + "refs": [ + "app.bsky.embed.images#view", + "app.bsky.embed.video#view", + "app.bsky.embed.gallery#view", + "app.bsky.embed.external#view", + "app.bsky.embed.record#view", + "app.bsky.embed.recordWithMedia#view" + ], + "type": "union" + }, + "author": { + "ref": "app.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "record": { + "type": "unknown" + }, + "viewer": { + "ref": "#viewerState", + "type": "ref" + }, + "indexedAt": { + "type": "string", + "format": "datetime" + }, + "likeCount": { + "type": "integer" + }, + "quoteCount": { + "type": "integer" + }, + "replyCount": { + "type": "integer" + }, + "threadgate": { + "ref": "#threadgateView", + "type": "ref" + }, + "repostCount": { + "type": "integer" + }, + "bookmarkCount": { + "type": "integer" + } + } + }, + "replyRef": { + "type": "object", + "required": [ + "root", + "parent" + ], + "properties": { + "root": { + "refs": [ + "#postView", + "#notFoundPost", + "#blockedPost" + ], + "type": "union" + }, + "parent": { + "refs": [ + "#postView", + "#notFoundPost", + "#blockedPost" + ], + "type": "union" + }, + "grandparentAuthor": { + "ref": "app.bsky.actor.defs#profileViewBasic", + "type": "ref", + "description": "When parent is a reply to another post, this is the author of that post." + } + } + }, + "reasonPin": { + "type": "object", + "properties": {} + }, + "blockedPost": { + "type": "object", + "required": [ + "uri", + "blocked", + "author" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "author": { + "ref": "#blockedAuthor", + "type": "ref" + }, + "blocked": { + "type": "boolean", + "const": true + } + } + }, + "interaction": { + "type": "object", + "properties": { + "item": { + "type": "string", + "format": "at-uri" + }, + "event": { + "type": "string", + "knownValues": [ + "app.bsky.feed.defs#requestLess", + "app.bsky.feed.defs#requestMore", + "app.bsky.feed.defs#clickthroughItem", + "app.bsky.feed.defs#clickthroughAuthor", + "app.bsky.feed.defs#clickthroughReposter", + "app.bsky.feed.defs#clickthroughEmbed", + "app.bsky.feed.defs#interactionSeen", + "app.bsky.feed.defs#interactionLike", + "app.bsky.feed.defs#interactionRepost", + "app.bsky.feed.defs#interactionReply", + "app.bsky.feed.defs#interactionQuote", + "app.bsky.feed.defs#interactionShare" + ] + }, + "reqId": { + "type": "string", + "maxLength": 100, + "description": "Unique identifier per request that may be passed back alongside interactions." + }, + "feedContext": { + "type": "string", + "maxLength": 2000, + "description": "Context on a feed item that was originally supplied by the feed generator on getFeedSkeleton." + } + } + }, + "requestLess": { + "type": "token", + "description": "Request that less content like the given feed item be shown in the feed" + }, + "requestMore": { + "type": "token", + "description": "Request that more content like the given feed item be shown in the feed" + }, + "viewerState": { + "type": "object", + "properties": { + "like": { + "type": "string", + "format": "at-uri" + }, + "pinned": { + "type": "boolean" + }, + "repost": { + "type": "string", + "format": "at-uri" + }, + "bookmarked": { + "type": "boolean" + }, + "threadMuted": { + "type": "boolean" + }, + "replyDisabled": { + "type": "boolean" + }, + "embeddingDisabled": { + "type": "boolean" + } + }, + "description": "Metadata about the requesting account's relationship with the subject content. Only has meaningful content for authed requests." + }, + "feedViewPost": { + "type": "object", + "required": [ + "post" + ], + "properties": { + "post": { + "ref": "#postView", + "type": "ref" + }, + "reply": { + "ref": "#replyRef", + "type": "ref" + }, + "reqId": { + "type": "string", + "maxLength": 100, + "description": "Unique identifier per request that may be passed back alongside interactions." + }, + "reason": { + "refs": [ + "#reasonRepost", + "#reasonPin" + ], + "type": "union" + }, + "feedContext": { + "type": "string", + "maxLength": 2000, + "description": "Context provided by feed generator that may be passed back alongside interactions." + } + } + }, + "notFoundPost": { + "type": "object", + "required": [ + "uri", + "notFound" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "notFound": { + "type": "boolean", + "const": true + } + } + }, + "reasonRepost": { + "type": "object", + "required": [ + "by", + "indexedAt" + ], + "properties": { + "by": { + "ref": "app.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "indexedAt": { + "type": "string", + "format": "datetime" + } + } + }, + "blockedAuthor": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "viewer": { + "ref": "app.bsky.actor.defs#viewerState", + "type": "ref" + } + } + }, + "generatorView": { + "type": "object", + "required": [ + "uri", + "cid", + "did", + "creator", + "displayName", + "indexedAt" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "did": { + "type": "string", + "format": "did" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "avatar": { + "type": "string", + "format": "uri" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "viewer": { + "ref": "#generatorViewerState", + "type": "ref" + }, + "creator": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + }, + "indexedAt": { + "type": "string", + "format": "datetime" + }, + "likeCount": { + "type": "integer", + "minimum": 0 + }, + "contentMode": { + "type": "string", + "knownValues": [ + "app.bsky.feed.defs#contentModeUnspecified", + "app.bsky.feed.defs#contentModeVideo" + ] + }, + "description": { + "type": "string", + "maxLength": 3000, + "maxGraphemes": 300 + }, + "displayName": { + "type": "string" + }, + "descriptionFacets": { + "type": "array", + "items": { + "ref": "app.bsky.richtext.facet", + "type": "ref" + } + }, + "acceptsInteractions": { + "type": "boolean" + } + } + }, + "threadContext": { + "type": "object", + "properties": { + "rootAuthorLike": { + "type": "string", + "format": "at-uri" + } + }, + "description": "Metadata about this post within the context of the thread it is in." + }, + "threadViewPost": { + "type": "object", + "required": [ + "post" + ], + "properties": { + "post": { + "ref": "#postView", + "type": "ref" + }, + "parent": { + "refs": [ + "#threadViewPost", + "#notFoundPost", + "#blockedPost" + ], + "type": "union" + }, + "replies": { + "type": "array", + "items": { + "refs": [ + "#threadViewPost", + "#notFoundPost", + "#blockedPost" + ], + "type": "union" + } + }, + "threadContext": { + "ref": "#threadContext", + "type": "ref" + } + } + }, + "threadgateView": { + "type": "object", + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "lists": { + "type": "array", + "items": { + "ref": "app.bsky.graph.defs#listViewBasic", + "type": "ref" + } + }, + "record": { + "type": "unknown" + } + } + }, + "interactionLike": { + "type": "token", + "description": "User liked the feed item" + }, + "interactionSeen": { + "type": "token", + "description": "Feed item was seen by user" + }, + "clickthroughItem": { + "type": "token", + "description": "User clicked through to the feed item" + }, + "contentModeVideo": { + "type": "token", + "description": "Declares the feed generator returns posts containing app.bsky.embed.video embeds." + }, + "interactionQuote": { + "type": "token", + "description": "User quoted the feed item" + }, + "interactionReply": { + "type": "token", + "description": "User replied to the feed item" + }, + "interactionShare": { + "type": "token", + "description": "User shared the feed item" + }, + "skeletonFeedPost": { + "type": "object", + "required": [ + "post" + ], + "properties": { + "post": { + "type": "string", + "format": "at-uri" + }, + "reason": { + "refs": [ + "#skeletonReasonRepost", + "#skeletonReasonPin" + ], + "type": "union" + }, + "feedContext": { + "type": "string", + "maxLength": 2000, + "description": "Context that will be passed through to client and may be passed to feed generator back alongside interactions." + } + } + }, + "clickthroughEmbed": { + "type": "token", + "description": "User clicked through to the embedded content of the feed item" + }, + "interactionRepost": { + "type": "token", + "description": "User reposted the feed item" + }, + "skeletonReasonPin": { + "type": "object", + "properties": {} + }, + "clickthroughAuthor": { + "type": "token", + "description": "User clicked through to the author of the feed item" + }, + "clickthroughReposter": { + "type": "token", + "description": "User clicked through to the reposter of the feed item" + }, + "generatorViewerState": { + "type": "object", + "properties": { + "like": { + "type": "string", + "format": "at-uri" + } + } + }, + "skeletonReasonRepost": { + "type": "object", + "required": [ + "repost" + ], + "properties": { + "repost": { + "type": "string", + "format": "at-uri" + } + } + }, + "contentModeUnspecified": { + "type": "token", + "description": "Declares the feed generator returns any types of posts." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/describeFeedGenerator.json b/lexicons/app/bsky/feed/describeFeedGenerator.json new file mode 100644 index 0000000000..1549935910 --- /dev/null +++ b/lexicons/app/bsky/feed/describeFeedGenerator.json @@ -0,0 +1,61 @@ +{ + "id": "app.bsky.feed.describeFeedGenerator", + "defs": { + "feed": { + "type": "object", + "required": [ + "uri" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + } + } + }, + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "did", + "feeds" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "feeds": { + "type": "array", + "items": { + "ref": "#feed", + "type": "ref" + } + }, + "links": { + "ref": "#links", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Get information about a feed generator, including policies and offered feed URIs. Does not require auth; implemented by Feed Generator services (not App View)." + }, + "links": { + "type": "object", + "properties": { + "privacyPolicy": { + "type": "string" + }, + "termsOfService": { + "type": "string" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/generator.json b/lexicons/app/bsky/feed/generator.json new file mode 100644 index 0000000000..082a6ca372 --- /dev/null +++ b/lexicons/app/bsky/feed/generator.json @@ -0,0 +1,73 @@ +{ + "id": "app.bsky.feed.generator", + "defs": { + "main": { + "key": "any", + "type": "record", + "record": { + "type": "object", + "required": [ + "did", + "displayName", + "createdAt" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "avatar": { + "type": "blob", + "accept": [ + "image/png", + "image/jpeg" + ], + "maxSize": 1000000 + }, + "labels": { + "refs": [ + "com.atproto.label.defs#selfLabels" + ], + "type": "union", + "description": "Self-label values" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "contentMode": { + "type": "string", + "knownValues": [ + "app.bsky.feed.defs#contentModeUnspecified", + "app.bsky.feed.defs#contentModeVideo" + ] + }, + "description": { + "type": "string", + "maxLength": 3000, + "maxGraphemes": 300 + }, + "displayName": { + "type": "string", + "maxLength": 240, + "maxGraphemes": 24 + }, + "descriptionFacets": { + "type": "array", + "items": { + "ref": "app.bsky.richtext.facet", + "type": "ref" + } + }, + "acceptsInteractions": { + "type": "boolean", + "description": "Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions" + } + } + }, + "description": "Record declaring of the existence of a feed generator, and containing metadata about it. The record can exist in any repository." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getActorFeeds.json b/lexicons/app/bsky/feed/getActorFeeds.json new file mode 100644 index 0000000000..f0a41e2cb4 --- /dev/null +++ b/lexicons/app/bsky/feed/getActorFeeds.json @@ -0,0 +1,53 @@ +{ + "id": "app.bsky.feed.getActorFeeds", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "feeds" + ], + "properties": { + "feeds": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#generatorView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier" + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get a list of feeds (feed generator records) created by the actor (in the actor's repo)." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getActorLikes.json b/lexicons/app/bsky/feed/getActorLikes.json new file mode 100644 index 0000000000..532b7df2f4 --- /dev/null +++ b/lexicons/app/bsky/feed/getActorLikes.json @@ -0,0 +1,61 @@ +{ + "id": "app.bsky.feed.getActorLikes", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "BlockedActor" + }, + { + "name": "BlockedByActor" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "feed" + ], + "properties": { + "feed": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#feedViewPost", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier" + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get a list of posts liked by an actor. Requires auth, actor must be the requesting account." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getAuthorFeed.json b/lexicons/app/bsky/feed/getAuthorFeed.json new file mode 100644 index 0000000000..4d113c3123 --- /dev/null +++ b/lexicons/app/bsky/feed/getAuthorFeed.json @@ -0,0 +1,77 @@ +{ + "id": "app.bsky.feed.getAuthorFeed", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "BlockedActor" + }, + { + "name": "BlockedByActor" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "feed" + ], + "properties": { + "feed": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#feedViewPost", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier" + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + }, + "filter": { + "type": "string", + "default": "posts_with_replies", + "description": "Combinations of post/repost types to include in response.", + "knownValues": [ + "posts_with_replies", + "posts_no_replies", + "posts_with_media", + "posts_and_author_threads", + "posts_with_video" + ] + }, + "includePins": { + "type": "boolean", + "default": false + } + } + }, + "description": "Get a view of an actor's 'author feed' (post and reposts by the author). Does not require auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getFeed.json b/lexicons/app/bsky/feed/getFeed.json new file mode 100644 index 0000000000..6d19d64b96 --- /dev/null +++ b/lexicons/app/bsky/feed/getFeed.json @@ -0,0 +1,58 @@ +{ + "id": "app.bsky.feed.getFeed", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "UnknownFeed" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "feed" + ], + "properties": { + "feed": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#feedViewPost", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "feed" + ], + "properties": { + "feed": { + "type": "string", + "format": "at-uri" + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get a hydrated feed from an actor's selected feed generator. Implemented by App View." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getFeedGenerator.json b/lexicons/app/bsky/feed/getFeedGenerator.json new file mode 100644 index 0000000000..9403bbc5fa --- /dev/null +++ b/lexicons/app/bsky/feed/getFeedGenerator.json @@ -0,0 +1,49 @@ +{ + "id": "app.bsky.feed.getFeedGenerator", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "view", + "isOnline", + "isValid" + ], + "properties": { + "view": { + "ref": "app.bsky.feed.defs#generatorView", + "type": "ref" + }, + "isValid": { + "type": "boolean", + "description": "Indicates whether the feed generator service is compatible with the record declaration." + }, + "isOnline": { + "type": "boolean", + "description": "Indicates whether the feed generator service has been online recently, or else seems to be inactive." + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "feed" + ], + "properties": { + "feed": { + "type": "string", + "format": "at-uri", + "description": "AT-URI of the feed generator record." + } + } + }, + "description": "Get information about a feed generator. Implemented by AppView." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getFeedGenerators.json b/lexicons/app/bsky/feed/getFeedGenerators.json new file mode 100644 index 0000000000..79938c932b --- /dev/null +++ b/lexicons/app/bsky/feed/getFeedGenerators.json @@ -0,0 +1,44 @@ +{ + "id": "app.bsky.feed.getFeedGenerators", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "feeds" + ], + "properties": { + "feeds": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#generatorView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "feeds" + ], + "properties": { + "feeds": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + } + } + } + }, + "description": "Get information about a list of feed generators." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getFeedSkeleton.json b/lexicons/app/bsky/feed/getFeedSkeleton.json new file mode 100644 index 0000000000..f9ca591405 --- /dev/null +++ b/lexicons/app/bsky/feed/getFeedSkeleton.json @@ -0,0 +1,64 @@ +{ + "id": "app.bsky.feed.getFeedSkeleton", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "UnknownFeed" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "feed" + ], + "properties": { + "feed": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#skeletonFeedPost", + "type": "ref" + } + }, + "reqId": { + "type": "string", + "maxLength": 100, + "description": "Unique identifier per request that may be passed back alongside interactions." + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "feed" + ], + "properties": { + "feed": { + "type": "string", + "format": "at-uri", + "description": "Reference to feed generator record describing the specific feed being requested." + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get a skeleton of a feed provided by a feed generator. Auth is optional, depending on provider requirements, and provides the DID of the requester. Implemented by Feed Generator Service." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getLikes.json b/lexicons/app/bsky/feed/getLikes.json new file mode 100644 index 0000000000..db5c83ecda --- /dev/null +++ b/lexicons/app/bsky/feed/getLikes.json @@ -0,0 +1,90 @@ +{ + "id": "app.bsky.feed.getLikes", + "defs": { + "like": { + "type": "object", + "required": [ + "indexedAt", + "createdAt", + "actor" + ], + "properties": { + "actor": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "indexedAt": { + "type": "string", + "format": "datetime" + } + } + }, + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "uri", + "likes" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "likes": { + "type": "array", + "items": { + "ref": "#like", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "uri" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid", + "description": "CID of the subject record (aka, specific version of record), to filter likes." + }, + "uri": { + "type": "string", + "format": "at-uri", + "description": "AT-URI of the subject (eg, a post record)." + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get like records which reference a subject (by AT-URI and CID)." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getListFeed.json b/lexicons/app/bsky/feed/getListFeed.json new file mode 100644 index 0000000000..7d9311799f --- /dev/null +++ b/lexicons/app/bsky/feed/getListFeed.json @@ -0,0 +1,59 @@ +{ + "id": "app.bsky.feed.getListFeed", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "UnknownList" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "feed" + ], + "properties": { + "feed": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#feedViewPost", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "list" + ], + "properties": { + "list": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to the list record." + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get a feed of recent posts from a list (posts and reposts from any actors on the list). Does not require auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getPostThread.json b/lexicons/app/bsky/feed/getPostThread.json new file mode 100644 index 0000000000..b62a78a00a --- /dev/null +++ b/lexicons/app/bsky/feed/getPostThread.json @@ -0,0 +1,66 @@ +{ + "id": "app.bsky.feed.getPostThread", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "NotFound" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "thread" + ], + "properties": { + "thread": { + "refs": [ + "app.bsky.feed.defs#threadViewPost", + "app.bsky.feed.defs#notFoundPost", + "app.bsky.feed.defs#blockedPost" + ], + "type": "union" + }, + "threadgate": { + "ref": "app.bsky.feed.defs#threadgateView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "uri" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to post record." + }, + "depth": { + "type": "integer", + "default": 6, + "maximum": 1000, + "minimum": 0, + "description": "How many levels of reply depth should be included in response." + }, + "parentHeight": { + "type": "integer", + "default": 80, + "maximum": 1000, + "minimum": 0, + "description": "How many levels of parent (and grandparent, etc) post to include." + } + } + }, + "description": "Get posts in a thread. Does not require auth, but additional metadata and filtering will be applied for authed requests." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getPosts.json b/lexicons/app/bsky/feed/getPosts.json new file mode 100644 index 0000000000..c52de1aecd --- /dev/null +++ b/lexicons/app/bsky/feed/getPosts.json @@ -0,0 +1,46 @@ +{ + "id": "app.bsky.feed.getPosts", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "posts" + ], + "properties": { + "posts": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#postView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "uris" + ], + "properties": { + "uris": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + }, + "maxLength": 25, + "description": "List of post AT-URIs to return hydrated views for." + } + } + }, + "description": "Gets post views for a specified list of posts (by AT-URI). This is sometimes referred to as 'hydrating' a 'feed skeleton'." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getQuotes.json b/lexicons/app/bsky/feed/getQuotes.json new file mode 100644 index 0000000000..cc507ec925 --- /dev/null +++ b/lexicons/app/bsky/feed/getQuotes.json @@ -0,0 +1,68 @@ +{ + "id": "app.bsky.feed.getQuotes", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "uri", + "posts" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "posts": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#postView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "uri" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid", + "description": "If supplied, filters to quotes of specific version (by CID) of the post record." + }, + "uri": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) of post record" + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get a list of quotes for a given post." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getRepostedBy.json b/lexicons/app/bsky/feed/getRepostedBy.json new file mode 100644 index 0000000000..3df7fa8f53 --- /dev/null +++ b/lexicons/app/bsky/feed/getRepostedBy.json @@ -0,0 +1,68 @@ +{ + "id": "app.bsky.feed.getRepostedBy", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "uri", + "repostedBy" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "cursor": { + "type": "string" + }, + "repostedBy": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "uri" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid", + "description": "If supplied, filters to reposts of specific version (by CID) of the post record." + }, + "uri": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) of post record" + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get a list of reposts for a given post." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getSuggestedFeeds.json b/lexicons/app/bsky/feed/getSuggestedFeeds.json new file mode 100644 index 0000000000..26cb0d1b49 --- /dev/null +++ b/lexicons/app/bsky/feed/getSuggestedFeeds.json @@ -0,0 +1,46 @@ +{ + "id": "app.bsky.feed.getSuggestedFeeds", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "feeds" + ], + "properties": { + "feeds": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#generatorView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get a list of suggested feeds (feed generators) for the requesting account." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/getTimeline.json b/lexicons/app/bsky/feed/getTimeline.json new file mode 100644 index 0000000000..e35dbbc1cb --- /dev/null +++ b/lexicons/app/bsky/feed/getTimeline.json @@ -0,0 +1,50 @@ +{ + "id": "app.bsky.feed.getTimeline", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "feed" + ], + "properties": { + "feed": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#feedViewPost", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + }, + "algorithm": { + "type": "string", + "description": "Variant 'algorithm' for timeline. Implementation-specific. NOTE: most feed flexibility has been moved to feed generator mechanism." + } + } + }, + "description": "Get a view of the requesting account's home timeline. This is expected to be some form of reverse-chronological feed." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/like.json b/lexicons/app/bsky/feed/like.json new file mode 100644 index 0000000000..2b941f743c --- /dev/null +++ b/lexicons/app/bsky/feed/like.json @@ -0,0 +1,33 @@ +{ + "id": "app.bsky.feed.like", + "defs": { + "main": { + "key": "tid", + "type": "record", + "record": { + "type": "object", + "required": [ + "subject", + "createdAt" + ], + "properties": { + "via": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "subject": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "createdAt": { + "type": "string", + "format": "datetime" + } + } + }, + "description": "Record declaring a 'like' of a piece of subject content." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/post.json b/lexicons/app/bsky/feed/post.json new file mode 100644 index 0000000000..50b8a53c88 --- /dev/null +++ b/lexicons/app/bsky/feed/post.json @@ -0,0 +1,146 @@ +{ + "id": "app.bsky.feed.post", + "defs": { + "main": { + "key": "tid", + "type": "record", + "record": { + "type": "object", + "required": [ + "text", + "createdAt" + ], + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string", + "maxLength": 640, + "maxGraphemes": 64 + }, + "maxLength": 8, + "description": "Additional hashtags, in addition to any included in post text and facets." + }, + "text": { + "type": "string", + "maxLength": 3000, + "description": "The primary post content. May be an empty string, if there are embeds.", + "maxGraphemes": 300 + }, + "embed": { + "refs": [ + "app.bsky.embed.images", + "app.bsky.embed.video", + "app.bsky.embed.gallery", + "app.bsky.embed.external", + "app.bsky.embed.record", + "app.bsky.embed.recordWithMedia" + ], + "type": "union" + }, + "langs": { + "type": "array", + "items": { + "type": "string", + "format": "language" + }, + "maxLength": 3, + "description": "Indicates human language of post primary text content." + }, + "reply": { + "ref": "#replyRef", + "type": "ref" + }, + "facets": { + "type": "array", + "items": { + "ref": "app.bsky.richtext.facet", + "type": "ref" + }, + "description": "Annotations of text (mentions, URLs, hashtags, etc)" + }, + "labels": { + "refs": [ + "com.atproto.label.defs#selfLabels" + ], + "type": "union", + "description": "Self-label values for this post. Effectively content warnings." + }, + "entities": { + "type": "array", + "items": { + "ref": "#entity", + "type": "ref" + }, + "description": "DEPRECATED: replaced by app.bsky.richtext.facet." + }, + "createdAt": { + "type": "string", + "format": "datetime", + "description": "Client-declared timestamp when this post was originally created." + } + } + }, + "description": "Record containing a Bluesky post." + }, + "entity": { + "type": "object", + "required": [ + "index", + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "description": "Expected values are 'mention' and 'link'." + }, + "index": { + "ref": "#textSlice", + "type": "ref" + }, + "value": { + "type": "string" + } + }, + "description": "Deprecated: use facets instead." + }, + "replyRef": { + "type": "object", + "required": [ + "root", + "parent" + ], + "properties": { + "root": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "parent": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + } + } + }, + "textSlice": { + "type": "object", + "required": [ + "start", + "end" + ], + "properties": { + "end": { + "type": "integer", + "minimum": 0 + }, + "start": { + "type": "integer", + "minimum": 0 + } + }, + "description": "Deprecated. Use app.bsky.richtext instead -- A text segment. Start is inclusive, end is exclusive. Indices are for utf16-encoded strings." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/postgate.json b/lexicons/app/bsky/feed/postgate.json new file mode 100644 index 0000000000..9fa0775a68 --- /dev/null +++ b/lexicons/app/bsky/feed/postgate.json @@ -0,0 +1,55 @@ +{ + "id": "app.bsky.feed.postgate", + "defs": { + "main": { + "key": "tid", + "type": "record", + "record": { + "type": "object", + "required": [ + "post", + "createdAt" + ], + "properties": { + "post": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to the post record." + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "embeddingRules": { + "type": "array", + "items": { + "refs": [ + "#disableRule" + ], + "type": "union" + }, + "maxLength": 5, + "description": "List of rules defining who can embed this post. If value is an empty array or is undefined, no particular rules apply and anyone can embed." + }, + "detachedEmbeddingUris": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + }, + "maxLength": 50, + "description": "List of AT-URIs embedding this post that the author has detached from." + } + } + }, + "description": "Record defining interaction rules for a post. The record key (rkey) of the postgate record must match the record key of the post, and that record must be in the same repository." + }, + "disableRule": { + "type": "object", + "properties": {}, + "description": "Disables embedding of this post." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/repost.json b/lexicons/app/bsky/feed/repost.json new file mode 100644 index 0000000000..6563b1f5e4 --- /dev/null +++ b/lexicons/app/bsky/feed/repost.json @@ -0,0 +1,33 @@ +{ + "id": "app.bsky.feed.repost", + "defs": { + "main": { + "key": "tid", + "type": "record", + "record": { + "type": "object", + "required": [ + "subject", + "createdAt" + ], + "properties": { + "via": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "subject": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "createdAt": { + "type": "string", + "format": "datetime" + } + } + }, + "description": "Record representing a 'repost' of an existing Bluesky post." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/searchPosts.json b/lexicons/app/bsky/feed/searchPosts.json new file mode 100644 index 0000000000..23ba045c24 --- /dev/null +++ b/lexicons/app/bsky/feed/searchPosts.json @@ -0,0 +1,113 @@ +{ + "id": "app.bsky.feed.searchPosts", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "BadQueryString" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "posts" + ], + "properties": { + "posts": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#postView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + }, + "hitsTotal": { + "type": "integer", + "description": "Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits." + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "q" + ], + "properties": { + "q": { + "type": "string", + "description": "Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended." + }, + "tag": { + "type": "array", + "items": { + "type": "string", + "maxLength": 640, + "maxGraphemes": 64 + }, + "description": "Filter to posts with the given tag (hashtag), based on rich-text facet or tag field. Do not include the hash (#) prefix. Multiple tags can be specified, with 'AND' matching." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Filter to posts with links (facet links or embeds) pointing to this URL. Server may apply URL normalization or fuzzy matching." + }, + "lang": { + "type": "string", + "format": "language", + "description": "Filter to posts in the given language. Expected to be based on post language field, though server may override language detection." + }, + "sort": { + "type": "string", + "default": "latest", + "description": "Specifies the ranking order of results.", + "knownValues": [ + "top", + "latest" + ] + }, + "limit": { + "type": "integer", + "default": 25, + "maximum": 100, + "minimum": 1 + }, + "since": { + "type": "string", + "description": "Filter results for posts after the indicated datetime (inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYYY-MM-DD)." + }, + "until": { + "type": "string", + "description": "Filter results for posts before the indicated datetime (not inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYY-MM-DD)." + }, + "author": { + "type": "string", + "format": "at-identifier", + "description": "Filter to posts by the given account. Handles are resolved to DID before query-time." + }, + "cursor": { + "type": "string", + "description": "Optional pagination mechanism; may not necessarily allow scrolling through entire result set." + }, + "domain": { + "type": "string", + "description": "Filter to posts with URLs (facet links or embeds) linking to the given domain (hostname). Server may apply hostname normalization." + }, + "mentions": { + "type": "string", + "format": "at-identifier", + "description": "Filter to posts which mention the given account. Handles are resolved to DID before query-time. Only matches rich-text facet mentions." + } + } + }, + "description": "Find posts matching search criteria, returning views of those posts. Note that this API endpoint may require authentication (eg, not public) for some service providers and implementations." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/searchPostsV2.json b/lexicons/app/bsky/feed/searchPostsV2.json new file mode 100644 index 0000000000..0d3ba5cc16 --- /dev/null +++ b/lexicons/app/bsky/feed/searchPostsV2.json @@ -0,0 +1,251 @@ +{ + "id": "app.bsky.feed.searchPostsV2", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "BadQueryString" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "posts" + ], + "properties": { + "posts": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#postView", + "type": "ref" + }, + "description": "Hydrated views of matching posts." + }, + "cursor": { + "type": "string", + "description": "Cursor for the next page of results." + }, + "hitsTotal": { + "type": "integer", + "description": "Estimated total number of matching hits. May be rounded or truncated." + }, + "detectedQueryLanguages": { + "type": "array", + "items": { + "type": "string", + "knownValues": [ + "ja", + "zh", + "ko", + "th", + "ar" + ] + }, + "description": "Query languages detected for CJK, Thai, or Arabic text. Empty or omitted for other scripts." + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [], + "properties": { + "sort": { + "type": "string", + "description": "Ranking order for results. 'recent' sorts by recency; 'top' uses search ranking.", + "knownValues": [ + "recent", + "top" + ] + }, + "urls": { + "type": "array", + "items": { + "type": "string", + "format": "uri" + }, + "description": "Include posts that link to any of these URLs." + }, + "limit": { + "type": "integer", + "default": 25, + "maximum": 100, + "minimum": 1, + "description": "Maximum number of results to return." + }, + "query": { + "type": "string", + "description": "Search query string. A query or at least one filter is required." + }, + "since": { + "type": "string", + "description": "Include posts indexed at or after this timestamp. Can be a datetime, or just an ISO date (YYYY-MM-DD)." + }, + "until": { + "type": "string", + "description": "Include posts indexed before this timestamp. Defaults to the current time. Can be a datetime, or just an ISO date (YYYY-MM-DD)." + }, + "cursor": { + "type": "string", + "description": "Optional pagination cursor." + }, + "allTime": { + "type": "boolean", + "description": "Search the full index instead of the recent-post window." + }, + "authors": { + "type": "array", + "items": { + "type": "string", + "format": "at-identifier" + }, + "description": "Include posts by any of these authors. Handles are resolved to DIDs before searching." + }, + "domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Include posts that link to any of these domains." + }, + "hasMedia": { + "type": "boolean", + "description": "Include only posts with media." + }, + "hasVideo": { + "type": "boolean", + "description": "Include only posts with video." + }, + "hashtags": { + "type": "array", + "items": { + "type": "string", + "maxLength": 640, + "maxGraphemes": 64 + }, + "description": "Include posts tagged with any of these hashtags. Do not include the hash (#) prefix." + }, + "mentions": { + "type": "array", + "items": { + "type": "string", + "format": "at-identifier" + }, + "description": "Include posts that mention any of these accounts. Handles are resolved to DIDs before searching." + }, + "following": { + "type": "boolean", + "description": "Include only posts from accounts followed by the viewer." + }, + "languages": { + "type": "array", + "items": { + "type": "string", + "format": "language" + }, + "description": "Include posts whose language matches any of these language codes." + }, + "excludeUrls": { + "type": "array", + "items": { + "type": "string", + "format": "uri" + }, + "description": "Exclude posts that link to any of these URLs." + }, + "repliesOnly": { + "type": "boolean", + "description": "Include only replies. Mutually exclusive with excludeReplies." + }, + "queryLanguage": { + "type": "string", + "description": "Language analyzer hint for the query text. If unset, the server auto-detects when possible.", + "knownValues": [ + "ja", + "zh", + "ko", + "th", + "ar" + ] + }, + "threadRootUri": { + "type": "string", + "format": "at-uri", + "description": "Include only posts in the thread rooted at this post URI." + }, + "embeddedAtUris": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + }, + "description": "Include posts that embed any of these AT URIs." + }, + "excludeAuthors": { + "type": "array", + "items": { + "type": "string", + "format": "at-identifier" + }, + "description": "Exclude posts by any of these authors. Handles are resolved to DIDs before searching." + }, + "excludeDomains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Exclude posts that link to any of these domains." + }, + "excludeReplies": { + "type": "boolean", + "description": "Exclude replies from results. Mutually exclusive with repliesOnly." + }, + "replyParentUri": { + "type": "string", + "format": "at-uri", + "description": "Include only direct replies to this parent post URI." + }, + "excludeHashtags": { + "type": "array", + "items": { + "type": "string", + "maxLength": 640, + "maxGraphemes": 64 + }, + "description": "Exclude posts tagged with any of these hashtags. Do not include the hash (#) prefix." + }, + "excludeMentions": { + "type": "array", + "items": { + "type": "string", + "format": "at-identifier" + }, + "description": "Exclude posts that mention any of these accounts. Handles are resolved to DIDs before searching." + }, + "excludeLanguages": { + "type": "array", + "items": { + "type": "string", + "format": "language" + }, + "description": "Exclude posts whose language matches any of these language codes." + }, + "excludeEmbeddedAtUris": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + }, + "description": "Exclude posts that embed any of these AT URIs." + } + } + }, + "description": "Find posts matching a search query or filters, returning search hits for matching post records." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/sendInteractions.json b/lexicons/app/bsky/feed/sendInteractions.json new file mode 100644 index 0000000000..eea9f6c1fc --- /dev/null +++ b/lexicons/app/bsky/feed/sendInteractions.json @@ -0,0 +1,40 @@ +{ + "id": "app.bsky.feed.sendInteractions", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "interactions" + ], + "properties": { + "feed": { + "type": "string", + "format": "at-uri" + }, + "interactions": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#interaction", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "output": { + "schema": { + "type": "object", + "properties": {} + }, + "encoding": "application/json" + }, + "description": "Send information about interactions with feed items back to the feed generator that served them." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/feed/threadgate.json b/lexicons/app/bsky/feed/threadgate.json new file mode 100644 index 0000000000..8b4d3c6117 --- /dev/null +++ b/lexicons/app/bsky/feed/threadgate.json @@ -0,0 +1,81 @@ +{ + "id": "app.bsky.feed.threadgate", + "defs": { + "main": { + "key": "tid", + "type": "record", + "record": { + "type": "object", + "required": [ + "post", + "createdAt" + ], + "properties": { + "post": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to the post record." + }, + "allow": { + "type": "array", + "items": { + "refs": [ + "#mentionRule", + "#followerRule", + "#followingRule", + "#listRule" + ], + "type": "union" + }, + "maxLength": 5, + "description": "List of rules defining who can reply to this post. If value is an empty array, no one can reply. If value is undefined, anyone can reply." + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "hiddenReplies": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + }, + "maxLength": 300, + "description": "List of hidden reply URIs." + } + } + }, + "description": "Record defining interaction gating rules for a thread (aka, reply controls). The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository." + }, + "listRule": { + "type": "object", + "required": [ + "list" + ], + "properties": { + "list": { + "type": "string", + "format": "at-uri" + } + }, + "description": "Allow replies from actors on a list." + }, + "mentionRule": { + "type": "object", + "properties": {}, + "description": "Allow replies from actors mentioned in your post." + }, + "followerRule": { + "type": "object", + "properties": {}, + "description": "Allow replies from actors who follow you." + }, + "followingRule": { + "type": "object", + "properties": {}, + "description": "Allow replies from actors you follow." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/block.json b/lexicons/app/bsky/graph/block.json new file mode 100644 index 0000000000..bc0cd214df --- /dev/null +++ b/lexicons/app/bsky/graph/block.json @@ -0,0 +1,30 @@ +{ + "id": "app.bsky.graph.block", + "defs": { + "main": { + "key": "tid", + "type": "record", + "record": { + "type": "object", + "required": [ + "subject", + "createdAt" + ], + "properties": { + "subject": { + "type": "string", + "format": "did", + "description": "DID of the account to be blocked." + }, + "createdAt": { + "type": "string", + "format": "datetime" + } + } + }, + "description": "Record declaring a 'block' relationship against another account. NOTE: blocks are public in Bluesky; see blog posts for details." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/defs.json b/lexicons/app/bsky/graph/defs.json new file mode 100644 index 0000000000..eca42fc875 --- /dev/null +++ b/lexicons/app/bsky/graph/defs.json @@ -0,0 +1,353 @@ +{ + "id": "app.bsky.graph.defs", + "defs": { + "modlist": { + "type": "token", + "description": "A list of actors to apply an aggregate moderation action (mute/block) on." + }, + "listView": { + "type": "object", + "required": [ + "uri", + "cid", + "creator", + "name", + "purpose", + "indexedAt" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1 + }, + "avatar": { + "type": "string", + "format": "uri" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "viewer": { + "ref": "#listViewerState", + "type": "ref" + }, + "creator": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + }, + "purpose": { + "ref": "#listPurpose", + "type": "ref" + }, + "indexedAt": { + "type": "string", + "format": "datetime" + }, + "description": { + "type": "string", + "maxLength": 3000, + "maxGraphemes": 300 + }, + "listItemCount": { + "type": "integer", + "minimum": 0 + }, + "descriptionFacets": { + "type": "array", + "items": { + "ref": "app.bsky.richtext.facet", + "type": "ref" + } + } + } + }, + "curatelist": { + "type": "token", + "description": "A list of actors used for curation purposes such as list feeds or interaction gating." + }, + "listPurpose": { + "type": "string", + "knownValues": [ + "app.bsky.graph.defs#modlist", + "app.bsky.graph.defs#curatelist", + "app.bsky.graph.defs#referencelist" + ] + }, + "listItemView": { + "type": "object", + "required": [ + "uri", + "subject" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "subject": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + } + } + }, + "relationship": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "blocking": { + "type": "string", + "format": "at-uri", + "description": "if the actor blocks this DID, this is the AT-URI of the block record" + }, + "blockedBy": { + "type": "string", + "format": "at-uri", + "description": "if the actor is blocked by this DID, contains the AT-URI of the block record" + }, + "following": { + "type": "string", + "format": "at-uri", + "description": "if the actor follows this DID, this is the AT-URI of the follow record" + }, + "followedBy": { + "type": "string", + "format": "at-uri", + "description": "if the actor is followed by this DID, contains the AT-URI of the follow record" + }, + "blockedByList": { + "type": "string", + "format": "at-uri", + "description": "if the actor is blocked by this DID via a block list, contains the AT-URI of the listblock record" + }, + "blockingByList": { + "type": "string", + "format": "at-uri", + "description": "if the actor blocks this DID via a block list, this is the AT-URI of the listblock record" + } + }, + "description": "lists the bi-directional graph relationships between one actor (not indicated in the object), and the target actors (the DID included in the object)" + }, + "listViewBasic": { + "type": "object", + "required": [ + "uri", + "cid", + "name", + "purpose" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1 + }, + "avatar": { + "type": "string", + "format": "uri" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "viewer": { + "ref": "#listViewerState", + "type": "ref" + }, + "purpose": { + "ref": "#listPurpose", + "type": "ref" + }, + "indexedAt": { + "type": "string", + "format": "datetime" + }, + "listItemCount": { + "type": "integer", + "minimum": 0 + } + } + }, + "notFoundActor": { + "type": "object", + "required": [ + "actor", + "notFound" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier" + }, + "notFound": { + "type": "boolean", + "const": true + } + }, + "description": "indicates that a handle or DID could not be resolved" + }, + "referencelist": { + "type": "token", + "description": "A list of actors used for only for reference purposes such as within a starter pack." + }, + "listViewerState": { + "type": "object", + "properties": { + "muted": { + "type": "boolean" + }, + "blocked": { + "type": "string", + "format": "at-uri" + } + } + }, + "starterPackView": { + "type": "object", + "required": [ + "uri", + "cid", + "record", + "creator", + "indexedAt" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "list": { + "ref": "#listViewBasic", + "type": "ref" + }, + "feeds": { + "type": "array", + "items": { + "ref": "app.bsky.feed.defs#generatorView", + "type": "ref" + }, + "maxLength": 3 + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "record": { + "type": "unknown" + }, + "creator": { + "ref": "app.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "indexedAt": { + "type": "string", + "format": "datetime" + }, + "joinedWeekCount": { + "type": "integer", + "minimum": 0 + }, + "listItemsSample": { + "type": "array", + "items": { + "ref": "#listItemView", + "type": "ref" + }, + "maxLength": 12 + }, + "joinedAllTimeCount": { + "type": "integer", + "minimum": 0 + } + } + }, + "starterPackViewBasic": { + "type": "object", + "required": [ + "uri", + "cid", + "record", + "creator", + "indexedAt" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "record": { + "type": "unknown" + }, + "creator": { + "ref": "app.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "indexedAt": { + "type": "string", + "format": "datetime" + }, + "listItemCount": { + "type": "integer", + "minimum": 0 + }, + "joinedWeekCount": { + "type": "integer", + "minimum": 0 + }, + "joinedAllTimeCount": { + "type": "integer", + "minimum": 0 + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/follow.json b/lexicons/app/bsky/graph/follow.json new file mode 100644 index 0000000000..1918ba075d --- /dev/null +++ b/lexicons/app/bsky/graph/follow.json @@ -0,0 +1,33 @@ +{ + "id": "app.bsky.graph.follow", + "defs": { + "main": { + "key": "tid", + "type": "record", + "record": { + "type": "object", + "required": [ + "subject", + "createdAt" + ], + "properties": { + "via": { + "ref": "com.atproto.repo.strongRef", + "type": "ref" + }, + "subject": { + "type": "string", + "format": "did" + }, + "createdAt": { + "type": "string", + "format": "datetime" + } + } + }, + "description": "Record declaring a social 'follow' relationship of another account. Duplicate follows will be ignored by the AppView." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getActorStarterPacks.json b/lexicons/app/bsky/graph/getActorStarterPacks.json new file mode 100644 index 0000000000..8cccf2b909 --- /dev/null +++ b/lexicons/app/bsky/graph/getActorStarterPacks.json @@ -0,0 +1,53 @@ +{ + "id": "app.bsky.graph.getActorStarterPacks", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "starterPacks" + ], + "properties": { + "cursor": { + "type": "string" + }, + "starterPacks": { + "type": "array", + "items": { + "ref": "app.bsky.graph.defs#starterPackViewBasic", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier" + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get a list of starter packs created by the actor." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getBlocks.json b/lexicons/app/bsky/graph/getBlocks.json new file mode 100644 index 0000000000..35e2b289fa --- /dev/null +++ b/lexicons/app/bsky/graph/getBlocks.json @@ -0,0 +1,46 @@ +{ + "id": "app.bsky.graph.getBlocks", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "blocks" + ], + "properties": { + "blocks": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Enumerates which accounts the requesting account is currently blocking. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getFollowers.json b/lexicons/app/bsky/graph/getFollowers.json new file mode 100644 index 0000000000..80374a1331 --- /dev/null +++ b/lexicons/app/bsky/graph/getFollowers.json @@ -0,0 +1,66 @@ +{ + "id": "app.bsky.graph.getFollowers", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "subject", + "followers" + ], + "properties": { + "cursor": { + "type": "string" + }, + "subject": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + }, + "followers": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "sort": { + "type": "string", + "description": "Sort order of the returned followers.", + "knownValues": [ + "latest", + "top" + ] + }, + "actor": { + "type": "string", + "format": "at-identifier" + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Enumerates accounts which follow a specified account (actor)." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getFollows.json b/lexicons/app/bsky/graph/getFollows.json new file mode 100644 index 0000000000..eab30c336c --- /dev/null +++ b/lexicons/app/bsky/graph/getFollows.json @@ -0,0 +1,66 @@ +{ + "id": "app.bsky.graph.getFollows", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "subject", + "follows" + ], + "properties": { + "cursor": { + "type": "string" + }, + "follows": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + } + }, + "subject": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "sort": { + "type": "string", + "description": "Sort order of the returned follows.", + "knownValues": [ + "latest", + "top" + ] + }, + "actor": { + "type": "string", + "format": "at-identifier" + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Enumerates accounts which a specified account (actor) follows." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getKnownFollowers.json b/lexicons/app/bsky/graph/getKnownFollowers.json new file mode 100644 index 0000000000..2c705fc3c1 --- /dev/null +++ b/lexicons/app/bsky/graph/getKnownFollowers.json @@ -0,0 +1,58 @@ +{ + "id": "app.bsky.graph.getKnownFollowers", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "subject", + "followers" + ], + "properties": { + "cursor": { + "type": "string" + }, + "subject": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + }, + "followers": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier" + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Enumerates accounts which follow a specified account (actor) and are followed by the viewer." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getList.json b/lexicons/app/bsky/graph/getList.json new file mode 100644 index 0000000000..b5a6e226ae --- /dev/null +++ b/lexicons/app/bsky/graph/getList.json @@ -0,0 +1,59 @@ +{ + "id": "app.bsky.graph.getList", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "list", + "items" + ], + "properties": { + "list": { + "ref": "app.bsky.graph.defs#listView", + "type": "ref" + }, + "items": { + "type": "array", + "items": { + "ref": "app.bsky.graph.defs#listItemView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "list" + ], + "properties": { + "list": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) of the list record to hydrate." + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Gets a 'view' (with additional context) of a specified list." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getListBlocks.json b/lexicons/app/bsky/graph/getListBlocks.json new file mode 100644 index 0000000000..03477a13b6 --- /dev/null +++ b/lexicons/app/bsky/graph/getListBlocks.json @@ -0,0 +1,46 @@ +{ + "id": "app.bsky.graph.getListBlocks", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "lists" + ], + "properties": { + "lists": { + "type": "array", + "items": { + "ref": "app.bsky.graph.defs#listView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get mod lists that the requesting account (actor) is blocking. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getListMutes.json b/lexicons/app/bsky/graph/getListMutes.json new file mode 100644 index 0000000000..ea54aab6d2 --- /dev/null +++ b/lexicons/app/bsky/graph/getListMutes.json @@ -0,0 +1,46 @@ +{ + "id": "app.bsky.graph.getListMutes", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "lists" + ], + "properties": { + "lists": { + "type": "array", + "items": { + "ref": "app.bsky.graph.defs#listView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Enumerates mod lists that the requesting account (actor) currently has muted. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getLists.json b/lexicons/app/bsky/graph/getLists.json new file mode 100644 index 0000000000..03c960da01 --- /dev/null +++ b/lexicons/app/bsky/graph/getLists.json @@ -0,0 +1,65 @@ +{ + "id": "app.bsky.graph.getLists", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "lists" + ], + "properties": { + "lists": { + "type": "array", + "items": { + "ref": "app.bsky.graph.defs#listView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier", + "description": "The account (actor) to enumerate lists from." + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + }, + "purposes": { + "type": "array", + "items": { + "type": "string", + "knownValues": [ + "modlist", + "curatelist" + ] + }, + "description": "Optional filter by list purpose. If not specified, all supported types are returned." + } + } + }, + "description": "Enumerates the lists created by a specified account (actor)." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getListsWithMembership.json b/lexicons/app/bsky/graph/getListsWithMembership.json new file mode 100644 index 0000000000..7e0b34a666 --- /dev/null +++ b/lexicons/app/bsky/graph/getListsWithMembership.json @@ -0,0 +1,82 @@ +{ + "id": "app.bsky.graph.getListsWithMembership", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "listsWithMembership" + ], + "properties": { + "cursor": { + "type": "string" + }, + "listsWithMembership": { + "type": "array", + "items": { + "ref": "#listWithMembership", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier", + "description": "The account (actor) to check for membership." + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + }, + "purposes": { + "type": "array", + "items": { + "type": "string", + "knownValues": [ + "modlist", + "curatelist" + ] + }, + "description": "Optional filter by list purpose. If not specified, all supported types are returned." + } + } + }, + "description": "Enumerates the lists created by the session user, and includes membership information about `actor` in those lists. Only supports curation and moderation lists (no reference lists, used in starter packs). Requires auth." + }, + "listWithMembership": { + "type": "object", + "required": [ + "list" + ], + "properties": { + "list": { + "ref": "app.bsky.graph.defs#listView", + "type": "ref" + }, + "listItem": { + "ref": "app.bsky.graph.defs#listItemView", + "type": "ref" + } + }, + "description": "A list and an optional list item indicating membership of a target user to that list." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getMutes.json b/lexicons/app/bsky/graph/getMutes.json new file mode 100644 index 0000000000..ad6303cd0d --- /dev/null +++ b/lexicons/app/bsky/graph/getMutes.json @@ -0,0 +1,46 @@ +{ + "id": "app.bsky.graph.getMutes", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "mutes" + ], + "properties": { + "mutes": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Enumerates accounts that the requesting account (actor) currently has fully muted. Mutes scoped to specific kinds of content (only reposts, only quote posts) are not included. Responses may contain more items than the requested limit. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getRelationships.json b/lexicons/app/bsky/graph/getRelationships.json new file mode 100644 index 0000000000..84cc69fe59 --- /dev/null +++ b/lexicons/app/bsky/graph/getRelationships.json @@ -0,0 +1,64 @@ +{ + "id": "app.bsky.graph.getRelationships", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "ActorNotFound", + "description": "the primary actor at-identifier could not be resolved" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "relationships" + ], + "properties": { + "actor": { + "type": "string", + "format": "did" + }, + "relationships": { + "type": "array", + "items": { + "refs": [ + "app.bsky.graph.defs#relationship", + "app.bsky.graph.defs#notFoundActor" + ], + "type": "union" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier", + "description": "Primary account requesting relationships for." + }, + "others": { + "type": "array", + "items": { + "type": "string", + "format": "at-identifier" + }, + "maxLength": 30, + "description": "List of 'other' accounts to be related back to the primary." + } + } + }, + "description": "Enumerates public relationships between one account, and a list of other accounts. Does not require auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getStarterPack.json b/lexicons/app/bsky/graph/getStarterPack.json new file mode 100644 index 0000000000..aa8ef6ce2a --- /dev/null +++ b/lexicons/app/bsky/graph/getStarterPack.json @@ -0,0 +1,39 @@ +{ + "id": "app.bsky.graph.getStarterPack", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "starterPack" + ], + "properties": { + "starterPack": { + "ref": "app.bsky.graph.defs#starterPackView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "starterPack" + ], + "properties": { + "starterPack": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) of the starter pack record." + } + } + }, + "description": "Gets a view of a starter pack." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getStarterPacks.json b/lexicons/app/bsky/graph/getStarterPacks.json new file mode 100644 index 0000000000..15d73a1e55 --- /dev/null +++ b/lexicons/app/bsky/graph/getStarterPacks.json @@ -0,0 +1,45 @@ +{ + "id": "app.bsky.graph.getStarterPacks", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "starterPacks" + ], + "properties": { + "starterPacks": { + "type": "array", + "items": { + "ref": "app.bsky.graph.defs#starterPackViewBasic", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "uris" + ], + "properties": { + "uris": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + }, + "maxLength": 25 + } + } + }, + "description": "Get views for a list of starter packs." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getStarterPacksWithMembership.json b/lexicons/app/bsky/graph/getStarterPacksWithMembership.json new file mode 100644 index 0000000000..c57d254ce3 --- /dev/null +++ b/lexicons/app/bsky/graph/getStarterPacksWithMembership.json @@ -0,0 +1,71 @@ +{ + "id": "app.bsky.graph.getStarterPacksWithMembership", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "starterPacksWithMembership" + ], + "properties": { + "cursor": { + "type": "string" + }, + "starterPacksWithMembership": { + "type": "array", + "items": { + "ref": "#starterPackWithMembership", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier", + "description": "The account (actor) to check for membership." + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Enumerates the starter packs created by the session user, and includes membership information about `actor` in those starter packs. Requires auth." + }, + "starterPackWithMembership": { + "type": "object", + "required": [ + "starterPack" + ], + "properties": { + "listItem": { + "ref": "app.bsky.graph.defs#listItemView", + "type": "ref" + }, + "starterPack": { + "ref": "app.bsky.graph.defs#starterPackView", + "type": "ref" + } + }, + "description": "A starter pack and an optional list item indicating membership of a target user to that starter pack." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/getSuggestedFollowsByActor.json b/lexicons/app/bsky/graph/getSuggestedFollowsByActor.json new file mode 100644 index 0000000000..12b617d0a4 --- /dev/null +++ b/lexicons/app/bsky/graph/getSuggestedFollowsByActor.json @@ -0,0 +1,54 @@ +{ + "id": "app.bsky.graph.getSuggestedFollowsByActor", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "suggestions" + ], + "properties": { + "recId": { + "type": "integer", + "description": "DEPRECATED: use recIdStr instead." + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + }, + "isFallback": { + "type": "boolean", + "default": false, + "description": "DEPRECATED, unused. Previously: if true, response has fallen-back to generic results, and is not scoped using relativeToDid" + }, + "suggestions": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier" + } + } + }, + "description": "Enumerates follows similar to a given account (actor). Expected use is to recommend additional accounts immediately after following one account." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/list.json b/lexicons/app/bsky/graph/list.json new file mode 100644 index 0000000000..dd67d4e99b --- /dev/null +++ b/lexicons/app/bsky/graph/list.json @@ -0,0 +1,63 @@ +{ + "id": "app.bsky.graph.list", + "defs": { + "main": { + "key": "tid", + "type": "record", + "record": { + "type": "object", + "required": [ + "name", + "purpose", + "createdAt" + ], + "properties": { + "name": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "description": "Display name for list; can not be empty." + }, + "avatar": { + "type": "blob", + "accept": [ + "image/png", + "image/jpeg" + ], + "maxSize": 1000000 + }, + "labels": { + "refs": [ + "com.atproto.label.defs#selfLabels" + ], + "type": "union" + }, + "purpose": { + "ref": "app.bsky.graph.defs#listPurpose", + "type": "ref", + "description": "Defines the purpose of the list (aka, moderation-oriented or curration-oriented)" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "description": { + "type": "string", + "maxLength": 3000, + "maxGraphemes": 300 + }, + "descriptionFacets": { + "type": "array", + "items": { + "ref": "app.bsky.richtext.facet", + "type": "ref" + } + } + } + }, + "description": "Record representing a list of accounts (actors). Scope includes both moderation-oriented lists and curration-oriented lists." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/listblock.json b/lexicons/app/bsky/graph/listblock.json new file mode 100644 index 0000000000..cef4b85255 --- /dev/null +++ b/lexicons/app/bsky/graph/listblock.json @@ -0,0 +1,30 @@ +{ + "id": "app.bsky.graph.listblock", + "defs": { + "main": { + "key": "tid", + "type": "record", + "record": { + "type": "object", + "required": [ + "subject", + "createdAt" + ], + "properties": { + "subject": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to the mod list record." + }, + "createdAt": { + "type": "string", + "format": "datetime" + } + } + }, + "description": "Record representing a block relationship against an entire an entire list of accounts (actors)." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/listitem.json b/lexicons/app/bsky/graph/listitem.json new file mode 100644 index 0000000000..edfb268b35 --- /dev/null +++ b/lexicons/app/bsky/graph/listitem.json @@ -0,0 +1,36 @@ +{ + "id": "app.bsky.graph.listitem", + "defs": { + "main": { + "key": "tid", + "type": "record", + "record": { + "type": "object", + "required": [ + "subject", + "list", + "createdAt" + ], + "properties": { + "list": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to the list record (app.bsky.graph.list)." + }, + "subject": { + "type": "string", + "format": "did", + "description": "The account which is included on the list." + }, + "createdAt": { + "type": "string", + "format": "datetime" + } + } + }, + "description": "Record representing an account's inclusion on a specific list. The AppView will ignore duplicate listitem records." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/muteActor.json b/lexicons/app/bsky/graph/muteActor.json new file mode 100644 index 0000000000..3f5866cca0 --- /dev/null +++ b/lexicons/app/bsky/graph/muteActor.json @@ -0,0 +1,34 @@ +{ + "id": "app.bsky.graph.muteActor", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier" + }, + "onlyReposts": { + "type": "boolean", + "description": "Restrict the mute to the account's reposts. When any 'only' scope is set, just the scoped content is muted; when none are set, the account is fully muted. Repeat calls replace the stored scope rather than adding to it." + }, + "onlyQuoteposts": { + "type": "boolean", + "description": "Restrict the mute to the account's quote posts. See onlyReposts." + } + } + }, + "encoding": "application/json" + }, + "description": "Creates a mute relationship for the specified account. If a mute already exists for the account, it is updated in place: the stored scope is replaced with the scope in this request. Mutes are private in Bluesky. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/muteActorList.json b/lexicons/app/bsky/graph/muteActorList.json new file mode 100644 index 0000000000..7a2d060d03 --- /dev/null +++ b/lexicons/app/bsky/graph/muteActorList.json @@ -0,0 +1,26 @@ +{ + "id": "app.bsky.graph.muteActorList", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "list" + ], + "properties": { + "list": { + "type": "string", + "format": "at-uri" + } + } + }, + "encoding": "application/json" + }, + "description": "Creates a mute relationship for the specified list of accounts. Mutes are private in Bluesky. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/muteThread.json b/lexicons/app/bsky/graph/muteThread.json new file mode 100644 index 0000000000..7cfb6890f2 --- /dev/null +++ b/lexicons/app/bsky/graph/muteThread.json @@ -0,0 +1,26 @@ +{ + "id": "app.bsky.graph.muteThread", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "root" + ], + "properties": { + "root": { + "type": "string", + "format": "at-uri" + } + } + }, + "encoding": "application/json" + }, + "description": "Mutes a thread preventing notifications from the thread and any of its children. Mutes are private in Bluesky. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/searchStarterPacks.json b/lexicons/app/bsky/graph/searchStarterPacks.json new file mode 100644 index 0000000000..20ac032f3f --- /dev/null +++ b/lexicons/app/bsky/graph/searchStarterPacks.json @@ -0,0 +1,53 @@ +{ + "id": "app.bsky.graph.searchStarterPacks", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "starterPacks" + ], + "properties": { + "cursor": { + "type": "string" + }, + "starterPacks": { + "type": "array", + "items": { + "ref": "app.bsky.graph.defs#starterPackViewBasic", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "q" + ], + "properties": { + "q": { + "type": "string", + "description": "Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended." + }, + "limit": { + "type": "integer", + "default": 25, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Find starter packs matching search criteria. Does not require auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/searchStarterPacksV2.json b/lexicons/app/bsky/graph/searchStarterPacksV2.json new file mode 100644 index 0000000000..9d6949e0e2 --- /dev/null +++ b/lexicons/app/bsky/graph/searchStarterPacksV2.json @@ -0,0 +1,57 @@ +{ + "id": "app.bsky.graph.searchStarterPacksV2", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "starterPacks" + ], + "properties": { + "cursor": { + "type": "string" + }, + "hitsTotal": { + "type": "integer", + "description": "Estimated total number of matching hits. May be rounded or truncated." + }, + "starterPacks": { + "type": "array", + "items": { + "ref": "app.bsky.graph.defs#starterPackView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "q" + ], + "properties": { + "q": { + "type": "string", + "description": "Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended." + }, + "limit": { + "type": "integer", + "default": 25, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Find starter packs matching search criteria. Does not require auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/starterpack.json b/lexicons/app/bsky/graph/starterpack.json new file mode 100644 index 0000000000..add9530335 --- /dev/null +++ b/lexicons/app/bsky/graph/starterpack.json @@ -0,0 +1,70 @@ +{ + "id": "app.bsky.graph.starterpack", + "defs": { + "main": { + "key": "tid", + "type": "record", + "record": { + "type": "object", + "required": [ + "name", + "list", + "createdAt" + ], + "properties": { + "list": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to the list record." + }, + "name": { + "type": "string", + "maxLength": 500, + "minLength": 1, + "description": "Display name for starter pack; can not be empty.", + "maxGraphemes": 50 + }, + "feeds": { + "type": "array", + "items": { + "ref": "#feedItem", + "type": "ref" + }, + "maxLength": 3 + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "description": { + "type": "string", + "maxLength": 3000, + "maxGraphemes": 300 + }, + "descriptionFacets": { + "type": "array", + "items": { + "ref": "app.bsky.richtext.facet", + "type": "ref" + } + } + } + }, + "description": "Record defining a starter pack of actors and feeds for new users." + }, + "feedItem": { + "type": "object", + "required": [ + "uri" + ], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/unmuteActor.json b/lexicons/app/bsky/graph/unmuteActor.json new file mode 100644 index 0000000000..8b9ca6cdb2 --- /dev/null +++ b/lexicons/app/bsky/graph/unmuteActor.json @@ -0,0 +1,26 @@ +{ + "id": "app.bsky.graph.unmuteActor", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "at-identifier" + } + } + }, + "encoding": "application/json" + }, + "description": "Unmutes the specified account. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/unmuteActorList.json b/lexicons/app/bsky/graph/unmuteActorList.json new file mode 100644 index 0000000000..b770634928 --- /dev/null +++ b/lexicons/app/bsky/graph/unmuteActorList.json @@ -0,0 +1,26 @@ +{ + "id": "app.bsky.graph.unmuteActorList", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "list" + ], + "properties": { + "list": { + "type": "string", + "format": "at-uri" + } + } + }, + "encoding": "application/json" + }, + "description": "Unmutes the specified list of accounts. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/unmuteThread.json b/lexicons/app/bsky/graph/unmuteThread.json new file mode 100644 index 0000000000..5b86269582 --- /dev/null +++ b/lexicons/app/bsky/graph/unmuteThread.json @@ -0,0 +1,26 @@ +{ + "id": "app.bsky.graph.unmuteThread", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "root" + ], + "properties": { + "root": { + "type": "string", + "format": "at-uri" + } + } + }, + "encoding": "application/json" + }, + "description": "Unmutes the specified thread. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/graph/verification.json b/lexicons/app/bsky/graph/verification.json new file mode 100644 index 0000000000..6d55ebf909 --- /dev/null +++ b/lexicons/app/bsky/graph/verification.json @@ -0,0 +1,42 @@ +{ + "id": "app.bsky.graph.verification", + "defs": { + "main": { + "key": "tid", + "type": "record", + "record": { + "type": "object", + "required": [ + "subject", + "handle", + "displayName", + "createdAt" + ], + "properties": { + "handle": { + "type": "string", + "format": "handle", + "description": "Handle of the subject the verification applies to at the moment of verifying, which might not be the same at the time of viewing. The verification is only valid if the current handle matches the one at the time of verifying." + }, + "subject": { + "type": "string", + "format": "did", + "description": "DID of the subject the verification applies to." + }, + "createdAt": { + "type": "string", + "format": "datetime", + "description": "Date of when the verification was created." + }, + "displayName": { + "type": "string", + "description": "Display name of the subject the verification applies to at the moment of verifying, which might not be the same at the time of viewing. The verification is only valid if the current displayName matches the one at the time of verifying." + } + } + }, + "description": "Record declaring a verification relationship between two accounts. Verifications are only considered valid by an app if issued by an account the app considers trusted." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/labeler/defs.json b/lexicons/app/bsky/labeler/defs.json new file mode 100644 index 0000000000..20de6e051b --- /dev/null +++ b/lexicons/app/bsky/labeler/defs.json @@ -0,0 +1,153 @@ +{ + "id": "app.bsky.labeler.defs", + "defs": { + "labelerView": { + "type": "object", + "required": [ + "uri", + "cid", + "creator", + "indexedAt" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "viewer": { + "ref": "#labelerViewerState", + "type": "ref" + }, + "creator": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + }, + "indexedAt": { + "type": "string", + "format": "datetime" + }, + "likeCount": { + "type": "integer", + "minimum": 0 + } + } + }, + "labelerPolicies": { + "type": "object", + "required": [ + "labelValues" + ], + "properties": { + "labelValues": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#labelValue", + "type": "ref" + }, + "description": "The label values which this labeler publishes. May include global or custom labels." + }, + "labelValueDefinitions": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#labelValueDefinition", + "type": "ref" + }, + "description": "Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler." + } + } + }, + "labelerViewerState": { + "type": "object", + "properties": { + "like": { + "type": "string", + "format": "at-uri" + } + } + }, + "labelerViewDetailed": { + "type": "object", + "required": [ + "uri", + "cid", + "creator", + "policies", + "indexedAt" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "viewer": { + "ref": "#labelerViewerState", + "type": "ref" + }, + "creator": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + }, + "policies": { + "ref": "app.bsky.labeler.defs#labelerPolicies", + "type": "ref" + }, + "indexedAt": { + "type": "string", + "format": "datetime" + }, + "likeCount": { + "type": "integer", + "minimum": 0 + }, + "reasonTypes": { + "type": "array", + "items": { + "ref": "com.atproto.moderation.defs#reasonType", + "type": "ref" + }, + "description": "The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed." + }, + "subjectTypes": { + "type": "array", + "items": { + "ref": "com.atproto.moderation.defs#subjectType", + "type": "ref" + }, + "description": "The set of subject types (account, record, etc) this service accepts reports on." + }, + "subjectCollections": { + "type": "array", + "items": { + "type": "string", + "format": "nsid" + }, + "description": "Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type." + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/labeler/getServices.json b/lexicons/app/bsky/labeler/getServices.json new file mode 100644 index 0000000000..84a8856569 --- /dev/null +++ b/lexicons/app/bsky/labeler/getServices.json @@ -0,0 +1,51 @@ +{ + "id": "app.bsky.labeler.getServices", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "views" + ], + "properties": { + "views": { + "type": "array", + "items": { + "refs": [ + "app.bsky.labeler.defs#labelerView", + "app.bsky.labeler.defs#labelerViewDetailed" + ], + "type": "union" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "dids" + ], + "properties": { + "dids": { + "type": "array", + "items": { + "type": "string", + "format": "did" + } + }, + "detailed": { + "type": "boolean", + "default": false + } + } + }, + "description": "Get information about a list of labeler services." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/labeler/service.json b/lexicons/app/bsky/labeler/service.json new file mode 100644 index 0000000000..65f20e1fee --- /dev/null +++ b/lexicons/app/bsky/labeler/service.json @@ -0,0 +1,59 @@ +{ + "id": "app.bsky.labeler.service", + "defs": { + "main": { + "key": "literal:self", + "type": "record", + "record": { + "type": "object", + "required": [ + "policies", + "createdAt" + ], + "properties": { + "labels": { + "refs": [ + "com.atproto.label.defs#selfLabels" + ], + "type": "union" + }, + "policies": { + "ref": "app.bsky.labeler.defs#labelerPolicies", + "type": "ref" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "reasonTypes": { + "type": "array", + "items": { + "ref": "com.atproto.moderation.defs#reasonType", + "type": "ref" + }, + "description": "The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed." + }, + "subjectTypes": { + "type": "array", + "items": { + "ref": "com.atproto.moderation.defs#subjectType", + "type": "ref" + }, + "description": "The set of subject types (account, record, etc) this service accepts reports on." + }, + "subjectCollections": { + "type": "array", + "items": { + "type": "string", + "format": "nsid" + }, + "description": "Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type." + } + } + }, + "description": "A declaration of the existence of labeler service." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/notification/declaration.json b/lexicons/app/bsky/notification/declaration.json new file mode 100644 index 0000000000..185af94404 --- /dev/null +++ b/lexicons/app/bsky/notification/declaration.json @@ -0,0 +1,29 @@ +{ + "id": "app.bsky.notification.declaration", + "defs": { + "main": { + "key": "literal:self", + "type": "record", + "record": { + "type": "object", + "required": [ + "allowSubscriptions" + ], + "properties": { + "allowSubscriptions": { + "type": "string", + "description": "A declaration of the user's preference for allowing activity subscriptions from other users. Absence of a record implies 'followers'.", + "knownValues": [ + "followers", + "mutuals", + "none" + ] + } + } + }, + "description": "A declaration of the user's choices related to notifications that can be produced by them." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/notification/defs.json b/lexicons/app/bsky/notification/defs.json new file mode 100644 index 0000000000..3e767ca298 --- /dev/null +++ b/lexicons/app/bsky/notification/defs.json @@ -0,0 +1,175 @@ +{ + "id": "app.bsky.notification.defs", + "defs": { + "preference": { + "type": "object", + "required": [ + "list", + "push" + ], + "properties": { + "list": { + "type": "boolean" + }, + "push": { + "type": "boolean" + } + } + }, + "preferences": { + "type": "object", + "required": [ + "chat", + "follow", + "like", + "likeViaRepost", + "mention", + "quote", + "reply", + "repost", + "repostViaRepost", + "starterpackJoined", + "subscribedPost", + "unverified", + "verified" + ], + "properties": { + "chat": { + "ref": "#chatPreference", + "type": "ref", + "description": "Deprecated: use chat.bsky.notification preferences instead. This will only return a default value." + }, + "like": { + "ref": "#filterablePreference", + "type": "ref" + }, + "quote": { + "ref": "#filterablePreference", + "type": "ref" + }, + "reply": { + "ref": "#filterablePreference", + "type": "ref" + }, + "follow": { + "ref": "#filterablePreference", + "type": "ref" + }, + "repost": { + "ref": "#filterablePreference", + "type": "ref" + }, + "mention": { + "ref": "#filterablePreference", + "type": "ref" + }, + "verified": { + "ref": "#preference", + "type": "ref" + }, + "unverified": { + "ref": "#preference", + "type": "ref" + }, + "likeViaRepost": { + "ref": "#filterablePreference", + "type": "ref" + }, + "subscribedPost": { + "ref": "#preference", + "type": "ref" + }, + "repostViaRepost": { + "ref": "#filterablePreference", + "type": "ref" + }, + "starterpackJoined": { + "ref": "#preference", + "type": "ref" + } + } + }, + "recordDeleted": { + "type": "object", + "properties": {} + }, + "chatPreference": { + "type": "object", + "required": [ + "include", + "push" + ], + "properties": { + "push": { + "type": "boolean" + }, + "include": { + "type": "string", + "knownValues": [ + "all", + "accepted" + ] + } + }, + "description": "Deprecated: use chat.bsky.notification preferences instead. This will only return a default value." + }, + "activitySubscription": { + "type": "object", + "required": [ + "post", + "reply" + ], + "properties": { + "post": { + "type": "boolean" + }, + "reply": { + "type": "boolean" + } + } + }, + "filterablePreference": { + "type": "object", + "required": [ + "include", + "list", + "push" + ], + "properties": { + "list": { + "type": "boolean" + }, + "push": { + "type": "boolean" + }, + "include": { + "type": "string", + "knownValues": [ + "all", + "follows" + ] + } + } + }, + "subjectActivitySubscription": { + "type": "object", + "required": [ + "subject", + "activitySubscription" + ], + "properties": { + "subject": { + "type": "string", + "format": "did" + }, + "activitySubscription": { + "ref": "#activitySubscription", + "type": "ref" + } + }, + "description": "Object used to store activity subscription data in stash." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/notification/getPreferences.json b/lexicons/app/bsky/notification/getPreferences.json new file mode 100644 index 0000000000..54482fb455 --- /dev/null +++ b/lexicons/app/bsky/notification/getPreferences.json @@ -0,0 +1,30 @@ +{ + "id": "app.bsky.notification.getPreferences", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "preferences" + ], + "properties": { + "preferences": { + "ref": "app.bsky.notification.defs#preferences", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": {} + }, + "description": "Get notification-related preferences for an account. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/notification/getUnreadCount.json b/lexicons/app/bsky/notification/getUnreadCount.json new file mode 100644 index 0000000000..87a37810d9 --- /dev/null +++ b/lexicons/app/bsky/notification/getUnreadCount.json @@ -0,0 +1,37 @@ +{ + "id": "app.bsky.notification.getUnreadCount", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "count" + ], + "properties": { + "count": { + "type": "integer" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "seenAt": { + "type": "string", + "format": "datetime" + }, + "priority": { + "type": "boolean" + } + } + }, + "description": "Count the number of unread notifications for the requesting account. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/notification/listActivitySubscriptions.json b/lexicons/app/bsky/notification/listActivitySubscriptions.json new file mode 100644 index 0000000000..350b822ac9 --- /dev/null +++ b/lexicons/app/bsky/notification/listActivitySubscriptions.json @@ -0,0 +1,46 @@ +{ + "id": "app.bsky.notification.listActivitySubscriptions", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "subscriptions" + ], + "properties": { + "cursor": { + "type": "string" + }, + "subscriptions": { + "type": "array", + "items": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Enumerate all accounts to which the requesting account is subscribed to receive notifications for. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/notification/listNotifications.json b/lexicons/app/bsky/notification/listNotifications.json new file mode 100644 index 0000000000..2c2aa20207 --- /dev/null +++ b/lexicons/app/bsky/notification/listNotifications.json @@ -0,0 +1,139 @@ +{ + "id": "app.bsky.notification.listNotifications", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "notifications" + ], + "properties": { + "cursor": { + "type": "string" + }, + "seenAt": { + "type": "string", + "format": "datetime" + }, + "priority": { + "type": "boolean" + }, + "notifications": { + "type": "array", + "items": { + "ref": "#notification", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + }, + "seenAt": { + "type": "string", + "format": "datetime" + }, + "reasons": { + "type": "array", + "items": { + "type": "string", + "description": "A reason that matches the reason property of #notification." + }, + "description": "Notification reasons to include in response." + }, + "priority": { + "type": "boolean" + } + } + }, + "description": "Enumerate notifications for the requesting account. Requires auth." + }, + "notification": { + "type": "object", + "required": [ + "uri", + "cid", + "author", + "reason", + "record", + "isRead", + "indexedAt" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "author": { + "ref": "app.bsky.actor.defs#profileView", + "type": "ref" + }, + "isRead": { + "type": "boolean" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "reason": { + "type": "string", + "description": "The reason why this notification was delivered - e.g. your post was liked, or you received a new follower.", + "knownValues": [ + "like", + "repost", + "follow", + "mention", + "reply", + "quote", + "starterpack-joined", + "verified", + "unverified", + "like-via-repost", + "repost-via-repost", + "subscribed-post", + "contact-match" + ] + }, + "record": { + "type": "unknown" + }, + "indexedAt": { + "type": "string", + "format": "datetime" + }, + "starterPack": { + "ref": "app.bsky.graph.defs#starterPackViewBasic", + "type": "ref", + "description": "The starter pack associated with this notification. Present when the notification is for a follow originating from a starter pack." + }, + "reasonSubject": { + "type": "string", + "format": "at-uri" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/notification/putActivitySubscription.json b/lexicons/app/bsky/notification/putActivitySubscription.json new file mode 100644 index 0000000000..65cc67b335 --- /dev/null +++ b/lexicons/app/bsky/notification/putActivitySubscription.json @@ -0,0 +1,50 @@ +{ + "id": "app.bsky.notification.putActivitySubscription", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "subject", + "activitySubscription" + ], + "properties": { + "subject": { + "type": "string", + "format": "did" + }, + "activitySubscription": { + "ref": "app.bsky.notification.defs#activitySubscription", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "output": { + "schema": { + "type": "object", + "required": [ + "subject" + ], + "properties": { + "subject": { + "type": "string", + "format": "did" + }, + "activitySubscription": { + "ref": "app.bsky.notification.defs#activitySubscription", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Puts an activity subscription entry. The key should be omitted for creation and provided for updates. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/notification/putPreferences.json b/lexicons/app/bsky/notification/putPreferences.json new file mode 100644 index 0000000000..2875de467b --- /dev/null +++ b/lexicons/app/bsky/notification/putPreferences.json @@ -0,0 +1,25 @@ +{ + "id": "app.bsky.notification.putPreferences", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "priority" + ], + "properties": { + "priority": { + "type": "boolean" + } + } + }, + "encoding": "application/json" + }, + "description": "Set notification-related preferences for an account. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/notification/putPreferencesV2.json b/lexicons/app/bsky/notification/putPreferencesV2.json new file mode 100644 index 0000000000..6a5d98fbda --- /dev/null +++ b/lexicons/app/bsky/notification/putPreferencesV2.json @@ -0,0 +1,87 @@ +{ + "id": "app.bsky.notification.putPreferencesV2", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "properties": { + "chat": { + "ref": "app.bsky.notification.defs#chatPreference", + "type": "ref", + "description": "Deprecated: use chat.bsky.notification preferences instead. Setting this won't stick and the default values will be returned." + }, + "like": { + "ref": "app.bsky.notification.defs#filterablePreference", + "type": "ref" + }, + "quote": { + "ref": "app.bsky.notification.defs#filterablePreference", + "type": "ref" + }, + "reply": { + "ref": "app.bsky.notification.defs#filterablePreference", + "type": "ref" + }, + "follow": { + "ref": "app.bsky.notification.defs#filterablePreference", + "type": "ref" + }, + "repost": { + "ref": "app.bsky.notification.defs#filterablePreference", + "type": "ref" + }, + "mention": { + "ref": "app.bsky.notification.defs#filterablePreference", + "type": "ref" + }, + "verified": { + "ref": "app.bsky.notification.defs#preference", + "type": "ref" + }, + "unverified": { + "ref": "app.bsky.notification.defs#preference", + "type": "ref" + }, + "likeViaRepost": { + "ref": "app.bsky.notification.defs#filterablePreference", + "type": "ref" + }, + "subscribedPost": { + "ref": "app.bsky.notification.defs#preference", + "type": "ref" + }, + "repostViaRepost": { + "ref": "app.bsky.notification.defs#filterablePreference", + "type": "ref" + }, + "starterpackJoined": { + "ref": "app.bsky.notification.defs#preference", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "output": { + "schema": { + "type": "object", + "required": [ + "preferences" + ], + "properties": { + "preferences": { + "ref": "app.bsky.notification.defs#preferences", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Set notification-related preferences for an account. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/notification/registerPush.json b/lexicons/app/bsky/notification/registerPush.json new file mode 100644 index 0000000000..db1fff3ab7 --- /dev/null +++ b/lexicons/app/bsky/notification/registerPush.json @@ -0,0 +1,47 @@ +{ + "id": "app.bsky.notification.registerPush", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "serviceDid", + "token", + "platform", + "appId" + ], + "properties": { + "appId": { + "type": "string" + }, + "token": { + "type": "string" + }, + "platform": { + "type": "string", + "knownValues": [ + "ios", + "android", + "web" + ] + }, + "serviceDid": { + "type": "string", + "format": "did" + }, + "ageRestricted": { + "type": "boolean", + "description": "Set to true when the actor is age restricted" + } + } + }, + "encoding": "application/json" + }, + "description": "Register to receive push notifications, via a specified service, for the requesting account. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/notification/unregisterPush.json b/lexicons/app/bsky/notification/unregisterPush.json new file mode 100644 index 0000000000..a73bd6afde --- /dev/null +++ b/lexicons/app/bsky/notification/unregisterPush.json @@ -0,0 +1,43 @@ +{ + "id": "app.bsky.notification.unregisterPush", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "serviceDid", + "token", + "platform", + "appId" + ], + "properties": { + "appId": { + "type": "string" + }, + "token": { + "type": "string" + }, + "platform": { + "type": "string", + "knownValues": [ + "ios", + "android", + "web" + ] + }, + "serviceDid": { + "type": "string", + "format": "did" + } + } + }, + "encoding": "application/json" + }, + "description": "The inverse of registerPush - inform a specified service that push notifications should no longer be sent to the given token for the requesting account. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/notification/updateSeen.json b/lexicons/app/bsky/notification/updateSeen.json new file mode 100644 index 0000000000..128b8611e3 --- /dev/null +++ b/lexicons/app/bsky/notification/updateSeen.json @@ -0,0 +1,26 @@ +{ + "id": "app.bsky.notification.updateSeen", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "seenAt" + ], + "properties": { + "seenAt": { + "type": "string", + "format": "datetime" + } + } + }, + "encoding": "application/json" + }, + "description": "Notify server that the requesting account has seen notifications. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/richtext/facet.json b/lexicons/app/bsky/richtext/facet.json new file mode 100644 index 0000000000..39b039b790 --- /dev/null +++ b/lexicons/app/bsky/richtext/facet.json @@ -0,0 +1,90 @@ +{ + "id": "app.bsky.richtext.facet", + "defs": { + "tag": { + "type": "object", + "required": [ + "tag" + ], + "properties": { + "tag": { + "type": "string", + "maxLength": 640, + "maxGraphemes": 64 + } + }, + "description": "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags')." + }, + "link": { + "type": "object", + "required": [ + "uri" + ], + "properties": { + "uri": { + "type": "string", + "format": "uri" + } + }, + "description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL." + }, + "main": { + "type": "object", + "required": [ + "index", + "features" + ], + "properties": { + "index": { + "ref": "#byteSlice", + "type": "ref" + }, + "features": { + "type": "array", + "items": { + "refs": [ + "#mention", + "#link", + "#tag" + ], + "type": "union" + } + } + }, + "description": "Annotation of a sub-string within rich text." + }, + "mention": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + } + }, + "description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID." + }, + "byteSlice": { + "type": "object", + "required": [ + "byteStart", + "byteEnd" + ], + "properties": { + "byteEnd": { + "type": "integer", + "minimum": 0 + }, + "byteStart": { + "type": "integer", + "minimum": 0 + } + }, + "description": "Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/unspecced/defs.json b/lexicons/app/bsky/unspecced/defs.json new file mode 100644 index 0000000000..3dd830a6d2 --- /dev/null +++ b/lexicons/app/bsky/unspecced/defs.json @@ -0,0 +1,208 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.defs", + "defs": { + "skeletonSearchPost": { + "type": "object", + "required": ["uri"], + "properties": { + "uri": { "type": "string", "format": "at-uri" } + } + }, + "skeletonSearchActor": { + "type": "object", + "required": ["did"], + "properties": { + "did": { "type": "string", "format": "did" } + } + }, + "skeletonSearchStarterPack": { + "type": "object", + "required": ["uri"], + "properties": { + "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" } + } + }, + "skeletonTrend": { + "type": "object", + "required": [ + "topic", + "displayName", + "link", + "startedAt", + "postCount", + "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" }, + "dids": { + "type": "array", + "items": { + "type": "string", + "format": "did" + } + } + } + }, + "trendView": { + "type": "object", + "required": [ + "topic", + "displayName", + "link", + "startedAt", + "postCount", + "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" }, + "actors": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileViewBasic" + } + } + } + }, + "threadItemPost": { + "type": "object", + "required": [ + "post", + "moreParents", + "moreReplies", + "opThread", + "hiddenByThreadgate", + "mutedByViewer" + ], + "properties": { + "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." + }, + "moreReplies": { + "type": "integer", + "description": "This post has more replies that were not present in the response. This is a numeric value, which is best-effort and might not be accurate." + }, + "opThread": { + "type": "boolean", + "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", + "description": "The threadgate created by the author indicates this post as a reply to be hidden for everyone consuming the thread." + }, + "mutedByViewer": { + "type": "boolean", + "description": "This is by an account muted by the viewer requesting it." + } + } + }, + "threadItemNoUnauthenticated": { + "type": "object", + "properties": {} + }, + "threadItemNotFound": { + "type": "object", + "properties": {} + }, + "threadItemBlocked": { + "type": "object", + "required": ["author"], + "properties": { + "author": { "type": "ref", "ref": "app.bsky.feed.defs#blockedAuthor" } + } + }, + "ageAssuranceState": { + "type": "object", + "description": "The computed state of the age assurance process, returned to the user in question on certain authenticated requests.", + "required": ["status"], + "properties": { + "lastInitiatedAt": { + "type": "string", + "format": "datetime", + "description": "The timestamp when this state was last updated." + }, + "status": { + "type": "string", + "description": "The status of the age assurance process.", + "knownValues": ["unknown", "pending", "assured", "blocked"] + } + } + }, + "ageAssuranceEvent": { + "type": "object", + "description": "Object used to store age assurance data in stash.", + "required": ["createdAt", "status", "attemptId"], + "properties": { + "createdAt": { + "type": "string", + "format": "datetime", + "description": "The date and time of this write operation." + }, + "status": { + "type": "string", + "description": "The status of the age assurance process.", + "knownValues": ["unknown", "pending", "assured"] + }, + "attemptId": { + "type": "string", + "description": "The unique identifier for this instance of the age assurance flow, in UUID format." + }, + "email": { + "type": "string", + "description": "The email used for AA." + }, + "initIp": { + "type": "string", + "description": "The IP address used when initiating the AA flow." + }, + "initUa": { + "type": "string", + "description": "The user agent used when initiating the AA flow." + }, + "completeIp": { + "type": "string", + "description": "The IP address used when completing the AA flow." + }, + "completeUa": { + "type": "string", + "description": "The user agent used when completing the AA flow." + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getAgeAssuranceState.json b/lexicons/app/bsky/unspecced/getAgeAssuranceState.json new file mode 100644 index 0000000000..3bd42b3246 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getAgeAssuranceState.json @@ -0,0 +1,17 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getAgeAssuranceState", + "defs": { + "main": { + "type": "query", + "description": "Returns the current state of the age assurance process for an account. This is used to check if the user has completed age assurance or if further action is required.", + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "app.bsky.unspecced.defs#ageAssuranceState" + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getConfig.json b/lexicons/app/bsky/unspecced/getConfig.json new file mode 100644 index 0000000000..286bc48058 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getConfig.json @@ -0,0 +1,37 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getConfig", + "defs": { + "main": { + "type": "query", + "description": "Get miscellaneous runtime configuration.", + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": [], + "properties": { + "checkEmailConfirmed": { "type": "boolean" }, + "liveNow": { + "type": "array", + "items": { "type": "ref", "ref": "#liveNowConfig" } + } + } + } + } + }, + "liveNowConfig": { + "type": "object", + "required": ["did", "domains"], + "properties": { + "did": { "type": "string", "format": "did" }, + "domains": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getOnboardingSuggestedStarterPacks.json b/lexicons/app/bsky/unspecced/getOnboardingSuggestedStarterPacks.json new file mode 100644 index 0000000000..60b1490137 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getOnboardingSuggestedStarterPacks.json @@ -0,0 +1,37 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getOnboardingSuggestedStarterPacks", + "defs": { + "main": { + "type": "query", + "description": "Get a list of suggested starterpacks for onboarding", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 25, + "default": 10 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["starterPacks"], + "properties": { + "starterPacks": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.graph.defs#starterPackView" + } + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getOnboardingSuggestedStarterPacksSkeleton.json b/lexicons/app/bsky/unspecced/getOnboardingSuggestedStarterPacksSkeleton.json new file mode 100644 index 0000000000..40f3a76264 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getOnboardingSuggestedStarterPacksSkeleton.json @@ -0,0 +1,42 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getOnboardingSuggestedStarterPacksSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Get a skeleton of suggested starterpacks for onboarding. Intended to be called and hydrated by app.bsky.unspecced.getOnboardingSuggestedStarterPacks", + "parameters": { + "type": "params", + "properties": { + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries)." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 25, + "default": 10 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["starterPacks"], + "properties": { + "starterPacks": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + } + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getOnboardingSuggestedUsersSkeleton.json b/lexicons/app/bsky/unspecced/getOnboardingSuggestedUsersSkeleton.json new file mode 100644 index 0000000000..1303ac174d --- /dev/null +++ b/lexicons/app/bsky/unspecced/getOnboardingSuggestedUsersSkeleton.json @@ -0,0 +1,54 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getOnboardingSuggestedUsersSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Get a skeleton of suggested users for onboarding. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedOnboardingUsers", + "parameters": { + "type": "params", + "properties": { + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries)." + }, + "category": { + "type": "string", + "description": "Category of users to get suggestions for." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 25 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["dids"], + "properties": { + "dids": { + "type": "array", + "items": { + "type": "string", + "format": "did" + } + }, + "recId": { + "type": "string", + "description": "DEPRECATED: use recIdStr instead." + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getPopularFeedGenerators.json b/lexicons/app/bsky/unspecced/getPopularFeedGenerators.json new file mode 100644 index 0000000000..ee05c16a1c --- /dev/null +++ b/lexicons/app/bsky/unspecced/getPopularFeedGenerators.json @@ -0,0 +1,40 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getPopularFeedGenerators", + "defs": { + "main": { + "type": "query", + "description": "An unspecced view of globally popular feed generators.", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" }, + "query": { "type": "string" } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["feeds"], + "properties": { + "cursor": { "type": "string" }, + "feeds": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#generatorView" + } + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getPostThreadOtherV2.json b/lexicons/app/bsky/unspecced/getPostThreadOtherV2.json new file mode 100644 index 0000000000..7fab28bfeb --- /dev/null +++ b/lexicons/app/bsky/unspecced/getPostThreadOtherV2.json @@ -0,0 +1,56 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getPostThreadOtherV2", + "defs": { + "main": { + "type": "query", + "description": "(NOTE: this endpoint is under development and WILL change without notice. Don't use it until it is moved out of `unspecced` or your application WILL break) Get additional posts under a thread e.g. replies hidden by threadgate. Based on an anchor post at any depth of the tree, returns top-level replies below that anchor. It does not include ancestors nor the anchor itself. This should be called after exhausting `app.bsky.unspecced.getPostThreadV2`. Does not require auth, but additional metadata and filtering will be applied for authed requests.", + "parameters": { + "type": "params", + "required": ["anchor"], + "properties": { + "anchor": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to post record. This is the anchor post." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["thread"], + "properties": { + "thread": { + "type": "array", + "description": "A flat list of other thread items. The depth of each item is indicated by the depth property inside the item.", + "items": { + "type": "ref", + "ref": "#threadItem" + } + } + } + } + } + }, + "threadItem": { + "type": "object", + "required": ["uri", "depth", "value"], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "depth": { + "type": "integer", + "description": "The nesting level of this item in the thread. Depth 0 means the anchor item. Items above have negative depths, items below have positive depths." + }, + "value": { + "type": "union", + "refs": ["app.bsky.unspecced.defs#threadItemPost"] + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getPostThreadV2.json b/lexicons/app/bsky/unspecced/getPostThreadV2.json new file mode 100644 index 0000000000..e38dfde964 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getPostThreadV2.json @@ -0,0 +1,94 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getPostThreadV2", + "defs": { + "main": { + "type": "query", + "description": "(NOTE: this endpoint is under development and WILL change without notice. Don't use it until it is moved out of `unspecced` or your application WILL break) Get posts in a thread. It is based in an anchor post at any depth of the tree, and returns posts above it (recursively resolving the parent, without further branching to their replies) and below it (recursive replies, with branching to their replies). Does not require auth, but additional metadata and filtering will be applied for authed requests.", + "parameters": { + "type": "params", + "required": ["anchor"], + "properties": { + "anchor": { + "type": "string", + "format": "at-uri", + "description": "Reference (AT-URI) to post record. This is the anchor post, and the thread will be built around it. It can be any post in the tree, not necessarily a root post." + }, + "above": { + "type": "boolean", + "description": "Whether to include parents above the anchor.", + "default": true + }, + "below": { + "type": "integer", + "description": "How many levels of replies to include below the anchor.", + "default": 6, + "minimum": 0, + "maximum": 20 + }, + "branchingFactor": { + "type": "integer", + "description": "Maximum of replies to include at each level of the thread, except for the direct replies to the anchor, which are (NOTE: currently, during unspecced phase) all returned (NOTE: later they might be paginated).", + "default": 10, + "minimum": 0, + "maximum": 100 + }, + "sort": { + "type": "string", + "description": "Sorting for the thread replies.", + "knownValues": ["newest", "oldest", "top"], + "default": "oldest" + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["thread", "hasOtherReplies"], + "properties": { + "thread": { + "type": "array", + "description": "A flat list of thread items. The depth of each item is indicated by the depth property inside the item.", + "items": { + "type": "ref", + "ref": "#threadItem" + } + }, + "threadgate": { + "type": "ref", + "ref": "app.bsky.feed.defs#threadgateView" + }, + "hasOtherReplies": { + "type": "boolean", + "description": "Whether this thread has additional replies. If true, a call can be made to the `getPostThreadOtherV2` endpoint to retrieve them." + } + } + } + } + }, + "threadItem": { + "type": "object", + "required": ["uri", "depth", "value"], + "properties": { + "uri": { + "type": "string", + "format": "at-uri" + }, + "depth": { + "type": "integer", + "description": "The nesting level of this item in the thread. Depth 0 means the anchor item. Items above have negative depths, items below have positive depths." + }, + "value": { + "type": "union", + "refs": [ + "app.bsky.unspecced.defs#threadItemPost", + "app.bsky.unspecced.defs#threadItemNoUnauthenticated", + "app.bsky.unspecced.defs#threadItemNotFound", + "app.bsky.unspecced.defs#threadItemBlocked" + ] + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestedFeeds.json b/lexicons/app/bsky/unspecced/getSuggestedFeeds.json new file mode 100644 index 0000000000..1a723a0bbb --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestedFeeds.json @@ -0,0 +1,37 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestedFeeds", + "defs": { + "main": { + "type": "query", + "description": "Get a list of suggested feeds", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 25, + "default": 10 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["feeds"], + "properties": { + "feeds": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.feed.defs#generatorView" + } + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestedFeedsSkeleton.json b/lexicons/app/bsky/unspecced/getSuggestedFeedsSkeleton.json new file mode 100644 index 0000000000..c0b72da680 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestedFeedsSkeleton.json @@ -0,0 +1,42 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestedFeedsSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Get a skeleton of suggested feeds. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedFeeds", + "parameters": { + "type": "params", + "properties": { + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries)." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 25, + "default": 10 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["feeds"], + "properties": { + "feeds": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + } + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestedOnboardingUsers.json b/lexicons/app/bsky/unspecced/getSuggestedOnboardingUsers.json new file mode 100644 index 0000000000..24e45566ef --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestedOnboardingUsers.json @@ -0,0 +1,49 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestedOnboardingUsers", + "defs": { + "main": { + "type": "query", + "description": "Get a list of suggested users for onboarding", + "parameters": { + "type": "params", + "properties": { + "category": { + "type": "string", + "description": "Category of users to get suggestions for." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 25 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["actors"], + "properties": { + "actors": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + } + }, + "recId": { + "type": "string", + "description": "DEPRECATED: use recIdStr instead." + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestedStarterPacks.json b/lexicons/app/bsky/unspecced/getSuggestedStarterPacks.json new file mode 100644 index 0000000000..d4bdd22643 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestedStarterPacks.json @@ -0,0 +1,37 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestedStarterPacks", + "defs": { + "main": { + "type": "query", + "description": "Get a list of suggested starterpacks", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 25, + "default": 10 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["starterPacks"], + "properties": { + "starterPacks": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.graph.defs#starterPackView" + } + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestedStarterPacksSkeleton.json b/lexicons/app/bsky/unspecced/getSuggestedStarterPacksSkeleton.json new file mode 100644 index 0000000000..cf5d88e060 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestedStarterPacksSkeleton.json @@ -0,0 +1,42 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestedStarterPacksSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Get a skeleton of suggested starterpacks. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedStarterpacks", + "parameters": { + "type": "params", + "properties": { + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries)." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 25, + "default": 10 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["starterPacks"], + "properties": { + "starterPacks": { + "type": "array", + "items": { + "type": "string", + "format": "at-uri" + } + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestedUsers.json b/lexicons/app/bsky/unspecced/getSuggestedUsers.json new file mode 100644 index 0000000000..705e21c9d6 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestedUsers.json @@ -0,0 +1,49 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestedUsers", + "defs": { + "main": { + "type": "query", + "description": "Get a list of suggested users", + "parameters": { + "type": "params", + "properties": { + "category": { + "type": "string", + "description": "Category of users to get suggestions for." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 25 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["actors"], + "properties": { + "actors": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + } + }, + "recId": { + "type": "string", + "description": "DEPRECATED: use recIdStr instead." + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestedUsersForDiscover.json b/lexicons/app/bsky/unspecced/getSuggestedUsersForDiscover.json new file mode 100644 index 0000000000..7d0f289d12 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestedUsersForDiscover.json @@ -0,0 +1,41 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestedUsersForDiscover", + "defs": { + "main": { + "type": "query", + "description": "Get a list of suggested users for the Discover page", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 25 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["actors"], + "properties": { + "actors": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + } + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestedUsersForDiscoverSkeleton.json b/lexicons/app/bsky/unspecced/getSuggestedUsersForDiscoverSkeleton.json new file mode 100644 index 0000000000..7f017ca5e6 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestedUsersForDiscoverSkeleton.json @@ -0,0 +1,46 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestedUsersForDiscoverSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Get a skeleton of suggested users for the Discover page. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedUsersForDiscover", + "parameters": { + "type": "params", + "properties": { + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries)." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 25 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["dids"], + "properties": { + "dids": { + "type": "array", + "items": { + "type": "string", + "format": "did" + } + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestedUsersForExplore.json b/lexicons/app/bsky/unspecced/getSuggestedUsersForExplore.json new file mode 100644 index 0000000000..a37a21a7eb --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestedUsersForExplore.json @@ -0,0 +1,45 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestedUsersForExplore", + "defs": { + "main": { + "type": "query", + "description": "Get a list of suggested users for the Explore page", + "parameters": { + "type": "params", + "properties": { + "category": { + "type": "string", + "description": "Category of users to get suggestions for." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 25 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["actors"], + "properties": { + "actors": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + } + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestedUsersForExploreSkeleton.json b/lexicons/app/bsky/unspecced/getSuggestedUsersForExploreSkeleton.json new file mode 100644 index 0000000000..b3f3186e84 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestedUsersForExploreSkeleton.json @@ -0,0 +1,50 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestedUsersForExploreSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Get a skeleton of suggested users for the Explore page. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedUsersForExplore", + "parameters": { + "type": "params", + "properties": { + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries)." + }, + "category": { + "type": "string", + "description": "Category of users to get suggestions for." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 25 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["dids"], + "properties": { + "dids": { + "type": "array", + "items": { + "type": "string", + "format": "did" + } + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestedUsersForSeeMore.json b/lexicons/app/bsky/unspecced/getSuggestedUsersForSeeMore.json new file mode 100644 index 0000000000..1938df7e55 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestedUsersForSeeMore.json @@ -0,0 +1,45 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestedUsersForSeeMore", + "defs": { + "main": { + "type": "query", + "description": "Get a list of suggested users for the See More page", + "parameters": { + "type": "params", + "properties": { + "category": { + "type": "string", + "description": "Category of users to get suggestions for." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 25 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["actors"], + "properties": { + "actors": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.actor.defs#profileView" + } + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestedUsersForSeeMoreSkeleton.json b/lexicons/app/bsky/unspecced/getSuggestedUsersForSeeMoreSkeleton.json new file mode 100644 index 0000000000..954b518d77 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestedUsersForSeeMoreSkeleton.json @@ -0,0 +1,50 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestedUsersForSeeMoreSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Get a skeleton of suggested users for the See More page. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedUsersForSeeMore", + "parameters": { + "type": "params", + "properties": { + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries)." + }, + "category": { + "type": "string", + "description": "Category of users to get suggestions for." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 25 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["dids"], + "properties": { + "dids": { + "type": "array", + "items": { + "type": "string", + "format": "did" + } + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestedUsersSkeleton.json b/lexicons/app/bsky/unspecced/getSuggestedUsersSkeleton.json new file mode 100644 index 0000000000..3fed8dd95d --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestedUsersSkeleton.json @@ -0,0 +1,54 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestedUsersSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Get a skeleton of suggested users. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedUsers", + "parameters": { + "type": "params", + "properties": { + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries)." + }, + "category": { + "type": "string", + "description": "Category of users to get suggestions for." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 50, + "default": 25 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["dids"], + "properties": { + "dids": { + "type": "array", + "items": { + "type": "string", + "format": "did" + } + }, + "recId": { + "type": "string", + "description": "DEPRECATED: use recIdStr instead." + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getSuggestionsSkeleton.json b/lexicons/app/bsky/unspecced/getSuggestionsSkeleton.json new file mode 100644 index 0000000000..f97504c31d --- /dev/null +++ b/lexicons/app/bsky/unspecced/getSuggestionsSkeleton.json @@ -0,0 +1,62 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getSuggestionsSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Get a skeleton of suggested actors. Intended to be called and then hydrated through app.bsky.actor.getSuggestions", + "parameters": { + "type": "params", + "properties": { + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 50 + }, + "cursor": { "type": "string" }, + "relativeToDid": { + "type": "string", + "format": "did", + "description": "DID of the account to get suggestions relative to. If not provided, suggestions will be based on the viewer." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["actors"], + "properties": { + "cursor": { "type": "string" }, + "actors": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.unspecced.defs#skeletonSearchActor" + } + }, + "relativeToDid": { + "type": "string", + "format": "did", + "description": "DID of the account these suggestions are relative to. If this is returned undefined, suggestions are based on the viewer." + }, + "recId": { + "type": "integer", + "description": "DEPRECATED: use recIdStr instead." + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getTaggedSuggestions.json b/lexicons/app/bsky/unspecced/getTaggedSuggestions.json new file mode 100644 index 0000000000..9fd98ffefb --- /dev/null +++ b/lexicons/app/bsky/unspecced/getTaggedSuggestions.json @@ -0,0 +1,42 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getTaggedSuggestions", + "defs": { + "main": { + "type": "query", + "description": "Get a list of suggestions (feeds and users) tagged with categories", + "parameters": { + "type": "params", + "properties": {} + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["suggestions"], + "properties": { + "suggestions": { + "type": "array", + "items": { + "type": "ref", + "ref": "#suggestion" + } + } + } + } + } + }, + "suggestion": { + "type": "object", + "required": ["tag", "subjectType", "subject"], + "properties": { + "tag": { "type": "string" }, + "subjectType": { + "type": "string", + "knownValues": ["actor", "feed"] + }, + "subject": { "type": "string", "format": "uri" } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getTrendingTopics.json b/lexicons/app/bsky/unspecced/getTrendingTopics.json new file mode 100644 index 0000000000..097dc1c54d --- /dev/null +++ b/lexicons/app/bsky/unspecced/getTrendingTopics.json @@ -0,0 +1,49 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getTrendingTopics", + "defs": { + "main": { + "type": "query", + "description": "Get a list of trending topics", + "parameters": { + "type": "params", + "properties": { + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 25, + "default": 10 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["topics", "suggested"], + "properties": { + "topics": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.unspecced.defs#trendingTopic" + } + }, + "suggested": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.unspecced.defs#trendingTopic" + } + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getTrends.json b/lexicons/app/bsky/unspecced/getTrends.json new file mode 100644 index 0000000000..b857c8681b --- /dev/null +++ b/lexicons/app/bsky/unspecced/getTrends.json @@ -0,0 +1,41 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getTrends", + "defs": { + "main": { + "type": "query", + "description": "Get the current trends on the network", + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 25, + "default": 10 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["trends"], + "properties": { + "trends": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.unspecced.defs#trendView" + } + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/getTrendsSkeleton.json b/lexicons/app/bsky/unspecced/getTrendsSkeleton.json new file mode 100644 index 0000000000..3cb5a15b77 --- /dev/null +++ b/lexicons/app/bsky/unspecced/getTrendsSkeleton.json @@ -0,0 +1,46 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.getTrendsSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Get the skeleton of trends on the network. Intended to be called and then hydrated through app.bsky.unspecced.getTrends", + "parameters": { + "type": "params", + "properties": { + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries)." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 25, + "default": 10 + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["trends"], + "properties": { + "trends": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.unspecced.defs#skeletonTrend" + } + }, + "recIdStr": { + "type": "string", + "description": "Snowflake for this recommendation, use when submitting recommendation events." + } + } + } + } + } + } +} diff --git a/lexicons/app/bsky/unspecced/initAgeAssurance.json b/lexicons/app/bsky/unspecced/initAgeAssurance.json new file mode 100644 index 0000000000..43c4d3cf5e --- /dev/null +++ b/lexicons/app/bsky/unspecced/initAgeAssurance.json @@ -0,0 +1,43 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.initAgeAssurance", + "defs": { + "main": { + "type": "procedure", + "description": "Initiate age assurance for an account. This is a one-time action that will start the process of verifying the user's age.", + "input": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["email", "language", "countryCode"], + "properties": { + "email": { + "type": "string", + "description": "The user's email address to receive assurance instructions." + }, + "language": { + "type": "string", + "description": "The user's preferred language for communication during the assurance process." + }, + "countryCode": { + "type": "string", + "description": "An ISO 3166-1 alpha-2 code of the user's location." + } + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "ref", + "ref": "app.bsky.unspecced.defs#ageAssuranceState" + } + }, + "errors": [ + { "name": "InvalidEmail" }, + { "name": "DidTooLong" }, + { "name": "InvalidInitiation" } + ] + } + } +} diff --git a/lexicons/app/bsky/unspecced/searchActorsSkeleton.json b/lexicons/app/bsky/unspecced/searchActorsSkeleton.json new file mode 100644 index 0000000000..f6489140b9 --- /dev/null +++ b/lexicons/app/bsky/unspecced/searchActorsSkeleton.json @@ -0,0 +1,61 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.searchActorsSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Backend Actors (profile) search, returns only skeleton.", + "parameters": { + "type": "params", + "required": ["q"], + "properties": { + "q": { + "type": "string", + "description": "Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. For typeahead search, only simple term match is supported, not full syntax." + }, + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking." + }, + "typeahead": { + "type": "boolean", + "description": "If true, acts as fast/simple 'typeahead' query." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + }, + "cursor": { + "type": "string", + "description": "Optional pagination mechanism; may not necessarily allow scrolling through entire result set." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["actors"], + "properties": { + "cursor": { "type": "string" }, + "hitsTotal": { + "type": "integer", + "description": "Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits." + }, + "actors": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.unspecced.defs#skeletonSearchActor" + } + } + } + } + }, + "errors": [{ "name": "BadQueryString" }] + } + } +} diff --git a/lexicons/app/bsky/unspecced/searchPostsSkeleton.json b/lexicons/app/bsky/unspecced/searchPostsSkeleton.json new file mode 100644 index 0000000000..3be2c1b757 --- /dev/null +++ b/lexicons/app/bsky/unspecced/searchPostsSkeleton.json @@ -0,0 +1,104 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.searchPostsSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Backend Posts search, returns only skeleton", + "parameters": { + "type": "params", + "required": ["q"], + "properties": { + "q": { + "type": "string", + "description": "Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended." + }, + "sort": { + "type": "string", + "knownValues": ["top", "latest"], + "default": "latest", + "description": "Specifies the ranking order of results." + }, + "since": { + "type": "string", + "description": "Filter results for posts after the indicated datetime (inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYYY-MM-DD)." + }, + "until": { + "type": "string", + "description": "Filter results for posts before the indicated datetime (not inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYY-MM-DD)." + }, + "mentions": { + "type": "string", + "format": "at-identifier", + "description": "Filter to posts which mention the given account. Handles are resolved to DID before query-time. Only matches rich-text facet mentions." + }, + "author": { + "type": "string", + "format": "at-identifier", + "description": "Filter to posts by the given account. Handles are resolved to DID before query-time." + }, + "lang": { + "type": "string", + "format": "language", + "description": "Filter to posts in the given language. Expected to be based on post language field, though server may override language detection." + }, + "domain": { + "type": "string", + "description": "Filter to posts with URLs (facet links or embeds) linking to the given domain (hostname). Server may apply hostname normalization." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Filter to posts with links (facet links or embeds) pointing to this URL. Server may apply URL normalization or fuzzy matching." + }, + "tag": { + "type": "array", + "items": { + "type": "string", + "maxLength": 640, + "maxGraphemes": 64 + }, + "description": "Filter to posts with the given tag (hashtag), based on rich-text facet or tag field. Do not include the hash (#) prefix. Multiple tags can be specified, with 'AND' matching." + }, + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries). Used for 'from:me' queries." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + }, + "cursor": { + "type": "string", + "description": "Optional pagination mechanism; may not necessarily allow scrolling through entire result set." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["posts"], + "properties": { + "cursor": { "type": "string" }, + "hitsTotal": { + "type": "integer", + "description": "Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits." + }, + "posts": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.unspecced.defs#skeletonSearchPost" + } + } + } + } + }, + "errors": [{ "name": "BadQueryString" }] + } + } +} diff --git a/lexicons/app/bsky/unspecced/searchStarterPacksSkeleton.json b/lexicons/app/bsky/unspecced/searchStarterPacksSkeleton.json new file mode 100644 index 0000000000..80c1b88179 --- /dev/null +++ b/lexicons/app/bsky/unspecced/searchStarterPacksSkeleton.json @@ -0,0 +1,63 @@ +{ + "lexicon": 1, + "id": "app.bsky.unspecced.searchStarterPacksSkeleton", + "defs": { + "main": { + "type": "query", + "description": "Backend Starter Pack search, returns only skeleton.", + "parameters": { + "type": "params", + "required": ["q"], + "properties": { + "q": { + "type": "string", + "description": "Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended." + }, + "viewer": { + "type": "string", + "format": "did", + "description": "DID of the account making the request (not included for public/unauthenticated queries)." + }, + "limit": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 25 + }, + "cursor": { + "type": "string", + "description": "Optional pagination mechanism; may not necessarily allow scrolling through entire result set." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["starterPacks"], + "properties": { + "cursor": { + "type": "string" + }, + "hitsTotal": { + "type": "integer", + "description": "Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits." + }, + "starterPacks": { + "type": "array", + "items": { + "type": "ref", + "ref": "app.bsky.unspecced.defs#skeletonSearchStarterPack" + } + } + } + } + }, + "errors": [ + { + "name": "BadQueryString" + } + ] + } + } +} diff --git a/lexicons/app/bsky/video/defs.json b/lexicons/app/bsky/video/defs.json new file mode 100644 index 0000000000..278c17b35e --- /dev/null +++ b/lexicons/app/bsky/video/defs.json @@ -0,0 +1,65 @@ +{ + "id": "app.bsky.video.defs", + "defs": { + "jobStatus": { + "type": "object", + "required": [ + "jobId", + "did", + "state" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "blob": { + "type": "blob" + }, + "error": { + "type": "string" + }, + "jobId": { + "type": "string" + }, + "state": { + "type": "string", + "description": "The state of the video processing job. All values not listed as a known value indicate that the job is in process.", + "knownValues": [ + "JOB_STATE_CREATED", + "JOB_STATE_ENCODING", + "JOB_STATE_ENCODED", + "JOB_STATE_SCANNING", + "JOB_STATE_SCANNED", + "JOB_STATE_UPLOADING", + "JOB_STATE_UPLOADED", + "JOB_STATE_COMPLETED", + "JOB_STATE_FAILED" + ] + }, + "message": { + "type": "string" + }, + "progress": { + "type": "integer", + "maximum": 100, + "minimum": 0, + "description": "Progress within the current processing state." + }, + "failureCode": { + "type": "string", + "description": "A machine-readable code for why the video processing job failed.", + "knownValues": [ + "validation_failure", + "encoding_failure", + "pds_upload_failure", + "pds_upload_unsupported_blob_size", + "generic_failure" + ] + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/video/getJobStatus.json b/lexicons/app/bsky/video/getJobStatus.json new file mode 100644 index 0000000000..7fb3ae4a46 --- /dev/null +++ b/lexicons/app/bsky/video/getJobStatus.json @@ -0,0 +1,37 @@ +{ + "id": "app.bsky.video.getJobStatus", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "jobStatus" + ], + "properties": { + "jobStatus": { + "ref": "app.bsky.video.defs#jobStatus", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "jobId" + ], + "properties": { + "jobId": { + "type": "string" + } + } + }, + "description": "Get status details for a video processing job." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/video/getUploadLimits.json b/lexicons/app/bsky/video/getUploadLimits.json new file mode 100644 index 0000000000..fdc11082ec --- /dev/null +++ b/lexicons/app/bsky/video/getUploadLimits.json @@ -0,0 +1,37 @@ +{ + "id": "app.bsky.video.getUploadLimits", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "canUpload" + ], + "properties": { + "error": { + "type": "string" + }, + "message": { + "type": "string" + }, + "canUpload": { + "type": "boolean" + }, + "remainingDailyBytes": { + "type": "integer" + }, + "remainingDailyVideos": { + "type": "integer" + } + } + }, + "encoding": "application/json" + }, + "description": "Get video upload limits for the authenticated user." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/app/bsky/video/uploadVideo.json b/lexicons/app/bsky/video/uploadVideo.json new file mode 100644 index 0000000000..01330b1b9a --- /dev/null +++ b/lexicons/app/bsky/video/uploadVideo.json @@ -0,0 +1,29 @@ +{ + "id": "app.bsky.video.uploadVideo", + "defs": { + "main": { + "type": "procedure", + "input": { + "encoding": "video/mp4" + }, + "output": { + "schema": { + "type": "object", + "required": [ + "jobStatus" + ], + "properties": { + "jobStatus": { + "ref": "app.bsky.video.defs#jobStatus", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Upload a video to be processed then stored on the PDS." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/actor/declaration.json b/lexicons/chat/bsky/actor/declaration.json new file mode 100644 index 0000000000..de04440ac7 --- /dev/null +++ b/lexicons/chat/bsky/actor/declaration.json @@ -0,0 +1,37 @@ +{ + "id": "chat.bsky.actor.declaration", + "defs": { + "main": { + "key": "literal:self", + "type": "record", + "record": { + "type": "object", + "required": [ + "allowIncoming" + ], + "properties": { + "allowIncoming": { + "type": "string", + "knownValues": [ + "all", + "none", + "following" + ] + }, + "allowGroupInvites": { + "type": "string", + "description": "Declaration about group chat invitation preferences for the record owner.", + "knownValues": [ + "all", + "none", + "following" + ] + } + } + }, + "description": "A declaration of a Bluesky chat account." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/actor/defs.json b/lexicons/chat/bsky/actor/defs.json new file mode 100644 index 0000000000..87b18d714b --- /dev/null +++ b/lexicons/chat/bsky/actor/defs.json @@ -0,0 +1,105 @@ +{ + "id": "chat.bsky.actor.defs", + "defs": { + "memberRole": { + "type": "string", + "knownValues": [ + "owner", + "standard" + ] + }, + "groupConvoMember": { + "type": "object", + "required": [ + "role" + ], + "properties": { + "role": { + "ref": "#memberRole", + "type": "ref", + "description": "The member's role within this conversation. Only present in group conversation member lists." + }, + "addedBy": { + "ref": "#profileViewBasic", + "type": "ref", + "description": "Who added this member. Only present if the member was added (instead of joining via link)." + } + }, + "description": "A current group convo member." + }, + "profileViewBasic": { + "type": "object", + "required": [ + "did", + "handle" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "kind": { + "refs": [ + "#directConvoMember", + "#groupConvoMember", + "#pastGroupConvoMember" + ], + "type": "union", + "description": "Union field that has data specific to different kinds of convos." + }, + "avatar": { + "type": "string", + "format": "uri" + }, + "handle": { + "type": "string", + "format": "handle" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + }, + "viewer": { + "ref": "app.bsky.actor.defs#viewerState", + "type": "ref" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "associated": { + "ref": "app.bsky.actor.defs#profileAssociated", + "type": "ref" + }, + "displayName": { + "type": "string", + "maxLength": 640, + "maxGraphemes": 64 + }, + "chatDisabled": { + "type": "boolean", + "description": "Set to true when the actor cannot actively participate in conversations" + }, + "verification": { + "ref": "app.bsky.actor.defs#verificationState", + "type": "ref" + } + } + }, + "directConvoMember": { + "type": "object", + "properties": {} + }, + "pastGroupConvoMember": { + "type": "object", + "required": [], + "properties": {}, + "description": "A past group convo member." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/actor/deleteAccount.json b/lexicons/chat/bsky/actor/deleteAccount.json new file mode 100644 index 0000000000..f8955411db --- /dev/null +++ b/lexicons/chat/bsky/actor/deleteAccount.json @@ -0,0 +1,17 @@ +{ + "id": "chat.bsky.actor.deleteAccount", + "defs": { + "main": { + "type": "procedure", + "output": { + "schema": { + "type": "object", + "properties": {} + }, + "encoding": "application/json" + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/actor/exportAccountData.json b/lexicons/chat/bsky/actor/exportAccountData.json new file mode 100644 index 0000000000..dd8b9bfdda --- /dev/null +++ b/lexicons/chat/bsky/actor/exportAccountData.json @@ -0,0 +1,13 @@ +{ + "id": "chat.bsky.actor.exportAccountData", + "defs": { + "main": { + "type": "query", + "output": { + "encoding": "application/jsonl" + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/actor/getStatus.json b/lexicons/chat/bsky/actor/getStatus.json new file mode 100644 index 0000000000..4e1311a97b --- /dev/null +++ b/lexicons/chat/bsky/actor/getStatus.json @@ -0,0 +1,36 @@ +{ + "id": "chat.bsky.actor.getStatus", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "chatDisabled", + "canCreateGroups", + "groupMemberLimit" + ], + "properties": { + "chatDisabled": { + "type": "boolean", + "description": "True when the viewer's account is disabled and cannot actively participate in chat." + }, + "canCreateGroups": { + "type": "boolean", + "description": "Whether the viewer's account is allowed to create group chats. New accounts are restricted from creating groups." + }, + "groupMemberLimit": { + "type": "integer", + "description": "The maximum number of members allowed in a group conversation." + } + } + }, + "encoding": "application/json" + }, + "description": "Get the authenticated viewer's chat status: whether their account is chat-disabled and whether their group-membership additions are restricted to accounts they follow." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/acceptConvo.json b/lexicons/chat/bsky/convo/acceptConvo.json new file mode 100644 index 0000000000..bafc162b11 --- /dev/null +++ b/lexicons/chat/bsky/convo/acceptConvo.json @@ -0,0 +1,42 @@ +{ + "id": "chat.bsky.convo.acceptConvo", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + } + ], + "output": { + "schema": { + "type": "object", + "properties": { + "rev": { + "type": "string", + "description": "Rev when the convo was accepted. If not present, the convo was already accepted." + } + } + }, + "encoding": "application/json" + }, + "description": "Marks a conversation as accepted, so it is shown in the list of accepted convos instead on the request convos." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/addReaction.json b/lexicons/chat/bsky/convo/addReaction.json new file mode 100644 index 0000000000..7f317c84a9 --- /dev/null +++ b/lexicons/chat/bsky/convo/addReaction.json @@ -0,0 +1,73 @@ +{ + "id": "chat.bsky.convo.addReaction", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId", + "messageId", + "value" + ], + "properties": { + "value": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "maxGraphemes": 1, + "minGraphemes": 1 + }, + "convoId": { + "type": "string" + }, + "messageId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + }, + { + "name": "ReactionNotAllowed", + "description": "Indicates that reactions are not allowed on this message, e.g. because it is a system message." + }, + { + "name": "ReactionMessageDeleted", + "description": "Indicates that the message has been deleted and reactions can no longer be added/removed." + }, + { + "name": "ReactionLimitReached", + "description": "Indicates that the message has the maximum number of reactions allowed for a single user, and the requested reaction wasn't yet present. If it was already present, the request will not fail since it is idempotent." + }, + { + "name": "ReactionInvalidValue", + "description": "Indicates the value for the reaction is not acceptable. In general, this means it is not an emoji." + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "ref": "chat.bsky.convo.defs#messageView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Adds an emoji reaction to a message. Requires authentication. It is idempotent, so multiple calls from the same user with the same emoji result in a single reaction." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/defs.json b/lexicons/chat/bsky/convo/defs.json new file mode 100644 index 0000000000..eecef5ac5c --- /dev/null +++ b/lexicons/chat/bsky/convo/defs.json @@ -0,0 +1,1312 @@ +{ + "id": "chat.bsky.convo.defs", + "defs": { + "convoRef": { + "type": "object", + "required": [ + "did", + "convoId" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "convoId": { + "type": "string" + } + } + }, + "replyRef": { + "type": "object", + "required": [ + "messageId" + ], + "properties": { + "messageId": { + "type": "string" + } + }, + "description": "A reference to another message within the same convo, used to indicate that a message is a reply to it." + }, + "convoKind": { + "type": "string", + "knownValues": [ + "direct", + "group" + ] + }, + "convoView": { + "type": "object", + "required": [ + "id", + "rev", + "members", + "muted", + "unreadCount" + ], + "properties": { + "id": { + "type": "string" + }, + "rev": { + "type": "string" + }, + "kind": { + "refs": [ + "#directConvo", + "#groupConvo" + ], + "type": "union", + "description": "Union field that has data specific to different kinds of convos." + }, + "muted": { + "type": "boolean" + }, + "status": { + "ref": "#convoStatus", + "type": "ref", + "description": "Convo status for the viewer member (not the convo itself)." + }, + "members": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "description": "Members of this conversation. For direct convos, it will be an immutable list of the 2 members. For group convos, it will a list of important members (the first few members, the viewer, the member who added the viewer, the member who sent the last message, the member who sent the last reaction), but will not contain the full list of members. Use chat.bsky.convo.getConvoMembers to list all members." + }, + "lastMessage": { + "refs": [ + "#messageView", + "#deletedMessageView", + "#systemMessageView" + ], + "type": "union" + }, + "unreadCount": { + "type": "integer" + }, + "lastReaction": { + "refs": [ + "#messageAndReactionView" + ], + "type": "union" + } + } + }, + "groupConvo": { + "type": "object", + "required": [ + "createdAt", + "lockStatus", + "lockStatusModerationOverride", + "memberCount", + "memberLimit", + "name" + ], + "properties": { + "name": { + "type": "string", + "maxLength": 500, + "description": "The display name of the group conversation.", + "maxGraphemes": 50 + }, + "joinLink": { + "ref": "chat.bsky.group.defs#joinLinkView", + "type": "ref" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "lockStatus": { + "ref": "#convoLockStatus", + "type": "ref", + "description": "The lock status of the conversation." + }, + "memberCount": { + "type": "integer", + "description": "The total number of members in the group conversation." + }, + "memberLimit": { + "type": "integer", + "description": "The maximum number of members allowed in the group conversation." + }, + "joinRequestCount": { + "type": "integer", + "description": "The total number of pending join requests for the group conversation. Only present for the owner. Capped at 21." + }, + "unreadJoinRequestCount": { + "type": "integer", + "description": "The number of unread join requests for the group conversation. Only present for the owner." + }, + "lockStatusModerationOverride": { + "type": "boolean", + "description": "Whether the lock status is being forced by a moderation override (account inactivation or convo takedown) rather than the owner's own setting." + } + } + }, + "messageRef": { + "type": "object", + "required": [ + "did", + "messageId", + "convoId" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "convoId": { + "type": "string" + }, + "messageId": { + "type": "string" + } + } + }, + "convoStatus": { + "type": "string", + "knownValues": [ + "request", + "accepted" + ] + }, + "directConvo": { + "type": "object", + "properties": {} + }, + "messageView": { + "type": "object", + "required": [ + "id", + "rev", + "text", + "sender", + "sentAt" + ], + "properties": { + "id": { + "type": "string" + }, + "rev": { + "type": "string" + }, + "text": { + "type": "string", + "maxLength": 10000, + "maxGraphemes": 1000 + }, + "embed": { + "refs": [ + "app.bsky.embed.record#view", + "chat.bsky.embed.joinLink#view" + ], + "type": "union" + }, + "facets": { + "type": "array", + "items": { + "ref": "app.bsky.richtext.facet", + "type": "ref" + }, + "description": "Annotations of text (mentions, URLs, hashtags, etc)" + }, + "sender": { + "ref": "#messageViewSender", + "type": "ref" + }, + "sentAt": { + "type": "string", + "format": "datetime" + }, + "replyTo": { + "refs": [ + "#messageView", + "#deletedMessageView", + "#messageBeforeUserJoinedGroupView" + ], + "type": "union", + "description": "If set, the message this message is replying to. The full view of the referenced message is embedded so the client can render it inline. Only a single level is embedded: the embedded message will not itself have a populated 'replyTo' field even if it was also a reply." + }, + "reactions": { + "type": "array", + "items": { + "ref": "#reactionView", + "type": "ref" + }, + "description": "Reactions to this message, in ascending order of creation time." + } + } + }, + "logAddMember": { + "type": "object", + "required": [ + "rev", + "convoId", + "message", + "relatedProfiles" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "ref": "#systemMessageView", + "type": "ref", + "description": "A system message with data of type #systemMessageDataAddMember" + }, + "relatedProfiles": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "description": "Profiles referred in the system message." + } + }, + "description": "Event indicating a member was added to a group convo. The member who was added gets a logBeginConvo (to create the convo) but also a logAddMember (to show the system message as the first message the user sees)." + }, + "logEditGroup": { + "type": "object", + "required": [ + "rev", + "convoId", + "message" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "ref": "#systemMessageView", + "type": "ref", + "description": "A system message with data of type #systemMessageDataEditGroup" + } + }, + "description": "Event indicating info about group convo was edited." + }, + "logLockConvo": { + "type": "object", + "required": [ + "rev", + "convoId", + "message", + "relatedProfiles" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "ref": "#systemMessageView", + "type": "ref", + "description": "A system message with data of type #systemMessageDataLockConvo" + }, + "relatedProfiles": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "description": "Profiles referred in the system message." + } + }, + "description": "Event indicating a group convo was locked." + }, + "logMuteConvo": { + "type": "object", + "required": [ + "rev", + "convoId" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + } + }, + "description": "Event indicating the viewer muted a convo. Can be direct or group." + }, + "logReadConvo": { + "type": "object", + "required": [ + "rev", + "convoId", + "message" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "refs": [ + "#messageView", + "#deletedMessageView", + "#systemMessageView" + ], + "type": "union" + } + }, + "description": "Event indicating a convo was read up to a certain message." + }, + "messageInput": { + "type": "object", + "required": [ + "text" + ], + "properties": { + "text": { + "type": "string", + "maxLength": 10000, + "maxGraphemes": 1000 + }, + "embed": { + "refs": [ + "app.bsky.embed.record", + "chat.bsky.embed.joinLink" + ], + "type": "union" + }, + "facets": { + "type": "array", + "items": { + "ref": "app.bsky.richtext.facet", + "type": "ref" + }, + "description": "Annotations of text (mentions, URLs, hashtags, etc)" + }, + "replyTo": { + "ref": "#replyRef", + "type": "ref", + "description": "If set, the message this message is replying to. The referenced message must be in the same convo." + } + } + }, + "reactionView": { + "type": "object", + "required": [ + "value", + "sender", + "createdAt" + ], + "properties": { + "value": { + "type": "string" + }, + "sender": { + "ref": "#reactionViewSender", + "type": "ref" + }, + "createdAt": { + "type": "string", + "format": "datetime" + } + } + }, + "logBeginConvo": { + "type": "object", + "required": [ + "rev", + "convoId" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + } + }, + "description": "Event indicating a convo containing the viewer was started. Can be direct or group. When a member is added to a group convo, they also get this event." + }, + "logLeaveConvo": { + "type": "object", + "required": [ + "rev", + "convoId" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + } + }, + "description": "Event indicating the viewer left a convo. Can be direct or group." + }, + "logMemberJoin": { + "type": "object", + "required": [ + "rev", + "convoId", + "message", + "relatedProfiles" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "ref": "#systemMessageView", + "type": "ref", + "description": "A system message with data of type #systemMessageDataMemberJoin" + }, + "relatedProfiles": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "description": "Profiles referred in the system message." + } + }, + "description": "Event indicating a member joined a group convo via join link. The member who was added gets a logBeginConvo (to create the convo) but also a logMemberJoin (to show the system message as the first message the user sees)." + }, + "logAcceptConvo": { + "type": "object", + "required": [ + "rev", + "convoId" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + } + }, + "description": "Event indicating the viewer accepted a convo, and it can be moved out of the request inbox. Can be direct or group." + }, + "logAddReaction": { + "type": "object", + "required": [ + "rev", + "convoId", + "message", + "reaction" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "refs": [ + "#messageView", + "#deletedMessageView" + ], + "type": "union" + }, + "reaction": { + "ref": "#reactionView", + "type": "ref" + }, + "relatedProfiles": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "description": "Profiles referred in the message and reaction views. This isn't required for compatibility, because it was added later, but should generally be present." + } + }, + "description": "Event indicating a reaction was added to a message." + }, + "logMemberLeave": { + "type": "object", + "required": [ + "rev", + "convoId", + "message", + "relatedProfiles" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "ref": "#systemMessageView", + "type": "ref", + "description": "A system message with data of type #systemMessageDataMemberLeave" + }, + "relatedProfiles": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "description": "Profiles referred in the system message." + } + }, + "description": "Event indicating a member voluntarily left a group convo. The member who was removed gets a logLeaveConvo (to leave the convo) but not a logMemberLeave (because they already left, so can't see the system message)." + }, + "logReadMessage": { + "type": "object", + "required": [ + "rev", + "convoId", + "message" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "refs": [ + "#messageView", + "#deletedMessageView", + "#systemMessageView" + ], + "type": "union" + } + }, + "description": "DEPRECATED: use logReadConvo instead. Event indicating a convo was read up to a certain message." + }, + "logUnlockConvo": { + "type": "object", + "required": [ + "rev", + "convoId", + "message", + "relatedProfiles" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "ref": "#systemMessageView", + "type": "ref", + "description": "A system message with data of type #systemMessageDataUnlockConvo" + }, + "relatedProfiles": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "description": "Profiles referred in the system message." + } + }, + "description": "Event indicating a group convo was unlocked." + }, + "logUnmuteConvo": { + "type": "object", + "required": [ + "rev", + "convoId" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + } + }, + "description": "Event indicating the viewer unmuted a convo. Can be direct or group." + }, + "convoLockStatus": { + "type": "string", + "knownValues": [ + "unlocked", + "locked", + "locked-permanently" + ] + }, + "logEditJoinLink": { + "type": "object", + "required": [ + "rev", + "convoId", + "message" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "ref": "#systemMessageView", + "type": "ref", + "description": "A system message with data of type #systemMessageDataEditJoinLink" + } + }, + "description": "Event indicating a settings about a join link for a group convo were edited." + }, + "logRemoveMember": { + "type": "object", + "required": [ + "rev", + "convoId", + "message", + "relatedProfiles" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "ref": "#systemMessageView", + "type": "ref", + "description": "A system message with data of type #systemMessageDataRemoveMember" + }, + "relatedProfiles": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "description": "Profiles referred in the system message." + } + }, + "description": "Event indicating a member was removed from a group convo. The member who was removed gets a logLeaveConvo (to leave the convo) but not a logRemoveMember (because they already left, so can't see the system message)." + }, + "logCreateMessage": { + "type": "object", + "required": [ + "rev", + "convoId", + "message" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "refs": [ + "#messageView", + "#deletedMessageView" + ], + "type": "union" + }, + "relatedProfiles": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "description": "Profiles referred to in the message view. This isn't required for compatibility, because it was added later, but should generally be present." + } + }, + "description": "Event indicating a user-originated message was created. Is not emitted for system messages." + }, + "logDeleteMessage": { + "type": "object", + "required": [ + "rev", + "convoId", + "message" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "refs": [ + "#messageView", + "#deletedMessageView" + ], + "type": "union" + } + }, + "description": "Event indicating a user-originated message was deleted. Is not emitted for system messages." + }, + "logCreateJoinLink": { + "type": "object", + "required": [ + "rev", + "convoId", + "message" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "ref": "#systemMessageView", + "type": "ref", + "description": "A system message with data of type #systemMessageDataCreateJoinLink" + } + }, + "description": "Event indicating a join link was created for a group convo." + }, + "logEnableJoinLink": { + "type": "object", + "required": [ + "rev", + "convoId", + "message" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "ref": "#systemMessageView", + "type": "ref", + "description": "A system message with data of type #systemMessageDataEnableJoinLink" + } + }, + "description": "Event indicating a join link was enabled for a group convo." + }, + "logRemoveReaction": { + "type": "object", + "required": [ + "rev", + "convoId", + "message", + "reaction" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "refs": [ + "#messageView", + "#deletedMessageView" + ], + "type": "union" + }, + "reaction": { + "ref": "#reactionView", + "type": "ref" + }, + "relatedProfiles": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "description": "Profiles referred in the message and reaction views. This isn't required for compatibility, because it was added later, but should generally be present." + } + }, + "description": "Event indicating a reaction was removed from a message." + }, + "messageViewSender": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + } + } + }, + "systemMessageView": { + "type": "object", + "required": [ + "id", + "rev", + "sentAt", + "data" + ], + "properties": { + "id": { + "type": "string" + }, + "rev": { + "type": "string" + }, + "data": { + "refs": [ + "#systemMessageDataAddMember", + "#systemMessageDataRemoveMember", + "#systemMessageDataMemberJoin", + "#systemMessageDataMemberLeave", + "#systemMessageDataLockConvo", + "#systemMessageDataUnlockConvo", + "#systemMessageDataLockConvoPermanently", + "#systemMessageDataEditGroup", + "#systemMessageDataCreateJoinLink", + "#systemMessageDataEditJoinLink", + "#systemMessageDataEnableJoinLink", + "#systemMessageDataDisableJoinLink" + ], + "type": "union" + }, + "sentAt": { + "type": "string", + "format": "datetime" + } + } + }, + "deletedMessageView": { + "type": "object", + "required": [ + "id", + "rev", + "sender", + "sentAt" + ], + "properties": { + "id": { + "type": "string" + }, + "rev": { + "type": "string" + }, + "sender": { + "ref": "#messageViewSender", + "type": "ref" + }, + "sentAt": { + "type": "string", + "format": "datetime" + } + } + }, + "logDisableJoinLink": { + "type": "object", + "required": [ + "rev", + "convoId", + "message" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "ref": "#systemMessageView", + "type": "ref", + "description": "A system message with data of type #systemMessageDataDisableJoinLink" + } + }, + "description": "Event indicating a join link was disabled for a group convo." + }, + "reactionViewSender": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + } + } + }, + "logReadJoinRequests": { + "type": "object", + "required": [ + "rev", + "convoId" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + } + }, + "description": "Event indicating the group owner marked join requests as read. Only the owner gets this." + }, + "logRejectJoinRequest": { + "type": "object", + "required": [ + "rev", + "convoId", + "member" + ], + "properties": { + "rev": { + "type": "string" + }, + "member": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref", + "description": "Prospective member who requested to join." + }, + "convoId": { + "type": "string" + } + }, + "description": "Event indicating a join request was rejected by the viewer. Only the owner gets this." + }, + "logApproveJoinRequest": { + "type": "object", + "required": [ + "rev", + "convoId", + "member" + ], + "properties": { + "rev": { + "type": "string" + }, + "member": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref", + "description": "Prospective member who requested to join." + }, + "convoId": { + "type": "string" + } + }, + "description": "Event indicating a join request was approved by the viewer. Only the owner gets this. The approved member gets a logBeginConvo." + }, + "logIncomingJoinRequest": { + "type": "object", + "required": [ + "rev", + "convoId", + "member" + ], + "properties": { + "rev": { + "type": "string" + }, + "member": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref", + "description": "Prospective member who requested to join." + }, + "convoId": { + "type": "string" + } + }, + "description": "Event indicating a join request was made to a group the viewer owns. Only the owner gets this." + }, + "logOutgoingJoinRequest": { + "type": "object", + "required": [ + "rev", + "convoId" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + } + }, + "description": "Event indicating a join request was made by the requester. Only requester actor gets this." + }, + "messageAndReactionView": { + "type": "object", + "required": [ + "message", + "reaction" + ], + "properties": { + "message": { + "ref": "#messageView", + "type": "ref" + }, + "reaction": { + "ref": "#reactionView", + "type": "ref" + } + } + }, + "logLockConvoPermanently": { + "type": "object", + "required": [ + "rev", + "convoId", + "message", + "relatedProfiles" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "message": { + "ref": "#systemMessageView", + "type": "ref", + "description": "A system message with data of type #systemMessageDataLockConvoPermanently" + }, + "relatedProfiles": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "description": "Profiles referred in the system message." + } + }, + "description": "Event indicating a group convo was locked permanently." + }, + "systemMessageReferredUser": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + } + } + }, + "systemMessageDataAddMember": { + "type": "object", + "required": [ + "member", + "role", + "addedBy" + ], + "properties": { + "role": { + "ref": "chat.bsky.actor.defs#memberRole", + "type": "ref", + "description": "Role the user was added to the group with. The role from 'member' will reflect the current data, not historical." + }, + "member": { + "ref": "#systemMessageReferredUser", + "type": "ref", + "description": "Current view of the member who was added." + }, + "addedBy": { + "ref": "#systemMessageReferredUser", + "type": "ref" + } + }, + "description": "System message indicating a user was added to the group convo." + }, + "systemMessageDataEditGroup": { + "type": "object", + "properties": { + "newName": { + "type": "string", + "description": "Group name that replaced the old." + }, + "oldName": { + "type": "string", + "description": "Group name that was replaced." + } + }, + "description": "System message indicating the group info was edited." + }, + "systemMessageDataLockConvo": { + "type": "object", + "required": [ + "lockedBy" + ], + "properties": { + "lockedBy": { + "ref": "#systemMessageReferredUser", + "type": "ref", + "description": "Current view of the member who locked the group." + } + }, + "description": "System message indicating the group convo was locked." + }, + "systemMessageDataMemberJoin": { + "type": "object", + "required": [ + "member", + "role" + ], + "properties": { + "role": { + "ref": "chat.bsky.actor.defs#memberRole", + "type": "ref", + "description": "Role the user was added to the group with. The role from 'member' will reflect the current data, not historical." + }, + "member": { + "ref": "#systemMessageReferredUser", + "type": "ref", + "description": "Current view of the member who joined." + }, + "approvedBy": { + "ref": "#systemMessageReferredUser", + "type": "ref", + "description": "If join link was configured to require approval, this will be set to who approved the request. Undefined if approval was not required." + } + }, + "description": "System message indicating a user joined the group convo via join link." + }, + "systemMessageDataMemberLeave": { + "type": "object", + "required": [ + "member" + ], + "properties": { + "member": { + "ref": "#systemMessageReferredUser", + "type": "ref", + "description": "Current view of the member who left the group." + } + }, + "description": "System message indicating a user voluntarily left the group convo." + }, + "systemMessageDataUnlockConvo": { + "type": "object", + "required": [ + "unlockedBy" + ], + "properties": { + "unlockedBy": { + "ref": "#systemMessageReferredUser", + "type": "ref", + "description": "Current view of the member who unlocked the group." + } + }, + "description": "System message indicating the group convo was unlocked." + }, + "systemMessageDataEditJoinLink": { + "type": "object", + "properties": {}, + "description": "System message indicating the group join link was edited." + }, + "systemMessageDataRemoveMember": { + "type": "object", + "required": [ + "member", + "removedBy" + ], + "properties": { + "member": { + "ref": "#systemMessageReferredUser", + "type": "ref", + "description": "Current view of the member who was removed." + }, + "removedBy": { + "ref": "#systemMessageReferredUser", + "type": "ref" + } + }, + "description": "System message indicating a user was removed from the group convo." + }, + "logWithdrawIncomingJoinRequest": { + "type": "object", + "required": [ + "rev", + "convoId", + "member" + ], + "properties": { + "rev": { + "type": "string" + }, + "member": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref", + "description": "Prospective member who withdrew their join request." + }, + "convoId": { + "type": "string" + } + }, + "description": "Event indicating a prospective member withdrew their join request. Only the owner gets this." + }, + "logWithdrawOutgoingJoinRequest": { + "type": "object", + "required": [ + "rev", + "convoId" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + } + }, + "description": "Event indicating the viewer withdrew their own join request. Only requester actor gets this." + }, + "systemMessageDataCreateJoinLink": { + "type": "object", + "properties": {}, + "description": "System message indicating the group join link was created." + }, + "systemMessageDataEnableJoinLink": { + "type": "object", + "properties": {}, + "description": "System message indicating the group join link was enabled." + }, + "messageBeforeUserJoinedGroupView": { + "type": "object", + "properties": {}, + "description": "Placeholder embedded in place of a reply's parent message when that parent was sent before the viewer joined the group convo. The viewer has no access to that history, so no message data is carried." + }, + "systemMessageDataDisableJoinLink": { + "type": "object", + "properties": {}, + "description": "System message indicating the group join link was disabled." + }, + "systemMessageDataLockConvoPermanently": { + "type": "object", + "required": [ + "lockedBy" + ], + "properties": { + "lockedBy": { + "ref": "#systemMessageReferredUser", + "type": "ref", + "description": "Current view of the member who locked the group." + } + }, + "description": "System message indicating the group convo was locked permanently." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/deleteMessageForSelf.json b/lexicons/chat/bsky/convo/deleteMessageForSelf.json new file mode 100644 index 0000000000..39e4c109a0 --- /dev/null +++ b/lexicons/chat/bsky/convo/deleteMessageForSelf.json @@ -0,0 +1,45 @@ +{ + "id": "chat.bsky.convo.deleteMessageForSelf", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId", + "messageId" + ], + "properties": { + "convoId": { + "type": "string" + }, + "messageId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + }, + { + "name": "MessageDeleteNotAllowed", + "description": "Indicates that this message cannot be deleted, e.g. because it is a system message." + } + ], + "output": { + "schema": { + "ref": "chat.bsky.convo.defs#deletedMessageView", + "type": "ref" + }, + "encoding": "application/json" + }, + "description": "Marks a message as deleted for the viewer, so they won't see that message in future enumerations." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/getConvo.json b/lexicons/chat/bsky/convo/getConvo.json new file mode 100644 index 0000000000..cbe3b2684e --- /dev/null +++ b/lexicons/chat/bsky/convo/getConvo.json @@ -0,0 +1,42 @@ +{ + "id": "chat.bsky.convo.getConvo", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "InvalidConvo" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convo" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + } + } + }, + "description": "Gets an existing conversation by its ID." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/getConvoAvailability.json b/lexicons/chat/bsky/convo/getConvoAvailability.json new file mode 100644 index 0000000000..37c3c745cf --- /dev/null +++ b/lexicons/chat/bsky/convo/getConvoAvailability.json @@ -0,0 +1,46 @@ +{ + "id": "chat.bsky.convo.getConvoAvailability", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "canChat" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + }, + "canChat": { + "type": "boolean" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "members" + ], + "properties": { + "members": { + "type": "array", + "items": { + "type": "string", + "format": "did" + }, + "maxLength": 10, + "minLength": 1 + } + } + }, + "description": "Check whether the requester and the other members can start a 1-1 chat. Only applicable to direct (non-group) conversations. If an existing convo is found for these members, it is returned. Does not create a new convo if it doesn't exist." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/getConvoForMembers.json b/lexicons/chat/bsky/convo/getConvoForMembers.json new file mode 100644 index 0000000000..21c12ead32 --- /dev/null +++ b/lexicons/chat/bsky/convo/getConvoForMembers.json @@ -0,0 +1,63 @@ +{ + "id": "chat.bsky.convo.getConvoForMembers", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "AccountSuspended" + }, + { + "name": "BlockedActor" + }, + { + "name": "BlockedSubject" + }, + { + "name": "MessagesDisabled" + }, + { + "name": "NotFollowedBySender" + }, + { + "name": "RecipientNotFound" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convo" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "members" + ], + "properties": { + "members": { + "type": "array", + "items": { + "type": "string", + "format": "did" + }, + "maxLength": 10, + "minLength": 1 + } + } + }, + "description": "Get or create a 1-1 conversation for the given members. Always returns the same direct (non-group) conversation. To create a group conversation, use createGroup." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/getConvoMembers.json b/lexicons/chat/bsky/convo/getConvoMembers.json new file mode 100644 index 0000000000..0e89f0e916 --- /dev/null +++ b/lexicons/chat/bsky/convo/getConvoMembers.json @@ -0,0 +1,57 @@ +{ + "id": "chat.bsky.convo.getConvoMembers", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "InvalidConvo" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "members" + ], + "properties": { + "cursor": { + "type": "string" + }, + "members": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "convoId" + ], + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + }, + "convoId": { + "type": "string" + } + } + }, + "description": "Returns a paginated list of members from a conversation." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/getLog.json b/lexicons/chat/bsky/convo/getLog.json new file mode 100644 index 0000000000..42c738d46d --- /dev/null +++ b/lexicons/chat/bsky/convo/getLog.json @@ -0,0 +1,71 @@ +{ + "id": "chat.bsky.convo.getLog", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "logs" + ], + "properties": { + "logs": { + "type": "array", + "items": { + "refs": [ + "chat.bsky.convo.defs#logBeginConvo", + "chat.bsky.convo.defs#logAcceptConvo", + "chat.bsky.convo.defs#logLeaveConvo", + "chat.bsky.convo.defs#logMuteConvo", + "chat.bsky.convo.defs#logUnmuteConvo", + "chat.bsky.convo.defs#logCreateMessage", + "chat.bsky.convo.defs#logDeleteMessage", + "chat.bsky.convo.defs#logReadMessage", + "chat.bsky.convo.defs#logAddReaction", + "chat.bsky.convo.defs#logRemoveReaction", + "chat.bsky.convo.defs#logReadConvo", + "chat.bsky.convo.defs#logAddMember", + "chat.bsky.convo.defs#logRemoveMember", + "chat.bsky.convo.defs#logMemberJoin", + "chat.bsky.convo.defs#logMemberLeave", + "chat.bsky.convo.defs#logLockConvo", + "chat.bsky.convo.defs#logUnlockConvo", + "chat.bsky.convo.defs#logLockConvoPermanently", + "chat.bsky.convo.defs#logEditGroup", + "chat.bsky.convo.defs#logCreateJoinLink", + "chat.bsky.convo.defs#logEditJoinLink", + "chat.bsky.convo.defs#logEnableJoinLink", + "chat.bsky.convo.defs#logDisableJoinLink", + "chat.bsky.convo.defs#logIncomingJoinRequest", + "chat.bsky.convo.defs#logApproveJoinRequest", + "chat.bsky.convo.defs#logRejectJoinRequest", + "chat.bsky.convo.defs#logOutgoingJoinRequest", + "chat.bsky.convo.defs#logWithdrawIncomingJoinRequest", + "chat.bsky.convo.defs#logWithdrawOutgoingJoinRequest", + "chat.bsky.convo.defs#logReadJoinRequests" + ], + "type": "union" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [], + "properties": { + "cursor": { + "type": "string" + } + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/getMessages.json b/lexicons/chat/bsky/convo/getMessages.json new file mode 100644 index 0000000000..b157290de8 --- /dev/null +++ b/lexicons/chat/bsky/convo/getMessages.json @@ -0,0 +1,69 @@ +{ + "id": "chat.bsky.convo.getMessages", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "InvalidConvo" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "messages" + ], + "properties": { + "cursor": { + "type": "string" + }, + "messages": { + "type": "array", + "items": { + "refs": [ + "chat.bsky.convo.defs#messageView", + "chat.bsky.convo.defs#deletedMessageView", + "chat.bsky.convo.defs#systemMessageView" + ], + "type": "union" + } + }, + "relatedProfiles": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "description": "Set of all members who authored or reacted to the returned messages. Members referred to by system messages are also included." + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "convoId" + ], + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + }, + "convoId": { + "type": "string" + } + } + }, + "description": "Returns a page of messages from a conversation." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/getUnreadCounts.json b/lexicons/chat/bsky/convo/getUnreadCounts.json new file mode 100644 index 0000000000..ddfe955a2b --- /dev/null +++ b/lexicons/chat/bsky/convo/getUnreadCounts.json @@ -0,0 +1,41 @@ +{ + "id": "chat.bsky.convo.getUnreadCounts", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "unreadAcceptedConvos", + "unreadRequestConvos" + ], + "properties": { + "unreadRequestConvos": { + "type": "integer", + "description": "Number of unread, unlocked request convos. Includes convos with unread messages, but not with unread join request, since only the owner of a group has join requests to read, and the group would necessarily be accepted. Capped at 100, where 100 means more than 99." + }, + "unreadAcceptedConvos": { + "type": "integer", + "description": "Number of unread, unlocked accepted convos. Counts convos with unread messages and unread join requests. Capped at 100, where 100 means more than 99." + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "includeGroupChats": { + "type": "boolean", + "default": true, + "description": "When false, group convos are excluded from the counts." + } + } + }, + "description": "Returns unread conversation counts for conversations that are unlocked, not muted, split by convo status. Direct convos are excluded when a block relationship exists between the actor and the other member, or when the other member's account is deleted or deactivated. Group convos are considered unread if they have unread join request counts." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/leaveConvo.json b/lexicons/chat/bsky/convo/leaveConvo.json new file mode 100644 index 0000000000..b1e74ca2d2 --- /dev/null +++ b/lexicons/chat/bsky/convo/leaveConvo.json @@ -0,0 +1,52 @@ +{ + "id": "chat.bsky.convo.leaveConvo", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + }, + { + "name": "OwnerCannotLeave", + "description": "The owner of a group conversation cannot leave before locking the group." + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convoId", + "rev" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "description": "Leaves a conversation (direct or group). For group, this effectively removes membership. For direct, membership is never removed, only changed to remove from enumerations by the user who left." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/listConvoRequests.json b/lexicons/chat/bsky/convo/listConvoRequests.json new file mode 100644 index 0000000000..6be7fea5fc --- /dev/null +++ b/lexicons/chat/bsky/convo/listConvoRequests.json @@ -0,0 +1,49 @@ +{ + "id": "chat.bsky.convo.listConvoRequests", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "requests" + ], + "properties": { + "cursor": { + "type": "string" + }, + "requests": { + "type": "array", + "items": { + "refs": [ + "chat.bsky.convo.defs#convoView", + "chat.bsky.group.defs#joinRequestConvoView" + ], + "type": "union" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Returns a page of incoming conversation requests for the user. Direct convo requests are returned as convoView; group join requests made by the user are returned as joinRequestConvoView." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/listConvos.json b/lexicons/chat/bsky/convo/listConvos.json new file mode 100644 index 0000000000..2ac41d00b3 --- /dev/null +++ b/lexicons/chat/bsky/convo/listConvos.json @@ -0,0 +1,77 @@ +{ + "id": "chat.bsky.convo.listConvos", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "convos" + ], + "properties": { + "convos": { + "type": "array", + "items": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "kind": { + "type": "string", + "description": "Filter by conversation kind.", + "knownValues": [ + "direct", + "group" + ] + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + }, + "status": { + "type": "string", + "description": "Filter convos by their status. It is discouraged to call with \"request\" and preferred to call chat.bsky.convo.listConvoRequests, which also includes group join requests made by the user.", + "knownValues": [ + "request", + "accepted" + ] + }, + "readState": { + "type": "string", + "knownValues": [ + "unread" + ] + }, + "lockStatus": { + "type": "string", + "description": "Filter by conversation lock status. Values follow chat.bsky.convo.defs#convoLockStatus.", + "knownValues": [ + "unlocked", + "locked", + "locked-permanently" + ] + } + } + }, + "description": "Returns a page of conversations (direct or group) for the user." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/lockConvo.json b/lexicons/chat/bsky/convo/lockConvo.json new file mode 100644 index 0000000000..fb0177b0b6 --- /dev/null +++ b/lexicons/chat/bsky/convo/lockConvo.json @@ -0,0 +1,51 @@ +{ + "id": "chat.bsky.convo.lockConvo", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "ConvoLocked" + }, + { + "name": "InvalidConvo" + }, + { + "name": "InsufficientRole" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convo" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Locks a group convo so no more content (messages, reactions) can be added to it." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/muteConvo.json b/lexicons/chat/bsky/convo/muteConvo.json new file mode 100644 index 0000000000..091136aaf6 --- /dev/null +++ b/lexicons/chat/bsky/convo/muteConvo.json @@ -0,0 +1,45 @@ +{ + "id": "chat.bsky.convo.muteConvo", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convo" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Mutes a conversation, preventing notifications related to it." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/removeReaction.json b/lexicons/chat/bsky/convo/removeReaction.json new file mode 100644 index 0000000000..8bcdd28e21 --- /dev/null +++ b/lexicons/chat/bsky/convo/removeReaction.json @@ -0,0 +1,69 @@ +{ + "id": "chat.bsky.convo.removeReaction", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId", + "messageId", + "value" + ], + "properties": { + "value": { + "type": "string", + "maxLength": 64, + "minLength": 1, + "maxGraphemes": 1, + "minGraphemes": 1 + }, + "convoId": { + "type": "string" + }, + "messageId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + }, + { + "name": "ReactionNotAllowed", + "description": "Indicates that reactions are not allowed on this message, e.g. because it is a system message." + }, + { + "name": "ReactionMessageDeleted", + "description": "Indicates that the message has been deleted and reactions can no longer be added/removed." + }, + { + "name": "ReactionInvalidValue", + "description": "Indicates the value for the reaction is not acceptable. In general, this means it is not an emoji." + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "ref": "chat.bsky.convo.defs#messageView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Removes an emoji reaction from a message. Requires authentication. It is idempotent, so multiple calls from the same user with the same emoji result in that reaction not being present, even if it already wasn't." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/sendMessage.json b/lexicons/chat/bsky/convo/sendMessage.json new file mode 100644 index 0000000000..612bc292ca --- /dev/null +++ b/lexicons/chat/bsky/convo/sendMessage.json @@ -0,0 +1,48 @@ +{ + "id": "chat.bsky.convo.sendMessage", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId", + "message" + ], + "properties": { + "convoId": { + "type": "string" + }, + "message": { + "ref": "chat.bsky.convo.defs#messageInput", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "ConvoLocked" + }, + { + "name": "InvalidConvo" + }, + { + "name": "ReplyTargetNotFound" + } + ], + "output": { + "schema": { + "ref": "chat.bsky.convo.defs#messageView", + "type": "ref" + }, + "encoding": "application/json" + }, + "description": "Sends a message to a conversation." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/sendMessageBatch.json b/lexicons/chat/bsky/convo/sendMessageBatch.json new file mode 100644 index 0000000000..843443f167 --- /dev/null +++ b/lexicons/chat/bsky/convo/sendMessageBatch.json @@ -0,0 +1,75 @@ +{ + "id": "chat.bsky.convo.sendMessageBatch", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "ref": "#batchItem", + "type": "ref" + }, + "maxLength": 100 + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "ConvoLocked" + }, + { + "name": "InvalidConvo" + }, + { + "name": "ReplyTargetNotFound" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "items" + ], + "properties": { + "items": { + "type": "array", + "items": { + "ref": "chat.bsky.convo.defs#messageView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "description": "Sends a batch of messages to a conversation." + }, + "batchItem": { + "type": "object", + "required": [ + "convoId", + "message" + ], + "properties": { + "convoId": { + "type": "string" + }, + "message": { + "ref": "chat.bsky.convo.defs#messageInput", + "type": "ref" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/unlockConvo.json b/lexicons/chat/bsky/convo/unlockConvo.json new file mode 100644 index 0000000000..83c3c8fe8a --- /dev/null +++ b/lexicons/chat/bsky/convo/unlockConvo.json @@ -0,0 +1,51 @@ +{ + "id": "chat.bsky.convo.unlockConvo", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + }, + { + "name": "InsufficientRole" + }, + { + "name": "ConvoLockedByModeration" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convo" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Unlocks a group convo so it is able to receive new content." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/unmuteConvo.json b/lexicons/chat/bsky/convo/unmuteConvo.json new file mode 100644 index 0000000000..813146cd53 --- /dev/null +++ b/lexicons/chat/bsky/convo/unmuteConvo.json @@ -0,0 +1,45 @@ +{ + "id": "chat.bsky.convo.unmuteConvo", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convo" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Unmutes a conversation, allowing notifications related to it." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/updateAllRead.json b/lexicons/chat/bsky/convo/updateAllRead.json new file mode 100644 index 0000000000..288e873196 --- /dev/null +++ b/lexicons/chat/bsky/convo/updateAllRead.json @@ -0,0 +1,41 @@ +{ + "id": "chat.bsky.convo.updateAllRead", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "properties": { + "status": { + "type": "string", + "knownValues": [ + "request", + "accepted" + ] + } + } + }, + "encoding": "application/json" + }, + "output": { + "schema": { + "type": "object", + "required": [ + "updatedCount" + ], + "properties": { + "updatedCount": { + "type": "integer", + "description": "The count of updated convos." + } + } + }, + "encoding": "application/json" + }, + "description": "Sets conversations from a user as read to the latest message, with filters." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/convo/updateRead.json b/lexicons/chat/bsky/convo/updateRead.json new file mode 100644 index 0000000000..1aa6d1152c --- /dev/null +++ b/lexicons/chat/bsky/convo/updateRead.json @@ -0,0 +1,48 @@ +{ + "id": "chat.bsky.convo.updateRead", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + }, + "messageId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convo" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Updates the read state of a conversation from, optionally specifying the last read message." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/embed/joinLink.json b/lexicons/chat/bsky/embed/joinLink.json new file mode 100644 index 0000000000..0dc09657b9 --- /dev/null +++ b/lexicons/chat/bsky/embed/joinLink.json @@ -0,0 +1,36 @@ +{ + "id": "chat.bsky.embed.joinLink", + "defs": { + "main": { + "type": "object", + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string", + "description": "The join link code." + } + } + }, + "view": { + "type": "object", + "required": [ + "joinLinkPreview" + ], + "properties": { + "joinLinkPreview": { + "refs": [ + "chat.bsky.group.defs#joinLinkPreviewView", + "chat.bsky.group.defs#disabledJoinLinkPreviewView", + "chat.bsky.group.defs#invalidJoinLinkPreviewView" + ], + "type": "union" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "description": "A join link embedded in a chat message." +} diff --git a/lexicons/chat/bsky/group/addMembers.json b/lexicons/chat/bsky/group/addMembers.json new file mode 100644 index 0000000000..39a14a5b83 --- /dev/null +++ b/lexicons/chat/bsky/group/addMembers.json @@ -0,0 +1,88 @@ +{ + "id": "chat.bsky.group.addMembers", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId", + "members" + ], + "properties": { + "convoId": { + "type": "string" + }, + "members": { + "type": "array", + "items": { + "type": "string", + "format": "did" + }, + "minLength": 1 + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "AccountSuspended" + }, + { + "name": "BlockedActor" + }, + { + "name": "BlockedSubject" + }, + { + "name": "ConvoLocked" + }, + { + "name": "InsufficientRole" + }, + { + "name": "InvalidConvo" + }, + { + "name": "MemberLimitReached" + }, + { + "name": "NotFollowedBySender" + }, + { + "name": "RecipientNotFound" + }, + { + "name": "UserForbidsGroups" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convo" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + }, + "addedMembers": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "description": "Adds members to a group. The members are added in 'request' status, so they have to accept it. This creates convo memberships." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/approveJoinRequest.json b/lexicons/chat/bsky/group/approveJoinRequest.json new file mode 100644 index 0000000000..c8186db259 --- /dev/null +++ b/lexicons/chat/bsky/group/approveJoinRequest.json @@ -0,0 +1,56 @@ +{ + "id": "chat.bsky.group.approveJoinRequest", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId", + "member" + ], + "properties": { + "member": { + "type": "string", + "format": "did" + }, + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + }, + { + "name": "InsufficientRole" + }, + { + "name": "MemberLimitReached" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convo" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Approves a request to join a group (via join link) the user owns. Action taken by the group owner." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/createGroup.json b/lexicons/chat/bsky/group/createGroup.json new file mode 100644 index 0000000000..efe5e72041 --- /dev/null +++ b/lexicons/chat/bsky/group/createGroup.json @@ -0,0 +1,76 @@ +{ + "id": "chat.bsky.group.createGroup", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "members", + "name" + ], + "properties": { + "name": { + "type": "string", + "maxLength": 500, + "minLength": 1, + "maxGraphemes": 50 + }, + "members": { + "type": "array", + "items": { + "type": "string", + "format": "did" + }, + "maxLength": 10000, + "description": "The members to add to the group. The owner is automatically added. Implementations may enforce a lower maximum than the 10,000-item schema limit; Bluesky currently supports up to 100 total members. If the owner is included in this list, the list may contain up to the implementation's total member limit. Otherwise, it may contain one fewer." + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "AccountSuspended" + }, + { + "name": "BlockedActor" + }, + { + "name": "BlockedSubject" + }, + { + "name": "NewAccountCannotCreateGroup" + }, + { + "name": "NotFollowedBySender" + }, + { + "name": "RecipientNotFound" + }, + { + "name": "UserForbidsGroups" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convo" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Creates a group convo, specifying the members to be added to it. Unlike getConvoForMembers, this isn't idempotent. It will create new groups even if the membership is identical to pre-existing groups. Will create 'request' membership for all members, except the owner who is 'accepted'." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/createJoinLink.json b/lexicons/chat/bsky/group/createJoinLink.json new file mode 100644 index 0000000000..679f1bc3b3 --- /dev/null +++ b/lexicons/chat/bsky/group/createJoinLink.json @@ -0,0 +1,60 @@ +{ + "id": "chat.bsky.group.createJoinLink", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId", + "joinRule" + ], + "properties": { + "convoId": { + "type": "string" + }, + "joinRule": { + "ref": "chat.bsky.group.defs#joinRule", + "type": "ref" + }, + "requireApproval": { + "type": "boolean", + "default": false + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "EnabledJoinLinkAlreadyExists" + }, + { + "name": "InvalidConvo" + }, + { + "name": "InsufficientRole" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "joinLink" + ], + "properties": { + "joinLink": { + "ref": "chat.bsky.group.defs#joinLinkView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Creates a join link for the group convo." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/defs.json b/lexicons/chat/bsky/group/defs.json new file mode 100644 index 0000000000..3b1cc4bef3 --- /dev/null +++ b/lexicons/chat/bsky/group/defs.json @@ -0,0 +1,192 @@ +{ + "id": "chat.bsky.group.defs", + "defs": { + "joinRule": { + "type": "string", + "knownValues": [ + "anyone", + "followedByOwner" + ] + }, + "joinLinkView": { + "type": "object", + "required": [ + "code", + "enabledStatus", + "requireApproval", + "joinRule", + "createdAt" + ], + "properties": { + "code": { + "type": "string" + }, + "joinRule": { + "ref": "#joinRule", + "type": "ref" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "enabledStatus": { + "ref": "#linkEnabledStatus", + "type": "ref" + }, + "requireApproval": { + "type": "boolean" + } + }, + "description": "Join link view to be used within a group view, so the convo is surrounding, not specified inside this view." + }, + "joinRequestView": { + "type": "object", + "required": [ + "convoId", + "requestedBy", + "requestedAt" + ], + "properties": { + "convoId": { + "type": "string" + }, + "requestedAt": { + "type": "string", + "format": "datetime" + }, + "requestedBy": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + } + }, + "description": "A join request from the perspective of the group owner." + }, + "linkEnabledStatus": { + "type": "string", + "knownValues": [ + "enabled", + "disabled" + ] + }, + "joinLinkPreviewView": { + "type": "object", + "required": [ + "convoId", + "code", + "name", + "owner", + "memberCount", + "memberLimit", + "requireApproval", + "joinRule" + ], + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref", + "description": "Present only if the request is authenticated and the user is a member of the group." + }, + "owner": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "viewer": { + "ref": "#joinLinkViewerState", + "type": "ref" + }, + "convoId": { + "type": "string" + }, + "joinRule": { + "ref": "#joinRule", + "type": "ref" + }, + "memberCount": { + "type": "integer" + }, + "memberLimit": { + "type": "integer" + }, + "requireApproval": { + "type": "boolean" + } + }, + "description": "Preview that can be shown in feeds, including to unauthenticated viewers." + }, + "joinLinkViewerState": { + "type": "object", + "properties": { + "requestedAt": { + "type": "string", + "format": "datetime" + } + } + }, + "joinRequestConvoView": { + "type": "object", + "required": [ + "convoId", + "name", + "owner", + "memberCount", + "memberLimit", + "viewer" + ], + "properties": { + "name": { + "type": "string" + }, + "owner": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + }, + "viewer": { + "ref": "#joinLinkViewerState", + "type": "ref" + }, + "convoId": { + "type": "string" + }, + "memberCount": { + "type": "integer" + }, + "memberLimit": { + "type": "integer" + } + }, + "description": "A join request from the perspective of the requester, including enough group context to render the request in a list (e.g. group name, owner, member count)." + }, + "invalidJoinLinkPreviewView": { + "type": "object", + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string" + } + }, + "description": "Preview for a join link code that does not map to an existing link. Carries only the code so clients can correlate with the input and render an invalid state." + }, + "disabledJoinLinkPreviewView": { + "type": "object", + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string" + } + }, + "description": "Preview for a disabled join link. Carries only the code so clients can correlate with the input and render a disabled state." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/disableJoinLink.json b/lexicons/chat/bsky/group/disableJoinLink.json new file mode 100644 index 0000000000..e240344a17 --- /dev/null +++ b/lexicons/chat/bsky/group/disableJoinLink.json @@ -0,0 +1,51 @@ +{ + "id": "chat.bsky.group.disableJoinLink", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + }, + { + "name": "InsufficientRole" + }, + { + "name": "NoJoinLink" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "joinLink" + ], + "properties": { + "joinLink": { + "ref": "chat.bsky.group.defs#joinLinkView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Disables the active join link for the group convo." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/editGroup.json b/lexicons/chat/bsky/group/editGroup.json new file mode 100644 index 0000000000..2ce53704cd --- /dev/null +++ b/lexicons/chat/bsky/group/editGroup.json @@ -0,0 +1,58 @@ +{ + "id": "chat.bsky.group.editGroup", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId", + "name" + ], + "properties": { + "name": { + "type": "string", + "maxLength": 500, + "minLength": 1, + "maxGraphemes": 50 + }, + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "ConvoLocked" + }, + { + "name": "InvalidConvo" + }, + { + "name": "InsufficientRole" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convo" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Edits group settings." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/editJoinLink.json b/lexicons/chat/bsky/group/editJoinLink.json new file mode 100644 index 0000000000..17bc143555 --- /dev/null +++ b/lexicons/chat/bsky/group/editJoinLink.json @@ -0,0 +1,58 @@ +{ + "id": "chat.bsky.group.editJoinLink", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + }, + "joinRule": { + "ref": "chat.bsky.group.defs#joinRule", + "type": "ref" + }, + "requireApproval": { + "type": "boolean" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + }, + { + "name": "InsufficientRole" + }, + { + "name": "NoJoinLink" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "joinLink" + ], + "properties": { + "joinLink": { + "ref": "chat.bsky.group.defs#joinLinkView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Edits the existing join link settings for the group convo." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/enableJoinLink.json b/lexicons/chat/bsky/group/enableJoinLink.json new file mode 100644 index 0000000000..ed82a96495 --- /dev/null +++ b/lexicons/chat/bsky/group/enableJoinLink.json @@ -0,0 +1,54 @@ +{ + "id": "chat.bsky.group.enableJoinLink", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + }, + { + "name": "InsufficientRole" + }, + { + "name": "NoJoinLink" + }, + { + "name": "LinkAlreadyEnabled" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "joinLink" + ], + "properties": { + "joinLink": { + "ref": "chat.bsky.group.defs#joinLinkView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Re-enables a previously disabled join link for the group convo." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/getJoinLinkPreviews.json b/lexicons/chat/bsky/group/getJoinLinkPreviews.json new file mode 100644 index 0000000000..c9c8491cad --- /dev/null +++ b/lexicons/chat/bsky/group/getJoinLinkPreviews.json @@ -0,0 +1,49 @@ +{ + "id": "chat.bsky.group.getJoinLinkPreviews", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "joinLinkPreviews" + ], + "properties": { + "joinLinkPreviews": { + "type": "array", + "items": { + "refs": [ + "chat.bsky.group.defs#joinLinkPreviewView", + "chat.bsky.group.defs#disabledJoinLinkPreviewView", + "chat.bsky.group.defs#invalidJoinLinkPreviewView" + ], + "type": "union" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "codes" + ], + "properties": { + "codes": { + "type": "array", + "items": { + "type": "string" + }, + "maxLength": 50, + "minLength": 1 + } + } + }, + "description": "Get public information about groups from join links. The output array matches the input codes one-to-one by position (and each view also carries its 'code'). Disabled codes return a disabledJoinLinkPreviewView, and codes that do not map to a previewable link return an invalidJoinLinkPreviewView." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/listJoinRequests.json b/lexicons/chat/bsky/group/listJoinRequests.json new file mode 100644 index 0000000000..b8ecd295dd --- /dev/null +++ b/lexicons/chat/bsky/group/listJoinRequests.json @@ -0,0 +1,60 @@ +{ + "id": "chat.bsky.group.listJoinRequests", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "InvalidConvo" + }, + { + "name": "InsufficientRole" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "requests" + ], + "properties": { + "cursor": { + "type": "string" + }, + "requests": { + "type": "array", + "items": { + "ref": "chat.bsky.group.defs#joinRequestView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "convoId" + ], + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + }, + "convoId": { + "type": "string" + } + } + }, + "description": "Lists a page of request to join a group (via join link) the user owns. Shows the data from the owner's point of view." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/listMutualGroups.json b/lexicons/chat/bsky/group/listMutualGroups.json new file mode 100644 index 0000000000..26d04f1edd --- /dev/null +++ b/lexicons/chat/bsky/group/listMutualGroups.json @@ -0,0 +1,53 @@ +{ + "id": "chat.bsky.group.listMutualGroups", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "convos" + ], + "properties": { + "convos": { + "type": "array", + "items": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "subject" + ], + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + }, + "subject": { + "type": "string", + "format": "did" + } + } + }, + "description": "Returns a page of group conversations that both the requester and the specified actor are members of." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/rejectJoinRequest.json b/lexicons/chat/bsky/group/rejectJoinRequest.json new file mode 100644 index 0000000000..fb8edf64ea --- /dev/null +++ b/lexicons/chat/bsky/group/rejectJoinRequest.json @@ -0,0 +1,46 @@ +{ + "id": "chat.bsky.group.rejectJoinRequest", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId", + "member" + ], + "properties": { + "member": { + "type": "string", + "format": "did" + }, + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + }, + { + "name": "InsufficientRole" + } + ], + "output": { + "schema": { + "type": "object", + "required": [], + "properties": {} + }, + "encoding": "application/json" + }, + "description": "Rejects a request to join a group (via join link) the user owns. Action taken by the group owner." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/removeMembers.json b/lexicons/chat/bsky/group/removeMembers.json new file mode 100644 index 0000000000..e7e795a6bb --- /dev/null +++ b/lexicons/chat/bsky/group/removeMembers.json @@ -0,0 +1,57 @@ +{ + "id": "chat.bsky.group.removeMembers", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId", + "members" + ], + "properties": { + "convoId": { + "type": "string" + }, + "members": { + "type": "array", + "items": { + "type": "string", + "format": "did" + }, + "minLength": 1 + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + }, + { + "name": "InsufficientRole" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convo" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Removes members from a group. This deletes convo memberships, doesn't just set a status." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/requestJoin.json b/lexicons/chat/bsky/group/requestJoin.json new file mode 100644 index 0000000000..b3cbbd6b4c --- /dev/null +++ b/lexicons/chat/bsky/group/requestJoin.json @@ -0,0 +1,68 @@ +{ + "id": "chat.bsky.group.requestJoin", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "ConvoLocked" + }, + { + "name": "FollowRequired" + }, + { + "name": "InvalidCode" + }, + { + "name": "LinkDisabled" + }, + { + "name": "MemberLimitReached" + }, + { + "name": "UserKicked" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "status" + ], + "properties": { + "convo": { + "ref": "chat.bsky.convo.defs#convoView", + "type": "ref", + "description": "The group convo joined. This is only present in the case of status=joined" + }, + "status": { + "type": "string", + "knownValues": [ + "joined", + "pending" + ] + } + } + }, + "encoding": "application/json" + }, + "description": "Sends a request to join a group (via join link) to the group owner. Action taken by the prospective group member." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/updateJoinRequestsRead.json b/lexicons/chat/bsky/group/updateJoinRequestsRead.json new file mode 100644 index 0000000000..e80c671d5e --- /dev/null +++ b/lexicons/chat/bsky/group/updateJoinRequestsRead.json @@ -0,0 +1,41 @@ +{ + "id": "chat.bsky.group.updateJoinRequestsRead", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidConvo" + }, + { + "name": "InsufficientRole" + } + ], + "output": { + "schema": { + "type": "object", + "required": [], + "properties": {} + }, + "encoding": "application/json" + }, + "description": "Marks all join requests as read for the group owner." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/group/withdrawJoinRequest.json b/lexicons/chat/bsky/group/withdrawJoinRequest.json new file mode 100644 index 0000000000..4d13bb4249 --- /dev/null +++ b/lexicons/chat/bsky/group/withdrawJoinRequest.json @@ -0,0 +1,38 @@ +{ + "id": "chat.bsky.group.withdrawJoinRequest", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidJoinRequest" + } + ], + "output": { + "schema": { + "type": "object", + "required": [], + "properties": {} + }, + "encoding": "application/json" + }, + "description": "Withdraws a pending request to join a group. Action taken by the prospective member who originally requested to join." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/moderation/defs.json b/lexicons/chat/bsky/moderation/defs.json new file mode 100644 index 0000000000..895b3bee77 --- /dev/null +++ b/lexicons/chat/bsky/moderation/defs.json @@ -0,0 +1,81 @@ +{ + "id": "chat.bsky.moderation.defs", + "defs": { + "convoView": { + "type": "object", + "required": [ + "id", + "rev" + ], + "properties": { + "id": { + "type": "string" + }, + "rev": { + "type": "string" + }, + "kind": { + "refs": [ + "#directConvo", + "#groupConvo" + ], + "type": "union", + "description": "Union field that has data specific to different kinds of convos." + } + }, + "description": "A view of a conversation for moderation purposes. Unlike chat.bsky.convo.defs#convoView, it does not include viewer-specific data (such as muted, unreadCount, status, lastMessage, lastReaction), since the requester is a moderator and not a member of the conversation. The member list is not included; use chat.bsky.moderation.getConvoMembers to list members." + }, + "groupConvo": { + "type": "object", + "required": [ + "createdAt", + "joinRequestCount", + "lockStatus", + "memberCount", + "memberLimit", + "name" + ], + "properties": { + "name": { + "type": "string", + "maxLength": 500, + "description": "The display name of the group conversation.", + "maxGraphemes": 50 + }, + "joinLink": { + "ref": "chat.bsky.group.defs#joinLinkView", + "type": "ref" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "lockStatus": { + "ref": "chat.bsky.convo.defs#convoLockStatus", + "type": "ref", + "description": "The lock status of the conversation." + }, + "memberCount": { + "type": "integer", + "description": "The total number of members in the group conversation." + }, + "memberLimit": { + "type": "integer", + "description": "The maximum number of members allowed in the group conversation." + }, + "joinRequestCount": { + "type": "integer", + "description": "The total number of pending join requests for the group conversation. This information is only visible to the owner and to moderators. Capped at 21." + } + }, + "description": "Data specific to a group conversation, for moderation purposes. Unlike chat.bsky.convo.defs#groupConvo, it does not include viewer-specific data (such as unreadJoinRequestCount), since the requester is a moderator and not a member of the conversation." + }, + "directConvo": { + "type": "object", + "properties": {}, + "description": "Data specific to a direct conversation, for moderation purposes." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/moderation/getActorMetadata.json b/lexicons/chat/bsky/moderation/getActorMetadata.json new file mode 100644 index 0000000000..3f20fe2170 --- /dev/null +++ b/lexicons/chat/bsky/moderation/getActorMetadata.json @@ -0,0 +1,70 @@ +{ + "id": "chat.bsky.moderation.getActorMetadata", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "day", + "month", + "all" + ], + "properties": { + "all": { + "ref": "#metadata", + "type": "ref" + }, + "day": { + "ref": "#metadata", + "type": "ref" + }, + "month": { + "ref": "#metadata", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "actor" + ], + "properties": { + "actor": { + "type": "string", + "format": "did" + } + } + } + }, + "metadata": { + "type": "object", + "required": [ + "messagesSent", + "messagesReceived", + "convos", + "convosStarted" + ], + "properties": { + "convos": { + "type": "integer" + }, + "messagesSent": { + "type": "integer" + }, + "convosStarted": { + "type": "integer" + }, + "messagesReceived": { + "type": "integer" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/moderation/getConvo.json b/lexicons/chat/bsky/moderation/getConvo.json new file mode 100644 index 0000000000..a9213df585 --- /dev/null +++ b/lexicons/chat/bsky/moderation/getConvo.json @@ -0,0 +1,42 @@ +{ + "id": "chat.bsky.moderation.getConvo", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "InvalidConvo" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "convo" + ], + "properties": { + "convo": { + "ref": "chat.bsky.moderation.defs#convoView", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "convoId" + ], + "properties": { + "convoId": { + "type": "string" + } + } + }, + "description": "Gets an existing conversation by its ID, for moderation purposes. Does not require the requester to be a member of the conversation." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/moderation/getConvoMembers.json b/lexicons/chat/bsky/moderation/getConvoMembers.json new file mode 100644 index 0000000000..236663422c --- /dev/null +++ b/lexicons/chat/bsky/moderation/getConvoMembers.json @@ -0,0 +1,57 @@ +{ + "id": "chat.bsky.moderation.getConvoMembers", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "InvalidConvo" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "members" + ], + "properties": { + "cursor": { + "type": "string" + }, + "members": { + "type": "array", + "items": { + "ref": "chat.bsky.actor.defs#profileViewBasic", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "convoId" + ], + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + }, + "convoId": { + "type": "string" + } + } + }, + "description": "Returns a paginated list of members from a conversation, for moderation purposes. Does not require the requester to be a member of the conversation." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/moderation/getConvos.json b/lexicons/chat/bsky/moderation/getConvos.json new file mode 100644 index 0000000000..2169dc64dd --- /dev/null +++ b/lexicons/chat/bsky/moderation/getConvos.json @@ -0,0 +1,45 @@ +{ + "id": "chat.bsky.moderation.getConvos", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "convos" + ], + "properties": { + "convos": { + "type": "array", + "items": { + "ref": "chat.bsky.moderation.defs#convoView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "convoIds" + ], + "properties": { + "convoIds": { + "type": "array", + "items": { + "type": "string" + }, + "maxLength": 100, + "minLength": 1 + } + } + }, + "description": "Gets existing conversations by their IDs, for moderation purposes. Does not require the requester to be a member of the conversations. Unknown IDs are silently omitted from the response." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/moderation/getMessageContext.json b/lexicons/chat/bsky/moderation/getMessageContext.json new file mode 100644 index 0000000000..f88034c105 --- /dev/null +++ b/lexicons/chat/bsky/moderation/getMessageContext.json @@ -0,0 +1,63 @@ +{ + "id": "chat.bsky.moderation.getMessageContext", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "messages" + ], + "properties": { + "messages": { + "type": "array", + "items": { + "refs": [ + "chat.bsky.convo.defs#messageView", + "chat.bsky.convo.defs#systemMessageView" + ], + "type": "union" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "messageId" + ], + "properties": { + "after": { + "type": "integer", + "default": 5, + "description": "Number of user messages after the target to include. System messages between the target and the latest returned user message are also included, capped per gap by `maxInterleavedSystemMessages`. If there are no user messages after the target, up to `maxInterleavedSystemMessages` system messages immediately following the target are returned instead." + }, + "before": { + "type": "integer", + "default": 5, + "description": "Number of user messages before the target to include. System messages between the earliest returned user message and the target are also included, capped per gap by `maxInterleavedSystemMessages`. If there are no user messages before the target, up to `maxInterleavedSystemMessages` system messages immediately preceding the target are returned instead." + }, + "convoId": { + "type": "string", + "description": "Conversation that the message is from. NOTE: this field will eventually be required." + }, + "messageId": { + "type": "string" + }, + "maxInterleavedSystemMessages": { + "type": "integer", + "default": 10, + "maximum": 1000, + "minimum": 0, + "description": "Maximum number of system messages to include per gap between consecutive returned messages (and per side when there are no user messages on that side). Within a gap, the system messages closest to the earlier message are kept." + } + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/moderation/subscribeModEvents.json b/lexicons/chat/bsky/moderation/subscribeModEvents.json new file mode 100644 index 0000000000..277c85b248 --- /dev/null +++ b/lexicons/chat/bsky/moderation/subscribeModEvents.json @@ -0,0 +1,675 @@ +{ + "id": "chat.bsky.moderation.subscribeModEvents", + "defs": { + "main": { + "type": "subscription", + "errors": [ + { + "name": "FutureCursor" + }, + { + "name": "ConsumerTooSlow", + "description": "If the consumer of the stream can not keep up with events, and a backlog gets too large, the server will drop the connection." + } + ], + "message": { + "schema": { + "refs": [ + "#eventConvoFirstMessage", + "#eventGroupChatCreated", + "#eventGroupChatMemberAdded", + "#eventGroupChatMemberJoined", + "#eventGroupChatJoinRequest", + "#eventGroupChatJoinRequestApproved", + "#eventGroupChatJoinRequestRejected", + "#eventChatAccepted", + "#eventGroupChatMemberLeft", + "#eventGroupChatUpdated", + "#eventRateLimitExceeded" + ], + "type": "union" + } + }, + "parameters": { + "type": "params", + "properties": { + "cursor": { + "type": "string", + "description": "The last known event seq number to backfill from. Use '2222222222222' to backfill from the beginning. Don't specify a cursor to listen only for new events." + } + } + }, + "description": "Subscribe to stream of chat events targeted to moderation. Private endpoint." + }, + "eventChatAccepted": { + "type": "object", + "required": [ + "actorDid", + "convoCreatedAt", + "convoId", + "createdAt", + "method", + "rev" + ], + "properties": { + "rev": { + "type": "string" + }, + "method": { + "type": "string", + "description": "How the convo was accepted.", + "knownValues": [ + "explicit", + "message" + ] + }, + "convoId": { + "type": "string" + }, + "actorDid": { + "type": "string", + "format": "did", + "description": "The DID of the person accepting the convo." + }, + "ownerDid": { + "type": "string", + "format": "did", + "description": "The DID of the group chat owner. Only present for group convos." + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "groupName": { + "type": "string", + "description": "The name of the group chat. Only present for group convos." + }, + "convoCreatedAt": { + "type": "string", + "format": "datetime", + "description": "When the convo was originally created." + }, + "groupMemberCount": { + "type": "integer", + "description": "Current member count at the time of the event. Only present for group convos." + } + }, + "description": "Fired when a user accepts a chat convo, either explicitly or by sending a message." + }, + "eventGroupChatCreated": { + "type": "object", + "required": [ + "actorDid", + "convoCreatedAt", + "convoId", + "createdAt", + "groupMemberCount", + "groupName", + "initialMemberDids", + "ownerDid", + "rev" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "actorDid": { + "type": "string", + "format": "did", + "description": "The DID of the actor performing the action. For this event, same as ownerDid." + }, + "ownerDid": { + "type": "string", + "format": "did", + "description": "The DID of the group chat owner." + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "groupName": { + "type": "string", + "description": "The name set at creation time." + }, + "convoCreatedAt": { + "type": "string", + "format": "datetime", + "description": "When the group was originally created." + }, + "groupMemberCount": { + "type": "integer", + "description": "Current member count at the time of the event." + }, + "initialMemberDids": { + "type": "array", + "items": { + "type": "string", + "format": "did" + }, + "description": "DIDs of everyone added at creation time." + } + }, + "description": "Fire when a group chat is created." + }, + "eventGroupChatUpdated": { + "type": "object", + "required": [ + "actorDid", + "convoCreatedAt", + "convoId", + "createdAt", + "groupMemberCount", + "groupName", + "ownerDid", + "rev", + "updateType" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "newName": { + "type": "string", + "description": "The new group name. Only present when updateType is 'name_changed'." + }, + "oldName": { + "type": "string", + "description": "The previous group name. Only present when updateType is 'name_changed'." + }, + "actorDid": { + "type": "string", + "format": "did", + "description": "The DID of the actor performing the action (the owner)." + }, + "ownerDid": { + "type": "string", + "format": "did", + "description": "The DID of the group chat owner." + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "groupName": { + "type": "string", + "description": "Current group name." + }, + "lockReason": { + "type": "string", + "description": "Why the group was locked. Only present when updateType is 'locked'.", + "knownValues": [ + "owner_action", + "owner_left", + "owner_deactivated", + "owner_deleted", + "owner_suspended", + "owner_taken_down", + "label_applied", + "convo_taken_down" + ] + }, + "updateType": { + "type": "string", + "description": "What changed.", + "knownValues": [ + "name_changed", + "locked", + "locked_permanently", + "unlocked", + "join_link_created", + "join_link_disabled", + "join_link_settings_changed" + ] + }, + "joinLinkCode": { + "type": "string", + "description": "The code of the join link. Only present when updateType is join-link-related." + }, + "convoCreatedAt": { + "type": "string", + "format": "datetime", + "description": "When the group was originally created." + }, + "groupMemberCount": { + "type": "integer", + "description": "Current member count at the time of the event." + }, + "joinLinkFollowersOnly": { + "type": "boolean", + "description": "Whether the join link is restricted to followers of the owner. Only present when updateType is join-link-related." + }, + "joinLinkRequiresApproval": { + "type": "boolean", + "description": "Whether the join link requires owner approval to join. Only present when updateType is join-link-related." + } + }, + "description": "Fired when a group chat's metadata or status changes." + }, + "eventConvoFirstMessage": { + "type": "object", + "required": [ + "createdAt", + "rev", + "convoId", + "user", + "recipients" + ], + "properties": { + "rev": { + "type": "string" + }, + "user": { + "type": "string", + "format": "did", + "description": "The DID of the message author." + }, + "convoId": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "messageId": { + "type": "string" + }, + "recipients": { + "type": "array", + "items": { + "type": "string", + "format": "did" + }, + "description": "The list of DIDs message recipients. Does not include the sender, which is in the `user` field" + } + }, + "description": "Fired when the first message was sent on a convo." + }, + "eventRateLimitExceeded": { + "type": "object", + "required": [ + "actorDid", + "createdAt", + "endpoint", + "rev" + ], + "properties": { + "rev": { + "type": "string" + }, + "actorDid": { + "type": "string", + "format": "did", + "description": "The DID of the user who hit the rate limit." + }, + "endpoint": { + "type": "string", + "description": "The NSID of the endpoint that was rate limited." + }, + "createdAt": { + "type": "string", + "format": "datetime" + } + }, + "description": "Fired when a user exceeds a rate limit." + }, + "eventGroupChatMemberLeft": { + "type": "object", + "required": [ + "actorDid", + "convoCreatedAt", + "convoId", + "createdAt", + "groupMemberCount", + "groupName", + "leaveMethod", + "ownerDid", + "rev", + "subjectDid" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "actorDid": { + "type": "string", + "format": "did", + "description": "The DID of the actor. For voluntary: the person leaving. For kicked: the owner." + }, + "ownerDid": { + "type": "string", + "format": "did", + "description": "The DID of the group chat owner." + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "groupName": { + "type": "string" + }, + "subjectDid": { + "type": "string", + "format": "did", + "description": "The DID of the member who left or was removed." + }, + "leaveMethod": { + "type": "string", + "description": "How the member left.", + "knownValues": [ + "voluntary", + "kicked" + ] + }, + "convoCreatedAt": { + "type": "string", + "format": "datetime", + "description": "When the group was originally created." + }, + "groupMemberCount": { + "type": "integer", + "description": "Current member count at the time of the event." + } + }, + "description": "Fired when a member leaves or is removed from a group chat." + }, + "eventGroupChatJoinRequest": { + "type": "object", + "required": [ + "actorDid", + "convoCreatedAt", + "convoId", + "createdAt", + "groupMemberCount", + "groupName", + "joinLinkCode", + "ownerDid", + "rev", + "subjectFollowsOwner" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "actorDid": { + "type": "string", + "format": "did", + "description": "The DID of the person requesting to join." + }, + "ownerDid": { + "type": "string", + "format": "did", + "description": "The DID of the group chat owner." + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "groupName": { + "type": "string" + }, + "joinLinkCode": { + "type": "string", + "description": "The code of the join link used to request joining." + }, + "convoCreatedAt": { + "type": "string", + "format": "datetime", + "description": "When the group was originally created." + }, + "groupMemberCount": { + "type": "integer", + "description": "Current member count at the time of the event." + }, + "subjectFollowsOwner": { + "type": "boolean", + "description": "Whether the requesting member follows the group owner." + } + }, + "description": "Fired when a user requests to join a group chat via an join link that requires approval." + }, + "eventGroupChatMemberAdded": { + "type": "object", + "required": [ + "actorDid", + "convoCreatedAt", + "convoId", + "createdAt", + "groupMemberCount", + "groupName", + "ownerDid", + "requestMembersCount", + "rev", + "subjectDid", + "subjectFollowsOwner" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "actorDid": { + "type": "string", + "format": "did", + "description": "The DID of the actor performing the action. For this event, same as ownerDid." + }, + "ownerDid": { + "type": "string", + "format": "did", + "description": "The DID of the group chat owner." + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "groupName": { + "type": "string" + }, + "subjectDid": { + "type": "string", + "format": "did", + "description": "The DID of the member who was added." + }, + "convoCreatedAt": { + "type": "string", + "format": "datetime", + "description": "When the group was originally created." + }, + "groupMemberCount": { + "type": "integer", + "description": "Current member count at the time of the event." + }, + "requestMembersCount": { + "type": "integer", + "description": "The number of members who have not yet accepted the convo." + }, + "subjectFollowsOwner": { + "type": "boolean", + "description": "Whether the added member follows the group owner." + } + }, + "description": "Fired when a member is added to a group chat. Note that members are added in the 'request' state." + }, + "eventGroupChatMemberJoined": { + "type": "object", + "required": [ + "actorDid", + "convoCreatedAt", + "convoId", + "createdAt", + "groupMemberCount", + "groupName", + "joinLinkCode", + "ownerDid", + "rev", + "subjectFollowsOwner" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "actorDid": { + "type": "string", + "format": "did", + "description": "The DID of the person joining." + }, + "ownerDid": { + "type": "string", + "format": "did", + "description": "The DID of the group chat owner." + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "groupName": { + "type": "string" + }, + "joinLinkCode": { + "type": "string", + "description": "The code of the join link used to join." + }, + "convoCreatedAt": { + "type": "string", + "format": "datetime", + "description": "When the group was originally created." + }, + "groupMemberCount": { + "type": "integer", + "description": "Current member count at the time of the event." + }, + "subjectFollowsOwner": { + "type": "boolean", + "description": "Whether the joining member follows the group owner." + } + }, + "description": "Fired when a member joins a group chat via an join link that does not require approval." + }, + "eventGroupChatJoinRequestApproved": { + "type": "object", + "required": [ + "actorDid", + "convoCreatedAt", + "convoId", + "createdAt", + "groupMemberCount", + "groupName", + "ownerDid", + "rev", + "subjectDid" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "actorDid": { + "type": "string", + "format": "did", + "description": "The DID of the owner approving the request." + }, + "ownerDid": { + "type": "string", + "format": "did", + "description": "The DID of the group chat owner." + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "groupName": { + "type": "string" + }, + "subjectDid": { + "type": "string", + "format": "did", + "description": "The DID of the member whose request was approved." + }, + "convoCreatedAt": { + "type": "string", + "format": "datetime", + "description": "When the group was originally created." + }, + "groupMemberCount": { + "type": "integer", + "description": "Current member count at the time of the event." + } + }, + "description": "Fired when a join request is approved by the group owner." + }, + "eventGroupChatJoinRequestRejected": { + "type": "object", + "required": [ + "actorDid", + "convoCreatedAt", + "convoId", + "createdAt", + "groupMemberCount", + "groupName", + "ownerDid", + "rev", + "subjectDid" + ], + "properties": { + "rev": { + "type": "string" + }, + "convoId": { + "type": "string" + }, + "actorDid": { + "type": "string", + "format": "did", + "description": "The DID of the owner rejecting the request." + }, + "ownerDid": { + "type": "string", + "format": "did", + "description": "The DID of the group chat owner." + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "groupName": { + "type": "string" + }, + "subjectDid": { + "type": "string", + "format": "did", + "description": "The DID of the member whose request was rejected." + }, + "convoCreatedAt": { + "type": "string", + "format": "datetime", + "description": "When the group was originally created." + }, + "groupMemberCount": { + "type": "integer", + "description": "Current member count at the time of the event." + } + }, + "description": "Fired when a join request is rejected by the group owner." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/moderation/updateActorAccess.json b/lexicons/chat/bsky/moderation/updateActorAccess.json new file mode 100644 index 0000000000..8e2d711a17 --- /dev/null +++ b/lexicons/chat/bsky/moderation/updateActorAccess.json @@ -0,0 +1,32 @@ +{ + "id": "chat.bsky.moderation.updateActorAccess", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "actor", + "allowAccess" + ], + "properties": { + "ref": { + "type": "string" + }, + "actor": { + "type": "string", + "format": "did" + }, + "allowAccess": { + "type": "boolean" + } + } + }, + "encoding": "application/json" + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/notification/defs.json b/lexicons/chat/bsky/notification/defs.json new file mode 100644 index 0000000000..927f7242de --- /dev/null +++ b/lexicons/chat/bsky/notification/defs.json @@ -0,0 +1,43 @@ +{ + "id": "chat.bsky.notification.defs", + "defs": { + "preferences": { + "type": "object", + "required": [ + "chat", + "chatRequest" + ], + "properties": { + "chat": { + "ref": "#chatPreference", + "type": "ref" + }, + "chatRequest": { + "ref": "#chatPreference", + "type": "ref" + } + } + }, + "chatPreference": { + "type": "object", + "required": [ + "include", + "push" + ], + "properties": { + "push": { + "type": "boolean" + }, + "include": { + "type": "string", + "knownValues": [ + "all", + "follows" + ] + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/notification/getPreferences.json b/lexicons/chat/bsky/notification/getPreferences.json new file mode 100644 index 0000000000..e1924b0ea9 --- /dev/null +++ b/lexicons/chat/bsky/notification/getPreferences.json @@ -0,0 +1,26 @@ +{ + "id": "chat.bsky.notification.getPreferences", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "preferences" + ], + "properties": { + "preferences": { + "ref": "chat.bsky.notification.defs#preferences", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Get the requesting account's chat notification preferences. Defaults are returned for accounts that have not set any preferences. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/chat/bsky/notification/putPreferences.json b/lexicons/chat/bsky/notification/putPreferences.json new file mode 100644 index 0000000000..1d278ddb8a --- /dev/null +++ b/lexicons/chat/bsky/notification/putPreferences.json @@ -0,0 +1,42 @@ +{ + "id": "chat.bsky.notification.putPreferences", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "properties": { + "chat": { + "ref": "chat.bsky.notification.defs#chatPreference", + "type": "ref" + }, + "chatRequest": { + "ref": "chat.bsky.notification.defs#chatPreference", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "output": { + "schema": { + "type": "object", + "required": [ + "preferences" + ], + "properties": { + "preferences": { + "ref": "chat.bsky.notification.defs#preferences", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Set the requesting account's chat notification preferences. Only the provided preferences are updated; omitted preferences are left unchanged." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/defs.json b/lexicons/com/atproto/admin/defs.json new file mode 100644 index 0000000000..ee74d849c7 --- /dev/null +++ b/lexicons/com/atproto/admin/defs.json @@ -0,0 +1,132 @@ +{ + "id": "com.atproto.admin.defs", + "defs": { + "repoRef": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + } + } + }, + "statusAttr": { + "type": "object", + "required": [ + "applied" + ], + "properties": { + "ref": { + "type": "string" + }, + "applied": { + "type": "boolean" + } + } + }, + "accountView": { + "type": "object", + "required": [ + "did", + "handle", + "indexedAt" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "email": { + "type": "string" + }, + "handle": { + "type": "string", + "format": "handle" + }, + "invites": { + "type": "array", + "items": { + "ref": "com.atproto.server.defs#inviteCode", + "type": "ref" + } + }, + "indexedAt": { + "type": "string", + "format": "datetime" + }, + "invitedBy": { + "ref": "com.atproto.server.defs#inviteCode", + "type": "ref" + }, + "inviteNote": { + "type": "string" + }, + "deactivatedAt": { + "type": "string", + "format": "datetime" + }, + "relatedRecords": { + "type": "array", + "items": { + "type": "unknown" + } + }, + "invitesDisabled": { + "type": "boolean" + }, + "emailConfirmedAt": { + "type": "string", + "format": "datetime" + }, + "threatSignatures": { + "type": "array", + "items": { + "ref": "#threatSignature", + "type": "ref" + } + } + } + }, + "repoBlobRef": { + "type": "object", + "required": [ + "did", + "cid" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "did": { + "type": "string", + "format": "did" + }, + "recordUri": { + "type": "string", + "format": "at-uri" + } + } + }, + "threatSignature": { + "type": "object", + "required": [ + "property", + "value" + ], + "properties": { + "value": { + "type": "string" + }, + "property": { + "type": "string" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/deleteAccount.json b/lexicons/com/atproto/admin/deleteAccount.json new file mode 100644 index 0000000000..33497c78c1 --- /dev/null +++ b/lexicons/com/atproto/admin/deleteAccount.json @@ -0,0 +1,26 @@ +{ + "id": "com.atproto.admin.deleteAccount", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + } + } + }, + "encoding": "application/json" + }, + "description": "Delete a user account as an administrator." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/disableAccountInvites.json b/lexicons/com/atproto/admin/disableAccountInvites.json new file mode 100644 index 0000000000..3263b02e14 --- /dev/null +++ b/lexicons/com/atproto/admin/disableAccountInvites.json @@ -0,0 +1,30 @@ +{ + "id": "com.atproto.admin.disableAccountInvites", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "account" + ], + "properties": { + "note": { + "type": "string", + "description": "Optional reason for disabled invites." + }, + "account": { + "type": "string", + "format": "did" + } + } + }, + "encoding": "application/json" + }, + "description": "Disable an account from receiving new invite codes, but does not invalidate existing codes." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/disableInviteCodes.json b/lexicons/com/atproto/admin/disableInviteCodes.json new file mode 100644 index 0000000000..97ed72bd54 --- /dev/null +++ b/lexicons/com/atproto/admin/disableInviteCodes.json @@ -0,0 +1,31 @@ +{ + "id": "com.atproto.admin.disableInviteCodes", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "properties": { + "codes": { + "type": "array", + "items": { + "type": "string" + } + }, + "accounts": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "encoding": "application/json" + }, + "description": "Disable some set of codes and/or all codes associated with a set of users." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/enableAccountInvites.json b/lexicons/com/atproto/admin/enableAccountInvites.json new file mode 100644 index 0000000000..d7bc1f7dd2 --- /dev/null +++ b/lexicons/com/atproto/admin/enableAccountInvites.json @@ -0,0 +1,30 @@ +{ + "id": "com.atproto.admin.enableAccountInvites", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "account" + ], + "properties": { + "note": { + "type": "string", + "description": "Optional reason for enabled invites." + }, + "account": { + "type": "string", + "format": "did" + } + } + }, + "encoding": "application/json" + }, + "description": "Re-enable an account's ability to receive invite codes." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/getAccountInfo.json b/lexicons/com/atproto/admin/getAccountInfo.json new file mode 100644 index 0000000000..d64dd43920 --- /dev/null +++ b/lexicons/com/atproto/admin/getAccountInfo.json @@ -0,0 +1,30 @@ +{ + "id": "com.atproto.admin.getAccountInfo", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "ref": "com.atproto.admin.defs#accountView", + "type": "ref" + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + } + } + }, + "description": "Get details about an account." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/getAccountInfos.json b/lexicons/com/atproto/admin/getAccountInfos.json new file mode 100644 index 0000000000..b23ec065f8 --- /dev/null +++ b/lexicons/com/atproto/admin/getAccountInfos.json @@ -0,0 +1,44 @@ +{ + "id": "com.atproto.admin.getAccountInfos", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "infos" + ], + "properties": { + "infos": { + "type": "array", + "items": { + "ref": "com.atproto.admin.defs#accountView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "dids" + ], + "properties": { + "dids": { + "type": "array", + "items": { + "type": "string", + "format": "did" + } + } + } + }, + "description": "Get details about some accounts." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/getInviteCodes.json b/lexicons/com/atproto/admin/getInviteCodes.json new file mode 100644 index 0000000000..085de7dfdf --- /dev/null +++ b/lexicons/com/atproto/admin/getInviteCodes.json @@ -0,0 +1,54 @@ +{ + "id": "com.atproto.admin.getInviteCodes", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "codes" + ], + "properties": { + "codes": { + "type": "array", + "items": { + "ref": "com.atproto.server.defs#inviteCode", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "sort": { + "type": "string", + "default": "recent", + "knownValues": [ + "recent", + "usage" + ] + }, + "limit": { + "type": "integer", + "default": 100, + "maximum": 500, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get an admin view of invite codes." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/getSubjectStatus.json b/lexicons/com/atproto/admin/getSubjectStatus.json new file mode 100644 index 0000000000..ad10fd9d45 --- /dev/null +++ b/lexicons/com/atproto/admin/getSubjectStatus.json @@ -0,0 +1,55 @@ +{ + "id": "com.atproto.admin.getSubjectStatus", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "subject" + ], + "properties": { + "subject": { + "refs": [ + "com.atproto.admin.defs#repoRef", + "com.atproto.repo.strongRef", + "com.atproto.admin.defs#repoBlobRef" + ], + "type": "union" + }, + "takedown": { + "ref": "com.atproto.admin.defs#statusAttr", + "type": "ref" + }, + "deactivated": { + "ref": "com.atproto.admin.defs#statusAttr", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "blob": { + "type": "string", + "format": "cid" + } + } + }, + "description": "Get the service-specific admin status of a subject (account, record, or blob)." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/searchAccounts.json b/lexicons/com/atproto/admin/searchAccounts.json new file mode 100644 index 0000000000..57770a2f87 --- /dev/null +++ b/lexicons/com/atproto/admin/searchAccounts.json @@ -0,0 +1,49 @@ +{ + "id": "com.atproto.admin.searchAccounts", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "accounts" + ], + "properties": { + "cursor": { + "type": "string" + }, + "accounts": { + "type": "array", + "items": { + "ref": "com.atproto.admin.defs#accountView", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "email": { + "type": "string" + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Get list of accounts that matches your search query." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/sendEmail.json b/lexicons/com/atproto/admin/sendEmail.json new file mode 100644 index 0000000000..d619342572 --- /dev/null +++ b/lexicons/com/atproto/admin/sendEmail.json @@ -0,0 +1,56 @@ +{ + "id": "com.atproto.admin.sendEmail", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "recipientDid", + "content", + "senderDid" + ], + "properties": { + "comment": { + "type": "string", + "description": "Additional comment by the sender that won't be used in the email itself but helpful to provide more context for moderators/reviewers" + }, + "content": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "senderDid": { + "type": "string", + "format": "did" + }, + "recipientDid": { + "type": "string", + "format": "did" + } + } + }, + "encoding": "application/json" + }, + "output": { + "schema": { + "type": "object", + "required": [ + "sent" + ], + "properties": { + "sent": { + "type": "boolean" + } + } + }, + "encoding": "application/json" + }, + "description": "Send email to a user's account email address." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/updateAccountEmail.json b/lexicons/com/atproto/admin/updateAccountEmail.json new file mode 100644 index 0000000000..e42c2320fb --- /dev/null +++ b/lexicons/com/atproto/admin/updateAccountEmail.json @@ -0,0 +1,31 @@ +{ + "id": "com.atproto.admin.updateAccountEmail", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "account", + "email" + ], + "properties": { + "email": { + "type": "string" + }, + "account": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo." + } + } + }, + "encoding": "application/json" + }, + "description": "Administrative action to update an account's email." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/updateAccountHandle.json b/lexicons/com/atproto/admin/updateAccountHandle.json new file mode 100644 index 0000000000..aa1cc6d593 --- /dev/null +++ b/lexicons/com/atproto/admin/updateAccountHandle.json @@ -0,0 +1,31 @@ +{ + "id": "com.atproto.admin.updateAccountHandle", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "did", + "handle" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "handle": { + "type": "string", + "format": "handle" + } + } + }, + "encoding": "application/json" + }, + "description": "Administrative action to update an account's handle." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/updateAccountPassword.json b/lexicons/com/atproto/admin/updateAccountPassword.json new file mode 100644 index 0000000000..c87cf4e198 --- /dev/null +++ b/lexicons/com/atproto/admin/updateAccountPassword.json @@ -0,0 +1,30 @@ +{ + "id": "com.atproto.admin.updateAccountPassword", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "did", + "password" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "password": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "description": "Update the password for a user account as an administrator." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/updateAccountSigningKey.json b/lexicons/com/atproto/admin/updateAccountSigningKey.json new file mode 100644 index 0000000000..47f3584b8a --- /dev/null +++ b/lexicons/com/atproto/admin/updateAccountSigningKey.json @@ -0,0 +1,32 @@ +{ + "id": "com.atproto.admin.updateAccountSigningKey", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "did", + "signingKey" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "signingKey": { + "type": "string", + "format": "did", + "description": "Did-key formatted public key" + } + } + }, + "encoding": "application/json" + }, + "description": "Administrative action to update an account's signing key in their Did document." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/admin/updateSubjectStatus.json b/lexicons/com/atproto/admin/updateSubjectStatus.json new file mode 100644 index 0000000000..35ba17b451 --- /dev/null +++ b/lexicons/com/atproto/admin/updateSubjectStatus.json @@ -0,0 +1,61 @@ +{ + "id": "com.atproto.admin.updateSubjectStatus", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "subject" + ], + "properties": { + "subject": { + "refs": [ + "com.atproto.admin.defs#repoRef", + "com.atproto.repo.strongRef", + "com.atproto.admin.defs#repoBlobRef" + ], + "type": "union" + }, + "takedown": { + "ref": "com.atproto.admin.defs#statusAttr", + "type": "ref" + }, + "deactivated": { + "ref": "com.atproto.admin.defs#statusAttr", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "output": { + "schema": { + "type": "object", + "required": [ + "subject" + ], + "properties": { + "subject": { + "refs": [ + "com.atproto.admin.defs#repoRef", + "com.atproto.repo.strongRef", + "com.atproto.admin.defs#repoBlobRef" + ], + "type": "union" + }, + "takedown": { + "ref": "com.atproto.admin.defs#statusAttr", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Update the service-specific admin status of a subject (account, record, or blob)." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/identity/defs.json b/lexicons/com/atproto/identity/defs.json new file mode 100644 index 0000000000..3ee467f515 --- /dev/null +++ b/lexicons/com/atproto/identity/defs.json @@ -0,0 +1,30 @@ +{ + "id": "com.atproto.identity.defs", + "defs": { + "identityInfo": { + "type": "object", + "required": [ + "did", + "handle", + "didDoc" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "didDoc": { + "type": "unknown", + "description": "The complete DID document for the identity." + }, + "handle": { + "type": "string", + "format": "handle", + "description": "The validated handle of the account; or 'handle.invalid' if the handle did not bi-directionally match the DID document." + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/identity/getRecommendedDidCredentials.json b/lexicons/com/atproto/identity/getRecommendedDidCredentials.json new file mode 100644 index 0000000000..057c62e481 --- /dev/null +++ b/lexicons/com/atproto/identity/getRecommendedDidCredentials.json @@ -0,0 +1,38 @@ +{ + "id": "com.atproto.identity.getRecommendedDidCredentials", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "properties": { + "services": { + "type": "unknown" + }, + "alsoKnownAs": { + "type": "array", + "items": { + "type": "string" + } + }, + "rotationKeys": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Recommended rotation keys for PLC dids. Should be undefined (or ignored) for did:webs." + }, + "verificationMethods": { + "type": "unknown" + } + } + }, + "encoding": "application/json" + }, + "description": "Describe the credentials that should be included in the DID doc of an account that is migrating to this service." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/identity/refreshIdentity.json b/lexicons/com/atproto/identity/refreshIdentity.json new file mode 100644 index 0000000000..5dd7fe46b6 --- /dev/null +++ b/lexicons/com/atproto/identity/refreshIdentity.json @@ -0,0 +1,47 @@ +{ + "id": "com.atproto.identity.refreshIdentity", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "identifier" + ], + "properties": { + "identifier": { + "type": "string", + "format": "at-identifier" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "HandleNotFound", + "description": "The resolution process confirmed that the handle does not resolve to any DID." + }, + { + "name": "DidNotFound", + "description": "The DID resolution process confirmed that there is no current DID." + }, + { + "name": "DidDeactivated", + "description": "The DID previously existed, but has been deactivated." + } + ], + "output": { + "schema": { + "ref": "com.atproto.identity.defs#identityInfo", + "type": "ref" + }, + "encoding": "application/json" + }, + "description": "Request that the server re-resolve an identity (DID and handle). The server may ignore this request, or require authentication, depending on the role, implementation, and policy of the server." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/identity/requestPlcOperationSignature.json b/lexicons/com/atproto/identity/requestPlcOperationSignature.json new file mode 100644 index 0000000000..0d79ffc7aa --- /dev/null +++ b/lexicons/com/atproto/identity/requestPlcOperationSignature.json @@ -0,0 +1,11 @@ +{ + "id": "com.atproto.identity.requestPlcOperationSignature", + "defs": { + "main": { + "type": "procedure", + "description": "Request an email with a code to in order to request a signed PLC operation. Requires Auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/identity/resolveDid.json b/lexicons/com/atproto/identity/resolveDid.json new file mode 100644 index 0000000000..e6e8c88d90 --- /dev/null +++ b/lexicons/com/atproto/identity/resolveDid.json @@ -0,0 +1,49 @@ +{ + "id": "com.atproto.identity.resolveDid", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "DidNotFound", + "description": "The DID resolution process confirmed that there is no current DID." + }, + { + "name": "DidDeactivated", + "description": "The DID previously existed, but has been deactivated." + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "didDoc" + ], + "properties": { + "didDoc": { + "type": "unknown", + "description": "The complete DID document for the identity." + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "DID to resolve." + } + } + }, + "description": "Resolves DID to DID document. Does not bi-directionally verify handle." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/identity/resolveHandle.json b/lexicons/com/atproto/identity/resolveHandle.json new file mode 100644 index 0000000000..855e1b9827 --- /dev/null +++ b/lexicons/com/atproto/identity/resolveHandle.json @@ -0,0 +1,45 @@ +{ + "id": "com.atproto.identity.resolveHandle", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "HandleNotFound", + "description": "The resolution process confirmed that the handle does not resolve to any DID." + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "handle" + ], + "properties": { + "handle": { + "type": "string", + "format": "handle", + "description": "The handle to resolve." + } + } + }, + "description": "Resolves an atproto handle (hostname) to a DID. Does not necessarily bi-directionally verify against the the DID document." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/identity/resolveIdentity.json b/lexicons/com/atproto/identity/resolveIdentity.json new file mode 100644 index 0000000000..4da92470e3 --- /dev/null +++ b/lexicons/com/atproto/identity/resolveIdentity.json @@ -0,0 +1,45 @@ +{ + "id": "com.atproto.identity.resolveIdentity", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "HandleNotFound", + "description": "The resolution process confirmed that the handle does not resolve to any DID." + }, + { + "name": "DidNotFound", + "description": "The DID resolution process confirmed that there is no current DID." + }, + { + "name": "DidDeactivated", + "description": "The DID previously existed, but has been deactivated." + } + ], + "output": { + "schema": { + "ref": "com.atproto.identity.defs#identityInfo", + "type": "ref" + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "identifier" + ], + "properties": { + "identifier": { + "type": "string", + "format": "at-identifier", + "description": "Handle or DID to resolve." + } + } + }, + "description": "Resolves an identity (DID or Handle) to a full identity (DID document and verified handle)." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/identity/signPlcOperation.json b/lexicons/com/atproto/identity/signPlcOperation.json new file mode 100644 index 0000000000..0922c60e43 --- /dev/null +++ b/lexicons/com/atproto/identity/signPlcOperation.json @@ -0,0 +1,56 @@ +{ + "id": "com.atproto.identity.signPlcOperation", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string", + "description": "A token received through com.atproto.identity.requestPlcOperationSignature" + }, + "services": { + "type": "unknown" + }, + "alsoKnownAs": { + "type": "array", + "items": { + "type": "string" + } + }, + "rotationKeys": { + "type": "array", + "items": { + "type": "string" + } + }, + "verificationMethods": { + "type": "unknown" + } + } + }, + "encoding": "application/json" + }, + "output": { + "schema": { + "type": "object", + "required": [ + "operation" + ], + "properties": { + "operation": { + "type": "unknown", + "description": "A signed DID PLC operation." + } + } + }, + "encoding": "application/json" + }, + "description": "Signs a PLC operation to update some value(s) in the requesting DID's document." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/identity/submitPlcOperation.json b/lexicons/com/atproto/identity/submitPlcOperation.json new file mode 100644 index 0000000000..ee0a6b2206 --- /dev/null +++ b/lexicons/com/atproto/identity/submitPlcOperation.json @@ -0,0 +1,25 @@ +{ + "id": "com.atproto.identity.submitPlcOperation", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "operation" + ], + "properties": { + "operation": { + "type": "unknown" + } + } + }, + "encoding": "application/json" + }, + "description": "Validates a PLC operation to ensure that it doesn't violate a service's constraints or get the identity into a bad state, then submits it to the PLC registry" + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/identity/updateHandle.json b/lexicons/com/atproto/identity/updateHandle.json new file mode 100644 index 0000000000..5ad0daabf7 --- /dev/null +++ b/lexicons/com/atproto/identity/updateHandle.json @@ -0,0 +1,27 @@ +{ + "id": "com.atproto.identity.updateHandle", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "handle" + ], + "properties": { + "handle": { + "type": "string", + "format": "handle", + "description": "The new handle." + } + } + }, + "encoding": "application/json" + }, + "description": "Updates the current account's handle. Verifies handle validity, and updates did:plc document if necessary. Implemented by PDS, and requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/label/defs.json b/lexicons/com/atproto/label/defs.json new file mode 100644 index 0000000000..4c2a357295 --- /dev/null +++ b/lexicons/com/atproto/label/defs.json @@ -0,0 +1,190 @@ +{ + "id": "com.atproto.label.defs", + "defs": { + "label": { + "type": "object", + "required": [ + "src", + "uri", + "val", + "cts" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid", + "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to." + }, + "cts": { + "type": "string", + "format": "datetime", + "description": "Timestamp when this label was created." + }, + "exp": { + "type": "string", + "format": "datetime", + "description": "Timestamp at which this label expires (no longer applies)." + }, + "neg": { + "type": "boolean", + "description": "If true, this is a negation label, overwriting a previous label." + }, + "sig": { + "type": "bytes", + "description": "Signature of dag-cbor encoded label." + }, + "src": { + "type": "string", + "format": "did", + "description": "DID of the actor who created this label." + }, + "uri": { + "type": "string", + "format": "uri", + "description": "AT URI of the record, repository (account), or other resource that this label applies to." + }, + "val": { + "type": "string", + "maxLength": 128, + "description": "The short string name of the value or type of this label." + }, + "ver": { + "type": "integer", + "description": "The AT Protocol version of the label object." + } + }, + "description": "Metadata tag on an atproto resource (eg, repo or record)." + }, + "selfLabel": { + "type": "object", + "required": [ + "val" + ], + "properties": { + "val": { + "type": "string", + "maxLength": 128, + "description": "The short string name of the value or type of this label." + } + }, + "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel." + }, + "labelValue": { + "type": "string", + "knownValues": [ + "!hide", + "!warn", + "!no-unauthenticated", + "porn", + "sexual", + "nudity", + "graphic-media", + "bot" + ] + }, + "selfLabels": { + "type": "object", + "required": [ + "values" + ], + "properties": { + "values": { + "type": "array", + "items": { + "ref": "#selfLabel", + "type": "ref" + }, + "maxLength": 10 + } + }, + "description": "Metadata tags on an atproto record, published by the author within the record." + }, + "labelValueDefinition": { + "type": "object", + "required": [ + "identifier", + "severity", + "blurs", + "locales" + ], + "properties": { + "blurs": { + "type": "string", + "description": "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.", + "knownValues": [ + "content", + "media", + "none" + ] + }, + "locales": { + "type": "array", + "items": { + "ref": "#labelValueDefinitionStrings", + "type": "ref" + } + }, + "severity": { + "type": "string", + "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", + "knownValues": [ + "inform", + "alert", + "none" + ] + }, + "adultOnly": { + "type": "boolean", + "description": "Does the user need to have adult content enabled in order to configure this label?" + }, + "identifier": { + "type": "string", + "maxLength": 100, + "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", + "maxGraphemes": 100 + }, + "defaultSetting": { + "type": "string", + "default": "warn", + "description": "The default setting for this label.", + "knownValues": [ + "ignore", + "warn", + "hide" + ] + } + }, + "description": "Declares a label value and its expected interpretations and behaviors." + }, + "labelValueDefinitionStrings": { + "type": "object", + "required": [ + "lang", + "name", + "description" + ], + "properties": { + "lang": { + "type": "string", + "format": "language", + "description": "The code of the language these strings are written in." + }, + "name": { + "type": "string", + "maxLength": 640, + "description": "A short human-readable name for the label.", + "maxGraphemes": 64 + }, + "description": { + "type": "string", + "maxLength": 100000, + "description": "A longer description of what the label means and why it might be applied.", + "maxGraphemes": 10000 + } + }, + "description": "Strings which describe the label in the UI, localized into a specific language." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/label/queryLabels.json b/lexicons/com/atproto/label/queryLabels.json new file mode 100644 index 0000000000..d147ecbcbc --- /dev/null +++ b/lexicons/com/atproto/label/queryLabels.json @@ -0,0 +1,64 @@ +{ + "id": "com.atproto.label.queryLabels", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "labels" + ], + "properties": { + "cursor": { + "type": "string" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "uriPatterns" + ], + "properties": { + "limit": { + "type": "integer", + "default": 50, + "maximum": 250, + "minimum": 1 + }, + "cursor": { + "type": "string" + }, + "sources": { + "type": "array", + "items": { + "type": "string", + "format": "did" + }, + "description": "Optional list of label sources (DIDs) to filter on." + }, + "uriPatterns": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of AT URI patterns to match (boolean 'OR'). Each may be a prefix (ending with '*'; will match inclusive of the string leading to '*'), or a full URI." + } + } + }, + "description": "Find labels relevant to the provided AT-URI patterns. Public endpoint for moderation services, though may return different or additional results with auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/label/subscribeLabels.json b/lexicons/com/atproto/label/subscribeLabels.json new file mode 100644 index 0000000000..984ed19220 --- /dev/null +++ b/lexicons/com/atproto/label/subscribeLabels.json @@ -0,0 +1,70 @@ +{ + "id": "com.atproto.label.subscribeLabels", + "defs": { + "info": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "knownValues": [ + "OutdatedCursor" + ] + }, + "message": { + "type": "string" + } + } + }, + "main": { + "type": "subscription", + "errors": [ + { + "name": "FutureCursor" + } + ], + "message": { + "schema": { + "refs": [ + "#labels", + "#info" + ], + "type": "union" + } + }, + "parameters": { + "type": "params", + "properties": { + "cursor": { + "type": "integer", + "description": "The last known event seq number to backfill from." + } + } + }, + "description": "Subscribe to stream of labels (and negations). Public endpoint implemented by mod services. Uses same sequencing scheme as repo event stream." + }, + "labels": { + "type": "object", + "required": [ + "seq", + "labels" + ], + "properties": { + "seq": { + "type": "integer" + }, + "labels": { + "type": "array", + "items": { + "ref": "com.atproto.label.defs#label", + "type": "ref" + } + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/lexicon/resolveLexicon.json b/lexicons/com/atproto/lexicon/resolveLexicon.json new file mode 100644 index 0000000000..6807a4dcf8 --- /dev/null +++ b/lexicons/com/atproto/lexicon/resolveLexicon.json @@ -0,0 +1,58 @@ +{ + "id": "com.atproto.lexicon.resolveLexicon", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "LexiconNotFound", + "description": "No lexicon was resolved for the NSID." + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "uri", + "cid", + "schema" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid", + "description": "The CID of the lexicon schema record." + }, + "uri": { + "type": "string", + "format": "at-uri", + "description": "The AT-URI of the lexicon schema record." + }, + "schema": { + "ref": "com.atproto.lexicon.schema#main", + "type": "ref", + "description": "The resolved lexicon schema record." + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "nsid" + ], + "properties": { + "nsid": { + "type": "string", + "format": "nsid", + "description": "The lexicon NSID to resolve." + } + } + }, + "description": "Resolves an atproto lexicon (NSID) to a schema." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/lexicon/schema.json b/lexicons/com/atproto/lexicon/schema.json new file mode 100644 index 0000000000..824b1086d8 --- /dev/null +++ b/lexicons/com/atproto/lexicon/schema.json @@ -0,0 +1,24 @@ +{ + "id": "com.atproto.lexicon.schema", + "defs": { + "main": { + "key": "nsid", + "type": "record", + "record": { + "type": "object", + "required": [ + "lexicon" + ], + "properties": { + "lexicon": { + "type": "integer", + "description": "Indicates the 'version' of the Lexicon language. Must be '1' for the current atproto/Lexicon schema system." + } + } + }, + "description": "Representation of Lexicon schemas themselves, when published as atproto records. Note that the schema language is not defined in Lexicon; this meta schema currently only includes a single version field ('lexicon'). See the atproto specifications for description of the other expected top-level fields ('id', 'defs', etc)." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/moderation/createReport.json b/lexicons/com/atproto/moderation/createReport.json new file mode 100644 index 0000000000..ee6f58525f --- /dev/null +++ b/lexicons/com/atproto/moderation/createReport.json @@ -0,0 +1,104 @@ +{ + "id": "com.atproto.moderation.createReport", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "reasonType", + "subject" + ], + "properties": { + "reason": { + "type": "string", + "maxLength": 20000, + "description": "Additional context about the content and violation.", + "maxGraphemes": 2000 + }, + "modTool": { + "ref": "#modTool", + "type": "ref" + }, + "subject": { + "refs": [ + "com.atproto.admin.defs#repoRef", + "com.atproto.repo.strongRef" + ], + "type": "union" + }, + "reasonType": { + "ref": "com.atproto.moderation.defs#reasonType", + "type": "ref", + "description": "Indicates the broad category of violation the report is for." + } + } + }, + "encoding": "application/json" + }, + "output": { + "schema": { + "type": "object", + "required": [ + "id", + "reasonType", + "subject", + "reportedBy", + "createdAt" + ], + "properties": { + "id": { + "type": "integer" + }, + "reason": { + "type": "string", + "maxLength": 20000, + "maxGraphemes": 2000 + }, + "subject": { + "refs": [ + "com.atproto.admin.defs#repoRef", + "com.atproto.repo.strongRef" + ], + "type": "union" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "reasonType": { + "ref": "com.atproto.moderation.defs#reasonType", + "type": "ref" + }, + "reportedBy": { + "type": "string", + "format": "did" + } + } + }, + "encoding": "application/json" + }, + "description": "Submit a moderation report regarding an atproto account or record. Implemented by moderation services (with PDS proxying), and requires auth." + }, + "modTool": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "meta": { + "type": "unknown", + "description": "Additional arbitrary metadata about the source" + }, + "name": { + "type": "string", + "description": "Name/identifier of the source (e.g., 'bsky-app/android', 'bsky-web/chrome')" + } + }, + "description": "Moderation tool information for tracing the source of the action" + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/moderation/defs.json b/lexicons/com/atproto/moderation/defs.json new file mode 100644 index 0000000000..57fff90b21 --- /dev/null +++ b/lexicons/com/atproto/moderation/defs.json @@ -0,0 +1,96 @@ +{ + "id": "com.atproto.moderation.defs", + "defs": { + "reasonRude": { + "type": "token", + "description": "Rude, harassing, explicit, or otherwise unwelcoming behavior. Prefer new lexicon definition `tools.ozone.report.defs#reasonHarassmentOther`." + }, + "reasonSpam": { + "type": "token", + "description": "Spam: frequent unwanted promotion, replies, mentions. Prefer new lexicon definition `tools.ozone.report.defs#reasonMisleadingSpam`." + }, + "reasonType": { + "type": "string", + "knownValues": [ + "com.atproto.moderation.defs#reasonSpam", + "com.atproto.moderation.defs#reasonViolation", + "com.atproto.moderation.defs#reasonMisleading", + "com.atproto.moderation.defs#reasonSexual", + "com.atproto.moderation.defs#reasonRude", + "com.atproto.moderation.defs#reasonOther", + "com.atproto.moderation.defs#reasonAppeal", + "tools.ozone.report.defs#reasonAppeal", + "tools.ozone.report.defs#reasonOther", + "tools.ozone.report.defs#reasonViolenceAnimal", + "tools.ozone.report.defs#reasonViolenceThreats", + "tools.ozone.report.defs#reasonViolenceGraphicContent", + "tools.ozone.report.defs#reasonViolenceGlorification", + "tools.ozone.report.defs#reasonViolenceExtremistContent", + "tools.ozone.report.defs#reasonViolenceTrafficking", + "tools.ozone.report.defs#reasonViolenceOther", + "tools.ozone.report.defs#reasonSexualAbuseContent", + "tools.ozone.report.defs#reasonSexualNCII", + "tools.ozone.report.defs#reasonSexualDeepfake", + "tools.ozone.report.defs#reasonSexualAnimal", + "tools.ozone.report.defs#reasonSexualUnlabeled", + "tools.ozone.report.defs#reasonSexualOther", + "tools.ozone.report.defs#reasonChildSafetyCSAM", + "tools.ozone.report.defs#reasonChildSafetyGroom", + "tools.ozone.report.defs#reasonChildSafetyPrivacy", + "tools.ozone.report.defs#reasonChildSafetyHarassment", + "tools.ozone.report.defs#reasonChildSafetyOther", + "tools.ozone.report.defs#reasonHarassmentTroll", + "tools.ozone.report.defs#reasonHarassmentTargeted", + "tools.ozone.report.defs#reasonHarassmentHateSpeech", + "tools.ozone.report.defs#reasonHarassmentDoxxing", + "tools.ozone.report.defs#reasonHarassmentOther", + "tools.ozone.report.defs#reasonMisleadingBot", + "tools.ozone.report.defs#reasonMisleadingImpersonation", + "tools.ozone.report.defs#reasonMisleadingSpam", + "tools.ozone.report.defs#reasonMisleadingScam", + "tools.ozone.report.defs#reasonMisleadingElections", + "tools.ozone.report.defs#reasonMisleadingOther", + "tools.ozone.report.defs#reasonRuleSiteSecurity", + "tools.ozone.report.defs#reasonRuleProhibitedSales", + "tools.ozone.report.defs#reasonRuleBanEvasion", + "tools.ozone.report.defs#reasonRuleOther", + "tools.ozone.report.defs#reasonSelfHarmContent", + "tools.ozone.report.defs#reasonSelfHarmED", + "tools.ozone.report.defs#reasonSelfHarmStunts", + "tools.ozone.report.defs#reasonSelfHarmSubstances", + "tools.ozone.report.defs#reasonSelfHarmOther" + ] + }, + "reasonOther": { + "type": "token", + "description": "Reports not falling under another report category. Prefer new lexicon definition `tools.ozone.report.defs#reasonOther`." + }, + "subjectType": { + "type": "string", + "description": "Tag describing a type of subject that might be reported.", + "knownValues": [ + "account", + "record", + "chat" + ] + }, + "reasonAppeal": { + "type": "token", + "description": "Appeal a previously taken moderation action" + }, + "reasonSexual": { + "type": "token", + "description": "Unwanted or mislabeled sexual content. Prefer new lexicon definition `tools.ozone.report.defs#reasonSexualUnlabeled`." + }, + "reasonViolation": { + "type": "token", + "description": "Direct violation of server rules, laws, terms of service. Prefer new lexicon definition `tools.ozone.report.defs#reasonRuleOther`." + }, + "reasonMisleading": { + "type": "token", + "description": "Misleading identity, affiliation, or content. Prefer new lexicon definition `tools.ozone.report.defs#reasonMisleadingOther`." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/repo/applyWrites.json b/lexicons/com/atproto/repo/applyWrites.json new file mode 100644 index 0000000000..f82bf2ba88 --- /dev/null +++ b/lexicons/com/atproto/repo/applyWrites.json @@ -0,0 +1,196 @@ +{ + "id": "com.atproto.repo.applyWrites", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "repo", + "writes" + ], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo (aka, current account)." + }, + "writes": { + "type": "array", + "items": { + "refs": [ + "#create", + "#update", + "#delete" + ], + "type": "union", + "closed": true + } + }, + "validate": { + "type": "boolean", + "description": "Can be set to 'false' to skip Lexicon schema validation of record data across all operations, 'true' to require it, or leave unset to validate only for known Lexicons." + }, + "swapCommit": { + "type": "string", + "format": "cid", + "description": "If provided, the entire operation will fail if the current repo commit CID does not match this value. Used to prevent conflicting repo mutations." + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidSwap", + "description": "Indicates that the 'swapCommit' parameter did not match current commit." + } + ], + "output": { + "schema": { + "type": "object", + "required": [], + "properties": { + "commit": { + "ref": "com.atproto.repo.defs#commitMeta", + "type": "ref" + }, + "results": { + "type": "array", + "items": { + "refs": [ + "#createResult", + "#updateResult", + "#deleteResult" + ], + "type": "union", + "closed": true + } + } + } + }, + "encoding": "application/json" + }, + "description": "Apply a batch transaction of repository creates, updates, and deletes. Requires auth, implemented by PDS." + }, + "create": { + "type": "object", + "required": [ + "collection", + "value" + ], + "properties": { + "rkey": { + "type": "string", + "format": "record-key", + "maxLength": 512, + "description": "NOTE: maxLength is redundant with record-key format. Keeping it temporarily to ensure backwards compatibility." + }, + "value": { + "type": "unknown" + }, + "collection": { + "type": "string", + "format": "nsid" + } + }, + "description": "Operation which creates a new record." + }, + "delete": { + "type": "object", + "required": [ + "collection", + "rkey" + ], + "properties": { + "rkey": { + "type": "string", + "format": "record-key" + }, + "collection": { + "type": "string", + "format": "nsid" + } + }, + "description": "Operation which deletes an existing record." + }, + "update": { + "type": "object", + "required": [ + "collection", + "rkey", + "value" + ], + "properties": { + "rkey": { + "type": "string", + "format": "record-key" + }, + "value": { + "type": "unknown" + }, + "collection": { + "type": "string", + "format": "nsid" + } + }, + "description": "Operation which updates an existing record." + }, + "createResult": { + "type": "object", + "required": [ + "uri", + "cid" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "validationStatus": { + "type": "string", + "knownValues": [ + "valid", + "unknown" + ] + } + } + }, + "deleteResult": { + "type": "object", + "required": [], + "properties": {} + }, + "updateResult": { + "type": "object", + "required": [ + "uri", + "cid" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "validationStatus": { + "type": "string", + "knownValues": [ + "valid", + "unknown" + ] + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/repo/createRecord.json b/lexicons/com/atproto/repo/createRecord.json new file mode 100644 index 0000000000..2483457b9e --- /dev/null +++ b/lexicons/com/atproto/repo/createRecord.json @@ -0,0 +1,90 @@ +{ + "id": "com.atproto.repo.createRecord", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "repo", + "collection", + "record" + ], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo (aka, current account)." + }, + "rkey": { + "type": "string", + "format": "record-key", + "maxLength": 512, + "description": "The Record Key." + }, + "record": { + "type": "unknown", + "description": "The record itself. Must contain a $type field." + }, + "validate": { + "type": "boolean", + "description": "Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record collection." + }, + "swapCommit": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous commit by CID." + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidSwap", + "description": "Indicates that 'swapCommit' didn't match current repo commit." + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "uri", + "cid" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "commit": { + "ref": "com.atproto.repo.defs#commitMeta", + "type": "ref" + }, + "validationStatus": { + "type": "string", + "knownValues": [ + "valid", + "unknown" + ] + } + } + }, + "encoding": "application/json" + }, + "description": "Create a single new repository record. Requires auth, implemented by PDS." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/repo/defs.json b/lexicons/com/atproto/repo/defs.json new file mode 100644 index 0000000000..f0d13f7d45 --- /dev/null +++ b/lexicons/com/atproto/repo/defs.json @@ -0,0 +1,24 @@ +{ + "id": "com.atproto.repo.defs", + "defs": { + "commitMeta": { + "type": "object", + "required": [ + "cid", + "rev" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "rev": { + "type": "string", + "format": "tid" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/repo/deleteRecord.json b/lexicons/com/atproto/repo/deleteRecord.json new file mode 100644 index 0000000000..21a25165de --- /dev/null +++ b/lexicons/com/atproto/repo/deleteRecord.json @@ -0,0 +1,66 @@ +{ + "id": "com.atproto.repo.deleteRecord", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "repo", + "collection", + "rkey" + ], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo (aka, current account)." + }, + "rkey": { + "type": "string", + "format": "record-key", + "description": "The Record Key." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record collection." + }, + "swapCommit": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous commit by CID." + }, + "swapRecord": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous record by CID." + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidSwap" + } + ], + "output": { + "schema": { + "type": "object", + "properties": { + "commit": { + "ref": "com.atproto.repo.defs#commitMeta", + "type": "ref" + } + } + }, + "encoding": "application/json" + }, + "description": "Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/repo/describeRepo.json b/lexicons/com/atproto/repo/describeRepo.json new file mode 100644 index 0000000000..05e9cb42cd --- /dev/null +++ b/lexicons/com/atproto/repo/describeRepo.json @@ -0,0 +1,63 @@ +{ + "id": "com.atproto.repo.describeRepo", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "handle", + "did", + "didDoc", + "collections", + "handleIsCorrect" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "didDoc": { + "type": "unknown", + "description": "The complete DID document for this account." + }, + "handle": { + "type": "string", + "format": "handle" + }, + "collections": { + "type": "array", + "items": { + "type": "string", + "format": "nsid" + }, + "description": "List of all the collections (NSIDs) for which this repo contains at least one record." + }, + "handleIsCorrect": { + "type": "boolean", + "description": "Indicates if handle is currently valid (resolves bi-directionally)" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "repo" + ], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo." + } + } + }, + "description": "Get information about an account and repository, including the list of collections. Does not require auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/repo/getRecord.json b/lexicons/com/atproto/repo/getRecord.json new file mode 100644 index 0000000000..ffbaf8ee74 --- /dev/null +++ b/lexicons/com/atproto/repo/getRecord.json @@ -0,0 +1,69 @@ +{ + "id": "com.atproto.repo.getRecord", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "RecordNotFound" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "uri", + "value" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "value": { + "type": "unknown" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "repo", + "collection", + "rkey" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid", + "description": "The CID of the version of the record. If not specified, then return the most recent version." + }, + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo." + }, + "rkey": { + "type": "string", + "format": "record-key", + "description": "The Record Key." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record collection." + } + } + }, + "description": "Get a single record from a repository. Does not require auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/repo/importRepo.json b/lexicons/com/atproto/repo/importRepo.json new file mode 100644 index 0000000000..897e329160 --- /dev/null +++ b/lexicons/com/atproto/repo/importRepo.json @@ -0,0 +1,14 @@ +{ + "id": "com.atproto.repo.importRepo", + "defs": { + "main": { + "type": "procedure", + "input": { + "encoding": "application/vnd.ipld.car" + }, + "description": "Import a repo in the form of a CAR file. Requires Content-Length HTTP header to be set." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/repo/listMissingBlobs.json b/lexicons/com/atproto/repo/listMissingBlobs.json new file mode 100644 index 0000000000..41c831af66 --- /dev/null +++ b/lexicons/com/atproto/repo/listMissingBlobs.json @@ -0,0 +1,63 @@ +{ + "id": "com.atproto.repo.listMissingBlobs", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "blobs" + ], + "properties": { + "blobs": { + "type": "array", + "items": { + "ref": "#recordBlob", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 500, + "maximum": 1000, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Returns a list of missing blobs for the requesting account. Intended to be used in the account migration flow." + }, + "recordBlob": { + "type": "object", + "required": [ + "cid", + "recordUri" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "recordUri": { + "type": "string", + "format": "at-uri" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/repo/listRecords.json b/lexicons/com/atproto/repo/listRecords.json new file mode 100644 index 0000000000..c34f519ed8 --- /dev/null +++ b/lexicons/com/atproto/repo/listRecords.json @@ -0,0 +1,86 @@ +{ + "id": "com.atproto.repo.listRecords", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "records" + ], + "properties": { + "cursor": { + "type": "string" + }, + "records": { + "type": "array", + "items": { + "ref": "#record", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "repo", + "collection" + ], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo." + }, + "limit": { + "type": "integer", + "default": 50, + "maximum": 100, + "minimum": 1, + "description": "The number of records to return." + }, + "cursor": { + "type": "string" + }, + "reverse": { + "type": "boolean", + "description": "Flag to reverse the order of the returned records." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record type." + } + } + }, + "description": "List a range of records in a repository, matching a specific collection. Does not require auth." + }, + "record": { + "type": "object", + "required": [ + "uri", + "cid", + "value" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "value": { + "type": "unknown" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/repo/putRecord.json b/lexicons/com/atproto/repo/putRecord.json new file mode 100644 index 0000000000..6584a2048a --- /dev/null +++ b/lexicons/com/atproto/repo/putRecord.json @@ -0,0 +1,98 @@ +{ + "id": "com.atproto.repo.putRecord", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "nullable": [ + "swapRecord" + ], + "required": [ + "repo", + "collection", + "rkey", + "record" + ], + "properties": { + "repo": { + "type": "string", + "format": "at-identifier", + "description": "The handle or DID of the repo (aka, current account)." + }, + "rkey": { + "type": "string", + "format": "record-key", + "maxLength": 512, + "description": "The Record Key." + }, + "record": { + "type": "unknown", + "description": "The record to write." + }, + "validate": { + "type": "boolean", + "description": "Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons." + }, + "collection": { + "type": "string", + "format": "nsid", + "description": "The NSID of the record collection." + }, + "swapCommit": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous commit by CID." + }, + "swapRecord": { + "type": "string", + "format": "cid", + "description": "Compare and swap with the previous record by CID. WARNING: nullable and optional field; may cause problems with golang implementation" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidSwap" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "uri", + "cid" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + }, + "commit": { + "ref": "com.atproto.repo.defs#commitMeta", + "type": "ref" + }, + "validationStatus": { + "type": "string", + "knownValues": [ + "valid", + "unknown" + ] + } + } + }, + "encoding": "application/json" + }, + "description": "Write a repository record, creating or updating it as needed. Requires auth, implemented by PDS." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/repo/strongRef.json b/lexicons/com/atproto/repo/strongRef.json new file mode 100644 index 0000000000..f45cca3908 --- /dev/null +++ b/lexicons/com/atproto/repo/strongRef.json @@ -0,0 +1,25 @@ +{ + "id": "com.atproto.repo.strongRef", + "defs": { + "main": { + "type": "object", + "required": [ + "uri", + "cid" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "uri": { + "type": "string", + "format": "at-uri" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1, + "description": "A URI with a content-hash fingerprint." +} diff --git a/lexicons/com/atproto/repo/uploadBlob.json b/lexicons/com/atproto/repo/uploadBlob.json new file mode 100644 index 0000000000..9b1bdd4a93 --- /dev/null +++ b/lexicons/com/atproto/repo/uploadBlob.json @@ -0,0 +1,28 @@ +{ + "id": "com.atproto.repo.uploadBlob", + "defs": { + "main": { + "type": "procedure", + "input": { + "encoding": "*/*" + }, + "output": { + "schema": { + "type": "object", + "required": [ + "blob" + ], + "properties": { + "blob": { + "type": "blob" + } + } + }, + "encoding": "application/json" + }, + "description": "Upload a new blob, to be referenced from a repository record. The blob will be deleted if it is not referenced within a time window (eg, minutes). Blob restrictions (mimetype, size, etc) are enforced when the reference is created. Requires auth, implemented by PDS." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/activateAccount.json b/lexicons/com/atproto/server/activateAccount.json new file mode 100644 index 0000000000..459607c827 --- /dev/null +++ b/lexicons/com/atproto/server/activateAccount.json @@ -0,0 +1,11 @@ +{ + "id": "com.atproto.server.activateAccount", + "defs": { + "main": { + "type": "procedure", + "description": "Activates a currently deactivated account. Used to finalize account migration after the account's repo is imported and identity is setup." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/checkAccountStatus.json b/lexicons/com/atproto/server/checkAccountStatus.json new file mode 100644 index 0000000000..289ca52c2e --- /dev/null +++ b/lexicons/com/atproto/server/checkAccountStatus.json @@ -0,0 +1,58 @@ +{ + "id": "com.atproto.server.checkAccountStatus", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "activated", + "validDid", + "repoCommit", + "repoRev", + "repoBlocks", + "indexedRecords", + "privateStateValues", + "expectedBlobs", + "importedBlobs" + ], + "properties": { + "repoRev": { + "type": "string" + }, + "validDid": { + "type": "boolean" + }, + "activated": { + "type": "boolean" + }, + "repoBlocks": { + "type": "integer" + }, + "repoCommit": { + "type": "string", + "format": "cid" + }, + "expectedBlobs": { + "type": "integer" + }, + "importedBlobs": { + "type": "integer" + }, + "indexedRecords": { + "type": "integer" + }, + "privateStateValues": { + "type": "integer" + } + } + }, + "encoding": "application/json" + }, + "description": "Returns the status of an account, especially as pertaining to import or recovery. Can be called many times over the course of an account migration. Requires auth and can only be called pertaining to oneself." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/confirmEmail.json b/lexicons/com/atproto/server/confirmEmail.json new file mode 100644 index 0000000000..4412676285 --- /dev/null +++ b/lexicons/com/atproto/server/confirmEmail.json @@ -0,0 +1,43 @@ +{ + "id": "com.atproto.server.confirmEmail", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "email", + "token" + ], + "properties": { + "email": { + "type": "string" + }, + "token": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "AccountNotFound" + }, + { + "name": "ExpiredToken" + }, + { + "name": "InvalidToken" + }, + { + "name": "InvalidEmail" + } + ], + "description": "Confirm an email using a token from com.atproto.server.requestEmailConfirmation." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/createAccount.json b/lexicons/com/atproto/server/createAccount.json new file mode 100644 index 0000000000..5b14ca30ce --- /dev/null +++ b/lexicons/com/atproto/server/createAccount.json @@ -0,0 +1,113 @@ +{ + "id": "com.atproto.server.createAccount", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "handle" + ], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "Pre-existing atproto DID, being imported to a new account." + }, + "email": { + "type": "string" + }, + "plcOp": { + "type": "unknown", + "description": "A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented." + }, + "handle": { + "type": "string", + "format": "handle", + "description": "Requested handle for the account." + }, + "password": { + "type": "string", + "description": "Initial account password. May need to meet instance-specific password strength requirements." + }, + "inviteCode": { + "type": "string" + }, + "recoveryKey": { + "type": "string", + "description": "DID PLC rotation key (aka, recovery key) to be included in PLC creation operation." + }, + "verificationCode": { + "type": "string" + }, + "verificationPhone": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "InvalidHandle" + }, + { + "name": "InvalidPassword" + }, + { + "name": "InvalidInviteCode" + }, + { + "name": "HandleNotAvailable" + }, + { + "name": "UnsupportedDomain" + }, + { + "name": "UnresolvableDid" + }, + { + "name": "IncompatibleDidDoc" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "accessJwt", + "refreshJwt", + "handle", + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "The DID of the new account." + }, + "didDoc": { + "type": "unknown", + "description": "Complete DID document." + }, + "handle": { + "type": "string", + "format": "handle" + }, + "accessJwt": { + "type": "string" + }, + "refreshJwt": { + "type": "string" + } + }, + "description": "Account login session returned on successful account creation." + }, + "encoding": "application/json" + }, + "description": "Create an account. Implemented by PDS." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/createAppPassword.json b/lexicons/com/atproto/server/createAppPassword.json new file mode 100644 index 0000000000..72e24ba7b2 --- /dev/null +++ b/lexicons/com/atproto/server/createAppPassword.json @@ -0,0 +1,65 @@ +{ + "id": "com.atproto.server.createAppPassword", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "A short name for the App Password, to help distinguish them." + }, + "privileged": { + "type": "boolean", + "description": "If an app password has 'privileged' access to possibly sensitive account state. Meant for use with trusted clients." + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "AccountTakedown" + } + ], + "output": { + "schema": { + "ref": "#appPassword", + "type": "ref" + }, + "encoding": "application/json" + }, + "description": "Create an App Password." + }, + "appPassword": { + "type": "object", + "required": [ + "name", + "password", + "createdAt" + ], + "properties": { + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "privileged": { + "type": "boolean" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/createInviteCode.json b/lexicons/com/atproto/server/createInviteCode.json new file mode 100644 index 0000000000..d355ddfec3 --- /dev/null +++ b/lexicons/com/atproto/server/createInviteCode.json @@ -0,0 +1,43 @@ +{ + "id": "com.atproto.server.createInviteCode", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "useCount" + ], + "properties": { + "useCount": { + "type": "integer" + }, + "forAccount": { + "type": "string", + "format": "did" + } + } + }, + "encoding": "application/json" + }, + "output": { + "schema": { + "type": "object", + "required": [ + "code" + ], + "properties": { + "code": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "description": "Create an invite code." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/createInviteCodes.json b/lexicons/com/atproto/server/createInviteCodes.json new file mode 100644 index 0000000000..4843e28dc0 --- /dev/null +++ b/lexicons/com/atproto/server/createInviteCodes.json @@ -0,0 +1,73 @@ +{ + "id": "com.atproto.server.createInviteCodes", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "codeCount", + "useCount" + ], + "properties": { + "useCount": { + "type": "integer" + }, + "codeCount": { + "type": "integer", + "default": 1 + }, + "forAccounts": { + "type": "array", + "items": { + "type": "string", + "format": "did" + } + } + } + }, + "encoding": "application/json" + }, + "output": { + "schema": { + "type": "object", + "required": [ + "codes" + ], + "properties": { + "codes": { + "type": "array", + "items": { + "ref": "#accountCodes", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "description": "Create invite codes." + }, + "accountCodes": { + "type": "object", + "required": [ + "account", + "codes" + ], + "properties": { + "codes": { + "type": "array", + "items": { + "type": "string" + } + }, + "account": { + "type": "string" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/createSession.json b/lexicons/com/atproto/server/createSession.json new file mode 100644 index 0000000000..1b353cbb59 --- /dev/null +++ b/lexicons/com/atproto/server/createSession.json @@ -0,0 +1,97 @@ +{ + "id": "com.atproto.server.createSession", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "identifier", + "password" + ], + "properties": { + "password": { + "type": "string" + }, + "identifier": { + "type": "string", + "description": "Handle or other identifier supported by the server for the authenticating user." + }, + "allowTakendown": { + "type": "boolean", + "description": "When true, instead of throwing error for takendown accounts, a valid response with a narrow scoped token will be returned" + }, + "authFactorToken": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "AccountTakedown" + }, + { + "name": "AuthFactorTokenRequired" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "accessJwt", + "refreshJwt", + "handle", + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "email": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "didDoc": { + "type": "unknown" + }, + "handle": { + "type": "string", + "format": "handle" + }, + "status": { + "type": "string", + "description": "If active=false, this optional field indicates a possible reason for why the account is not active. If active=false and no status is supplied, then the host makes no claim for why the repository is no longer being hosted.", + "knownValues": [ + "takendown", + "suspended", + "deactivated" + ] + }, + "accessJwt": { + "type": "string" + }, + "refreshJwt": { + "type": "string" + }, + "emailConfirmed": { + "type": "boolean" + }, + "emailAuthFactor": { + "type": "boolean" + } + } + }, + "encoding": "application/json" + }, + "description": "Create an authentication session." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/deactivateAccount.json b/lexicons/com/atproto/server/deactivateAccount.json new file mode 100644 index 0000000000..0607e0efc6 --- /dev/null +++ b/lexicons/com/atproto/server/deactivateAccount.json @@ -0,0 +1,24 @@ +{ + "id": "com.atproto.server.deactivateAccount", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "properties": { + "deleteAfter": { + "type": "string", + "format": "datetime", + "description": "A recommendation to server as to how long they should hold onto the deactivated account before deleting." + } + } + }, + "encoding": "application/json" + }, + "description": "Deactivates a currently active account. Stops serving of repo, and future writes to repo until reactivated. Used to finalize account migration with the old host after the account has been activated on the new host." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/defs.json b/lexicons/com/atproto/server/defs.json new file mode 100644 index 0000000000..60cdf91571 --- /dev/null +++ b/lexicons/com/atproto/server/defs.json @@ -0,0 +1,64 @@ +{ + "id": "com.atproto.server.defs", + "defs": { + "inviteCode": { + "type": "object", + "required": [ + "code", + "available", + "disabled", + "forAccount", + "createdBy", + "createdAt", + "uses" + ], + "properties": { + "code": { + "type": "string" + }, + "uses": { + "type": "array", + "items": { + "ref": "#inviteCodeUse", + "type": "ref" + } + }, + "disabled": { + "type": "boolean" + }, + "available": { + "type": "integer" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "createdBy": { + "type": "string" + }, + "forAccount": { + "type": "string" + } + } + }, + "inviteCodeUse": { + "type": "object", + "required": [ + "usedBy", + "usedAt" + ], + "properties": { + "usedAt": { + "type": "string", + "format": "datetime" + }, + "usedBy": { + "type": "string", + "format": "did" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/deleteAccount.json b/lexicons/com/atproto/server/deleteAccount.json new file mode 100644 index 0000000000..6743a74431 --- /dev/null +++ b/lexicons/com/atproto/server/deleteAccount.json @@ -0,0 +1,42 @@ +{ + "id": "com.atproto.server.deleteAccount", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "did", + "password", + "token" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "token": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "ExpiredToken" + }, + { + "name": "InvalidToken" + } + ], + "description": "Delete an actor's account with a token and password. Can only be called after requesting a deletion token. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/deleteSession.json b/lexicons/com/atproto/server/deleteSession.json new file mode 100644 index 0000000000..e2cc504c39 --- /dev/null +++ b/lexicons/com/atproto/server/deleteSession.json @@ -0,0 +1,19 @@ +{ + "id": "com.atproto.server.deleteSession", + "defs": { + "main": { + "type": "procedure", + "errors": [ + { + "name": "InvalidToken" + }, + { + "name": "ExpiredToken" + } + ], + "description": "Delete the current session. Requires auth using the 'refreshJwt' (not the 'accessJwt')." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/describeServer.json b/lexicons/com/atproto/server/describeServer.json new file mode 100644 index 0000000000..25dc2d2912 --- /dev/null +++ b/lexicons/com/atproto/server/describeServer.json @@ -0,0 +1,73 @@ +{ + "id": "com.atproto.server.describeServer", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "did", + "availableUserDomains" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "links": { + "ref": "#links", + "type": "ref", + "description": "URLs of service policy documents." + }, + "contact": { + "ref": "#contact", + "type": "ref", + "description": "Contact information" + }, + "inviteCodeRequired": { + "type": "boolean", + "description": "If true, an invite code must be supplied to create an account on this instance." + }, + "availableUserDomains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of domain suffixes that can be used in account handles." + }, + "phoneVerificationRequired": { + "type": "boolean", + "description": "If true, a phone verification token must be supplied to create an account on this instance." + } + } + }, + "encoding": "application/json" + }, + "description": "Describes the server's account creation requirements and capabilities. Implemented by PDS." + }, + "links": { + "type": "object", + "properties": { + "privacyPolicy": { + "type": "string", + "format": "uri" + }, + "termsOfService": { + "type": "string", + "format": "uri" + } + } + }, + "contact": { + "type": "object", + "properties": { + "email": { + "type": "string" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/getAccountInviteCodes.json b/lexicons/com/atproto/server/getAccountInviteCodes.json new file mode 100644 index 0000000000..6b6bb1fa32 --- /dev/null +++ b/lexicons/com/atproto/server/getAccountInviteCodes.json @@ -0,0 +1,48 @@ +{ + "id": "com.atproto.server.getAccountInviteCodes", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "DuplicateCreate" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "codes" + ], + "properties": { + "codes": { + "type": "array", + "items": { + "ref": "com.atproto.server.defs#inviteCode", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "includeUsed": { + "type": "boolean", + "default": true + }, + "createAvailable": { + "type": "boolean", + "default": true, + "description": "Controls whether any new 'earned' but not 'created' invites should be created." + } + } + }, + "description": "Get all invite codes for the current account. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/getServiceAuth.json b/lexicons/com/atproto/server/getServiceAuth.json new file mode 100644 index 0000000000..80f5a1148c --- /dev/null +++ b/lexicons/com/atproto/server/getServiceAuth.json @@ -0,0 +1,53 @@ +{ + "id": "com.atproto.server.getServiceAuth", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "BadExpiration", + "description": "Indicates that the requested expiration date is not a valid. May be in the past or may be reliant on the requested scopes." + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "aud" + ], + "properties": { + "aud": { + "type": "string", + "format": "did", + "description": "The DID of the service that the token will be used to authenticate with" + }, + "exp": { + "type": "integer", + "description": "The time in Unix Epoch seconds that the JWT expires. Defaults to 60 seconds in the future. The service may enforce certain time bounds on tokens depending on the requested scope." + }, + "lxm": { + "type": "string", + "format": "nsid", + "description": "Lexicon (XRPC) method to bind the requested token to" + } + } + }, + "description": "Get a signed token on behalf of the requesting DID for the requested service." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/getSession.json b/lexicons/com/atproto/server/getSession.json new file mode 100644 index 0000000000..8e9810d72e --- /dev/null +++ b/lexicons/com/atproto/server/getSession.json @@ -0,0 +1,55 @@ +{ + "id": "com.atproto.server.getSession", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "handle", + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "email": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "didDoc": { + "type": "unknown" + }, + "handle": { + "type": "string", + "format": "handle" + }, + "status": { + "type": "string", + "description": "If active=false, this optional field indicates a possible reason for why the account is not active. If active=false and no status is supplied, then the host makes no claim for why the repository is no longer being hosted.", + "knownValues": [ + "takendown", + "suspended", + "deactivated" + ] + }, + "emailConfirmed": { + "type": "boolean" + }, + "emailAuthFactor": { + "type": "boolean" + } + } + }, + "encoding": "application/json" + }, + "description": "Get information about the current auth session. Requires auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/listAppPasswords.json b/lexicons/com/atproto/server/listAppPasswords.json new file mode 100644 index 0000000000..8ff2678ea3 --- /dev/null +++ b/lexicons/com/atproto/server/listAppPasswords.json @@ -0,0 +1,53 @@ +{ + "id": "com.atproto.server.listAppPasswords", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "AccountTakedown" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "passwords" + ], + "properties": { + "passwords": { + "type": "array", + "items": { + "ref": "#appPassword", + "type": "ref" + } + } + } + }, + "encoding": "application/json" + }, + "description": "List all App Passwords." + }, + "appPassword": { + "type": "object", + "required": [ + "name", + "createdAt" + ], + "properties": { + "name": { + "type": "string" + }, + "createdAt": { + "type": "string", + "format": "datetime" + }, + "privileged": { + "type": "boolean" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/refreshSession.json b/lexicons/com/atproto/server/refreshSession.json new file mode 100644 index 0000000000..3924c2b665 --- /dev/null +++ b/lexicons/com/atproto/server/refreshSession.json @@ -0,0 +1,74 @@ +{ + "id": "com.atproto.server.refreshSession", + "defs": { + "main": { + "type": "procedure", + "errors": [ + { + "name": "AccountTakedown" + }, + { + "name": "InvalidToken" + }, + { + "name": "ExpiredToken" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "accessJwt", + "refreshJwt", + "handle", + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "email": { + "type": "string" + }, + "active": { + "type": "boolean" + }, + "didDoc": { + "type": "unknown" + }, + "handle": { + "type": "string", + "format": "handle" + }, + "status": { + "type": "string", + "description": "Hosting status of the account. If not specified, then assume 'active'.", + "knownValues": [ + "takendown", + "suspended", + "deactivated" + ] + }, + "accessJwt": { + "type": "string" + }, + "refreshJwt": { + "type": "string" + }, + "emailConfirmed": { + "type": "boolean" + }, + "emailAuthFactor": { + "type": "boolean" + } + } + }, + "encoding": "application/json" + }, + "description": "Refresh an authentication session. Requires auth using the 'refreshJwt' (not the 'accessJwt')." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/requestAccountDelete.json b/lexicons/com/atproto/server/requestAccountDelete.json new file mode 100644 index 0000000000..94c21ae5a6 --- /dev/null +++ b/lexicons/com/atproto/server/requestAccountDelete.json @@ -0,0 +1,11 @@ +{ + "id": "com.atproto.server.requestAccountDelete", + "defs": { + "main": { + "type": "procedure", + "description": "Initiate a user account deletion via email." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/requestEmailConfirmation.json b/lexicons/com/atproto/server/requestEmailConfirmation.json new file mode 100644 index 0000000000..808c2aa4f7 --- /dev/null +++ b/lexicons/com/atproto/server/requestEmailConfirmation.json @@ -0,0 +1,11 @@ +{ + "id": "com.atproto.server.requestEmailConfirmation", + "defs": { + "main": { + "type": "procedure", + "description": "Request an email with a code to confirm ownership of email." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/requestEmailUpdate.json b/lexicons/com/atproto/server/requestEmailUpdate.json new file mode 100644 index 0000000000..158ba0f990 --- /dev/null +++ b/lexicons/com/atproto/server/requestEmailUpdate.json @@ -0,0 +1,25 @@ +{ + "id": "com.atproto.server.requestEmailUpdate", + "defs": { + "main": { + "type": "procedure", + "output": { + "schema": { + "type": "object", + "required": [ + "tokenRequired" + ], + "properties": { + "tokenRequired": { + "type": "boolean" + } + } + }, + "encoding": "application/json" + }, + "description": "Request a token in order to update email." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/requestPasswordReset.json b/lexicons/com/atproto/server/requestPasswordReset.json new file mode 100644 index 0000000000..fdb81f40e4 --- /dev/null +++ b/lexicons/com/atproto/server/requestPasswordReset.json @@ -0,0 +1,25 @@ +{ + "id": "com.atproto.server.requestPasswordReset", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "description": "Initiate a user account password reset via email." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/reserveSigningKey.json b/lexicons/com/atproto/server/reserveSigningKey.json new file mode 100644 index 0000000000..cd54f637fa --- /dev/null +++ b/lexicons/com/atproto/server/reserveSigningKey.json @@ -0,0 +1,39 @@ +{ + "id": "com.atproto.server.reserveSigningKey", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "The DID to reserve a key for." + } + } + }, + "encoding": "application/json" + }, + "output": { + "schema": { + "type": "object", + "required": [ + "signingKey" + ], + "properties": { + "signingKey": { + "type": "string", + "description": "The public key for the reserved signing key, in did:key serialization." + } + } + }, + "encoding": "application/json" + }, + "description": "Reserve a repo signing key, for use with account creation. Necessary so that a DID PLC update operation can be constructed during an account migraiton. Public and does not require auth; implemented by PDS. NOTE: this endpoint may change when full account migration is implemented." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/resetPassword.json b/lexicons/com/atproto/server/resetPassword.json new file mode 100644 index 0000000000..b8857cfca0 --- /dev/null +++ b/lexicons/com/atproto/server/resetPassword.json @@ -0,0 +1,37 @@ +{ + "id": "com.atproto.server.resetPassword", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "token", + "password" + ], + "properties": { + "token": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "ExpiredToken" + }, + { + "name": "InvalidToken" + } + ], + "description": "Reset a user account password using a token." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/revokeAppPassword.json b/lexicons/com/atproto/server/revokeAppPassword.json new file mode 100644 index 0000000000..362306cc96 --- /dev/null +++ b/lexicons/com/atproto/server/revokeAppPassword.json @@ -0,0 +1,25 @@ +{ + "id": "com.atproto.server.revokeAppPassword", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "description": "Revoke an App Password by name." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/server/updateEmail.json b/lexicons/com/atproto/server/updateEmail.json new file mode 100644 index 0000000000..a5390b4de3 --- /dev/null +++ b/lexicons/com/atproto/server/updateEmail.json @@ -0,0 +1,43 @@ +{ + "id": "com.atproto.server.updateEmail", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "email" + ], + "properties": { + "email": { + "type": "string" + }, + "token": { + "type": "string", + "description": "Requires a token from com.atproto.sever.requestEmailUpdate if the account's email has been confirmed." + }, + "emailAuthFactor": { + "type": "boolean" + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "ExpiredToken" + }, + { + "name": "InvalidToken" + }, + { + "name": "TokenRequired" + } + ], + "description": "Update an account's email." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/defs.json b/lexicons/com/atproto/sync/defs.json new file mode 100644 index 0000000000..3f2f42fe8c --- /dev/null +++ b/lexicons/com/atproto/sync/defs.json @@ -0,0 +1,17 @@ +{ + "id": "com.atproto.sync.defs", + "defs": { + "hostStatus": { + "type": "string", + "knownValues": [ + "active", + "idle", + "offline", + "throttled", + "banned" + ] + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/getBlob.json b/lexicons/com/atproto/sync/getBlob.json new file mode 100644 index 0000000000..b571d31736 --- /dev/null +++ b/lexicons/com/atproto/sync/getBlob.json @@ -0,0 +1,50 @@ +{ + "id": "com.atproto.sync.getBlob", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "BlobNotFound" + }, + { + "name": "RepoNotFound" + }, + { + "name": "RepoTakendown" + }, + { + "name": "RepoSuspended" + }, + { + "name": "RepoDeactivated" + } + ], + "output": { + "encoding": "*/*" + }, + "parameters": { + "type": "params", + "required": [ + "did", + "cid" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid", + "description": "The CID of the blob to fetch" + }, + "did": { + "type": "string", + "format": "did", + "description": "The DID of the account." + } + } + }, + "description": "Get a blob associated with a given account. Returns the full blob as originally uploaded. Does not require auth; implemented by PDS." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/getBlocks.json b/lexicons/com/atproto/sync/getBlocks.json new file mode 100644 index 0000000000..8cd269ecea --- /dev/null +++ b/lexicons/com/atproto/sync/getBlocks.json @@ -0,0 +1,52 @@ +{ + "id": "com.atproto.sync.getBlocks", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "BlockNotFound" + }, + { + "name": "RepoNotFound" + }, + { + "name": "RepoTakendown" + }, + { + "name": "RepoSuspended" + }, + { + "name": "RepoDeactivated" + } + ], + "output": { + "encoding": "application/vnd.ipld.car" + }, + "parameters": { + "type": "params", + "required": [ + "did", + "cids" + ], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "The DID of the repo." + }, + "cids": { + "type": "array", + "items": { + "type": "string", + "format": "cid" + } + } + } + }, + "description": "Get data blocks from a given repo, by CID. For example, intermediate MST nodes, or records. Does not require auth; implemented by PDS." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/getCheckout.json b/lexicons/com/atproto/sync/getCheckout.json new file mode 100644 index 0000000000..9e5b0b3066 --- /dev/null +++ b/lexicons/com/atproto/sync/getCheckout.json @@ -0,0 +1,27 @@ +{ + "id": "com.atproto.sync.getCheckout", + "defs": { + "main": { + "type": "query", + "output": { + "encoding": "application/vnd.ipld.car" + }, + "parameters": { + "type": "params", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "The DID of the repo." + } + } + }, + "description": "DEPRECATED - please use com.atproto.sync.getRepo instead" + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/getHead.json b/lexicons/com/atproto/sync/getHead.json new file mode 100644 index 0000000000..fc5af39d45 --- /dev/null +++ b/lexicons/com/atproto/sync/getHead.json @@ -0,0 +1,44 @@ +{ + "id": "com.atproto.sync.getHead", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "HeadNotFound" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "root" + ], + "properties": { + "root": { + "type": "string", + "format": "cid" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "The DID of the repo." + } + } + }, + "description": "DEPRECATED - please use com.atproto.sync.getLatestCommit instead" + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/getHostStatus.json b/lexicons/com/atproto/sync/getHostStatus.json new file mode 100644 index 0000000000..86d563fa24 --- /dev/null +++ b/lexicons/com/atproto/sync/getHostStatus.json @@ -0,0 +1,54 @@ +{ + "id": "com.atproto.sync.getHostStatus", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "HostNotFound" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "hostname" + ], + "properties": { + "seq": { + "type": "integer", + "description": "Recent repo stream event sequence number. May be delayed from actual stream processing (eg, persisted cursor not in-memory cursor)." + }, + "status": { + "ref": "com.atproto.sync.defs#hostStatus", + "type": "ref" + }, + "hostname": { + "type": "string" + }, + "accountCount": { + "type": "integer", + "description": "Number of accounts on the server which are associated with the upstream host. Note that the upstream may actually have more accounts." + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "hostname" + ], + "properties": { + "hostname": { + "type": "string", + "description": "Hostname of the host (eg, PDS or relay) being queried." + } + } + }, + "description": "Returns information about a specified upstream host, as consumed by the server. Implemented by relays." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/getLatestCommit.json b/lexicons/com/atproto/sync/getLatestCommit.json new file mode 100644 index 0000000000..e8ce2cd800 --- /dev/null +++ b/lexicons/com/atproto/sync/getLatestCommit.json @@ -0,0 +1,58 @@ +{ + "id": "com.atproto.sync.getLatestCommit", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "RepoNotFound" + }, + { + "name": "RepoTakendown" + }, + { + "name": "RepoSuspended" + }, + { + "name": "RepoDeactivated" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "cid", + "rev" + ], + "properties": { + "cid": { + "type": "string", + "format": "cid" + }, + "rev": { + "type": "string", + "format": "tid" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "The DID of the repo." + } + } + }, + "description": "Get the current commit CID & revision of the specified repo. Does not require auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/getRecord.json b/lexicons/com/atproto/sync/getRecord.json new file mode 100644 index 0000000000..330826046d --- /dev/null +++ b/lexicons/com/atproto/sync/getRecord.json @@ -0,0 +1,55 @@ +{ + "id": "com.atproto.sync.getRecord", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "RecordNotFound" + }, + { + "name": "RepoNotFound" + }, + { + "name": "RepoTakendown" + }, + { + "name": "RepoSuspended" + }, + { + "name": "RepoDeactivated" + } + ], + "output": { + "encoding": "application/vnd.ipld.car" + }, + "parameters": { + "type": "params", + "required": [ + "did", + "collection", + "rkey" + ], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "The DID of the repo." + }, + "rkey": { + "type": "string", + "format": "record-key", + "description": "Record Key" + }, + "collection": { + "type": "string", + "format": "nsid" + } + } + }, + "description": "Get data blocks needed to prove the existence or non-existence of record in the current version of repo. Does not require auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/getRepo.json b/lexicons/com/atproto/sync/getRepo.json new file mode 100644 index 0000000000..3ec4c0489b --- /dev/null +++ b/lexicons/com/atproto/sync/getRepo.json @@ -0,0 +1,46 @@ +{ + "id": "com.atproto.sync.getRepo", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "RepoNotFound" + }, + { + "name": "RepoTakendown" + }, + { + "name": "RepoSuspended" + }, + { + "name": "RepoDeactivated" + } + ], + "output": { + "encoding": "application/vnd.ipld.car" + }, + "parameters": { + "type": "params", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "The DID of the repo." + }, + "since": { + "type": "string", + "format": "tid", + "description": "The revision ('rev') of the repo to create a diff from." + } + } + }, + "description": "Download a repository export as CAR file. Optionally only a 'diff' since a previous revision. Does not require auth; implemented by PDS." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/getRepoStatus.json b/lexicons/com/atproto/sync/getRepoStatus.json new file mode 100644 index 0000000000..698b80d509 --- /dev/null +++ b/lexicons/com/atproto/sync/getRepoStatus.json @@ -0,0 +1,65 @@ +{ + "id": "com.atproto.sync.getRepoStatus", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "RepoNotFound" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "did", + "active" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "rev": { + "type": "string", + "format": "tid", + "description": "Optional field, the current rev of the repo, if active=true" + }, + "active": { + "type": "boolean" + }, + "status": { + "type": "string", + "description": "If active=false, this optional field indicates a possible reason for why the account is not active. If active=false and no status is supplied, then the host makes no claim for why the repository is no longer being hosted.", + "knownValues": [ + "takendown", + "suspended", + "deleted", + "deactivated", + "desynchronized", + "throttled" + ] + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "The DID of the repo." + } + } + }, + "description": "Get the hosting status for a repository, on this server. Expected to be implemented by PDS and Relay." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/listBlobs.json b/lexicons/com/atproto/sync/listBlobs.json new file mode 100644 index 0000000000..ee98c6ecb6 --- /dev/null +++ b/lexicons/com/atproto/sync/listBlobs.json @@ -0,0 +1,73 @@ +{ + "id": "com.atproto.sync.listBlobs", + "defs": { + "main": { + "type": "query", + "errors": [ + { + "name": "RepoNotFound" + }, + { + "name": "RepoTakendown" + }, + { + "name": "RepoSuspended" + }, + { + "name": "RepoDeactivated" + } + ], + "output": { + "schema": { + "type": "object", + "required": [ + "cids" + ], + "properties": { + "cids": { + "type": "array", + "items": { + "type": "string", + "format": "cid" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "The DID of the repo." + }, + "limit": { + "type": "integer", + "default": 500, + "maximum": 1000, + "minimum": 1 + }, + "since": { + "type": "string", + "format": "tid", + "description": "Optional revision of the repo to list blobs since." + }, + "cursor": { + "type": "string" + } + } + }, + "description": "List blob CIDs for an account, since some repo revision. Does not require auth; implemented by PDS." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/listHosts.json b/lexicons/com/atproto/sync/listHosts.json new file mode 100644 index 0000000000..66f78df6e1 --- /dev/null +++ b/lexicons/com/atproto/sync/listHosts.json @@ -0,0 +1,70 @@ +{ + "id": "com.atproto.sync.listHosts", + "defs": { + "host": { + "type": "object", + "required": [ + "hostname" + ], + "properties": { + "seq": { + "type": "integer", + "description": "Recent repo stream event sequence number. May be delayed from actual stream processing (eg, persisted cursor not in-memory cursor)." + }, + "status": { + "ref": "com.atproto.sync.defs#hostStatus", + "type": "ref" + }, + "hostname": { + "type": "string", + "description": "hostname of server; not a URL (no scheme)" + }, + "accountCount": { + "type": "integer" + } + } + }, + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "hosts" + ], + "properties": { + "hosts": { + "type": "array", + "items": { + "ref": "#host", + "type": "ref" + }, + "description": "Sort order is not formally specified. Recommended order is by time host was first seen by the server, with oldest first." + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 200, + "maximum": 1000, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Enumerates upstream hosts (eg, PDS or relay instances) that this service consumes from. Implemented by relays." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/listRepos.json b/lexicons/com/atproto/sync/listRepos.json new file mode 100644 index 0000000000..0341bccc9d --- /dev/null +++ b/lexicons/com/atproto/sync/listRepos.json @@ -0,0 +1,84 @@ +{ + "id": "com.atproto.sync.listRepos", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "repos" + ], + "properties": { + "repos": { + "type": "array", + "items": { + "ref": "#repo", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "properties": { + "limit": { + "type": "integer", + "default": 500, + "maximum": 1000, + "minimum": 1 + }, + "cursor": { + "type": "string" + } + } + }, + "description": "Enumerates all the DID, rev, and commit CID for all repos hosted by this service. Does not require auth; implemented by PDS and Relay." + }, + "repo": { + "type": "object", + "required": [ + "did", + "head", + "rev" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "rev": { + "type": "string", + "format": "tid" + }, + "head": { + "type": "string", + "format": "cid", + "description": "Current repo commit CID" + }, + "active": { + "type": "boolean" + }, + "status": { + "type": "string", + "description": "If active=false, this optional field indicates a possible reason for why the account is not active. If active=false and no status is supplied, then the host makes no claim for why the repository is no longer being hosted.", + "knownValues": [ + "takendown", + "suspended", + "deleted", + "deactivated", + "desynchronized", + "throttled" + ] + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/listReposByCollection.json b/lexicons/com/atproto/sync/listReposByCollection.json new file mode 100644 index 0000000000..c472ae9102 --- /dev/null +++ b/lexicons/com/atproto/sync/listReposByCollection.json @@ -0,0 +1,66 @@ +{ + "id": "com.atproto.sync.listReposByCollection", + "defs": { + "main": { + "type": "query", + "output": { + "schema": { + "type": "object", + "required": [ + "repos" + ], + "properties": { + "repos": { + "type": "array", + "items": { + "ref": "#repo", + "type": "ref" + } + }, + "cursor": { + "type": "string" + } + } + }, + "encoding": "application/json" + }, + "parameters": { + "type": "params", + "required": [ + "collection" + ], + "properties": { + "limit": { + "type": "integer", + "default": 500, + "maximum": 2000, + "minimum": 1, + "description": "Maximum size of response set. Recommend setting a large maximum (1000+) when enumerating large DID lists." + }, + "cursor": { + "type": "string" + }, + "collection": { + "type": "string", + "format": "nsid" + } + } + }, + "description": "Enumerates all the DIDs which have records with the given collection NSID." + }, + "repo": { + "type": "object", + "required": [ + "did" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/notifyOfUpdate.json b/lexicons/com/atproto/sync/notifyOfUpdate.json new file mode 100644 index 0000000000..aa674c7380 --- /dev/null +++ b/lexicons/com/atproto/sync/notifyOfUpdate.json @@ -0,0 +1,26 @@ +{ + "id": "com.atproto.sync.notifyOfUpdate", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "hostname" + ], + "properties": { + "hostname": { + "type": "string", + "description": "Hostname of the current service (usually a PDS) that is notifying of update." + } + } + }, + "encoding": "application/json" + }, + "description": "Notify a crawling service of a recent update, and that crawling should resume. Intended use is after a gap between repo stream events caused the crawling service to disconnect. Does not require auth; implemented by Relay. DEPRECATED: just use com.atproto.sync.requestCrawl" + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/requestCrawl.json b/lexicons/com/atproto/sync/requestCrawl.json new file mode 100644 index 0000000000..6245282cc8 --- /dev/null +++ b/lexicons/com/atproto/sync/requestCrawl.json @@ -0,0 +1,31 @@ +{ + "id": "com.atproto.sync.requestCrawl", + "defs": { + "main": { + "type": "procedure", + "input": { + "schema": { + "type": "object", + "required": [ + "hostname" + ], + "properties": { + "hostname": { + "type": "string", + "description": "Hostname of the current service (eg, PDS) that is requesting to be crawled." + } + } + }, + "encoding": "application/json" + }, + "errors": [ + { + "name": "HostBanned" + } + ], + "description": "Request a service to persistently crawl hosted repos. Expected use is new PDS instances declaring their existence to Relays. Does not require auth." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/sync/subscribeRepos.json b/lexicons/com/atproto/sync/subscribeRepos.json new file mode 100644 index 0000000000..4408da07e6 --- /dev/null +++ b/lexicons/com/atproto/sync/subscribeRepos.json @@ -0,0 +1,276 @@ +{ + "id": "com.atproto.sync.subscribeRepos", + "defs": { + "info": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "knownValues": [ + "OutdatedCursor" + ] + }, + "message": { + "type": "string" + } + } + }, + "main": { + "type": "subscription", + "errors": [ + { + "name": "FutureCursor" + }, + { + "name": "ConsumerTooSlow", + "description": "If the consumer of the stream can not keep up with events, and a backlog gets too large, the server will drop the connection." + } + ], + "message": { + "schema": { + "refs": [ + "#commit", + "#sync", + "#identity", + "#account", + "#info" + ], + "type": "union" + } + }, + "parameters": { + "type": "params", + "properties": { + "cursor": { + "type": "integer", + "description": "The last known event seq number to backfill from." + } + } + }, + "description": "Repository event stream, aka Firehose endpoint. Outputs repo commits with diff data, and identity update events, for all repositories on the current server. See the atproto specifications for details around stream sequencing, repo versioning, CAR diff format, and more. Public and does not require auth; implemented by PDS and Relay." + }, + "sync": { + "type": "object", + "required": [ + "seq", + "did", + "blocks", + "rev", + "time" + ], + "properties": { + "did": { + "type": "string", + "format": "did", + "description": "The account this repo event corresponds to. Must match that in the commit object." + }, + "rev": { + "type": "string", + "description": "The rev of the commit. This value must match that in the commit object." + }, + "seq": { + "type": "integer", + "description": "The stream sequence number of this message." + }, + "time": { + "type": "string", + "format": "datetime", + "description": "Timestamp of when this message was originally broadcast." + }, + "blocks": { + "type": "bytes", + "maxLength": 10000, + "description": "CAR file containing the commit, as a block. The CAR header must include the commit block CID as the first 'root'." + } + }, + "description": "Updates the repo to a new state, without necessarily including that state on the firehose. Used to recover from broken commit streams, data loss incidents, or in situations where upstream host does not know recent state of the repository." + }, + "commit": { + "type": "object", + "nullable": [ + "since" + ], + "required": [ + "seq", + "rebase", + "tooBig", + "repo", + "commit", + "rev", + "since", + "blocks", + "ops", + "blobs", + "time" + ], + "properties": { + "ops": { + "type": "array", + "items": { + "ref": "#repoOp", + "type": "ref", + "description": "List of repo mutation operations in this commit (eg, records created, updated, or deleted)." + }, + "maxLength": 200 + }, + "rev": { + "type": "string", + "format": "tid", + "description": "The rev of the emitted commit. Note that this information is also in the commit object included in blocks, unless this is a tooBig event." + }, + "seq": { + "type": "integer", + "description": "The stream sequence number of this message." + }, + "repo": { + "type": "string", + "format": "did", + "description": "The repo this event comes from. Note that all other message types name this field 'did'." + }, + "time": { + "type": "string", + "format": "datetime", + "description": "Timestamp of when this message was originally broadcast." + }, + "blobs": { + "type": "array", + "items": { + "type": "cid-link", + "description": "DEPRECATED -- will soon always be empty. List of new blobs (by CID) referenced by records in this commit." + } + }, + "since": { + "type": "string", + "format": "tid", + "description": "The rev of the last emitted commit from this repo (if any)." + }, + "blocks": { + "type": "bytes", + "maxLength": 2000000, + "description": "CAR file containing relevant blocks, as a diff since the previous repo state. The commit must be included as a block, and the commit block CID must be the first entry in the CAR header 'roots' list." + }, + "commit": { + "type": "cid-link", + "description": "Repo commit object CID." + }, + "rebase": { + "type": "boolean", + "description": "DEPRECATED -- unused" + }, + "tooBig": { + "type": "boolean", + "description": "DEPRECATED -- replaced by #sync event and data limits. Indicates that this commit contained too many ops, or data size was too large. Consumers will need to make a separate request to get missing data." + }, + "prevData": { + "type": "cid-link", + "description": "The root CID of the MST tree for the previous commit from this repo (indicated by the 'since' revision field in this message). Corresponds to the 'data' field in the repo commit object. NOTE: this field is effectively required for the 'inductive' version of firehose." + } + }, + "description": "Represents an update of repository state. Note that empty commits are allowed, which include no repo data changes, but an update to rev and signature." + }, + "repoOp": { + "type": "object", + "nullable": [ + "cid" + ], + "required": [ + "action", + "path", + "cid" + ], + "properties": { + "cid": { + "type": "cid-link", + "description": "For creates and updates, the new record CID. For deletions, null." + }, + "path": { + "type": "string" + }, + "prev": { + "type": "cid-link", + "description": "For updates and deletes, the previous record CID (required for inductive firehose). For creations, field should not be defined." + }, + "action": { + "type": "string", + "knownValues": [ + "create", + "update", + "delete" + ] + } + }, + "description": "A repo operation, ie a mutation of a single record." + }, + "account": { + "type": "object", + "required": [ + "seq", + "did", + "time", + "active" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "seq": { + "type": "integer" + }, + "time": { + "type": "string", + "format": "datetime" + }, + "active": { + "type": "boolean", + "description": "Indicates that the account has a repository which can be fetched from the host that emitted this event." + }, + "status": { + "type": "string", + "description": "If active=false, this optional field indicates a reason for why the account is not active.", + "knownValues": [ + "takendown", + "suspended", + "deleted", + "deactivated", + "desynchronized", + "throttled" + ] + } + }, + "description": "Represents a change to an account's status on a host (eg, PDS or Relay). The semantics of this event are that the status is at the host which emitted the event, not necessarily that at the currently active PDS. Eg, a Relay takedown would emit a takedown with active=false, even if the PDS is still active." + }, + "identity": { + "type": "object", + "required": [ + "seq", + "did", + "time" + ], + "properties": { + "did": { + "type": "string", + "format": "did" + }, + "seq": { + "type": "integer" + }, + "time": { + "type": "string", + "format": "datetime" + }, + "handle": { + "type": "string", + "format": "handle", + "description": "The current handle for the account, or 'handle.invalid' if validation fails. This field is optional, might have been validated or passed-through from an upstream source. Semantics and behaviors for PDS vs Relay may evolve in the future; see atproto specs for more details." + } + }, + "description": "Represents a change to an account's identity. Could be an updated handle, signing key, or pds hosting endpoint. Serves as a prod to all downstream services to refresh their identity cache." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/com/atproto/temp/checkHandleAvailability.json b/lexicons/com/atproto/temp/checkHandleAvailability.json new file mode 100644 index 0000000000..ffbdd6f732 --- /dev/null +++ b/lexicons/com/atproto/temp/checkHandleAvailability.json @@ -0,0 +1,88 @@ +{ + "lexicon": 1, + "id": "com.atproto.temp.checkHandleAvailability", + "defs": { + "main": { + "type": "query", + "description": "Checks whether the provided handle is available. If the handle is not available, available suggestions will be returned. Optional inputs will be used to generate suggestions.", + "parameters": { + "type": "params", + "required": ["handle"], + "properties": { + "handle": { + "type": "string", + "format": "handle", + "description": "Tentative handle. Will be checked for availability or used to build handle suggestions." + }, + "email": { + "type": "string", + "description": "User-provided email. Might be used to build handle suggestions." + }, + "birthDate": { + "type": "string", + "format": "datetime", + "description": "User-provided birth date. Might be used to build handle suggestions." + } + } + }, + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["handle", "result"], + "properties": { + "handle": { + "type": "string", + "format": "handle", + "description": "Echo of the input handle." + }, + "result": { + "type": "union", + "refs": ["#resultAvailable", "#resultUnavailable"] + } + } + } + }, + "errors": [ + { + "name": "InvalidEmail", + "description": "An invalid email was provided." + } + ] + }, + "resultAvailable": { + "type": "object", + "description": "Indicates the provided handle is available.", + "properties": {} + }, + "resultUnavailable": { + "type": "object", + "description": "Indicates the provided handle is unavailable and gives suggestions of available handles.", + "required": ["suggestions"], + "properties": { + "suggestions": { + "type": "array", + "description": "List of suggested handles based on the provided inputs.", + "items": { + "type": "ref", + "ref": "#suggestion" + } + } + } + }, + "suggestion": { + "type": "object", + "required": ["handle", "method"], + "properties": { + "handle": { + "type": "string", + "format": "handle" + }, + "method": { + "type": "string", + "description": "Method used to build this suggestion. Should be considered opaque to clients. Can be used for metrics." + } + } + } + } +} diff --git a/lexicons/com/atproto/temp/checkSignupQueue.json b/lexicons/com/atproto/temp/checkSignupQueue.json new file mode 100644 index 0000000000..b7d9e65d7c --- /dev/null +++ b/lexicons/com/atproto/temp/checkSignupQueue.json @@ -0,0 +1,22 @@ +{ + "lexicon": 1, + "id": "com.atproto.temp.checkSignupQueue", + "defs": { + "main": { + "type": "query", + "description": "Check accounts location in signup queue.", + "output": { + "encoding": "application/json", + "schema": { + "type": "object", + "required": ["activated"], + "properties": { + "activated": { "type": "boolean" }, + "placeInQueue": { "type": "integer" }, + "estimatedTimeMs": { "type": "integer" } + } + } + } + } + } +} diff --git a/lexicons/com/germnetwork/declaration.json b/lexicons/com/germnetwork/declaration.json new file mode 100644 index 0000000000..2c9e42486d --- /dev/null +++ b/lexicons/com/germnetwork/declaration.json @@ -0,0 +1,75 @@ +{ + "id": "com.germnetwork.declaration", + "defs": { + "main": { + "key": "literal:self", + "type": "record", + "record": { + "type": "object", + "required": [ + "version", + "currentKey" + ], + "properties": { + "version": { + "type": "string", + "maxLength": 14, + "minLength": 5, + "description": "Semver version number, without pre-release or build information, for the format of opaque content" + }, + "messageMe": { + "ref": "#messageMe", + "type": "ref", + "description": "Controls who can message this account" + }, + "currentKey": { + "type": "bytes", + "description": "Opaque value, an ed25519 public key prefixed with a byte enum" + }, + "keyPackage": { + "type": "bytes", + "description": "Opaque value, contains MLS KeyPackage(s), and other signature data, and is signed by the currentKey" + }, + "continuityProofs": { + "type": "array", + "items": { + "type": "bytes" + }, + "maxLength": 1000, + "description": "Array of opaque values to allow for key rolling" + } + } + }, + "description": "A declaration of a Germ Network account" + }, + "messageMe": { + "type": "object", + "required": [ + "showButtonTo", + "messageMeUrl" + ], + "properties": { + "messageMeUrl": { + "type": "string", + "format": "uri", + "maxLength": 2047, + "minLength": 1, + "description": "A URL to present to an account that does not have its own com.germnetwork.declaration record, must have an empty fragment component, where the app should fill in the fragment component with the DIDs of the two accounts who wish to message each other" + }, + "showButtonTo": { + "type": "string", + "maxLength": 100, + "minLength": 1, + "description": "The policy of who can message the account, this value is included in the keyPackage, but is duplicated here to allow applications to decide if they should show a 'Message on Germ' button to the viewer.", + "knownValues": [ + "none", + "usersIFollow", + "everyone" + ] + } + } + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/lexicons/tools/ozone/report/defs.json b/lexicons/tools/ozone/report/defs.json new file mode 100644 index 0000000000..2742fbd1ff --- /dev/null +++ b/lexicons/tools/ozone/report/defs.json @@ -0,0 +1,212 @@ +{ + "id": "tools.ozone.report.defs", + "defs": { + "reasonType": { + "type": "string", + "knownValues": [ + "tools.ozone.report.defs#reasonAppeal", + "tools.ozone.report.defs#reasonOther", + "tools.ozone.report.defs#reasonViolenceAnimal", + "tools.ozone.report.defs#reasonViolenceThreats", + "tools.ozone.report.defs#reasonViolenceGraphicContent", + "tools.ozone.report.defs#reasonViolenceGlorification", + "tools.ozone.report.defs#reasonViolenceExtremistContent", + "tools.ozone.report.defs#reasonViolenceTrafficking", + "tools.ozone.report.defs#reasonViolenceOther", + "tools.ozone.report.defs#reasonSexualAbuseContent", + "tools.ozone.report.defs#reasonSexualNCII", + "tools.ozone.report.defs#reasonSexualDeepfake", + "tools.ozone.report.defs#reasonSexualAnimal", + "tools.ozone.report.defs#reasonSexualUnlabeled", + "tools.ozone.report.defs#reasonSexualOther", + "tools.ozone.report.defs#reasonChildSafetyCSAM", + "tools.ozone.report.defs#reasonChildSafetyGroom", + "tools.ozone.report.defs#reasonChildSafetyPrivacy", + "tools.ozone.report.defs#reasonChildSafetyHarassment", + "tools.ozone.report.defs#reasonChildSafetyOther", + "tools.ozone.report.defs#reasonHarassmentTroll", + "tools.ozone.report.defs#reasonHarassmentTargeted", + "tools.ozone.report.defs#reasonHarassmentHateSpeech", + "tools.ozone.report.defs#reasonHarassmentDoxxing", + "tools.ozone.report.defs#reasonHarassmentOther", + "tools.ozone.report.defs#reasonMisleadingBot", + "tools.ozone.report.defs#reasonMisleadingImpersonation", + "tools.ozone.report.defs#reasonMisleadingSpam", + "tools.ozone.report.defs#reasonMisleadingScam", + "tools.ozone.report.defs#reasonMisleadingElections", + "tools.ozone.report.defs#reasonMisleadingOther", + "tools.ozone.report.defs#reasonRuleSiteSecurity", + "tools.ozone.report.defs#reasonRuleProhibitedSales", + "tools.ozone.report.defs#reasonRuleBanEvasion", + "tools.ozone.report.defs#reasonRuleOther", + "tools.ozone.report.defs#reasonSelfHarmContent", + "tools.ozone.report.defs#reasonSelfHarmED", + "tools.ozone.report.defs#reasonSelfHarmStunts", + "tools.ozone.report.defs#reasonSelfHarmSubstances", + "tools.ozone.report.defs#reasonSelfHarmOther" + ] + }, + "reasonOther": { + "type": "token", + "description": "An issue not included in these options" + }, + "reasonAppeal": { + "type": "token", + "description": "Appeal a previously taken moderation action" + }, + "reasonRuleOther": { + "type": "token", + "description": "Other" + }, + "reasonSelfHarmED": { + "type": "token", + "description": "Eating disorders" + }, + "reasonSexualNCII": { + "type": "token", + "description": "Non-consensual intimate imagery" + }, + "reasonSexualOther": { + "type": "token", + "description": "Other sexual violence content" + }, + "reasonSexualAnimal": { + "type": "token", + "description": "Animal sexual abuse" + }, + "reasonMisleadingBot": { + "type": "token", + "description": "Fake account or bot" + }, + "reasonSelfHarmOther": { + "type": "token", + "description": "Other dangerous content" + }, + "reasonViolenceOther": { + "type": "token", + "description": "Other violent content" + }, + "reasonMisleadingScam": { + "type": "token", + "description": "Scam" + }, + "reasonMisleadingSpam": { + "type": "token", + "description": "Spam" + }, + "reasonRuleBanEvasion": { + "type": "token", + "description": "Banned user returning" + }, + "reasonSelfHarmStunts": { + "type": "token", + "description": "Dangerous challenges or activities" + }, + "reasonSexualDeepfake": { + "type": "token", + "description": "Deepfake adult content" + }, + "reasonViolenceAnimal": { + "type": "token", + "description": "Animal welfare violations" + }, + "reasonChildSafetyCSAM": { + "type": "token", + "description": "Child sexual abuse material (CSAM). These reports will be sent only be sent to the application's Moderation Authority." + }, + "reasonHarassmentOther": { + "type": "token", + "description": "Other harassing or hateful content" + }, + "reasonHarassmentTroll": { + "type": "token", + "description": "Trolling" + }, + "reasonMisleadingOther": { + "type": "token", + "description": "Other misleading content" + }, + "reasonSelfHarmContent": { + "type": "token", + "description": "Content promoting or depicting self-harm" + }, + "reasonSexualUnlabeled": { + "type": "token", + "description": "Unlabelled adult content" + }, + "reasonViolenceThreats": { + "type": "token", + "description": "Threats or incitement" + }, + "reasonChildSafetyGroom": { + "type": "token", + "description": "Grooming or predatory behavior. These reports will be sent only be sent to the application's Moderation Authority." + }, + "reasonChildSafetyOther": { + "type": "token", + "description": "Other child safety. These reports will be sent only be sent to the application's Moderation Authority." + }, + "reasonRuleSiteSecurity": { + "type": "token", + "description": "Hacking or system attacks" + }, + "reasonHarassmentDoxxing": { + "type": "token", + "description": "Doxxing" + }, + "reasonChildSafetyPrivacy": { + "type": "token", + "description": "Privacy violation involving a minor" + }, + "reasonHarassmentTargeted": { + "type": "token", + "description": "Targeted harassment" + }, + "reasonSelfHarmSubstances": { + "type": "token", + "description": "Dangerous substances or drug abuse" + }, + "reasonSexualAbuseContent": { + "type": "token", + "description": "Adult sexual abuse content" + }, + "reasonMisleadingElections": { + "type": "token", + "description": "False information about elections" + }, + "reasonRuleProhibitedSales": { + "type": "token", + "description": "Promoting or selling prohibited items or services" + }, + "reasonViolenceTrafficking": { + "type": "token", + "description": "Human trafficking" + }, + "reasonHarassmentHateSpeech": { + "type": "token", + "description": "Hate speech" + }, + "reasonChildSafetyHarassment": { + "type": "token", + "description": "Harassment or bullying of minors" + }, + "reasonViolenceGlorification": { + "type": "token", + "description": "Glorification of violence" + }, + "reasonViolenceGraphicContent": { + "type": "token", + "description": "Graphic violent content" + }, + "reasonMisleadingImpersonation": { + "type": "token", + "description": "Impersonation" + }, + "reasonViolenceExtremistContent": { + "type": "token", + "description": "Extremist content. These reports will be sent only be sent to the application's Moderation Authority." + } + }, + "$type": "com.atproto.lexicon.schema", + "lexicon": 1 +} diff --git a/eslint/__disabled_tests__/avoid-unwrapped-text.disabled-test.js b/lint-rules/__disabled_tests__/avoid-unwrapped-text.disabled-test.js similarity index 100% rename from eslint/__disabled_tests__/avoid-unwrapped-text.disabled-test.js rename to lint-rules/__disabled_tests__/avoid-unwrapped-text.disabled-test.js diff --git a/eslint/__disabled_tests__/lingui-msg-rule.disabled-test.js b/lint-rules/__disabled_tests__/lingui-msg-rule.disabled-test.js similarity index 100% rename from eslint/__disabled_tests__/lingui-msg-rule.disabled-test.js rename to lint-rules/__disabled_tests__/lingui-msg-rule.disabled-test.js diff --git a/eslint/__disabled_tests__/note.md b/lint-rules/__disabled_tests__/note.md similarity index 100% rename from eslint/__disabled_tests__/note.md rename to lint-rules/__disabled_tests__/note.md diff --git a/eslint/avoid-unwrapped-text.js b/lint-rules/avoid-unwrapped-text.js similarity index 100% rename from eslint/avoid-unwrapped-text.js rename to lint-rules/avoid-unwrapped-text.js diff --git a/eslint/index.js b/lint-rules/index.js similarity index 85% rename from eslint/index.js rename to lint-rules/index.js index 1df4590245..4af3a9bc73 100644 --- a/eslint/index.js +++ b/lint-rules/index.js @@ -7,7 +7,6 @@ const plugin = { }, rules: { 'avoid-unwrapped-text': require('./avoid-unwrapped-text'), - 'use-exact-imports': require('./use-exact-imports'), 'use-prefixed-imports': require('./use-prefixed-imports'), 'lingui-msg-rule': require('./lingui-msg-rule'), }, diff --git a/eslint/lingui-msg-rule.js b/lint-rules/lingui-msg-rule.js similarity index 100% rename from eslint/lingui-msg-rule.js rename to lint-rules/lingui-msg-rule.js diff --git a/eslint/use-prefixed-imports.js b/lint-rules/use-prefixed-imports.js similarity index 100% rename from eslint/use-prefixed-imports.js rename to lint-rules/use-prefixed-imports.js diff --git a/oxlint-suppressions.json b/oxlint-suppressions.json index ae3513a254..1cb148b151 100644 --- a/oxlint-suppressions.json +++ b/oxlint-suppressions.json @@ -848,9 +848,6 @@ }, "typescript/no-misused-promises": { "count": 1 - }, - "typescript/no-unsafe-member-access": { - "count": 1 } }, "src/screens/E2E/SharedPreferencesTesterScreen.tsx": { @@ -1099,12 +1096,6 @@ }, "typescript/no-misused-promises": { "count": 2 - }, - "typescript/no-unsafe-call": { - "count": 2 - }, - "typescript/no-unsafe-member-access": { - "count": 2 } }, "src/screens/Settings/components/CopyButton.tsx": { @@ -1406,16 +1397,13 @@ }, "src/state/queries/postgate/index.ts": { "typescript/no-explicit-any": { - "count": 2 + "count": 1 }, "typescript/no-floating-promises": { "count": 1 }, - "typescript/no-unsafe-call": { - "count": 2 - }, "typescript/no-unsafe-member-access": { - "count": 3 + "count": 1 }, "typescript/require-await": { "count": 2 @@ -1432,18 +1420,9 @@ } }, "src/state/queries/threadgate/index.ts": { - "typescript/no-explicit-any": { - "count": 1 - }, "typescript/no-floating-promises": { "count": 3 }, - "typescript/no-unsafe-call": { - "count": 2 - }, - "typescript/no-unsafe-member-access": { - "count": 2 - }, "typescript/require-await": { "count": 3 } @@ -1456,11 +1435,6 @@ "count": 1 } }, - "src/state/session/agent.ts": { - "typescript/no-explicit-any": { - "count": 1 - } - }, "src/state/shell/color-mode.tsx": { "typescript/no-floating-promises": { "count": 2 @@ -1671,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 bdc5f67298..75fb7b4d00 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ }, "scripts": { "prepare": "is-ci || husky", - "postinstall": "pnpm intl:compile-if-needed", + "postinstall": "pnpm lexicons:generate && pnpm intl:compile-if-needed", "prebuild": "EXPO_NO_GIT_STATUS=1 expo prebuild --clean", "android": "expo run:android --variant debugOptimized", "android:prod": "expo run:android --variant release", @@ -62,6 +62,10 @@ "lint": "oxlint --quiet src modules", "lint-native": "swiftlint ./modules && ktlint ./modules", "lint-native:fix": "swiftlint --fix ./modules && ktlint --format ./modules", + "lexicons:generate": "lex build --clear --index-file --import-ext ''", + "lexicons:install": "lex install", + "lexicons:update": "lex install --update", + "lexicons:verify": "lex install --ci", "typecheck": "pnpm run typecheck:ios && pnpm run typecheck:android && pnpm run typecheck:web", "typecheck:ios": "tsc --project ./tsconfig.check.ios.json", "typecheck:android": "tsc --project ./tsconfig.check.android.json", @@ -96,9 +100,10 @@ "prettier": "prettier --check ." }, "dependencies": { - "@atproto/api": "0.20.40", - "@atproto/common-web": "0.5.7", - "@atproto/syntax": "0.7.2", + "@atproto/common-web": "0.5.9", + "@atproto/lex": "0.3.6", + "@atproto/lex-password-session": "0.2.0", + "@atproto/syntax": "0.7.4", "@bitdrift/react-native": "^0.6.8", "@braintree/sanitize-url": "^6.0.2", "@bsky.app/alf": "^0.1.15", @@ -113,6 +118,7 @@ "@bsky.app/tapper": "^0.6.1", "@bsky.app/video": "0.3.6", "@bsky.app/video-compressor": "0.2.0", + "@bsky/sdk": "1.0.0", "@emoji-mart/data": "^1.2.1", "@emoji-mart/react": "^1.1.1", "@expo/html-elements": "^0.12.5", @@ -282,9 +288,7 @@ "babel-plugin-module-resolver": "^5.0.2", "babel-plugin-react-compiler": "19.1.0-rc.3", "babel-preset-expo": "~54.0.10", - "eslint-plugin-bsky-internal": "link:eslint", - "eslint-plugin-react-compiler": "19.1.0-rc.2", - "eslint-plugin-react-native": "^5.0.0", + "eslint-plugin-bsky-internal": "link:lint-rules", "eslint-plugin-react-native-a11y": "^3.5.1", "eslint-plugin-simple-import-sort": "^13.0.0", "file-loader": "6.2.0", @@ -297,7 +301,7 @@ "lint-staged": "^17.0.8", "oxlint": "^1.73.0", "oxlint-tsgolint": "^7.0.2001", - "prettier": "^3.8.3", + "prettier": "3.9.6", "react-native-dotenv": "^3.4.11", "react-refresh": "^0.14.0", "svgo": "^4.0.2", @@ -326,14 +330,17 @@ "^multiformats/cid$": "/node_modules/multiformats/dist/src/cid.js", "^multiformats/bases/base32$": "/node_modules/multiformats/dist/src/bases/base32.js", "^multiformats/hashes/digest$": "/node_modules/multiformats/dist/src/hashes/digest.js", + "^multiformats/hashes/hasher$": "/node_modules/multiformats/dist/src/hashes/hasher.js", "^multiformats/hashes/sha2$": "/node_modules/multiformats/dist/src/hashes/sha2.js", "^uint8arrays/from-string$": "/node_modules/uint8arrays/dist/src/from-string.js", "^uint8arrays/to-string$": "/node_modules/uint8arrays/dist/src/to-string.js", "^unicode-segmenter/grapheme$": "/node_modules/unicode-segmenter/grapheme.cjs", - "^await-lock$": "/node_modules/await-lock/build/AwaitLock.js" + "^await-lock$": "/node_modules/await-lock/build/AwaitLock.js", + "^@ipld/dag-cbor$": "/node_modules/@ipld/dag-cbor/src/index.js", + "^cborg$": "/node_modules/cborg/cborg.js" }, "transformIgnorePatterns": [ - "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|nanoid|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|normalize-url|react-native-svg|@sentry/.*|sentry-expo|bcp-47-match|@atproto/.*|multiformats|uint8arrays|@ipld/.*|cborg|await-lock)" + "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|nanoid|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|normalize-url|react-native-svg|@sentry/.*|sentry-expo|bcp-47-match|@atproto/.*|@bsky/sdk|tlds|multiformats|uint8arrays|@ipld/.*|cborg|await-lock)" ], "modulePathIgnorePatterns": [ "__tests__/.*/__mocks__", @@ -367,7 +374,7 @@ }, "lint-staged": { "*{.js,.jsx,.ts,.tsx}": [ - "oxlint --fix" + "oxlint --fix --no-error-on-unmatched-pattern" ], "*{.js,.jsx,.ts,.tsx,.css}": [ "prettier --cache --write --ignore-unknown" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2bf710488..1df7fbd2f3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -215,6 +215,8 @@ overrides: '@types/psl': 1.1.1 react-native-screens: 4.24.0 +packageExtensionsChecksum: sha256-KbeCj471Qk3TA758q8nLPkiGCPDA0TX18iO/Coy89lo= + patchedDependencies: '@sentry/expo-upload-sourcemaps@8.18.0': 2368e1e1986165e7ead3c60017b78b2f9ade1b6a6b2f5ad27cd428608c6f12ae expo-age-range@0.2.18: c325225749993424461eeece1d97a99b19c00da2ebc958a73c12e4eca0c39306 @@ -242,15 +244,18 @@ importers: .: dependencies: - '@atproto/api': - specifier: 0.20.40 - version: 0.20.40 '@atproto/common-web': - specifier: 0.5.7 - version: 0.5.7 + specifier: 0.5.9 + version: 0.5.9 + '@atproto/lex': + specifier: 0.3.6 + version: 0.3.6 + '@atproto/lex-password-session': + specifier: 0.2.0 + version: 0.2.0 '@atproto/syntax': - specifier: 0.7.2 - version: 0.7.2 + specifier: 0.7.4 + version: 0.7.4 '@bitdrift/react-native': specifier: ^0.6.8 version: 0.6.14(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0)(supports-color@8.1.1) @@ -293,6 +298,9 @@ importers: '@bsky.app/video-compressor': specifier: 0.2.0 version: 0.2.0(83e0f64f4281e4798012214ad5b2d72d) + '@bsky/sdk': + specifier: 1.0.0 + version: 1.0.0(@atproto/lex@0.3.6) '@emoji-mart/data': specifier: ^1.2.1 version: 1.2.1 @@ -466,10 +474,10 @@ importers: version: 8.0.10(expo@54.0.35(@babel/core@7.29.0(supports-color@8.1.1))(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0)(supports-color@8.1.1)) expo-file-system: specifier: ~19.0.21 - version: 19.0.22(83f21d12a6c1aa99b4a65d070875cc68) + version: 19.0.23(83f21d12a6c1aa99b4a65d070875cc68) expo-font: specifier: ~14.0.11 - version: 14.0.11(83e0f64f4281e4798012214ad5b2d72d) + version: 14.0.12(83e0f64f4281e4798012214ad5b2d72d) expo-glass-effect: specifier: 0.1.10 version: 0.1.10(83e0f64f4281e4798012214ad5b2d72d) @@ -794,16 +802,10 @@ importers: version: 19.1.0-rc.3 babel-preset-expo: specifier: ~54.0.10 - version: 54.0.10(@babel/core@7.29.0(supports-color@8.1.1))(@babel/runtime@7.29.2)(expo@54.0.35(@babel/core@7.29.0(supports-color@8.1.1))(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0)(supports-color@8.1.1))(react-refresh@0.14.2)(supports-color@8.1.1) + version: 54.0.12(@babel/core@7.29.0(supports-color@8.1.1))(@babel/runtime@7.29.2)(expo@54.0.35(@babel/core@7.29.0(supports-color@8.1.1))(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0)(supports-color@8.1.1))(react-refresh@0.14.2)(supports-color@8.1.1) eslint-plugin-bsky-internal: - specifier: link:eslint - version: link:eslint - eslint-plugin-react-compiler: - specifier: 19.1.0-rc.2 - version: 19.1.0-rc.2(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1) - eslint-plugin-react-native: - specifier: ^5.0.0 - version: 5.0.0(eslint@8.57.1(supports-color@8.1.1)) + specifier: link:lint-rules + version: link:lint-rules eslint-plugin-react-native-a11y: specifier: ^3.5.1 version: 3.5.1(eslint@8.57.1(supports-color@8.1.1)) @@ -836,7 +838,7 @@ importers: version: 17.0.8 node: specifier: runtime:^24.18.0 - version: runtime:24.18.0 + version: runtime:24.19.0 oxlint: specifier: ^1.73.0 version: 1.73.0(oxlint-tsgolint@7.0.2001) @@ -844,8 +846,8 @@ importers: specifier: ^7.0.2001 version: 7.0.2001 prettier: - specifier: ^3.8.3 - version: 3.8.3 + specifier: 3.9.6 + version: 3.9.6 react-native-dotenv: specifier: ^3.4.11 version: 3.4.11(@babel/runtime@7.29.2) @@ -875,50 +877,99 @@ packages: graphql: optional: true - '@atproto/api@0.20.40': - resolution: {integrity: sha512-8ClSOeFEhtwLrE7G6eFOdLJLtu5K0A/joYRd+urHS3OZG2uiBrEoIU2JbYcPZjEJ5xtWMo9VJiKV5zPSH7rP3Q==} + '@atproto-labs/did-resolver@0.3.7': + resolution: {integrity: sha512-F3M3U5cU1QSAXX3J5auGEc5N2pgDgpirAjvyDFsytXuyWfrjWfTPd/H+DZrrED7gK+noW0cAWtxxjdX7/cTSVQ==} engines: {node: '>=22'} - '@atproto/common-web@0.5.7': - resolution: {integrity: sha512-lvU8lFO5UByacsl3AReXkpTWjcPBS8mBqt40B45HwCt+s58a+xT/YI/+AUpmzSmsPVZCpeCmP6wgsOSC8RILXg==} + '@atproto-labs/fetch@0.3.5': + resolution: {integrity: sha512-iDFZEoNqfL17EVKE+uNzdUD7YF8LWhEhxeBtP6x+PNuAxoXv3ip9vLmB8nNzNxFwVn++FipfDtSiPqmziv1bdA==} + engines: {node: '>=22'} + + '@atproto-labs/handle-resolver@0.4.8': + resolution: {integrity: sha512-38I+j8Efs+cCgW/NX9RFKKu7qv/AF+FqxlKS8sWjXlNZSHQZqPj5cnKVQhSsJCs4ypPB84iKch8w4/STd33bLg==} + engines: {node: '>=22'} + + '@atproto-labs/pipe@0.2.4': + resolution: {integrity: sha512-n67jCcrC+ouAeO10cWkpPzzLMlDi/lDCU30Us+LGqhOPhT6c4t5ASdBLQi9W3jUQtRzQBt3G9zipF+xKWNvVbw==} + engines: {node: '>=22'} + + '@atproto-labs/simple-store-memory@0.2.6': + resolution: {integrity: sha512-DD1v7MEfYF3BAcEpMTTpzfBLWLoI2HuyBhku2YpjHoqPrRrhCtE1alkxfPHjtjJVIjuU/XNU0cF/wB3+5FiKsA==} + engines: {node: '>=22'} + + '@atproto-labs/simple-store@0.5.1': + resolution: {integrity: sha512-vfvoDhu6ds6BT3Pqe+d2/LBU1WpDRtt69y6zltlfMCoucPm82m1j50/Wb6xTvv+oZ+2j0JyZVXsmXQ12SWYQJg==} engines: {node: '>=22'} '@atproto/common-web@0.5.9': resolution: {integrity: sha512-2c5C6YV8352JJz9Z5fNfcsstllaKGAb3cQW9aO7unMAGh/FzTGFq+xOwNMW2lTeagO6z530uxYm8AiSRM6O+DQ==} engines: {node: '>=22'} - '@atproto/lex-data@0.1.6': - resolution: {integrity: sha512-rvovRrYWDEuerX+ZeQbdI5dKs0bYvuMjhwDOtT+pkOvdY4MkfN41DJCz/RAYVAQ7noPdaalB6pAX9euukheE/w==} + '@atproto/common@0.8.0': + resolution: {integrity: sha512-fBtd08LdouLqNaL8SaGLEIa0l3Ue27yR810jhUu+iBv/xdoXp+1G0pxvPe6JUStH7YGtf/v/v0w/cRGh1xUOaA==} + engines: {node: '>=22'} + + '@atproto/crypto@0.5.4': + resolution: {integrity: sha512-UR0BkuYNYuFtw+dA+y/oPPxzX0SWRnGJ+1Cfh/jGP1BvjRUyezK3omjpeLms5fYrXbM9vnfX+ckJFJqkBgLOdw==} + engines: {node: '>=22'} + + '@atproto/did@0.5.4': + resolution: {integrity: sha512-BlnwQ+obL+4ZA71KH/EzZ3TY+cpSxnLiUI85mjBJIQwvDF/oN2sQA18wIj9jpduviIt2b/cMtlJuzHjzBkfXvw==} + engines: {node: '>=22'} + + '@atproto/lex-builder@0.1.12': + resolution: {integrity: sha512-uo3mcdoOgqBfQnf+PhYYLNEBdUTgoNe2ARYb3cUaiec8O0oritOeZndUDgmths06ACrlqFkZNTV1YJX6CsH71g==} + engines: {node: '>=22'} + + '@atproto/lex-cbor@0.1.6': + resolution: {integrity: sha512-PxLQy7jzV8u9zTGURNF8ZczLPk+ZH+WNx/2z/z7RacJbu7sWNpx26U2pC7cIkWtt4jyDaruaLhssw3w3atLotA==} + engines: {node: '>=22'} + + '@atproto/lex-client@0.3.3': + resolution: {integrity: sha512-sWChe7Jukm9uxDfxT6ElICp6vMTqAFmwcHdSLdMDtsKBZSrwO7vWL9F6rpoMPyxS29Ob9nNAhMg30kKfZrb+uw==} engines: {node: '>=22'} '@atproto/lex-data@0.1.7': resolution: {integrity: sha512-kW/dPLqo/WgCLV+XESR4JKwV6c1rZWJGOfuPupZGTjEDAKoBbKXdaEzX9/1vKQYbZ9U3j0DS/n7OFFK7wBugyQ==} engines: {node: '>=22'} - '@atproto/lex-json@0.1.5': - resolution: {integrity: sha512-qOp6+Za5p/xDXACYIP3vySbKaf9CJDwiZEN78ghPT1gRoJohKG3ONt/WmvOxBVJqc6VTRaqEpbG9vX9oLF2qsA==} + '@atproto/lex-document@0.1.9': + resolution: {integrity: sha512-TmpwttQ9pKCXlvwUlJ/5HMZPtJMFBvgZ1bunEeQD5JfU6n/sgK/fob8tfjhTdL2QrpZMtM6opMEk3w5STm1RDQ==} + engines: {node: '>=22'} + + '@atproto/lex-installer@0.1.15': + resolution: {integrity: sha512-4/xDyY7yxxs66vF0WZB+7fcuEy4I5IRDyDoQFzLQx7C6PlQ76/e/PxvYTff7x0/N6hknmOlANODnnxo3rtdnCA==} engines: {node: '>=22'} '@atproto/lex-json@0.1.6': resolution: {integrity: sha512-mvrAd0lbyuecIHjyld8QN6MN6CBf4j0GCxLzegsvLh0SvDf+GbYWklkcQqmITL44yFQOwmA/QNIQj0Uvh7+R/g==} engines: {node: '>=22'} - '@atproto/lexicon@0.7.11': - resolution: {integrity: sha512-ukCZMCWmiu7fAlssBbLs+IMaf2K2R9mADaTKz2jEeC/xeZ3jT77Ka0S2G+ELBB+Dc9ijBACU0iPMQLhquOkuzQ==} + '@atproto/lex-password-session@0.2.0': + resolution: {integrity: sha512-u958Etax/bPVr9FWko+/B5nRcU1klQHvrdiVtYb4lkgrM6uvV7BvaFJZYEu5DzlEeqNAZ9a65PmbC1F5oyUBjQ==} engines: {node: '>=22'} - '@atproto/syntax@0.7.2': - resolution: {integrity: sha512-tZ1Tr0R9pK4bI4Zs69t29cjMlCFQvRNBeNkqJC5pGeNuCt64D0eoF7s/AlqeVmYppClmQ0xJquggGgsMDP6j7w==} + '@atproto/lex-resolver@0.2.8': + resolution: {integrity: sha512-j3dIxeEAStp/RzJS4MECRDPuOFX41mX9fnteGgrQhe9053eRuTnl2oTyJbV7rKgzy+hEI4jH6X6Y4wOStjUb/Q==} + engines: {node: '>=22'} + + '@atproto/lex-schema@0.2.5': + resolution: {integrity: sha512-/tP3dRqaZu0EIoq0BSVzhvFGL6u0uu2KFDV21i73DpFsg6TXOTddKJ9bvgK83XdAJYL7PWi703XsaR31v9u5Qg==} + engines: {node: '>=22'} + + '@atproto/lex@0.3.6': + resolution: {integrity: sha512-8Hpc6MbfkPJKaO5CvvIerEylbihI5GfrY5DzapZD4rV2TWfm3RU+DzeRpsxQJ0W9WOZRYvp1JnBcq/gZ48Qb0g==} + engines: {node: '>=22'} + hasBin: true + + '@atproto/repo@0.10.11': + resolution: {integrity: sha512-QqDzEv8d6NUuqZ0xvWG+n8DfWd4Vuka04nyuD9oglKsj5avB1zbEsWx3Oit1oYtZFUf9JSVWMDqMXhu3UbB+lA==} engines: {node: '>=22'} '@atproto/syntax@0.7.4': 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==} @@ -1082,13 +1133,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-private-methods@7.18.6': - resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} - engines: {node: '>=6.9.0'} - deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead. - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} engines: {node: '>=6.9.0'} @@ -1716,6 +1760,12 @@ packages: react: '*' react-native: '*' + '@bsky/sdk@1.0.0': + resolution: {integrity: sha512-nnVYpbESETN5R/dyBLWE+5u52HkJO0+EWMv+bLDRZUkHEQtHV2RX3kLMqXaEWVJgijagziLPXb6179mkml8WZA==} + engines: {node: '>=22.12.0'} + peerDependencies: + '@atproto/lex': ^0.3.0 + '@crowdin/cli@4.14.2': resolution: {integrity: sha512-JRrHvitc0w+r/h6XXSOq917HxsjRoxPEtyNYSmylKirjiPNrRx84VKtqFlOIWIFCOZI5BoXg6hNpE0Zq7CwVBA==} hasBin: true @@ -1969,9 +2019,6 @@ packages: '@expo/image-utils@0.8.12': resolution: {integrity: sha512-3KguH7kyKqq7pNwLb9j6BBdD/bjmNwXZG/HPWT6GWIXbwrvAJt2JNyYTP5agWJ8jbbuys1yuCzmkX+TU6rmI7A==} - '@expo/json-file@10.0.14': - resolution: {integrity: sha512-yWwBFywFv+SxkJp/pIzzA416JVYflNUh7pqQzgaA6nXDqRyK7KfrqVzk8PdUfDnqbBcaZZxpzNssfQZzp5KHrA==} - '@expo/json-file@10.0.16': resolution: {integrity: sha512-fcVkWEj+hLuP2yt5W0aw6LmDRqSPWDLUSxOMcmFeV+algmIF59sQVKCwB9btjQLd4V6x9N0pISkQEkBubUHrCw==} @@ -2002,9 +2049,6 @@ packages: '@expo/plist@0.3.5': resolution: {integrity: sha512-9RYVU1iGyCJ7vWfg3e7c/NVyMFs8wbl+dMWZphtFtsqyN9zppGREU3ctlD3i8KUE0sCUTVnLjCWr+VeUIDep2g==} - '@expo/plist@0.4.8': - resolution: {integrity: sha512-pfNtErGGzzRwHP+5+RqswzPDKkZrx+Cli0mzjQaus1ZWFsog5ibL+nVT3NcporW51o8ggnt7x813vtRbPiyOrQ==} - '@expo/plist@0.4.9': resolution: {integrity: sha512-MPVpmKGfnQEnrCzgxuXcmPP/y/t6AVm+DcSb2Myp21LKWv1N3l8uFxMggesfF4ixAxkRlGmMMx9GyDC9M+XklQ==} @@ -2318,6 +2362,14 @@ packages: '@messageformat/parser@5.1.1': resolution: {integrity: sha512-3p0YRGCcTUCYvBKLIxtDDyrJ0YijGIwrTRu1DT8gIviIDZru8H23+FkY6MJBzM1n9n20CiM4VeDYuBsrrwnLjg==} + '@noble/curves@1.9.7': + resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} + engines: {node: ^14.21.3 || >=16} + + '@noble/hashes@1.8.0': + resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} + engines: {node: ^14.21.3 || >=16} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2482,6 +2534,9 @@ packages: cpu: [x64] os: [win32] + '@pinojs/redact@0.4.0': + resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -3531,6 +3586,9 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@tanstack/query-async-storage-persister@5.100.10': resolution: {integrity: sha512-NbxsCI9qyMbEooFnFsAR2dbVQ6cmxZpOWSjLFy62qfMkxXnNjkskqex7YgtzWSaD/ozOOLgrbDiyYWEPYKmMyQ==} @@ -3640,6 +3698,9 @@ packages: resolution: {integrity: sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==} engines: {node: '>= 10'} + '@ts-morph/common@0.28.1': + resolution: {integrity: sha512-W74iWf7ILp1ZKNYXY5qbddNaml7e9Sedv5lvU1V8lftlitkc9Pq1A+jlH23ltDgWYeZFFEqGCD1Ies9hqu3O+g==} + '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -4003,10 +4064,12 @@ packages: '@xmldom/xmldom@0.8.13': resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} engines: {node: '>=10.0.0'} + deprecated: this version has critical issues, please update to the latest version '@xmldom/xmldom@0.9.10': resolution: {integrity: sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==} engines: {node: '>=14.6'} + deprecated: this version has critical issues, please update to the latest version '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -4223,13 +4286,14 @@ packages: asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + available-typed-arrays@1.0.7: 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} @@ -4297,18 +4361,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 || ^8.0.0-0 - babel-preset-expo@54.0.10: - resolution: {integrity: sha512-wTt7POavLFypLcPW/uC5v8y+mtQKDJiyGLzYCjqr9tx0Qc3vCXcDKk1iCFIj/++Iy5CWhhTflEa7VvVPNWeCfw==} - peerDependencies: - '@babel/runtime': ^7.20.0 - expo: '*' - react-refresh: '>=0.14.0 <1.0.0' - peerDependenciesMeta: - '@babel/runtime': - optional: true - expo: - optional: true - babel-preset-expo@54.0.12: resolution: {integrity: sha512-6xeSkdaixmQhWSYL7tfLu0pOS0BY+8ftwmdNSHtpEFSizrXYZkCjk/B6Dxr+6nwNRihixMcS0aBlWS1wlDl3pw==} peerDependencies: @@ -4465,6 +4517,10 @@ packages: caniuse-lite@1.0.30001792: resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} + cborg@4.5.8: + resolution: {integrity: sha512-6/viltD51JklRhq4L7jC3zgy6gryuG5xfZ3kzpE+PravtyeQLeQmCYLREhQH7pWENg5pY4Yu/XCd6a7dKScVlw==} + hasBin: true + cborg@5.1.1: resolution: {integrity: sha512-BDbSRIp6XrQXkTc7g+DN0RB9RrDPTUfals2ecWUlt3juPLjbAvy/V72mJcXY0Ehu0Dq/3WpNCOCT68HUTbW+lw==} hasBin: true @@ -4568,6 +4624,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -4576,6 +4636,9 @@ packages: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + code-block-writer@13.0.3: + resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} + collect-v8-coverage@1.0.3: resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} @@ -4699,6 +4762,9 @@ packages: core-js-pure@3.49.0: resolution: {integrity: sha512-XM4RFka59xATyJv/cS3O3Kml72hQXUeGRuuTmMYFxwzc9/7C8OYTaIR/Ji+Yt8DXzsFLNhat15cE/JP15HrCgw==} + core-js@3.50.0: + resolution: {integrity: sha512-BRWgOLKkFeCgRudR6zrs8p9XJZcE14grzKMMssoYrk6krtuEZ7MTKPIY5RzOnqsEKIR9kst7wNzphttraT+Yqw==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -5181,30 +5247,22 @@ packages: engines: {node: '>=6.0'} hasBin: true - eslint-plugin-react-compiler@19.1.0-rc.2: - resolution: {integrity: sha512-oKalwDGcD+RX9mf3NEO4zOoUMeLvjSvcbbEOpquzmzqEEM2MQdp7/FY/Hx9NzmUwFzH1W9SKTz5fihfMldpEYw==} - engines: {node: ^14.17.0 || ^16.0.0 || >= 18.0.0} - peerDependencies: - eslint: '>=7' - eslint-plugin-react-native-a11y@3.5.1: resolution: {integrity: sha512-vqnXZpAiov0lxYNfEYgwABpkiBYRrt0dbtOafPkw6QaFeA0uZ+s3w9opeEMoFmV36WFxLiCxHb9fvOJ+EUc2xQ==} engines: {node: '>=12.0'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - - eslint-plugin-react-native-globals@0.1.2: - resolution: {integrity: sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g==} - - eslint-plugin-react-native@5.0.0: - resolution: {integrity: sha512-VyWlyCC/7FC/aONibOwLkzmyKg4j9oI8fzrk9WYNs4I8/m436JuOTAFwLvEn1CVvc7La4cPfbCyspP4OYpP52Q==} - peerDependencies: - eslint: ^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + peerDependenciesMeta: + eslint: + optional: true eslint-plugin-simple-import-sort@13.0.0: resolution: {integrity: sha512-McAc+/Nlvcg4byY/CABGH8kqnefWBj8s3JA2okEtz8ixbECQgU46p0HkTUKa4YS7wvgGceimlc34p1nXqbWqtA==} peerDependencies: eslint: '>=5.0.0' + peerDependenciesMeta: + eslint: + optional: true eslint-scope@5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} @@ -5380,25 +5438,12 @@ packages: expo-eas-client@1.0.8: resolution: {integrity: sha512-5or11NJhSeDoHHI6zyvQDW2cz/yFyE+1Cz8NTs5NK8JzC7J0JrkUgptWtxyfB6Xs/21YRNifd3qgbBN3hfKVgA==} - expo-file-system@19.0.22: - resolution: {integrity: sha512-l9pgahSc7sJD0bP9vBNeXvZjy8QKDpVHVxWmei/ESQOrzmoj5BidziqLVsyZdxsi+PfdbTtttLTAmddH/JafYA==} - peerDependencies: - expo: '*' - react-native: '*' - expo-file-system@19.0.23: resolution: {integrity: sha512-MeGkid9OeNILfT/qonaXHp4f2c15xaB28U/bcN7pqZej0Kx0+6+V7e9ZIXpPHm07zVatxA+QkMTPQEGfmvVOxA==} peerDependencies: expo: '*' react-native: '*' - expo-font@14.0.11: - resolution: {integrity: sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==} - peerDependencies: - expo: '*' - react: '*' - react-native: '*' - expo-font@14.0.12: resolution: {integrity: sha512-QQzunE2Mxk45AsCWm3tK7OpVljbtVnKD58q4/qliev+cbye1IOduUnRIdD+P7DyButw17G9MTX795kgaQiz5hQ==} peerDependencies: @@ -5948,9 +5993,6 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true - hermes-estree@0.25.1: - resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} - hermes-estree@0.29.1: resolution: {integrity: sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ==} @@ -5963,9 +6005,6 @@ packages: hermes-estree@0.36.0: resolution: {integrity: sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==} - hermes-parser@0.25.1: - resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - hermes-parser@0.29.1: resolution: {integrity: sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA==} @@ -7210,7 +7249,7 @@ packages: node-releases@2.0.44: resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} - node@runtime:24.18.0: + node@runtime:24.19.0: resolution: type: variations variants: @@ -7218,9 +7257,9 @@ packages: archive: tarball bin: node: bin/node - integrity: sha256-ZGOiNzn2sjAOvsXM1gJ14TjjhY/Br8bo+bxOT2pLdvo= + integrity: sha256-9ONcExZd5ogMqiVYwKpIyoitpH/iI0vtB9Ztu4CkfI0= type: binary - url: https://nodejs.org/download/release/v24.18.0/node-v24.18.0-aix-ppc64.tar.gz + url: https://nodejs.org/download/release/v24.19.0/node-v24.19.0-aix-ppc64.tar.gz targets: - cpu: ppc64 os: aix @@ -7228,9 +7267,9 @@ packages: archive: tarball bin: node: bin/node - integrity: sha256-4al+FMmcgD6WxzOUAyguoFpJnDL42D3v6e9exm+XntE= + integrity: sha256-gpS3qpsDmXSBwGur8eiycMhZNY8n2lehFQmv5TesOB0= type: binary - url: https://nodejs.org/download/release/v24.18.0/node-v24.18.0-darwin-arm64.tar.gz + url: https://nodejs.org/download/release/v24.19.0/node-v24.19.0-darwin-arm64.tar.gz targets: - cpu: arm64 os: darwin @@ -7238,9 +7277,9 @@ packages: archive: tarball bin: node: bin/node - integrity: sha256-39Db0+chUDQ033tyBecZ9hs6OjGyvPlym4uR/qJA8IA= + integrity: sha256-0bXpmdsVjGL+j3JnpEdrA12L2TsaYFusJKPw3RZuMxY= type: binary - url: https://nodejs.org/download/release/v24.18.0/node-v24.18.0-darwin-x64.tar.gz + url: https://nodejs.org/download/release/v24.19.0/node-v24.19.0-darwin-x64.tar.gz targets: - cpu: x64 os: darwin @@ -7248,9 +7287,9 @@ packages: archive: tarball bin: node: bin/node - integrity: sha256-a0SEwhkCdBdd+aqPKOLXWKgZyxwf5qtIHi+VtGOrhQg= + integrity: sha256-0oyKW/CoCPDtQ0odzoxUrpjwNxwL2GrFirxhP3PmZD8= type: binary - url: https://nodejs.org/download/release/v24.18.0/node-v24.18.0-linux-arm64.tar.gz + url: https://nodejs.org/download/release/v24.19.0/node-v24.19.0-linux-arm64.tar.gz targets: - cpu: arm64 os: linux @@ -7258,9 +7297,9 @@ packages: archive: tarball bin: node: bin/node - integrity: sha256-/hM4ly95KDxrwh5h2/RXa76MBa3tKZnUHIZDrTAmUUI= + integrity: sha256-sxqMTLxYn5FS8y/bQxhGl1nhZViltdk7f/L7KHsBPbI= type: binary - url: https://nodejs.org/download/release/v24.18.0/node-v24.18.0-linux-ppc64le.tar.gz + url: https://nodejs.org/download/release/v24.19.0/node-v24.19.0-linux-ppc64le.tar.gz targets: - cpu: ppc64le os: linux @@ -7268,9 +7307,9 @@ packages: archive: tarball bin: node: bin/node - integrity: sha256-Nx68E5RfwWlJPnUvLdr6+rbs2My0Ubz/RuCfacXdjHo= + integrity: sha256-s6tqQ5KCjbn9TtloOY1VGIKSvMFYGVQtU74LEQDjMvw= type: binary - url: https://nodejs.org/download/release/v24.18.0/node-v24.18.0-linux-s390x.tar.gz + url: https://nodejs.org/download/release/v24.19.0/node-v24.19.0-linux-s390x.tar.gz targets: - cpu: s390x os: linux @@ -7278,9 +7317,9 @@ packages: archive: tarball bin: node: bin/node - integrity: sha256-eDEwmElj23upy9AQierywu+wVcfBaTyUMXS5Z7MFDLg= + integrity: sha256-9iXZfNcH30/5YlSRb7xf8BTwnAnv/loeDKj21BqHidQ= type: binary - url: https://nodejs.org/download/release/v24.18.0/node-v24.18.0-linux-x64.tar.gz + url: https://nodejs.org/download/release/v24.19.0/node-v24.19.0-linux-x64.tar.gz targets: - cpu: x64 os: linux @@ -7288,10 +7327,10 @@ packages: archive: zip bin: node: node.exe - integrity: sha256-8nRmmtuTsf0Pv48h/QeGCencyEMz1PJxjS3eP5oWGgE= - prefix: node-v24.18.0-win-arm64 + integrity: sha256-hQL0pQtFjUzDjtjyABVWws0jnUZJIPdAF5Jsyx4cFX8= + prefix: node-v24.19.0-win-arm64 type: binary - url: https://nodejs.org/download/release/v24.18.0/node-v24.18.0-win-arm64.zip + url: https://nodejs.org/download/release/v24.19.0/node-v24.19.0-win-arm64.zip targets: - cpu: arm64 os: win32 @@ -7299,10 +7338,10 @@ packages: archive: zip bin: node: node.exe - integrity: sha256-CuaEBrQtdyVmHal5sUA+yZJtogXGdwgn8zqsnY8m6CE= - prefix: node-v24.18.0-win-x64 + integrity: sha256-V/cas2UueX2ErN3HnIHMn/HG3bKhl0zbg/AP7pv/THM= + prefix: node-v24.19.0-win-x64 type: binary - url: https://nodejs.org/download/release/v24.18.0/node-v24.18.0-win-x64.zip + url: https://nodejs.org/download/release/v24.19.0/node-v24.19.0-win-x64.zip targets: - cpu: x64 os: win32 @@ -7310,9 +7349,9 @@ packages: archive: tarball bin: node: bin/node - integrity: sha256-scbC3DG0bdj7IyL0/nWwfndcUSC8NyUd7uoo9SnUVns= + integrity: sha256-IIJOTTWUj65bM33M70eBOwTYmVMS9Z33OG8iVtn5q34= type: binary - url: https://unofficial-builds.nodejs.org/download/release/v24.18.0/node-v24.18.0-linux-arm64-musl.tar.gz + url: https://unofficial-builds.nodejs.org/download/release/v24.19.0/node-v24.19.0-linux-arm64-musl.tar.gz targets: - cpu: arm64 os: linux @@ -7321,14 +7360,14 @@ packages: archive: tarball bin: node: bin/node - integrity: sha256-6lhAmRHhQexrGdkXjvotkYWhMpUAWxy/VSGzFX7tHZU= + integrity: sha256-xgIjeG3xSl0j4iDruOYDGPUyJkCmL5Dm2eVNOhjaUy4= type: binary - url: https://unofficial-builds.nodejs.org/download/release/v24.18.0/node-v24.18.0-linux-x64-musl.tar.gz + url: https://unofficial-builds.nodejs.org/download/release/v24.19.0/node-v24.19.0-linux-x64-musl.tar.gz targets: - cpu: x64 os: linux libc: musl - version: 24.18.0 + version: 24.19.0 hasBin: true normalize-path@3.0.0: @@ -7398,6 +7437,10 @@ packages: obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + on-exit-leak-free@2.1.2: + resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} + engines: {node: '>=14.0.0'} + on-finished@2.3.0: resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} engines: {node: '>= 0.8'} @@ -7533,6 +7576,9 @@ packages: pascal-case@3.1.2: resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -7600,6 +7646,16 @@ packages: resolution: {integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==} engines: {node: '>=0.10.0'} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} + + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} + + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} + hasBin: true + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -7839,8 +7895,8 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.8.3: - resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} engines: {node: '>=14'} hasBin: true @@ -7866,6 +7922,9 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process-warning@5.1.0: + resolution: {integrity: sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==} + progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -7993,6 +8052,9 @@ packages: queue@6.0.2: resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + radix-ui@1.4.3: resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==} peerDependencies: @@ -8294,6 +8356,13 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + real-require@0.2.0: + resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} + engines: {node: '>= 12.13.0'} + + real-require@1.0.0: + resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==} + redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} @@ -8449,6 +8518,10 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -8625,6 +8698,9 @@ packages: sockjs@0.3.24: resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} + sonner-native@0.26.4: resolution: {integrity: sha512-wBE/pctNfz+ieXjpYX7uu5hH38ws8xRmH0AFmEdEgkOS363k6N8QfFoyjGqDmdUDy15m4AAvOjd2Wz/jATukIA==} peerDependencies: @@ -8689,6 +8765,10 @@ packages: resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} engines: {node: '>=6'} + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -8947,6 +9027,10 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + thread-stream@4.2.0: + resolution: {integrity: sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==} + engines: {node: '>=20'} + threads@1.7.0: resolution: {integrity: sha512-Mx5NBSHX3sQYR6iI9VYbgHKBLisyB+xROCBGjjWm1O9wb9vfLxdaGtmT/KCjUqMsSNW6nERzCW3T6H43LqjDZQ==} @@ -9013,6 +9097,9 @@ packages: ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + ts-morph@27.0.2: + resolution: {integrity: sha512-fhUhgeljcrdZ+9DZND1De1029PrE+cMkIP7ooqkLRTrRLTqcki2AstsyJm0vRNbTbVCNJ0idGlbBrfqc7/nA8w==} + ts-plugin-sort-import-suggestions@1.0.4: resolution: {integrity: sha512-85n5lm2OQQ+b7aRNK9omU1gmjMNXRsgeLwojm5u4OSY5sVBkAHTcgMQPEeHMNlyyfFW0uXnwgqAU0pNfhD96Bw==} @@ -9084,6 +9171,9 @@ packages: uc.micro@2.1.0: resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==} + uint8arrays@5.1.1: + resolution: {integrity: sha512-9muQwa4wZG4dKi9gMAIBtnk2Pw87SRpvWTH6lOGm19V2Uqxr4uomUf2PGqPnWc+qs06sN8owUU4jfcoWOcfwVQ==} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -9240,6 +9330,9 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + varint@6.0.0: + resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==} + vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -9516,6 +9609,10 @@ packages: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yargs@15.4.1: resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} engines: {node: '>=8'} @@ -9524,6 +9621,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yargs@18.1.0: + resolution: {integrity: sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + yauzl@3.3.0: resolution: {integrity: sha512-PtGEvEP30p7sbIBJKUBjUnqgTVOyMURc4dLo9iNyAJnNIEz9pm88cCXF21w94Kg3k6RXkeZh5DHOGS0qEONvNQ==} engines: {node: '>=12'} @@ -9532,12 +9633,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zod-validation-error@3.5.4: - resolution: {integrity: sha512-+hEiRIiPobgyuFlEojnqjJnhFvg4r/i3cqgcm67eehZf/WBaK3g6cD02YU9mtdVxZjv8CzCA9n/Rhrs3yAAvAw==} - engines: {node: '>=18.0.0'} - peerDependencies: - zod: ^3.24.4 - zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} @@ -9545,24 +9640,35 @@ snapshots: '@0no-co/graphql.web@1.2.0': {} - '@atproto/api@0.20.40': + '@atproto-labs/did-resolver@0.3.7': 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 + '@atproto-labs/fetch': 0.3.5 + '@atproto-labs/pipe': 0.2.4 + '@atproto-labs/simple-store': 0.5.1 + '@atproto-labs/simple-store-memory': 0.2.6 + '@atproto/did': 0.5.4 zod: 3.25.76 - '@atproto/common-web@0.5.7': + '@atproto-labs/fetch@0.3.5': dependencies: - '@atproto/lex-data': 0.1.6 - '@atproto/lex-json': 0.1.5 - '@atproto/syntax': 0.7.2 + '@atproto-labs/pipe': 0.2.4 + + '@atproto-labs/handle-resolver@0.4.8': + dependencies: + '@atproto-labs/simple-store': 0.5.1 + '@atproto-labs/simple-store-memory': 0.2.6 + '@atproto/did': 0.5.4 zod: 3.25.76 + '@atproto-labs/pipe@0.2.4': {} + + '@atproto-labs/simple-store-memory@0.2.6': + dependencies: + '@atproto-labs/simple-store': 0.5.1 + lru-cache: 10.4.3 + + '@atproto-labs/simple-store@0.5.1': {} + '@atproto/common-web@0.5.9': dependencies: '@atproto/lex-data': 0.1.7 @@ -9570,11 +9676,44 @@ snapshots: '@atproto/syntax': 0.7.4 zod: 3.25.76 - '@atproto/lex-data@0.1.6': + '@atproto/common@0.8.0': dependencies: + '@atproto/common-web': 0.5.9 + '@atproto/lex-cbor': 0.1.6 + '@atproto/lex-data': 0.1.7 multiformats: 13.4.2 + pino: 10.3.1 + + '@atproto/crypto@0.5.4': + dependencies: + '@noble/curves': 1.9.7 + '@noble/hashes': 1.8.0 + uint8arrays: 5.1.1 + + '@atproto/did@0.5.4': + dependencies: + zod: 3.25.76 + + '@atproto/lex-builder@0.1.12': + dependencies: + '@atproto/lex-document': 0.1.9 + '@atproto/lex-schema': 0.2.5 + prettier: 3.9.6 + ts-morph: 27.0.2 + tslib: 2.8.1 + + '@atproto/lex-cbor@0.1.6': + dependencies: + '@atproto/lex-data': 0.1.7 + cborg: 4.5.8 + tslib: 2.8.1 + + '@atproto/lex-client@0.3.3': + dependencies: + '@atproto/lex-data': 0.1.7 + '@atproto/lex-json': 0.1.6 + '@atproto/lex-schema': 0.2.5 tslib: 2.8.1 - unicode-segmenter: 0.14.5 '@atproto/lex-data@0.1.7': dependencies: @@ -9582,9 +9721,21 @@ snapshots: tslib: 2.8.1 unicode-segmenter: 0.14.5 - '@atproto/lex-json@0.1.5': + '@atproto/lex-document@0.1.9': dependencies: - '@atproto/lex-data': 0.1.6 + '@atproto/lex-schema': 0.2.5 + core-js: 3.50.0 + tslib: 2.8.1 + + '@atproto/lex-installer@0.1.15': + dependencies: + '@atproto/lex-builder': 0.1.12 + '@atproto/lex-cbor': 0.1.6 + '@atproto/lex-data': 0.1.7 + '@atproto/lex-document': 0.1.9 + '@atproto/lex-resolver': 0.2.8 + '@atproto/lex-schema': 0.2.5 + '@atproto/syntax': 0.7.4 tslib: 2.8.1 '@atproto/lex-json@0.1.6': @@ -9592,28 +9743,58 @@ snapshots: '@atproto/lex-data': 0.1.7 tslib: 2.8.1 - '@atproto/lexicon@0.7.11': + '@atproto/lex-password-session@0.2.0': dependencies: - '@atproto/common-web': 0.5.9 - '@atproto/syntax': 0.7.4 - multiformats: 13.4.2 - zod: 3.25.76 - - '@atproto/syntax@0.7.2': - dependencies: - iso-datestring-validator: 2.2.2 + '@atproto/lex-client': 0.3.3 + '@atproto/lex-schema': 0.2.5 tslib: 2.8.1 + '@atproto/lex-resolver@0.2.8': + dependencies: + '@atproto-labs/did-resolver': 0.3.7 + '@atproto/crypto': 0.5.4 + '@atproto/lex-client': 0.3.3 + '@atproto/lex-data': 0.1.7 + '@atproto/lex-document': 0.1.9 + '@atproto/lex-schema': 0.2.5 + '@atproto/repo': 0.10.11 + '@atproto/syntax': 0.7.4 + tslib: 2.8.1 + + '@atproto/lex-schema@0.2.5': + dependencies: + '@atproto/lex-data': 0.1.7 + '@atproto/syntax': 0.7.4 + '@standard-schema/spec': 1.1.0 + tslib: 2.8.1 + + '@atproto/lex@0.3.6': + dependencies: + '@atproto/lex-builder': 0.1.12 + '@atproto/lex-client': 0.3.3 + '@atproto/lex-data': 0.1.7 + '@atproto/lex-installer': 0.1.15 + '@atproto/lex-json': 0.1.6 + '@atproto/lex-schema': 0.2.5 + tslib: 2.8.1 + yargs: 18.1.0 + + '@atproto/repo@0.10.11': + dependencies: + '@atproto/common': 0.8.0 + '@atproto/common-web': 0.5.9 + '@atproto/crypto': 0.5.4 + '@atproto/lex-cbor': 0.1.6 + '@atproto/lex-data': 0.1.7 + '@atproto/syntax': 0.7.4 + varint: 6.0.0 + zod: 3.25.76 + '@atproto/syntax@0.7.4': dependencies: 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 @@ -9840,14 +10021,6 @@ snapshots: '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-proposal-private-methods@7.18.6(@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) - '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/helper-plugin-utils': 7.28.6 - transitivePeerDependencies: - - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: '@babel/core': 7.29.0(supports-color@8.1.1) @@ -10592,6 +10765,13 @@ snapshots: react: 19.1.0 react-native: 0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1) + '@bsky/sdk@1.0.0(@atproto/lex@0.3.6)': + dependencies: + '@atproto-labs/handle-resolver': 0.4.8 + '@atproto/lex': 0.3.6 + '@atproto/syntax': 0.7.4 + tlds: 1.261.0 + '@crowdin/cli@4.14.2': dependencies: command-exists-promise: 2.0.2 @@ -10697,8 +10877,10 @@ snapshots: dependencies: eslint: 8.57.1(supports-color@8.1.1) eslint-visitor-keys: 3.4.3 + optional: true - '@eslint-community/regexpp@4.12.2': {} + '@eslint-community/regexpp@4.12.2': + optional: true '@eslint/eslintrc@2.1.4(supports-color@8.1.1)': dependencies: @@ -10713,8 +10895,10 @@ snapshots: strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color + optional: true - '@eslint/js@8.57.1': {} + '@eslint/js@8.57.1': + optional: true '@expo/cli@54.0.25(c1408f750868568d2dc75671b50084a7)': dependencies: @@ -10797,8 +10981,8 @@ snapshots: '@expo/config-plugins@54.0.4(supports-color@8.1.1)': dependencies: '@expo/config-types': 54.0.10 - '@expo/json-file': 10.0.14 - '@expo/plist': 0.4.8 + '@expo/json-file': 10.0.16 + '@expo/plist': 0.4.9 '@expo/sdk-runtime-versions': 1.0.0 chalk: 4.1.2 debug: 4.4.3(supports-color@8.1.1) @@ -10841,7 +11025,7 @@ snapshots: '@babel/code-frame': 7.10.4 '@expo/config-plugins': 54.0.4(supports-color@8.1.1) '@expo/config-types': 54.0.10 - '@expo/json-file': 10.0.14 + '@expo/json-file': 10.2.0 deepmerge: 4.3.1 getenv: 2.0.0 glob: 13.0.6 @@ -10906,11 +11090,6 @@ snapshots: resolve-from: 5.0.0 semver: 7.8.0 - '@expo/json-file@10.0.14': - dependencies: - '@babel/code-frame': 7.29.0 - json5: 2.2.3 - '@expo/json-file@10.0.16': dependencies: '@babel/code-frame': 7.10.4 @@ -10983,7 +11162,7 @@ snapshots: '@expo/package-manager@1.10.5': dependencies: - '@expo/json-file': 10.0.14 + '@expo/json-file': 10.2.0 '@expo/spawn-async': 1.7.2 chalk: 4.1.2 npm-package-arg: 11.0.3 @@ -10996,12 +11175,6 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 - '@expo/plist@0.4.8': - dependencies: - '@xmldom/xmldom': 0.8.13 - base64-js: 1.5.1 - xmlbuilder: 15.1.1 - '@expo/plist@0.4.9': dependencies: '@xmldom/xmldom': 0.8.13 @@ -11014,7 +11187,7 @@ snapshots: '@expo/config-plugins': 54.0.4(supports-color@8.1.1) '@expo/config-types': 54.0.10 '@expo/image-utils': 0.8.12 - '@expo/json-file': 10.0.14 + '@expo/json-file': 10.2.0 '@react-native/normalize-colors': 0.81.5 debug: 4.4.3(supports-color@8.1.1) expo: 54.0.35(@babel/core@7.29.0(supports-color@8.1.1))(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0)(supports-color@8.1.1) @@ -11165,10 +11338,13 @@ snapshots: minimatch: 3.1.5 transitivePeerDependencies: - supports-color + optional: true - '@humanwhocodes/module-importer@1.0.1': {} + '@humanwhocodes/module-importer@1.0.1': + optional: true - '@humanwhocodes/object-schema@2.0.3': {} + '@humanwhocodes/object-schema@2.0.3': + optional: true '@ide/backoff@1.0.0': {} @@ -11514,6 +11690,12 @@ snapshots: dependencies: moo: 0.5.3 + '@noble/curves@1.9.7': + dependencies: + '@noble/hashes': 1.8.0 + + '@noble/hashes@1.8.0': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -11601,6 +11783,8 @@ snapshots: '@oxlint/binding-win32-x64-msvc@1.73.0': optional: true + '@pinojs/redact@0.4.0': {} + '@pkgjs/parseargs@0.11.0': optional: true @@ -12771,6 +12955,8 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@standard-schema/spec@1.1.0': {} + '@tanstack/query-async-storage-persister@5.100.10': dependencies: '@tanstack/query-core': 5.100.10 @@ -12893,6 +13079,12 @@ snapshots: '@tootallnate/once@2.0.1': {} + '@ts-morph/common@0.28.1': + dependencies: + minimatch: 10.2.5 + path-browserify: 1.0.1 + tinyglobby: 0.2.16 + '@types/babel__core@7.20.5': dependencies: '@babel/parser': 7.29.3 @@ -13306,6 +13498,7 @@ snapshots: acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 + optional: true acorn-walk@8.3.5: dependencies: @@ -13477,12 +13670,12 @@ snapshots: asynckit@0.4.0: {} + atomic-sleep@1.0.0: {} + available-typed-arrays@1.0.7: 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) @@ -13603,38 +13796,6 @@ snapshots: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0(supports-color@8.1.1)) - babel-preset-expo@54.0.10(@babel/core@7.29.0(supports-color@8.1.1))(@babel/runtime@7.29.2)(expo@54.0.35(@babel/core@7.29.0(supports-color@8.1.1))(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0)(supports-color@8.1.1))(react-refresh@0.14.2)(supports-color@8.1.1): - dependencies: - '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1) - '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) - '@babel/plugin-syntax-export-default-from': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) - '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) - '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0(supports-color@8.1.1)) - '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/plugin-transform-runtime': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/preset-react': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) - '@react-native/babel-preset': 0.81.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) - babel-plugin-react-compiler: 1.0.0 - babel-plugin-react-native-web: 0.21.2 - babel-plugin-syntax-hermes-parser: 0.29.1 - babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.0(supports-color@8.1.1)) - debug: 4.4.3(supports-color@8.1.1) - react-refresh: 0.14.2 - resolve-from: 5.0.0 - optionalDependencies: - '@babel/runtime': 7.29.2 - expo: 54.0.35(@babel/core@7.29.0(supports-color@8.1.1))(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0)(supports-color@8.1.1) - transitivePeerDependencies: - - '@babel/core' - - supports-color - babel-preset-expo@54.0.12(@babel/core@7.29.0(supports-color@8.1.1))(@babel/runtime@7.29.2)(expo@54.0.35(@babel/core@7.29.0(supports-color@8.1.1))(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0)(supports-color@8.1.1))(react-refresh@0.14.2)(supports-color@8.1.1): dependencies: '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1) @@ -13824,6 +13985,8 @@ snapshots: caniuse-lite@1.0.30001792: {} + cborg@4.5.8: {} + cborg@5.1.1: {} chalk@2.4.2: @@ -13946,10 +14109,18 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + clone@1.0.4: {} co@4.6.0: {} + code-block-writer@13.0.3: {} + collect-v8-coverage@1.0.3: {} color-convert@1.9.3: @@ -14061,6 +14232,8 @@ snapshots: core-js-pure@3.49.0: {} + core-js@3.50.0: {} + core-util-is@1.0.3: {} cosmiconfig@8.3.6(@typescript/typescript6@6.0.2): @@ -14297,7 +14470,8 @@ snapshots: deep-extend@0.6.0: {} - deep-is@0.1.4: {} + deep-is@0.1.4: + optional: true deepmerge@4.3.1: {} @@ -14366,6 +14540,7 @@ snapshots: doctrine@3.0.0: dependencies: esutils: 2.0.3 + optional: true dom-converter@0.2.0: dependencies: @@ -14610,34 +14785,16 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-plugin-react-compiler@19.1.0-rc.2(eslint@8.57.1(supports-color@8.1.1))(supports-color@8.1.1): - dependencies: - '@babel/core': 7.29.0(supports-color@8.1.1) - '@babel/parser': 7.29.3 - '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) - eslint: 8.57.1(supports-color@8.1.1) - hermes-parser: 0.25.1 - zod: 3.25.76 - zod-validation-error: 3.5.4(zod@3.25.76) - transitivePeerDependencies: - - supports-color - eslint-plugin-react-native-a11y@3.5.1(eslint@8.57.1(supports-color@8.1.1)): dependencies: '@babel/runtime': 7.29.2 ast-types-flow: 0.0.7 - eslint: 8.57.1(supports-color@8.1.1) jsx-ast-utils: 3.3.5 - - eslint-plugin-react-native-globals@0.1.2: {} - - eslint-plugin-react-native@5.0.0(eslint@8.57.1(supports-color@8.1.1)): - dependencies: + optionalDependencies: eslint: 8.57.1(supports-color@8.1.1) - eslint-plugin-react-native-globals: 0.1.2 eslint-plugin-simple-import-sort@13.0.0(eslint@8.57.1(supports-color@8.1.1)): - dependencies: + optionalDependencies: eslint: 8.57.1(supports-color@8.1.1) eslint-scope@5.1.1: @@ -14649,8 +14806,10 @@ snapshots: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 + optional: true - eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@3.4.3: + optional: true eslint@8.57.1(supports-color@8.1.1): dependencies: @@ -14694,6 +14853,7 @@ snapshots: text-table: 0.2.0 transitivePeerDependencies: - supports-color + optional: true esm@3.2.25: optional: true @@ -14703,12 +14863,14 @@ snapshots: acorn: 8.16.0 acorn-jsx: 5.3.2(acorn@8.16.0) eslint-visitor-keys: 3.4.3 + optional: true esprima@4.0.1: {} esquery@1.7.0: dependencies: estraverse: 5.3.0 + optional: true esrecurse@4.3.0: dependencies: @@ -14858,23 +15020,11 @@ snapshots: expo-eas-client@1.0.8: {} - expo-file-system@19.0.22(83f21d12a6c1aa99b4a65d070875cc68): - dependencies: - expo: 54.0.35(@babel/core@7.29.0(supports-color@8.1.1))(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0)(supports-color@8.1.1) - react-native: 0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1) - expo-file-system@19.0.23(83f21d12a6c1aa99b4a65d070875cc68): dependencies: expo: 54.0.35(@babel/core@7.29.0(supports-color@8.1.1))(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0)(supports-color@8.1.1) react-native: 0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1) - expo-font@14.0.11(83e0f64f4281e4798012214ad5b2d72d): - dependencies: - expo: 54.0.35(@babel/core@7.29.0(supports-color@8.1.1))(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0)(supports-color@8.1.1) - fontfaceobserver: 2.3.0 - react: 19.1.0 - react-native: 0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1) - expo-font@14.0.12(83e0f64f4281e4798012214ad5b2d72d): dependencies: expo: 54.0.35(@babel/core@7.29.0(supports-color@8.1.1))(react-native-webview@13.15.0(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0))(react-native@0.81.5(patch_hash=91fd85363059530dea649a5ca6012b933f79c8491737fbfdc685abc727e48403)(@babel/core@7.29.0(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.1.17)(react@19.1.0)(supports-color@8.1.1))(react@19.1.0)(supports-color@8.1.1) @@ -15055,7 +15205,7 @@ snapshots: expo-updates@29.0.17(patch_hash=04f28cb005b770e9ae8f0065eab96e43cbb1e58107f5f6ad1bdd18f6deb66487)(93a6abd34ab5ae64aff6a80525489f34): dependencies: '@expo/code-signing-certificates': 0.0.6 - '@expo/plist': 0.4.8 + '@expo/plist': 0.4.9 '@expo/spawn-async': 1.7.2 arg: 4.1.0 chalk: 4.1.2 @@ -15183,7 +15333,8 @@ snapshots: json-schema-ref-resolver: 3.0.0 rfdc: 1.4.1 - fast-levenshtein@2.0.6: {} + fast-levenshtein@2.0.6: + optional: true fast-text-encoding@1.0.6: {} @@ -15222,6 +15373,7 @@ snapshots: file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 + optional: true file-loader@6.2.0(webpack@5.106.2(clean-css@5.3.3)(cssnano@5.1.15(postcss@8.5.14))(csso@5.0.5)(esbuild@0.25.12)(html-minifier-terser@6.1.0)(lightningcss@1.32.0)(postcss@8.5.14)): dependencies: @@ -15292,8 +15444,10 @@ snapshots: flatted: 3.4.4 keyv: 4.5.4 rimraf: 3.0.2 + optional: true - flatted@3.4.4: {} + flatted@3.4.4: + optional: true flow-enums-runtime@0.0.6: {} @@ -15449,6 +15603,7 @@ snapshots: globals@13.24.0: dependencies: type-fest: 0.20.2 + optional: true globals@17.6.0: {} @@ -15478,7 +15633,8 @@ snapshots: graceful-fs@4.2.11: {} - graphemer@1.4.0: {} + graphemer@1.4.0: + optional: true gzip-size@6.0.0: dependencies: @@ -15512,8 +15668,6 @@ snapshots: he@1.2.0: {} - hermes-estree@0.25.1: {} - hermes-estree@0.29.1: {} hermes-estree@0.32.0: {} @@ -15522,10 +15676,6 @@ snapshots: hermes-estree@0.36.0: {} - hermes-parser@0.25.1: - dependencies: - hermes-estree: 0.25.1 - hermes-parser@0.29.1: dependencies: hermes-estree: 0.29.1 @@ -15854,7 +16004,8 @@ snapshots: dependencies: path-is-inside: 1.0.2 - is-path-inside@3.0.3: {} + is-path-inside@3.0.3: + optional: true is-plain-obj@2.1.0: {} @@ -16102,7 +16253,7 @@ snapshots: jest-expo@54.0.17(ff9d3150c2730c411fa3a2f331cc40ee): dependencies: '@expo/config': 12.0.13(supports-color@8.1.1) - '@expo/json-file': 10.0.14 + '@expo/json-file': 10.2.0 '@jest/create-cache-key-function': 29.7.0 '@jest/globals': 29.7.0(supports-color@8.1.1) babel-jest: 29.7.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) @@ -16448,7 +16599,8 @@ snapshots: jsesc@3.1.0: {} - json-buffer@3.0.1: {} + json-buffer@3.0.1: + optional: true json-parse-even-better-errors@2.3.1: {} @@ -16460,7 +16612,8 @@ snapshots: json-schema-traverse@1.0.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} + json-stable-stringify-without-jsonify@1.0.1: + optional: true json5@2.2.3: {} @@ -16482,6 +16635,7 @@ snapshots: keyv@4.5.4: dependencies: json-buffer: 3.0.1 + optional: true kleur@3.0.3: {} @@ -16502,6 +16656,7 @@ snapshots: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + optional: true libphonenumber-js@1.13.1: {} @@ -16613,7 +16768,8 @@ snapshots: lodash.memoize@4.1.2: {} - lodash.merge@4.6.2: {} + lodash.merge@4.6.2: + optional: true lodash.shuffle@4.2.0: {} @@ -17197,7 +17353,7 @@ snapshots: node-releases@2.0.44: {} - node@runtime:24.18.0: {} + node@runtime:24.19.0: {} normalize-path@3.0.0: {} @@ -17263,6 +17419,8 @@ snapshots: obuf@1.1.2: {} + on-exit-leak-free@2.1.2: {} + on-finished@2.3.0: dependencies: ee-first: 1.1.1 @@ -17310,6 +17468,7 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 word-wrap: 1.2.5 + optional: true ora@3.4.0: dependencies: @@ -17434,6 +17593,8 @@ snapshots: no-case: 3.0.4 tslib: 2.8.1 + path-browserify@1.0.1: {} + path-exists@3.0.0: {} path-exists@4.0.0: {} @@ -17478,6 +17639,26 @@ snapshots: pinkie@2.0.4: {} + pino-abstract-transport@3.0.0: + dependencies: + split2: 4.2.0 + + pino-std-serializers@7.1.0: {} + + pino@10.3.1: + dependencies: + '@pinojs/redact': 0.4.0 + atomic-sleep: 1.0.0 + on-exit-leak-free: 2.1.2 + pino-abstract-transport: 3.0.0 + pino-std-serializers: 7.1.0 + process-warning: 5.1.0 + quick-format-unescaped: 4.0.4 + real-require: 0.2.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 4.2.1 + thread-stream: 4.2.0 + pirates@4.0.7: {} pkg-dir@4.2.0: @@ -17695,9 +17876,10 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - prelude-ls@1.2.1: {} + prelude-ls@1.2.1: + optional: true - prettier@3.8.3: {} + prettier@3.9.6: {} pretty-bytes@5.6.0: {} @@ -17723,6 +17905,8 @@ snapshots: process-nextick-args@2.0.1: {} + process-warning@5.1.0: {} + progress@2.0.3: {} promise@7.3.1: @@ -17893,6 +18077,8 @@ snapshots: dependencies: inherits: 2.0.4 + quick-format-unescaped@4.0.4: {} + radix-ui@1.4.3(@types/react-dom@19.1.11(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@radix-ui/primitive': 1.1.3 @@ -18306,6 +18492,10 @@ snapshots: dependencies: picomatch: 2.3.2 + real-require@0.2.0: {} + + real-require@1.0.0: {} + redent@3.0.0: dependencies: indent-string: 4.0.0 @@ -18472,6 +18662,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} sax@1.6.0: {} @@ -18682,6 +18874,10 @@ snapshots: uuid: 8.3.2 websocket-driver: 0.7.4 + sonic-boom@4.2.1: + dependencies: + atomic-sleep: 1.0.0 + sonner-native@0.26.4(dd52c15d65a2604bcf203dfed917879b): dependencies: react: 19.1.0 @@ -18750,6 +18946,8 @@ snapshots: split-on-first@1.1.0: {} + split2@4.2.0: {} + sprintf-js@1.0.3: {} stable@0.1.8: {} @@ -18995,7 +19193,8 @@ snapshots: dependencies: utrie: 1.0.2 - text-table@0.2.0: {} + text-table@0.2.0: + optional: true thenify-all@1.6.0: dependencies: @@ -19005,6 +19204,10 @@ snapshots: dependencies: any-promise: 1.3.0 + thread-stream@4.2.0: + dependencies: + real-require: 1.0.0 + threads@1.7.0(supports-color@8.1.1): dependencies: callsites: 3.1.0 @@ -19071,6 +19274,11 @@ snapshots: ts-interface-checker@0.1.13: {} + ts-morph@27.0.2: + dependencies: + '@ts-morph/common': 0.28.1 + code-block-writer: 13.0.3 + ts-plugin-sort-import-suggestions@1.0.4: {} tslib@2.8.1: {} @@ -19078,10 +19286,12 @@ snapshots: type-check@0.4.0: dependencies: prelude-ls: 1.2.1 + optional: true type-detect@4.0.8: {} - type-fest@0.20.2: {} + type-fest@0.20.2: + optional: true type-fest@0.21.3: {} @@ -19159,6 +19369,10 @@ snapshots: uc.micro@2.1.0: {} + uint8arrays@5.1.1: + dependencies: + multiformats: 13.4.2 + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -19290,6 +19504,8 @@ snapshots: validate-npm-package-name@5.0.1: {} + varint@6.0.0: {} + vary@1.1.2: {} vlq@1.0.1: {} @@ -19528,7 +19744,8 @@ snapshots: wonka@6.3.6: {} - word-wrap@1.2.5: {} + word-wrap@1.2.5: + optional: true wrap-ansi@10.0.0: dependencies: @@ -19616,6 +19833,8 @@ snapshots: yargs-parser@21.1.1: {} + yargs-parser@22.0.0: {} + yargs@15.4.1: dependencies: cliui: 6.0.0 @@ -19640,6 +19859,15 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yargs@18.1.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 8.2.1 + y18n: 5.0.8 + yargs-parser: 22.0.0 + yauzl@3.3.0: dependencies: buffer-crc32: 0.2.13 @@ -19647,8 +19875,4 @@ snapshots: yocto-queue@0.1.0: {} - zod-validation-error@3.5.4(zod@3.25.76): - dependencies: - zod: 3.25.76 - zod@3.25.76: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index aa9a37f930..a68437184d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,15 @@ nodeLinker: 'hoisted' autoInstallPeers: true # default: true strictPeerDependencies: false # default: false +packageExtensions: + eslint-plugin-react-native-a11y: + peerDependenciesMeta: + eslint: + optional: true + eslint-plugin-simple-import-sort: + peerDependenciesMeta: + eslint: + optional: true trustPolicy: 'no-downgrade' # default: off trustPolicyIgnoreAfter: 10080 # 7 days, default: undefined overrides: @@ -16,8 +25,9 @@ overrides: 'react-native-screens': '4.24.0' allowBuilds: '@sentry/cli': true - 'core-js-pure': true - 'esbuild': true + 'core-js': false + 'core-js-pure': false + 'esbuild': false 'unrs-resolver': true patchedDependencies: '@sentry/expo-upload-sourcemaps@8.18.0': patches/@sentry__expo-upload-sourcemaps@8.18.0.patch @@ -43,12 +53,6 @@ patchedDependencies: 'react-native@0.81.5': patches/react-native@0.81.5.patch minimumReleaseAgeExclude: - '@atproto/*' + - '@atproto-labs/*' - '@bsky.app/*' - # todo: remove when old enough - - '@oxlint-tsgolint/darwin-arm64@7.0.2001' - - '@oxlint-tsgolint/darwin-x64@7.0.2001' - - '@oxlint-tsgolint/linux-arm64@7.0.2001' - - '@oxlint-tsgolint/linux-x64@7.0.2001' - - '@oxlint-tsgolint/win32-arm64@7.0.2001' - - '@oxlint-tsgolint/win32-x64@7.0.2001' - - oxlint-tsgolint@7.0.2001 + - '@bsky/*' diff --git a/src/ageAssurance/components/RedirectOverlay.tsx b/src/ageAssurance/components/RedirectOverlay.tsx index 5a6f5b2934..5b31e94365 100644 --- a/src/ageAssurance/components/RedirectOverlay.tsx +++ b/src/ageAssurance/components/RedirectOverlay.tsx @@ -16,7 +16,7 @@ import {Trans} from '@lingui/react/macro' import {retry} from '#/lib/async/retry' import {wait} from '#/lib/async/wait' import {parseLinkingUrl} from '#/lib/parseLinkingUrl' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, useSession} from '#/state/session' import {atoms as a, platform, useBreakpoints, useTheme} from '#/alf' import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' import {Button, ButtonText} from '#/components/Button' @@ -176,7 +176,7 @@ function Inner() { const t = useTheme() const ax = useAnalytics() const {_} = useLingui() - const agent = useAgent() + const appviewClient = useAppviewClient() const polling = useRef(false) const unmounted = useRef(false) const [error, setError] = useState(false) @@ -196,10 +196,10 @@ function Inner() { 5, () => true, async () => { - if (!agent.session) return + if (!appviewClient.did) return if (unmounted.current) return - const data = await refetchAgeAssuranceServerState({agent}) + const data = await refetchAgeAssuranceServerState({appviewClient}) if (data?.state.status !== 'assured') { throw new Error( @@ -214,7 +214,7 @@ function Inner() { ) .then(async data => { if (!data) return - if (!agent.session) return + if (!appviewClient.did) return if (unmounted.current) return setSuccess(true) @@ -230,7 +230,7 @@ function Inner() { return () => { unmounted.current = true } - }, [ax, agent]) + }, [ax, appviewClient]) if (success) { return ( 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 9cf6703dfc..807e195a4d 100644 --- a/src/ageAssurance/data.tsx +++ b/src/ageAssurance/data.tsx @@ -1,19 +1,13 @@ import {createContext, useCallback, useContext, useEffect, useMemo} from 'react' import * as AgeRange from 'expo-age-range' -import { - type AppBskyAgeassuranceDefs, - type AppBskyAgeassuranceGetConfig, - type AppBskyAgeassuranceGetState, - AtpAgent, - type ChatBskyActorDeclaration, -} from '@atproto/api' +import {type Client} from '@atproto/lex' +import {getPreferences} from '@bsky/sdk' import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister' import {focusManager, QueryClient, useQuery} from '@tanstack/react-query' import {persistQueryClient} from '@tanstack/react-query-persist-client' import debounce from 'lodash.debounce' import {networkRetry} from '#/lib/async/retry' -import {PUBLIC_BSKY_SERVICE} from '#/lib/constants' import {createPersistedQueryStorage} from '#/lib/persisted-query-storage' import {getAge} from '#/lib/strings/time' import { @@ -21,7 +15,8 @@ import { snoozeBirthdateUpdateAllowedForDid, } from '#/state/birthdate' import {fetchActorDeclarationRecord} from '#/state/queries/messages/actor-declaration' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' +import {getPublicAppviewClient} from '#/state/session/clients' import {DEVICE_SIGNALS_SUPPORTED} from '#/ageAssurance/const' import * as debug from '#/ageAssurance/debug' import {logger} from '#/ageAssurance/logger' @@ -37,6 +32,7 @@ import { } from '#/ageAssurance/util' import {IS_DEV} from '#/env' import {useGeolocation} from '#/geolocation' +import {app, type chat} from '#/lexicons' import {device} from '#/storage' /** @@ -63,12 +59,6 @@ const [, cacheHydrationPromise] = persistQueryClient({ persister, }) -export function getDidFromAgentSession(agent: AtpAgent) { - const sessionManager = agent.sessionManager - if (!sessionManager || !sessionManager.did) return - return sessionManager.did -} - /* * Optimistic data */ @@ -101,16 +91,16 @@ export function setBirthdateForDid({ export const configQueryKey = ['config'] export async function getConfig() { if (debug.enabled) return debug.resolve(debug.config) - const agent = new AtpAgent({ - service: PUBLIC_BSKY_SERVICE, - }) - const res = await agent.app.bsky.ageassurance.getConfig() - return res.data + /* + * An unauthenticated read against the public appview: the config is fetched + * 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) } export function getConfigFromCache(): - | AppBskyAgeassuranceGetConfig.OutputSchema - | undefined { - return qc.getQueryData( + app.bsky.ageassurance.getConfig.$OutputBody | undefined { + return qc.getQueryData( configQueryKey, ) } @@ -131,7 +121,7 @@ export function prefetchConfig() { try { logger.debug(`prefetchAgeAssuranceConfig: resolving...`) const res = await networkRetry(3, () => getConfig()) - qc.setQueryData( + qc.setQueryData( configQueryKey, res, ) @@ -147,7 +137,7 @@ export function prefetchConfig() { export async function refetchConfig() { logger.debug(`refetchConfig: fetching...`) const res = await getConfig() - qc.setQueryData( + qc.setQueryData( configQueryKey, res, ) @@ -187,7 +177,7 @@ export function useConfigQuery() { export function createServerStateQueryKey({did}: {did: string}) { return ['serverState', did] } -export async function getServerState({agent}: {agent: AtpAgent}) { +export async function getServerState({appviewClient}: {appviewClient: Client}) { if (debug.enabled && debug.serverState) return debug.resolve(debug.serverState) const geolocation = device.get(['mergedGeolocation']) @@ -195,17 +185,21 @@ export async function getServerState({agent}: {agent: AtpAgent}) { logger.error(`getServerState: missing geolocation countryCode`) return null } - const {data} = await agent.app.bsky.ageassurance.getState({ + const data = await appviewClient.call(app.bsky.ageassurance.getState, { countryCode: geolocation.countryCode, regionCode: geolocation.regionCode, }) - const did = getDidFromAgentSession(agent) + const did = appviewClient.did if (data && did && createdAtCache.has(did)) { /* * If account was just created, just use the local cache if available. On - * subsequent reloads, the server should have the correct value. + * subsequent reloads, the server should have the correct value. The cache + * holds ISO datetime strings (written from `new Date().toISOString()`), so + * assert the branded DatetimeString at this boundary. */ - data.metadata.accountCreatedAt = createdAtCache.get(did) + data.metadata.accountCreatedAt = createdAtCache.get( + did, + ) as typeof data.metadata.accountCreatedAt } return data ?? null } @@ -213,13 +207,17 @@ export function getServerStateFromCache({ did, }: { did: string -}): AppBskyAgeassuranceGetState.OutputSchema | undefined { - return qc.getQueryData( +}): app.bsky.ageassurance.getState.$OutputBody | undefined { + return qc.getQueryData( createServerStateQueryKey({did}), ) } -export async function prefetchServerState({agent}: {agent: AtpAgent}) { - const did = getDidFromAgentSession(agent) +export async function prefetchServerState({ + appviewClient, +}: { + appviewClient: Client +}) { + const did = appviewClient.did if (!did) return @@ -234,9 +232,9 @@ export async function prefetchServerState({agent}: {agent: AtpAgent}) { try { logger.debug(`prefetchServerState: resolving...`) - const res = await networkRetry(3, () => getServerState({agent})) + const res = await networkRetry(3, () => getServerState({appviewClient})) if (res) { - qc.setQueryData(qk, res) + qc.setQueryData(qk, res) } } catch (err) { const e = err as Error @@ -245,13 +243,17 @@ export async function prefetchServerState({agent}: {agent: AtpAgent}) { }) } } -export async function refetchServerState({agent}: {agent: AtpAgent}) { - const did = getDidFromAgentSession(agent) +export async function refetchServerState({ + appviewClient, +}: { + appviewClient: Client +}) { + const did = appviewClient.did if (!did) return logger.debug(`refetchServerState: fetching...`) - const res = await networkRetry(3, () => getServerState({agent})) + const res = await networkRetry(3, () => getServerState({appviewClient})) if (res) { - qc.setQueryData( + qc.setQueryData( createServerStateQueryKey({did}), res, ) @@ -261,16 +263,16 @@ export async function refetchServerState({agent}: {agent: AtpAgent}) { 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, ) @@ -279,8 +281,8 @@ export function usePatchServerState() { ) } export function useServerStateQuery() { - const agent = useAgent() - const did = getDidFromAgentSession(agent) + const appviewClient = useAppviewClient() + const did = appviewClient.did const query = useQuery( { enabled: !!did, @@ -290,7 +292,7 @@ export function useServerStateQuery() { }, queryKey: createServerStateQueryKey({did: did!}), async queryFn() { - return getServerState({agent}) + return getServerState({appviewClient}) }, }, qc, @@ -336,21 +338,21 @@ 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] } async function getOtherRequiredData({ - agent, + accountClient, }: { - agent: AtpAgent + accountClient: Client }): Promise { if (debug.enabled) return debug.resolve(debug.otherRequiredData) - const did = getDidFromAgentSession(agent) + const did = accountClient.did const [prefs, actorDeclaration] = await Promise.all([ - agent.getPreferences(), - fetchActorDeclarationRecord({did, agent}), + accountClient.call(getPreferences), + fetchActorDeclarationRecord({did, client: accountClient}), ]) const data: OtherRequiredData = { birthdate: prefs.birthDate ? prefs.birthDate.toISOString() : undefined, @@ -411,7 +413,7 @@ export function setOtherRequiredDataActorDeclarationCache({ actorDeclaration, }: { did: string - actorDeclaration: ChatBskyActorDeclaration.Main + actorDeclaration: chat.bsky.actor.declaration.Main }) { const prev = getOtherRequiredDataFromCache({did}) const next: OtherRequiredData = { @@ -426,8 +428,12 @@ export function setOtherRequiredDataActorDeclarationCache({ next, ) } -export async function prefetchOtherRequiredData({agent}: {agent: AtpAgent}) { - const did = getDidFromAgentSession(agent) +export async function prefetchOtherRequiredData({ + accountClient, +}: { + accountClient: Client +}) { + const did = accountClient.did if (!did) return @@ -442,7 +448,9 @@ export async function prefetchOtherRequiredData({agent}: {agent: AtpAgent}) { try { logger.debug(`prefetchOtherRequiredData: resolving...`) - const res = await networkRetry(3, () => getOtherRequiredData({agent})) + const res = await networkRetry(3, () => + getOtherRequiredData({accountClient}), + ) qc.setQueryData(qk, res) } catch (err) { const e = err as Error @@ -471,8 +479,8 @@ export function usePatchOtherRequiredData() { ) } export function useOtherRequiredDataQuery() { - const agent = useAgent() - const did = getDidFromAgentSession(agent) + const accountClient = usePdsClient() + const did = accountClient.did return useQuery( { enabled: !!did, @@ -482,7 +490,7 @@ export function useOtherRequiredDataQuery() { }, queryKey: createOtherRequiredDataQueryKey({did: did!}), async queryFn() { - return getOtherRequiredData({agent}) + return getOtherRequiredData({accountClient}) }, }, qc, @@ -547,7 +555,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] @@ -577,8 +585,12 @@ export function setDeviceSignalsForRegion({ prev => ({...prev, [regionKey]: signals}), ) } -export async function prefetchDeviceSignals({agent}: {agent: AtpAgent}) { - const did = getDidFromAgentSession(agent) +export async function prefetchDeviceSignals({ + appviewClient, +}: { + appviewClient: Client +}) { + const did = appviewClient.did if (!did) return /** @@ -613,8 +625,8 @@ export async function prefetchDeviceSignals({agent}: {agent: AtpAgent}) { */ } export function useDeviceSignalsQuery() { - const agent = useAgent() - const did = getDidFromAgentSession(agent) + const appviewClient = useAppviewClient() + const did = appviewClient.did const {data: config} = useConfigQuery() const geolocation = useGeolocation() /* @@ -659,13 +671,19 @@ export function useDeviceSignalsQuery() { /** * Helper to prefetch all age assurance data from the server. */ -export function prefetchAgeAssuranceServerData({agent}: {agent: AtpAgent}) { +export function prefetchAgeAssuranceServerData({ + appviewClient, + accountClient, +}: { + appviewClient: Client + accountClient: Client +}) { return Promise.allSettled([ // config fetch initiated at the top of the App.platform.tsx files, awaited here configPrefetchPromise, - prefetchServerState({agent}), - prefetchOtherRequiredData({agent}), - prefetchDeviceSignals({agent}), + prefetchServerState({appviewClient}), + prefetchOtherRequiredData({accountClient}), + prefetchDeviceSignals({appviewClient}), ]) } @@ -691,11 +709,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..4b8041a9de 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,30 @@ 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 +64,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 +120,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 +138,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 +156,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 +174,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 +188,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 +206,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..fb799f934e 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/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..b9b775396a 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/sdk/utils' import {logger} from '#/ageAssurance/logger' diff --git a/src/ageAssurance/useBeginAgeAssurance.ts b/src/ageAssurance/useBeginAgeAssurance.ts index 28a4591747..e6e79e348d 100644 --- a/src/ageAssurance/useBeginAgeAssurance.ts +++ b/src/ageAssurance/useBeginAgeAssurance.ts @@ -1,5 +1,4 @@ import {Platform} from 'react-native' -import {type AppBskyAgeassuranceBegin, AtpAgent} from '@atproto/api' import {useMutation} from '@tanstack/react-query' import {wait} from '#/lib/async/wait' @@ -9,26 +8,28 @@ import { PUBLIC_APPVIEW_DID, } from '#/lib/constants' import {isNetworkError} from '#/lib/hooks/useCleanError' -import {useAgent} from '#/state/session' +import {createLexClient} from '#/lib/lexClient' +import {usePdsClient} from '#/state/session' import {usePatchAgeAssuranceServerState} from '#/ageAssurance' import {logger} from '#/ageAssurance/logger' import {useAnalytics} from '#/analytics' import {BLUESKY_PROXY_DID} from '#/env' import {useGeolocation} from '#/geolocation' +import {app, com} from '#/lexicons' const IS_DEV_ENV = BLUESKY_PROXY_DID !== PUBLIC_APPVIEW_DID const APPVIEW = IS_DEV_ENV ? DEV_ENV_APPVIEW : PUBLIC_APPVIEW export function useBeginAgeAssurance() { const ax = useAnalytics() - const agent = useAgent() + const pdsClient = usePdsClient() const geolocation = useGeolocation() const patchAgeAssuranceStateResponse = usePatchAgeAssuranceServerState() return useMutation({ async mutationFn( props: Omit< - AppBskyAgeassuranceBegin.InputSchema, + app.bsky.ageassurance.begin.$InputBody, 'countryCode' | 'regionCode' >, ) { @@ -38,17 +39,22 @@ export function useBeginAgeAssurance() { throw new Error(`Geolocation not available, cannot init age assurance.`) } - const { - data: {token}, - } = await agent.com.atproto.server.getServiceAuth({ + const {token} = await pdsClient.call(com.atproto.server.getServiceAuth, { aud: BLUESKY_PROXY_DID, lxm: `app.bsky.ageassurance.begin`, }) - const appView = new AtpAgent({service: APPVIEW}) - appView.sessionManager.session = {...agent.session!} - appView.sessionManager.session.accessJwt = token - appView.sessionManager.session.refreshJwt = '' + /* + * A single-use client scoped to the service-auth token: it has no session, + * so nothing can refresh it, and the request goes straight to the appview + * with the token as a static `authorization` header. A raw client is + * allowed to preset that header where a session-backed one is not, which + * also makes the old `refreshJwt = ''` clone unnecessary. + */ + const scopedClient = createLexClient({ + service: APPVIEW, + headers: {authorization: `Bearer ${token}`}, + }) ax.metric('ageAssurance:api:begin', { platform: Platform.OS, @@ -60,9 +66,9 @@ export function useBeginAgeAssurance() { * 2s wait is good actually. Email sending takes a hot sec and this helps * ensure the email is ready for the user once they open their inbox. */ - const {data} = await wait( + const data = await wait( 2e3, - appView.app.bsky.ageassurance.begin({ + scopedClient.call(app.bsky.ageassurance.begin, { ...props, countryCode, regionCode, diff --git a/src/ageAssurance/useComputeAgeAssuranceRegionAccess.ts b/src/ageAssurance/useComputeAgeAssuranceRegionAccess.ts index 5ba9e1ba6d..5cda2ea7c3 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/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 2bceab4d16..d8657c157a 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/sdk/moderation' import { - AppBskyAgeassuranceDefs, computeAgeAssuranceRegionAccess, getAgeAssuranceRegionConfig, - type ModerationPrefs, -} from '@atproto/api' +} from '@bsky/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,9 +42,9 @@ 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 { +): app.bsky.ageassurance.defs.ConfigRegion | undefined { return getAgeAssuranceRegionConfig(config, { countryCode: geolocation.countryCode ?? '', regionCode: geolocation.regionCode, @@ -59,9 +60,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 +75,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 +87,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 +123,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 +184,7 @@ export function canBirthdateUpdateIncreaseAccess({ metadata, deviceSignals, }: { - region: AppBskyAgeassuranceDefs.ConfigRegion + region: app.bsky.ageassurance.defs.ConfigRegion metadata?: AgeAssuranceMetadata deviceSignals?: AgeRange.AgeRangeResponse }): boolean { @@ -212,8 +213,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 +310,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/analytics/metrics/types.ts b/src/analytics/metrics/types.ts index 9fd5188dd9..a63438eda4 100644 --- a/src/analytics/metrics/types.ts +++ b/src/analytics/metrics/types.ts @@ -276,12 +276,7 @@ export type Events = { } 'composer:open': { logContext: - | 'Fab' - | 'PostReply' - | 'QuotePost' - | 'ProfileFeed' - | 'Deeplink' - | 'Other' + 'Fab' | 'PostReply' | 'QuotePost' | 'ProfileFeed' | 'Deeplink' | 'Other' isReply: boolean hasQuote: boolean hasDraft: boolean @@ -596,10 +591,7 @@ export type Events = { } 'chat:create': { logContext: - | 'ProfileHeader' - | 'NewChatDialog' - | 'SendViaChatDialog' - | 'ConvoSettings' + 'ProfileHeader' | 'NewChatDialog' | 'SendViaChatDialog' | 'ConvoSettings' } 'chat:open': { logContext: @@ -1342,10 +1334,7 @@ export type Events = { // invite friends dialog opened, with the surface that triggered it 'invite:dialog:open': { logContext: - | 'ProfileHeader' - | 'Drawer' - | 'FindContactsSettings' - | 'NuxAnnouncement' + 'ProfileHeader' | 'Drawer' | 'FindContactsSettings' | 'NuxAnnouncement' } // user copied the invite link to clipboard 'invite:action:copy': {} 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/types.ts b/src/components/Autocomplete/types.ts index 6f2610d177..ddfc9c12ce 100644 --- a/src/components/Autocomplete/types.ts +++ b/src/components/Autocomplete/types.ts @@ -31,10 +31,7 @@ export type AutocompleteSearch = { } export type AutocompleteItem = - | AutocompleteProfile - | AutocompleteTag - | AutocompleteEmoji - | AutocompleteSearch + AutocompleteProfile | AutocompleteTag | AutocompleteEmoji | AutocompleteSearch export type AutocompleteItemType = AutocompleteItem['type'] diff --git a/src/components/Autocomplete/useAutocomplete/index.ts b/src/components/Autocomplete/useAutocomplete/index.ts index 498cc766cd..1834bfca67 100644 --- a/src/components/Autocomplete/useAutocomplete/index.ts +++ b/src/components/Autocomplete/useAutocomplete/index.ts @@ -1,18 +1,19 @@ import {useCallback, useMemo} from 'react' -import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@bsky/sdk/moderation' import {keepPreviousData, useQuery} from '@tanstack/react-query' import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {STALE} from '#/state/queries' import {DEFAULT_LOGGED_OUT_PREFERENCES} from '#/state/queries/preferences' -import {useAgent} from '#/state/session' +import {useAppviewClient} from '#/state/session' import { type AutocompleteApi, type AutocompleteItem, type AutocompleteItemType, type AutocompleteProfile, } from '#/components/Autocomplete/types' +import {app} from '#/lexicons' import {useEmojiSearch} from './useEmojiSearch' const DEFAULT_MOD_OPTS = { @@ -31,7 +32,7 @@ export function useAutocomplete({ limit?: number showSearchFallback?: boolean }): AutocompleteApi { - const agent = useAgent() + const client = useAppviewClient() const moderationOpts = useModerationOpts() const emojiSearch = useEmojiSearch() @@ -52,12 +53,12 @@ export function useAutocomplete({ // Going from "foo" to "foo." should not clear matches. q = q.toLowerCase().trim().replace(/\.$/, '') - const res = await agent.searchActorsTypeahead({ + const data = await client.call(app.bsky.actor.searchActorsTypeahead, { q, limit: limit || 8, }) - return (res?.data.actors || []).map(profile => ({ + return (data?.actors || []).map(profile => ({ key: profile.did, type: 'profile' as const, value: '@' + profile.handle, diff --git a/src/components/AvatarBubbles.tsx b/src/components/AvatarBubbles.tsx index 49ac4d006f..685b96c57d 100644 --- a/src/components/AvatarBubbles.tsx +++ b/src/components/AvatarBubbles.tsx @@ -8,7 +8,7 @@ import Animated, { withDelay, withTiming, } from 'react-native-reanimated' -import {moderateProfile} from '@atproto/api' +import {moderateProfile} from '@bsky/sdk/moderation' import {useMaybeProfileShadow} from '#/state/cache/profile-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' diff --git a/src/components/AvatarStack.tsx b/src/components/AvatarStack.tsx index ae42a7470b..8c5893ed76 100644 --- a/src/components/AvatarStack.tsx +++ b/src/components/AvatarStack.tsx @@ -1,5 +1,5 @@ import {View} from 'react-native' -import {moderateProfile} from '@atproto/api' +import {moderateProfile} from '@bsky/sdk/moderation' import {logger} from '#/logger' import {useModerationOpts} from '#/state/preferences/moderation-opts' 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/Button.tsx b/src/components/Button.tsx index 2d02bb832f..54332fbc07 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -86,8 +86,7 @@ export type ButtonState = { export type ButtonContext = VariantProps & ButtonState type NonTextElements = - | React.ReactElement - | Iterable + React.ReactElement | Iterable export type ButtonProps = Pick< PressableProps, diff --git a/src/components/FeedCard.tsx b/src/components/FeedCard.tsx index efd14be4ba..bf55f218d6 100644 --- a/src/components/FeedCard.tsx +++ b/src/components/FeedCard.tsx @@ -1,11 +1,7 @@ import {useCallback, useEffect, useMemo} from 'react' import {type GestureResponderEvent, View} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyGraphDefs, - AtUri, - RichText as RichTextApi, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {RichText as RichTextApi} from '@bsky/sdk/richtext' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' @@ -35,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 } @@ -258,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) { @@ -273,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) { @@ -383,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 502b08252f..2f1c115e64 100644 --- a/src/components/KnownFollowers.tsx +++ b/src/components/KnownFollowers.tsx @@ -1,10 +1,6 @@ import {useRef} from 'react' import {View} from 'react-native' -import { - type AppBskyActorDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@bsky/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {makeProfileLink} from '#/lib/routes/links' @@ -13,6 +9,7 @@ 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 @@ -26,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 } @@ -44,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 @@ -85,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/LabelingServiceCard/index.tsx b/src/components/LabelingServiceCard/index.tsx index 10d8c97333..dc40181735 100644 --- a/src/components/LabelingServiceCard/index.tsx +++ b/src/components/LabelingServiceCard/index.tsx @@ -1,5 +1,4 @@ import {View} from 'react-native' -import {type AppBskyLabelerDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' @@ -13,10 +12,11 @@ import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' import {Link as InternalLink, type LinkProps} from '#/components/Link' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' +import {type app} from '#/lexicons' import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron' type LabelingServiceProps = { - labeler: AppBskyLabelerDefs.LabelerViewDetailed + labeler: app.bsky.labeler.defs.LabelerViewDetailed } export function Outer({ @@ -187,7 +187,7 @@ export function Loader({ loading?: React.ComponentType<{}> error?: React.ComponentType<{error: string}> component: React.ComponentType<{ - labeler: AppBskyLabelerDefs.LabelerViewDetailed + labeler: app.bsky.labeler.defs.LabelerViewDetailed }> }) { const {isLoading, data, error} = useLabelerInfoQuery({did}) diff --git a/src/components/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 5b17ec4451..fc499a7fa0 100644 --- a/src/components/NewskieDialog.tsx +++ b/src/components/NewskieDialog.tsx @@ -1,6 +1,6 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' +import {moderateProfile} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -19,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() @@ -74,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 e38afb6c87..60d6d98557 100644 --- a/src/components/Pills.tsx +++ b/src/components/Pills.tsx @@ -1,6 +1,7 @@ import {useMemo} from 'react' import {View} from 'react-native' -import {BSKY_LABELER_DID, type ModerationCause} from '@atproto/api' +import {api} from '@bsky/sdk' +import {type ModerationCause} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' @@ -66,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 a726c313e1..48bf58e938 100644 --- a/src/components/Post/Embed/FeedEmbed.tsx +++ b/src/components/Post/Embed/FeedEmbed.tsx @@ -1,5 +1,5 @@ import {useMemo} from 'react' -import {moderateFeedGenerator} from '@atproto/api' +import {moderateFeedGenerator} from '@bsky/sdk/moderation' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {atoms as a, useTheme} from '#/alf' 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 47c5b2c287..0678f389de 100644 --- a/src/components/Post/Embed/ListEmbed.tsx +++ b/src/components/Post/Embed/ListEmbed.tsx @@ -1,5 +1,5 @@ import {useMemo} from 'react' -import {moderateUserList} from '@atproto/api' +import {moderateUserList} from '@bsky/sdk/moderation' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {atoms as a, useTheme} from '#/alf' 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 da930dc6fb..5e195ac2f9 100644 --- a/src/components/Post/Embed/index.tsx +++ b/src/components/Post/Embed/index.tsx @@ -1,13 +1,9 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' -import { - type $Typed, - type AppBskyFeedDefs, - AppBskyFeedPost, - AtUri, - moderatePost, - RichText as RichTextAPI, -} from '@atproto/api' +import {type $Typed} from '@atproto/lex' +import {AtUri} from '@atproto/syntax' +import {moderatePost} from '@bsky/sdk/moderation' +import {RichText as RichTextAPI} from '@bsky/sdk/richtext' import {Trans} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' @@ -29,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, @@ -266,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', @@ -286,13 +283,7 @@ 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: facets}) diff --git a/src/components/Post/Embed/types.ts b/src/components/Post/Embed/types.ts index 0ef5569c7d..3cf2f5ff03 100644 --- a/src/components/Post/Embed/types.ts +++ b/src/components/Post/Embed/types.ts @@ -1,5 +1,7 @@ import {type StyleProp, type ViewStyle} from 'react-native' -import {type AppBskyFeedDefs, type ModerationDecision} from '@atproto/api' +import {type ModerationDecision} from '@bsky/sdk/moderation' + +import {type app} from '#/lexicons' export enum PostEmbedViewContext { ThreadHighlighted = 'ThreadHighlighted', @@ -19,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 b51f6cb7cb..5cf9b0b5a4 100644 --- a/src/components/PostControls/PostMenu/PostMenuItems.tsx +++ b/src/components/PostControls/PostMenu/PostMenuItems.tsx @@ -6,13 +6,8 @@ import { type ViewStyle, } from 'react-native' import * as Clipboard from 'expo-clipboard' -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, - AtUri, - type RichText as RichTextAPI, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {type RichText as RichTextAPI} from '@bsky/sdk/richtext' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -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 a744efbaa8..23a3e52401 100644 --- a/src/components/PostControls/PostMenu/index.tsx +++ b/src/components/PostControls/PostMenu/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, - type RichText as RichTextAPI, -} from '@atproto/api' +import {type RichText as RichTextAPI} from '@bsky/sdk/richtext' import {useLingui} from '@lingui/react/macro' import {type Shadow} from '#/state/cache/post-shadow' @@ -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 bab3cbfded..783a0b800a 100644 --- a/src/components/PostControls/ShareMenu/RecentChats.tsx +++ b/src/components/PostControls/ShareMenu/RecentChats.tsx @@ -1,9 +1,5 @@ import {ScrollView, View} from 'react-native' -import { - type ChatBskyActorDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -25,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, @@ -116,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 5bc2a8fb6e..00ce8efe86 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, - type RichText as RichTextAPI, -} from '@atproto/api' +import {type RichText as RichTextAPI} from '@bsky/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 efac6a3f01..6c2188b612 100644 --- a/src/components/PostControls/ShareMenu/index.tsx +++ b/src/components/PostControls/ShareMenu/index.tsx @@ -1,12 +1,7 @@ import {memo, useMemo, useState} from 'react' import {type Insets} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, - AtUri, - type RichText as RichTextAPI, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {type RichText as RichTextAPI} from '@bsky/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 1f6be5ab6c..6457267780 100644 --- a/src/components/PostControls/index.tsx +++ b/src/components/PostControls/index.tsx @@ -1,11 +1,6 @@ import {memo, useMemo, useState} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedPost, - type AppBskyFeedThreadgate, - type RichText as RichTextAPI, -} from '@atproto/api' +import {type RichText as RichTextAPI} from '@bsky/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 c37f99c075..d827e0010b 100644 --- a/src/components/ProfileCard.tsx +++ b/src/components/ProfileCard.tsx @@ -6,11 +6,8 @@ import { View, type ViewStyle, } from 'react-native' -import { - moderateProfile, - type ModerationOpts, - RichText as RichTextApi, -} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@bsky/sdk/moderation' +import {RichText as RichTextApi} from '@bsky/sdk/richtext' import {useLingui} from '@lingui/react/macro' import {getModerationCauseKey} from '#/lib/moderation' diff --git a/src/components/ProfileHoverCard/index.web.tsx b/src/components/ProfileHoverCard/index.web.tsx index 46dade016b..ccc48b8951 100644 --- a/src/components/ProfileHoverCard/index.web.tsx +++ b/src/components/ProfileHoverCard/index.web.tsx @@ -1,10 +1,6 @@ import {memo, useCallback, useEffect, useMemo, useReducer, useRef} from 'react' import {View} from 'react-native' -import { - type AppBskyActorDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@bsky/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' @@ -42,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 = [ @@ -418,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/ProgressGuide/FollowDialog.tsx b/src/components/ProgressGuide/FollowDialog.tsx index 0ee575fdec..d7fa9c1fab 100644 --- a/src/components/ProgressGuide/FollowDialog.tsx +++ b/src/components/ProgressGuide/FollowDialog.tsx @@ -1,6 +1,6 @@ import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react' import {TextInput, View, type ViewToken} from 'react-native' -import {type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' diff --git a/src/components/RichText.tsx b/src/components/RichText.tsx index 5501193faf..0266dae0ce 100644 --- a/src/components/RichText.tsx +++ b/src/components/RichText.tsx @@ -1,6 +1,6 @@ import {useMemo} from 'react' import {type StyleProp, type TextStyle} from 'react-native' -import {AppBskyRichtextFacet, RichText as RichTextAPI} from '@atproto/api' +import {RichText as RichTextAPI} from '@bsky/sdk/richtext' import {toShortUrl} from '#/lib/strings/url-helpers' import {android, atoms as a, flatten, type TextStyleProp} from '#/alf' @@ -9,6 +9,8 @@ import {InlineLinkText, type LinkProps} from '#/components/Link' import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {RichTextTag} from '#/components/RichTextTag' import {Text, type TextProps} from '#/components/Typography' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' const WORD_WRAP = {wordWrap: 1} // lifted from facet detection in `RichText` impl, _without_ `gm` flags @@ -139,7 +141,7 @@ export function RichText({ if ( mention && (disableMentionFacetValidation || - AppBskyRichtextFacet.validateMention(mention).success) && + bsky.matches(app.bsky.richtext.facet.mention, mention)) && !disableLinks ) { els.push( @@ -156,7 +158,7 @@ export function RichText({ , ) - } else if (link && AppBskyRichtextFacet.validateLink(link).success) { + } else if (link && bsky.matches(app.bsky.richtext.facet.link, link)) { const isValidLink = URL_REGEX.test(link.uri) if (!isValidLink || disableLinks) { els.push(toShortUrl(segment.text)) @@ -181,7 +183,7 @@ export function RichText({ !disableLinks && enableTags && tag && - AppBskyRichtextFacet.validateTag(tag).success + bsky.matches(app.bsky.richtext.facet.tag, tag) ) { els.push( ( 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 d0c5d546db..30e4218094 100644 --- a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx +++ b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx @@ -1,11 +1,7 @@ import {useRef} from 'react' import {type ListRenderItemInfo} from 'react-native' import {View} from 'react-native' -import { - type AppBskyActorDefs, - type AppBskyFeedDefs, - type ModerationOpts, -} from '@atproto/api' +import {type ModerationOpts} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -25,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}` @@ -44,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 b48c656a08..39397f38e3 100644 --- a/src/components/StarterPack/Wizard/WizardListCard.tsx +++ b/src/components/StarterPack/Wizard/WizardListCard.tsx @@ -1,12 +1,10 @@ import {Keyboard, View} from 'react-native' import { - type AppBskyActorDefs, - type AppBskyFeedDefs, moderateFeedGenerator, moderateProfile, type ModerationOpts, type ModerationUI, -} from '@atproto/api' +} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -26,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({ @@ -41,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 @@ -183,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 ba3d83ec16..1005b0f49e 100644 --- a/src/components/VideoPostCard.tsx +++ b/src/components/VideoPostCard.tsx @@ -2,13 +2,7 @@ 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, - type ModerationDecision, -} from '@atproto/api' +import {type ModerationDecision} from '@bsky/sdk/moderation' import {useLingui} from '@lingui/react/macro' import {sanitizeHandle} from '#/lib/strings/handles' @@ -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/SubscribeProfileButton.tsx b/src/components/activity-notifications/SubscribeProfileButton.tsx index ce9dcd05ae..2749210e43 100644 --- a/src/components/activity-notifications/SubscribeProfileButton.tsx +++ b/src/components/activity-notifications/SubscribeProfileButton.tsx @@ -1,5 +1,5 @@ import {useCallback, useEffect, useState} from 'react' -import {type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' diff --git a/src/components/activity-notifications/SubscribeProfileDialog.tsx b/src/components/activity-notifications/SubscribeProfileDialog.tsx index 8a6b8314cd..0f4b9a9ee5 100644 --- a/src/components/activity-notifications/SubscribeProfileDialog.tsx +++ b/src/components/activity-notifications/SubscribeProfileDialog.tsx @@ -1,11 +1,7 @@ import {useMemo, useState} from 'react' import {View} from 'react-native' -import { - type AppBskyNotificationDefs, - type AppBskyNotificationListActivitySubscriptions, - type ModerationOpts, - type Un$Typed, -} from '@atproto/api' +import {type Un$Typed} from '@atproto/lex' +import {type ModerationOpts} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -20,7 +16,7 @@ import {cleanError} from '#/lib/strings/errors' import {sanitizeHandle} from '#/lib/strings/handles' import {updateProfileShadow} from '#/state/cache/profile-shadow' import {RQKEY_getActivitySubscriptions} from '#/state/queries/activity-subscriptions' -import {useAgent} from '#/state/session' +import {useAppviewClient} from '#/state/session' import {atoms as a, platform, useTheme, web} from '#/alf' import {Admonition} from '#/components/Admonition' import { @@ -37,6 +33,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 type * as bsky from '#/types/bsky' export function SubscribeProfileDialog({ @@ -74,7 +71,7 @@ function DialogInner({ const ax = useAnalytics() const {_} = useLingui() const t = useTheme() - const agent = useAgent() + const client = useAppviewClient() const control = Dialog.useDialogContext() const queryClient = useQueryClient() const initialState = parseActivitySubscription( @@ -120,9 +117,9 @@ function DialogInner({ error, } = useMutation({ mutationFn: async ( - activitySubscription: Un$Typed, + activitySubscription: Un$Typed, ) => { - await agent.app.bsky.notification.putActivitySubscription({ + await client.call(app.bsky.notification.putActivitySubscription, { subject: profile.did, activitySubscription, }) @@ -148,7 +145,7 @@ function DialogInner({ queryClient.setQueryData( RQKEY_getActivitySubscriptions, ( - old?: InfiniteData, + old?: InfiniteData, ) => { if (!old) return old return { @@ -308,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 1ee126eb28..10426d0ca6 100644 --- a/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceAppealDialog.tsx @@ -1,13 +1,12 @@ import {useState} from 'react' import {View} from 'react-native' -import {ToolsOzoneReportDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {useMutation} from '@tanstack/react-query' -import {BLUESKY_MOD_SERVICE_HEADERS} from '#/lib/constants' -import {useAgent, useSession} from '#/state/session' +import {MOD_PROXY_SERVICE} from '#/lib/constants' +import {useAppviewClient, useSession} from '#/state/session' import {atoms as a, useBreakpoints, web} from '#/alf' import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' import {Button, ButtonIcon, ButtonText} from '#/components/Button' @@ -17,6 +16,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {logger} from '#/ageAssurance' import {useAnalytics} from '#/analytics' +import {com, tools} from '#/lexicons' export function AgeAssuranceAppealDialog({ control, @@ -42,7 +42,7 @@ function Inner({control}: {control: Dialog.DialogControlProps}) { const ax = useAnalytics() const {currentAccount} = useSession() const {gtPhone} = useBreakpoints() - const agent = useAgent() + const client = useAppviewClient() const [details, setDetails] = useState('') const isInvalid = details.length > 1000 @@ -51,19 +51,21 @@ function Inner({control}: {control: Dialog.DialogControlProps}) { mutationFn: async () => { ax.metric('ageAssurance:appealDialogSubmit', {}) - await agent.createModerationReport( + if (!currentAccount) { + throw new Error('No current account, should be unreachable') + } + + await client.call( + com.atproto.moderation.createReport, { - reasonType: ToolsOzoneReportDefs.REASONAPPEAL, + reasonType: tools.ozone.report.defs.reasonAppeal.value, subject: { $type: 'com.atproto.admin.defs#repoRef', - did: currentAccount?.did, + did: currentAccount.did, }, reason: `AGE_ASSURANCE_INQUIRY: ` + details, }, - { - encoding: 'application/json', - headers: BLUESKY_MOD_SERVICE_HEADERS, - }, + {service: MOD_PROXY_SERVICE}, ) }, onError: err => { diff --git a/src/components/ageAssurance/AgeAssuranceInitDialog.tsx b/src/components/ageAssurance/AgeAssuranceInitDialog.tsx index 9d74b0c6dc..052c3a4731 100644 --- a/src/components/ageAssurance/AgeAssuranceInitDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceInitDialog.tsx @@ -1,6 +1,6 @@ import {useState} from 'react' import {View} from 'react-native' -import {XRPCError} from '@atproto/api' +import {XrpcResponseError} from '@atproto/lex' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -14,6 +14,7 @@ import { import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' import {useTLDs} from '#/lib/hooks/useTLDs' import {isEmailMaybeInvalid} from '#/lib/strings/email' +import {matchXrpcError} from '#/lib/xrpc-error' import {type AppLanguage} from '#/locale/languages' import {useLanguagePrefs} from '#/state/preferences' import {useSession} from '#/state/session' @@ -33,6 +34,7 @@ import {Text} from '#/components/Typography' import {useAgeAssurance} from '#/ageAssurance' import {useBeginAgeAssurance} from '#/ageAssurance/useBeginAgeAssurance' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' export {useDialogControl} from '#/components/Dialog/context' @@ -139,30 +141,37 @@ function Inner() { msg`Something went wrong, please try again`, ) - if (e instanceof XRPCError) { - if (e.error === 'InvalidEmail') { - error = _( - msg`Please enter a valid, non-temporary email address. You may need to access this email in the future.`, - ) - ax.metric('ageAssurance:initDialogError', {code: 'InvalidEmail'}) - } else if (e.error === 'DidTooLong') { - error = ( - <> - - We're having issues initializing the age assurance process for - your account. Please{' '} - - contact support - {' '} - for assistance. - - - ) - ax.metric('ageAssurance:initDialogError', {code: 'DidTooLong'}) - } else { - ax.metric('ageAssurance:initDialogError', {code: 'other'}) + if (e instanceof XrpcResponseError) { + switch (matchXrpcError(e, app.bsky.ageassurance.begin)) { + case 'InvalidEmail': + error = _( + msg`Please enter a valid, non-temporary email address. You may need to access this email in the future.`, + ) + ax.metric('ageAssurance:initDialogError', {code: 'InvalidEmail'}) + break + case 'DidTooLong': + error = ( + <> + + We're having issues initializing the age assurance process for + your account. Please{' '} + + contact support + {' '} + for assistance. + + + ) + ax.metric('ageAssurance:initDialogError', {code: 'DidTooLong'}) + break + default: + /* + * An undeclared code keeps the generic message rather than surfacing + * the server's text, as the old `e.error` fallthrough did. + */ + ax.metric('ageAssurance:initDialogError', {code: 'other'}) } } else { const {clean, raw} = cleanError(e) diff --git a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx index ad584ca203..acf20f38c5 100644 --- a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx @@ -6,7 +6,7 @@ import {Trans} from '@lingui/react/macro' import {retry} from '#/lib/async/retry' import {wait} from '#/lib/async/wait' -import {useAgent} from '#/state/session' +import {useAppviewClient} from '#/state/session' import {atoms as a, useTheme, web} from '#/alf' import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' import {Button, ButtonText} from '#/components/Button' @@ -84,7 +84,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { const t = useTheme() const ax = useAnalytics() const {_} = useLingui() - const agent = useAgent() + const appviewClient = useAppviewClient() const polling = useRef(false) const unmounted = useRef(false) const control = useAgeAssuranceRedirectDialogControl() @@ -104,10 +104,10 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { 5, () => true, async () => { - if (!agent.session) return + if (!appviewClient.did) return if (unmounted.current) return - const data = await refetchAgeAssuranceServerState({agent}) + const data = await refetchAgeAssuranceServerState({appviewClient}) if (data?.state.status !== 'assured') { throw new Error( @@ -122,7 +122,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { ) .then(async data => { if (!data) return - if (!agent.session) return + if (!appviewClient.did) return if (unmounted.current) return setSuccess(true) @@ -138,7 +138,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { return () => { unmounted.current = true } - }, [ax, agent, control]) + }, [ax, appviewClient, control]) if (success) { return ( 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/contacts/screens/GetContacts.tsx b/src/components/contacts/screens/GetContacts.tsx index 879864b162..534a9a708b 100644 --- a/src/components/contacts/screens/GetContacts.tsx +++ b/src/components/contacts/screens/GetContacts.tsx @@ -2,12 +2,10 @@ import {useContext} from 'react' import {Alert, View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' import * as Contacts from 'expo-contacts' -import type AtpAgent from '@atproto/api' -import { - type AppBskyActorProfile, - AppBskyContactImportContacts, - type Un$Typed, -} from '@atproto/api' +import {type Un$Typed} from '@atproto/lex' +import {type Client} from '@atproto/lex' +import {toDatetimeString} from '@atproto/syntax' +import {upsertProfile} from '@bsky/sdk' import {msg, t} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -15,9 +13,10 @@ import {useMutation, useQueryClient} from '@tanstack/react-query' import {uploadBlob} from '#/lib/api' import {cleanError, isNetworkError} from '#/lib/strings/errors' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {findContactsStatusQueryKey} from '#/state/queries/find-contacts' -import {useAgent} from '#/state/session' +import {useAppviewClient, usePdsClient} from '#/state/session' import { Context as OnboardingContext, type OnboardingAction, @@ -30,6 +29,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 { contactsWithPhoneNumbersOnly, filterMatchedNumbers, @@ -54,7 +54,8 @@ export function GetContacts({ }) { const {_} = useLingui() const ax = useAnalytics() - const agent = useAgent() + const pdsClient = usePdsClient() + const appviewClient = useAppviewClient() const insets = useSafeAreaInsets() const gutters = useGutters([0, 'wide']) const queryClient = useQueryClient() @@ -72,7 +73,7 @@ export function GetContacts({ */ if (context === 'Onboarding' && maybeOnboardingContext) { try { - await createProfileRecord(agent, maybeOnboardingContext) + await createProfileRecord(pdsClient, maybeOnboardingContext) } catch (error) { logger.debug('Error creating profile record:', {safeMessage: error}) } @@ -85,13 +86,13 @@ export function GetContacts({ ) if (phoneNumbers.length > 0) { - const res = await agent.app.bsky.contact.importContacts({ + const res = await appviewClient.call(app.bsky.contact.importContacts, { token: state.token, contacts: phoneNumbers.slice(0, MAX_UPLOAD_COUNT), }) return { - matches: res.data.matchesAndContactIndexes, + matches: res.matchesAndContactIndexes, indexToContactId, } } else { @@ -148,29 +149,30 @@ export function GetContacts({ ), {type: 'error'}, ) - } else if ( - err instanceof AppBskyContactImportContacts.TooManyContactsError - ) { - Toast.show( - _( - msg`Too many contacts - you've exceeded the number of contacts you can import to find your friends`, - ), - {type: 'error'}, - ) - } else if ( - err instanceof AppBskyContactImportContacts.InvalidTokenError - ) { - Toast.show( - _( - msg`Could not upload contacts. You need to re-verify your phone number to proceed`, - ), - {type: 'error'}, - ) - } else { - logger.error('Error uploading contacts', {safeMessage: err}) - Toast.show(_(msg`Could not upload contacts. ${cleanError(err)}`), { - type: 'error', - }) + return + } + switch (matchXrpcError(err, app.bsky.contact.importContacts)) { + case 'TooManyContacts': + Toast.show( + _( + msg`Too many contacts - you've exceeded the number of contacts you can import to find your friends`, + ), + {type: 'error'}, + ) + break + case 'InvalidToken': + Toast.show( + _( + msg`Could not upload contacts. You need to re-verify your phone number to proceed`, + ), + {type: 'error'}, + ) + break + default: + logger.error('Error uploading contacts', {safeMessage: err}) + Toast.show(_(msg`Could not upload contacts. ${cleanError(err)}`), { + type: 'error', + }) } }, }) @@ -325,7 +327,7 @@ function showPermissionDeniedAlert() { * Copied from `#/screens/Onboarding/StepFinished/index.tsx` */ async function createProfileRecord( - agent: AtpAgent, + pdsClient: Client, onboardingContext: { state: OnboardingState dispatch: React.Dispatch @@ -334,21 +336,25 @@ async function createProfileRecord( const profileStepResults = onboardingContext.state.profileStepResults const {imageUri, imageMime} = profileStepResults const blobPromise = - imageUri && imageMime ? uploadBlob(agent, imageUri, imageMime) : undefined + imageUri && imageMime + ? uploadBlob(pdsClient, imageUri, imageMime) + : undefined - await agent.upsertProfile(async existing => { - let next: Un$Typed = existing ?? {} + await pdsClient.call(upsertProfile, async existing => { + let next: Un$Typed = existing ?? {} if (blobPromise) { const res = await blobPromise - if (res.data.blob) { - next.avatar = res.data.blob + if (res.blob) { + next.avatar = res.blob } } next.displayName = '' - next.createdAt = new Date().toISOString() + if (!next.createdAt) { + next.createdAt = toDatetimeString(new Date()) + } return next }) } diff --git a/src/components/contacts/screens/PhoneInput.tsx b/src/components/contacts/screens/PhoneInput.tsx index 6904789a6e..95d63055c0 100644 --- a/src/components/contacts/screens/PhoneInput.tsx +++ b/src/components/contacts/screens/PhoneInput.tsx @@ -2,7 +2,6 @@ import {useState} from 'react' import {Keyboard, View} from 'react-native' import {KeyboardAvoidingView} from 'react-native-keyboard-controller' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {AppBskyContactStartPhoneVerification} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -14,8 +13,9 @@ import { getDefaultCountry, } from '#/lib/international-telephone-codes' import {cleanError, isNetworkError} from '#/lib/strings/errors' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' -import {useAgent} from '#/state/session' +import {useAppviewClient} from '#/state/session' import {OnboardingPosition} from '#/screens/Onboarding/Layout' import { android, @@ -34,6 +34,7 @@ import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {useGeolocation} from '#/geolocation' +import {app} from '#/lexicons' import {isFindContactsFeatureEnabled} from '../country-allowlist' import { constructFullPhoneNumber, @@ -56,7 +57,7 @@ export function PhoneInput({ const {_} = useLingui() const ax = useAnalytics() const t = useTheme() - const agent = useAgent() + const client = useAppviewClient() const location = useGeolocation() const [countryCode, setCountryCode] = useState( () => state.phoneCountryCode ?? getDefaultCountry(location), @@ -78,7 +79,7 @@ export function PhoneInput({ phoneNumber: string }) => { // sends a onetime code to the user's phone number - await agent.app.bsky.contact.startPhoneVerification({ + await client.call(app.bsky.contact.startPhoneVerification, { phone: constructFullPhoneNumber(phoneCountryCode, phoneNumber), }) }, @@ -102,23 +103,22 @@ export function PhoneInput({ msg`A network error occurred. Please check your internet connection`, ), ) - } else if ( - err instanceof - AppBskyContactStartPhoneVerification.RateLimitExceededError - ) { - setError(_(msg`Rate limit exceeded. Please try again later.`)) - } else if ( - err instanceof AppBskyContactStartPhoneVerification.InvalidPhoneError - ) { - setError( - _( - msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`, - ), - ) - } else { - logger.error('Verify phone number failed', {safeMessage: err}) - setError(_(msg`An error occurred. ${cleanError(err)}`)) + return } + switch (matchXrpcError(err, app.bsky.contact.startPhoneVerification)) { + case 'RateLimitExceeded': + setError(_(msg`Rate limit exceeded. Please try again later.`)) + return + case 'InvalidPhone': + setError( + _( + msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`, + ), + ) + return + } + logger.error('Verify phone number failed', {safeMessage: err}) + setError(_(msg`An error occurred. ${cleanError(err)}`)) }, }) diff --git a/src/components/contacts/screens/VerifyNumber.tsx b/src/components/contacts/screens/VerifyNumber.tsx index f9d062f691..0bd5369397 100644 --- a/src/components/contacts/screens/VerifyNumber.tsx +++ b/src/components/contacts/screens/VerifyNumber.tsx @@ -1,9 +1,5 @@ import {useEffect, useMemo, useState} from 'react' import {Text as NestedText, View} from 'react-native' -import { - AppBskyContactStartPhoneVerification, - AppBskyContactVerifyPhone, -} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -11,8 +7,9 @@ import {useMutation} from '@tanstack/react-query' import {clamp} from '#/lib/numbers' import {cleanError, isNetworkError} from '#/lib/strings/errors' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' -import {useAgent} from '#/state/session' +import {useAppviewClient} from '#/state/session' import {OnboardingPosition} from '#/screens/Onboarding/Layout' import {atoms as a, useGutters, useTheme} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' @@ -25,6 +22,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 {OTPInput} from '../components/OTPInput' import {constructFullPhoneNumber, prettyPhoneNumber} from '../phone-number' import {type Action, type State, useOnPressBackButton} from '../state' @@ -43,7 +41,7 @@ export function VerifyNumber({ const t = useTheme() const {_} = useLingui() const ax = useAnalytics() - const agent = useAgent() + const client = useAppviewClient() const gutters = useGutters([0, 'wide']) const [otpCode, setOtpCode] = useState('') @@ -72,8 +70,11 @@ export function VerifyNumber({ isSuccess, } = useMutation({ mutationFn: async (code: string) => { - const res = await agent.app.bsky.contact.verifyPhone({code, phone}) - return res.data.token + const data = await client.call(app.bsky.contact.verifyPhone, { + code, + phone, + }) + return data.token }, onSuccess: async token => { // let the success state show for a moment @@ -99,44 +100,47 @@ export function VerifyNumber({ msg`A network error occurred. Please check your internet connection.`, ), }) - } else if (err instanceof AppBskyContactVerifyPhone.InvalidCodeError) { - setError({ - retryable: true, - isResendError: true, - message: _(msg`This code is invalid. Resend to get a new code.`), - }) - } else if (err instanceof AppBskyContactVerifyPhone.InvalidPhoneError) { - setError({ - retryable: false, - isResendError: false, - message: _( - msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`, - ), - }) - } else if ( - err instanceof AppBskyContactVerifyPhone.RateLimitExceededError - ) { - setError({ - retryable: true, - isResendError: false, - message: _( - msg`Too many attempts. Please wait a few minutes and try again.`, - ), - }) - } else { - logger.error('Verify phone number failed', {safeMessage: err}) - setError({ - retryable: true, - isResendError: false, - message: _(msg`An error occurred. ${cleanError(err)}`), - }) + return } + switch (matchXrpcError(err, app.bsky.contact.verifyPhone)) { + case 'InvalidCode': + setError({ + retryable: true, + isResendError: true, + message: _(msg`This code is invalid. Resend to get a new code.`), + }) + return + case 'InvalidPhone': + setError({ + retryable: false, + isResendError: false, + message: _( + msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`, + ), + }) + return + case 'RateLimitExceeded': + setError({ + retryable: true, + isResendError: false, + message: _( + msg`Too many attempts. Please wait a few minutes and try again.`, + ), + }) + return + } + logger.error('Verify phone number failed', {safeMessage: err}) + setError({ + retryable: true, + isResendError: false, + message: _(msg`An error occurred. ${cleanError(err)}`), + }) }, }) const {mutate: resendCode, isPending: isResendingCode} = useMutation({ mutationFn: async () => { - await agent.app.bsky.contact.startPhoneVerification({phone: phone}) + await client.call(app.bsky.contact.startPhoneVerification, {phone: phone}) }, onSuccess: () => { dispatch({type: 'RESEND_VERIFICATION_CODE'}) @@ -155,35 +159,34 @@ export function VerifyNumber({ msg`A network error occurred. Please check your internet connection.`, ), }) - } else if ( - err instanceof AppBskyContactStartPhoneVerification.InvalidPhoneError - ) { - setError({ - retryable: false, - isResendError: true, - message: _( - msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`, - ), - }) - } else if ( - err instanceof - AppBskyContactStartPhoneVerification.RateLimitExceededError - ) { - setError({ - retryable: true, - isResendError: true, - message: _( - msg`Too many codes sent. Please wait a few minutes and try again.`, - ), - }) - } else { - logger.error('Resend failed', {safeMessage: err}) - setError({ - retryable: true, - isResendError: true, - message: _(msg`An error occurred. ${cleanError(err)}`), - }) + return } + switch (matchXrpcError(err, app.bsky.contact.startPhoneVerification)) { + case 'InvalidPhone': + setError({ + retryable: false, + isResendError: true, + message: _( + msg`The verification provider was unable to send a code to your phone number. Please check your phone number and try again.`, + ), + }) + return + case 'RateLimitExceeded': + setError({ + retryable: true, + isResendError: true, + message: _( + msg`Too many codes sent. Please wait a few minutes and try again.`, + ), + }) + return + } + logger.error('Resend failed', {safeMessage: err}) + setError({ + retryable: true, + isResendError: true, + message: _(msg`An error occurred. ${cleanError(err)}`), + }) }, }) diff --git a/src/components/contacts/screens/ViewMatches.tsx b/src/components/contacts/screens/ViewMatches.tsx index 3543c604f5..0d50bfdeeb 100644 --- a/src/components/contacts/screens/ViewMatches.tsx +++ b/src/components/contacts/screens/ViewMatches.tsx @@ -2,7 +2,8 @@ import {useCallback, useMemo, useRef, useState} from 'react' import {View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' import * as SMS from 'expo-sms' -import {type ModerationOpts} from '@atproto/api' +import {type DidString} from '@atproto/syntax' +import {type ModerationOpts} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' @@ -20,7 +21,7 @@ import { optimisticRemoveMatch, useMatchesPassthroughQuery, } from '#/state/queries/find-contacts' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' import {List, type ListMethods} from '#/view/com/util/List' import {UserAvatar} from '#/view/com/util/UserAvatar' import {OnboardingPosition} from '#/screens/Onboarding/Layout' @@ -41,6 +42,7 @@ import * as ProfileCard from '#/components/ProfileCard' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' import type * as bsky from '#/types/bsky' import {InviteInfo} from '../components/InviteInfo' import {type Action, type Contact, type Match, type State} from '../state' @@ -89,7 +91,8 @@ export function ViewMatches({ const gutter = useGutters([0, 'wide']) const moderationOpts = useModerationOpts() const queryClient = useQueryClient() - const agent = useAgent() + const pdsClient = usePdsClient() + const client = useAppviewClient() const insets = useSafeAreaInsets() const listRef = useRef(null) @@ -124,7 +127,10 @@ export function ViewMatches({ }) } - const uris = await wait(500, bulkWriteFollows(agent, followableDids)) + const uris = await wait( + 500, + bulkWriteFollows(pdsClient, client, followableDids), + ) for (const did of followableDids) { const uri = uris.get(did) @@ -218,7 +224,9 @@ export function ViewMatches({ const {mutate: dismissMatch} = useMutation({ mutationFn: async (did: string) => { - await agent.app.bsky.contact.dismissMatch({subject: did}) + await client.call(app.bsky.contact.dismissMatch, { + subject: did as DidString, + }) }, onMutate: did => { ax.metric('contacts:matches:dismiss', {entryPoint: context}) diff --git a/src/components/dialogs/EmailDialog/data/useAccountEmailState.ts b/src/components/dialogs/EmailDialog/data/useAccountEmailState.ts index f25369f8db..b9edd1629d 100644 --- a/src/components/dialogs/EmailDialog/data/useAccountEmailState.ts +++ b/src/components/dialogs/EmailDialog/data/useAccountEmailState.ts @@ -1,7 +1,7 @@ import {useEffect, useMemo, useState} from 'react' import {useQuery} from '@tanstack/react-query' -import {useAgent, useSessionApi} from '#/state/session' +import {useSession, useSessionApi} from '#/state/session' import {emitEmailVerified} from '#/components/dialogs/EmailDialog/events' export type AccountEmailState = { @@ -12,24 +12,29 @@ export type AccountEmailState = { export const accountEmailStateQueryKey = ['accountEmailState'] as const export function useAccountEmailState() { - const agent = useAgent() + /* + * Read from the account rather than the agent's session: `partialRefreshSession` + * patches the email fields on the account in the reducer (`PasswordSession` + * has no setter for them), so the account is the fresh source of truth. + */ + const {currentAccount} = useSession() const {partialRefreshSession} = useSessionApi() const [prevIsEmailVerified, setPrevEmailIsVerified] = useState( - !!agent.session?.emailConfirmed, + !!currentAccount?.emailConfirmed, ) const state: AccountEmailState = useMemo( () => ({ - isEmailVerified: !!agent.session?.emailConfirmed, - email2FAEnabled: !!agent.session?.emailAuthFactor, + isEmailVerified: !!currentAccount?.emailConfirmed, + email2FAEnabled: !!currentAccount?.emailAuthFactor, }), - [agent.session], + [currentAccount], ) /** * Only here to refetch on focus, when necessary */ useQuery({ - enabled: !!agent.session, + enabled: !!currentAccount, /** * Only refetch if the email verification s incomplete. */ diff --git a/src/components/dialogs/EmailDialog/data/useConfirmEmail.ts b/src/components/dialogs/EmailDialog/data/useConfirmEmail.ts index 67466be926..00472be52f 100644 --- a/src/components/dialogs/EmailDialog/data/useConfirmEmail.ts +++ b/src/components/dialogs/EmailDialog/data/useConfirmEmail.ts @@ -1,13 +1,15 @@ import {useMutation} from '@tanstack/react-query' -import {useAgent, useSession} from '#/state/session' +import {usePdsClient, useSession, useSessionApi} from '#/state/session' +import {com} from '#/lexicons' export function useConfirmEmail({ onSuccess, onError, }: {onSuccess?: () => void; onError?: () => void} = {}) { - const agent = useAgent() + const pdsClient = usePdsClient() const {currentAccount} = useSession() + const {refreshSession} = useSessionApi() return useMutation({ mutationFn: async ({token}: {token: string}) => { @@ -15,12 +17,12 @@ export function useConfirmEmail({ throw new Error('No email found for the current account') } - await agent.com.atproto.server.confirmEmail({ + await pdsClient.call(com.atproto.server.confirmEmail, { email: currentAccount.email.trim(), token: token.trim(), }) // will update session state at root of app - await agent.resumeSession(agent.session!) + await refreshSession() }, onSuccess, onError, diff --git a/src/components/dialogs/EmailDialog/data/useManageEmail2FA.ts b/src/components/dialogs/EmailDialog/data/useManageEmail2FA.ts index 358bf86544..9f811b4818 100644 --- a/src/components/dialogs/EmailDialog/data/useManageEmail2FA.ts +++ b/src/components/dialogs/EmailDialog/data/useManageEmail2FA.ts @@ -1,29 +1,30 @@ import {useMutation} from '@tanstack/react-query' -import {useAgent, useSession} from '#/state/session' +import {usePdsClient, useSession, useSessionApi} from '#/state/session' +import {com} from '#/lexicons' export function useManageEmail2FA() { - const agent = useAgent() + const pdsClient = usePdsClient() const {currentAccount} = useSession() + const {refreshSession} = useSessionApi() return useMutation({ mutationFn: async ({ enabled, token, }: - | {enabled: true; token?: undefined} - | {enabled: false; token: string}) => { + {enabled: true; token?: undefined} | {enabled: false; token: string}) => { if (!currentAccount?.email) { throw new Error('No email found for the current account') } - await agent.com.atproto.server.updateEmail({ + await pdsClient.call(com.atproto.server.updateEmail, { email: currentAccount.email, emailAuthFactor: enabled, token, }) // will update session state at root of app - await agent.resumeSession(agent.session!) + await refreshSession() }, }) } diff --git a/src/components/dialogs/EmailDialog/data/useRequestEmailUpdate.ts b/src/components/dialogs/EmailDialog/data/useRequestEmailUpdate.ts index a442662fcf..d6cd418444 100644 --- a/src/components/dialogs/EmailDialog/data/useRequestEmailUpdate.ts +++ b/src/components/dialogs/EmailDialog/data/useRequestEmailUpdate.ts @@ -1,13 +1,14 @@ import {useMutation} from '@tanstack/react-query' -import {useAgent} from '#/state/session' +import {usePdsClient} from '#/state/session' +import {com} from '#/lexicons' export function useRequestEmailUpdate() { - const agent = useAgent() + const client = usePdsClient() return useMutation({ mutationFn: async () => { - return (await agent.com.atproto.server.requestEmailUpdate()).data + return await client.call(com.atproto.server.requestEmailUpdate) }, }) } diff --git a/src/components/dialogs/EmailDialog/data/useRequestEmailVerification.ts b/src/components/dialogs/EmailDialog/data/useRequestEmailVerification.ts index ae308c7afc..4152e61e58 100644 --- a/src/components/dialogs/EmailDialog/data/useRequestEmailVerification.ts +++ b/src/components/dialogs/EmailDialog/data/useRequestEmailVerification.ts @@ -1,13 +1,14 @@ import {useMutation} from '@tanstack/react-query' -import {useAgent} from '#/state/session' +import {usePdsClient} from '#/state/session' +import {com} from '#/lexicons' export function useRequestEmailVerification() { - const agent = useAgent() + const client = usePdsClient() return useMutation({ mutationFn: async () => { - await agent.com.atproto.server.requestEmailConfirmation() + await client.call(com.atproto.server.requestEmailConfirmation) }, }) } diff --git a/src/components/dialogs/EmailDialog/data/useUpdateEmail.ts b/src/components/dialogs/EmailDialog/data/useUpdateEmail.ts index 2ec1eb6dc2..7293227f59 100644 --- a/src/components/dialogs/EmailDialog/data/useUpdateEmail.ts +++ b/src/components/dialogs/EmailDialog/data/useUpdateEmail.ts @@ -1,19 +1,26 @@ +import {type Client} from '@atproto/lex' import {useMutation} from '@tanstack/react-query' -import {useAgent} from '#/state/session' +import {usePdsClient, useSessionApi} from '#/state/session' import {useRequestEmailUpdate} from '#/components/dialogs/EmailDialog/data/useRequestEmailUpdate' +import {com} from '#/lexicons' async function updateEmailAndRefreshSession( - agent: ReturnType, + pdsClient: Client, + refreshSession: () => Promise, email: string, token?: string, ) { - await agent.com.atproto.server.updateEmail({email: email.trim(), token}) - await agent.resumeSession(agent.session!) + await pdsClient.call(com.atproto.server.updateEmail, { + email: email.trim(), + token, + }) + await refreshSession() } export function useUpdateEmail() { - const agent = useAgent() + const pdsClient = usePdsClient() + const {refreshSession} = useSessionApi() const {mutateAsync: requestEmailUpdate} = useRequestEmailUpdate() return useMutation< @@ -23,7 +30,12 @@ export function useUpdateEmail() { >({ mutationFn: async ({email, token}: {email: string; token?: string}) => { if (token) { - await updateEmailAndRefreshSession(agent, email, token) + await updateEmailAndRefreshSession( + pdsClient, + refreshSession, + email, + token, + ) return { status: 'success', } @@ -34,7 +46,12 @@ export function useUpdateEmail() { status: 'tokenRequired', } } else { - await updateEmailAndRefreshSession(agent, email, token) + await updateEmailAndRefreshSession( + pdsClient, + refreshSession, + email, + token, + ) return { status: 'success', } 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/MutedWords.tsx b/src/components/dialogs/MutedWords.tsx index a885f1bf5f..21f9a8728e 100644 --- a/src/components/dialogs/MutedWords.tsx +++ b/src/components/dialogs/MutedWords.tsx @@ -1,6 +1,7 @@ import {useCallback, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, sanitizeMutedWordValue} from '@atproto/api' +import {type DatetimeString, toDatetimeString} from '@atproto/syntax' +import {sanitizeMutedWordValue} from '@bsky/sdk/utils' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -35,6 +36,7 @@ import * as Menu from '#/components/Menu' import * as Prompt from '#/components/Prompt' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' const ONE_DAY = 24 * 60 * 60 * 1000 @@ -68,20 +70,20 @@ function MutedWordsInner() { const sanitizedValue = sanitizeMutedWordValue(field) const surfaces = ['tag', targets.includes('content') && 'content'].filter( Boolean, - ) as AppBskyActorDefs.MutedWord['targets'] + ) as app.bsky.actor.defs.MutedWord['targets'] const actorTarget = excludeFollowing ? 'exclude-following' : 'all' const now = Date.now() const rawDuration = durations.at(0) // undefined evaluates to 'forever' - let duration: string | undefined + let duration: DatetimeString | undefined if (rawDuration === '24_hours') { - duration = new Date(now + ONE_DAY).toISOString() + duration = toDatetimeString(new Date(now + ONE_DAY)) } else if (rawDuration === '7_days') { - duration = new Date(now + 7 * ONE_DAY).toISOString() + duration = toDatetimeString(new Date(now + 7 * ONE_DAY)) } else if (rawDuration === '30_days') { - duration = new Date(now + 30 * ONE_DAY).toISOString() + duration = toDatetimeString(new Date(now + 30 * ONE_DAY)) } if (!sanitizedValue || !surfaces.length) { @@ -421,7 +423,7 @@ function MutedWordsInner() { function MutedWordRow({ style, word, -}: ViewStyleProp & {word: AppBskyActorDefs.MutedWord}) { +}: ViewStyleProp & {word: app.bsky.actor.defs.MutedWord}) { const t = useTheme() const {_} = useLingui() const {isPending, mutateAsync: removeMutedWord} = useRemoveMutedWordMutation() @@ -440,7 +442,7 @@ function MutedWordRow({ updateMutedWord({ ...word, expiresAt: days - ? new Date(Date.now() + days * ONE_DAY).toISOString() + ? toDatetimeString(new Date(Date.now() + days * ONE_DAY)) : undefined, }) } diff --git a/src/components/dialogs/PostInteractionSettingsDialog.tsx b/src/components/dialogs/PostInteractionSettingsDialog.tsx index 8a5c7101cc..4b04c9fefa 100644 --- a/src/components/dialogs/PostInteractionSettingsDialog.tsx +++ b/src/components/dialogs/PostInteractionSettingsDialog.tsx @@ -1,10 +1,6 @@ import {useCallback, useMemo, useState} from 'react' import {LayoutAnimation, Text as NestedText, View} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedPostgate, - 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' @@ -36,7 +32,7 @@ import { PostThreadContextProvider, usePostThreadContext, } from '#/state/queries/usePostThread' -import {useAgent, useSession} from '#/state/session' +import {usePdsClient, useSession} from '#/state/session' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' @@ -53,6 +49,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' +import {type app} from '#/lexicons' export type PostInteractionSettingsFormProps = { canSave?: boolean @@ -63,8 +60,8 @@ export type PostInteractionSettingsFormProps = { persist?: boolean onChangePersist?: (v: boolean) => void - postgate: AppBskyFeedPostgate.Record - onChangePostgate: (v: AppBskyFeedPostgate.Record) => void + postgate: app.bsky.feed.postgate.Main + onChangePostgate: (v: app.bsky.feed.postgate.Main) => void threadgateAllowUISettings: ThreadgateAllowUISetting[] onChangeThreadgateAllowUISettings: (v: ThreadgateAllowUISetting[]) => void @@ -135,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 } /** @@ -178,7 +175,7 @@ export function PostInteractionSettingsDialogControlledInner( const {mutateAsync: setThreadgateAllow} = useSetThreadgateAllowMutation() const [editedPostgate, setEditedPostgate] = - useState() + useState() const [editedAllowUISettings, setEditedAllowUISettings] = useState() @@ -697,7 +694,7 @@ export function usePrefetchPostInteractionSettings({ }) { const ax = useAnalytics() const queryClient = useQueryClient() - const agent = useAgent() + const pdsClient = usePdsClient() const getPost = useGetPost() return useCallback(async () => { @@ -706,7 +703,7 @@ export function usePrefetchPostInteractionSettings({ queryClient.prefetchQuery({ queryKey: createPostgateQueryKey(postUri), queryFn: () => - getPostgateRecord({agent, postUri}).then(res => res ?? null), + getPostgateRecord({pdsClient, postUri}).then(res => res ?? null), staleTime: STALE.SECONDS.THIRTY, }), queryClient.prefetchQuery({ @@ -723,5 +720,5 @@ export function usePrefetchPostInteractionSettings({ safeMessage: e.message, }) } - }, [ax, queryClient, agent, postUri, rootPostUri, getPost]) + }, [ax, queryClient, pdsClient, postUri, rootPostUri, getPost]) } diff --git a/src/components/dialogs/SearchablePeopleList.tsx b/src/components/dialogs/SearchablePeopleList.tsx index cf106684a4..fdbdf0b7e3 100644 --- a/src/components/dialogs/SearchablePeopleList.tsx +++ b/src/components/dialogs/SearchablePeopleList.tsx @@ -7,7 +7,7 @@ import { useState, } from 'react' import {TextInput, View} from 'react-native' -import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@bsky/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' @@ -66,11 +66,7 @@ type ErrorItem = { } type Item = - | ProfileItem - | ExistingChatItem - | EmptyItem - | PlaceholderItem - | ErrorItem + ProfileItem | ExistingChatItem | EmptyItem | PlaceholderItem | ErrorItem export function SearchablePeopleList({ title, 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 3e74de5c67..0a528adedb 100644 --- a/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx +++ b/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx @@ -1,13 +1,7 @@ import {View} from 'react-native' -import { - type $Typed, - type AppBskyGraphDefs, - type AppBskyGraphListitem, - type AppBskyGraphStarterpack, - AtUri, - type ComAtprotoRepoApplyWrites, -} from '@atproto/api' import {TID} from '@atproto/common-web' +import {type $Typed} from '@atproto/lex' +import {AtUri, type AtUriString, toDatetimeString} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -20,7 +14,7 @@ import {wait} from '#/lib/async/wait' import {type NavigationProp} from '#/lib/routes/types' import {logger} from '#/logger' import {getAllListMembers} from '#/state/queries/list-members' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' import {atoms as a, platform, useTheme, web} from '#/alf' import {Admonition} from '#/components/Admonition' import {Button, ButtonText} from '#/components/Button' @@ -29,6 +23,7 @@ import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {app, com} from '#/lexicons' import {CreateOrEditListDialog} from './CreateOrEditListDialog' export function CreateListFromStarterPackDialog({ @@ -36,11 +31,12 @@ export function CreateListFromStarterPackDialog({ starterPack, }: { control: Dialog.DialogControlProps - starterPack: AppBskyGraphDefs.StarterPackView + starterPack: app.bsky.graph.defs.StarterPackView }) { const {_} = useLingui() const t = useTheme() - const agent = useAgent() + const appviewClient = useAppviewClient() + const pdsClient = usePdsClient() const ax = useAnalytics() const {currentAccount} = useSession() const navigation = useNavigation() @@ -48,7 +44,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()) @@ -73,16 +69,19 @@ export function CreateListFromStarterPackDialog({ const listItems = await wait( 3000, (async () => { - const items = await getAllListMembers(agent, starterPack.list!.uri) + const items = await getAllListMembers( + appviewClient, + starterPack.list!.uri, + ) if (items.length > 0) { - const listitemWrites: $Typed[] = + const listitemWrites: $Typed[] = items.map(item => { - const listitemRecord: $Typed = { + const listitemRecord: $Typed = { $type: 'app.bsky.graph.listitem', subject: item.subject.did, - list: listUri, - createdAt: new Date().toISOString(), + list: listUri as AtUriString, + createdAt: toDatetimeString(new Date()), } return { $type: 'com.atproto.repo.applyWrites#create', @@ -94,7 +93,7 @@ export function CreateListFromStarterPackDialog({ const chunks = chunk(listitemWrites, 50) for (const c of chunks) { - await agent.com.atproto.repo.applyWrites({ + await pdsClient.call(com.atproto.repo.applyWrites, { repo: currentAccount.did, writes: c, }) @@ -103,10 +102,10 @@ export function CreateListFromStarterPackDialog({ await until( 5, 1e3, - (res: {data: {items: unknown[]}}) => res.data.items.length > 0, + (res: {items: unknown[]}) => res.items.length > 0, () => - agent.app.bsky.graph.getList({ - list: listUri, + appviewClient.call(app.bsky.graph.getList, { + list: listUri as AtUriString, limit: 1, }), ) diff --git a/src/components/dialogs/lists/CreateOrEditListDialog.tsx b/src/components/dialogs/lists/CreateOrEditListDialog.tsx index 7164c3ebeb..c146d489f5 100644 --- a/src/components/dialogs/lists/CreateOrEditListDialog.tsx +++ b/src/components/dialogs/lists/CreateOrEditListDialog.tsx @@ -1,6 +1,6 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {View} from 'react-native' -import {type AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api' +import {RichText as RichTextAPI} from '@bsky/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' @@ -15,7 +15,7 @@ import { useListCreateMutation, useListMetadataMutation, } from '#/state/queries/list' -import {useAgent} from '#/state/session' +import {useAppviewClient} from '#/state/session' import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' import {EditableUserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, useTheme, web} from '#/alf' @@ -27,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 @@ -45,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 }) { @@ -113,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 @@ -133,7 +134,11 @@ function DialogInner({ const {_} = useLingui() const t = useTheme() - const agent = useAgent() + /* + * Facet/mention resolution is an appview job - it resolves handles through + * the appview, and the public fallback keeps it working when logged out. + */ + const appviewClient = useAppviewClient() const control = Dialog.useDialogContext() const { mutateAsync: createListMutation, @@ -163,7 +168,10 @@ 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}), false) + const serialized = richTextToString( + new RichTextAPI({text, facets: facets}), + false, + ) const richText = new RichTextAPI({text: serialized}) richText.detectFacetsWithoutResolution() @@ -224,7 +232,7 @@ function DialogInner({ {cleanNewlines: true}, ) - await richText.detectFacets(agent) + await richText.detectFacets(appviewClient) richText = shortenLinks(richText) richText = stripInvalidMentions(richText) @@ -272,7 +280,7 @@ function DialogInner({ setImageError, activePurpose, isCurateList, - agent, + appviewClient, _, ]) diff --git a/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx b/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx index 8129e9bdb4..78771fe413 100644 --- a/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx +++ b/src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx @@ -1,6 +1,6 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' -import {type AppBskyGraphDefs, type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -22,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({ @@ -30,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, @@ -51,7 +52,7 @@ function DialogInner({ list, onChange, }: { - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView onChange?: ( type: 'add' | 'remove', profile: bsky.profile.AnyProfileView, @@ -88,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) { @@ -106,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 ce58dba31c..f95d891104 100644 --- a/src/components/dms/ActionsWrapper.tsx +++ b/src/components/dms/ActionsWrapper.tsx @@ -1,10 +1,11 @@ import {View} from 'react-native' -import {type ChatBskyConvoDefs, type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky/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({ @@ -14,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 8d67051535..d71befbeee 100644 --- a/src/components/dms/ActionsWrapper.web.tsx +++ b/src/components/dms/ActionsWrapper.web.tsx @@ -1,6 +1,6 @@ import {useCallback, useRef, useState} from 'react' import {Pressable, View} from 'react-native' -import {type ChatBskyConvoDefs, type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky/sdk/moderation' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' @@ -13,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 { @@ -28,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/BlockedByListDialog.tsx b/src/components/dms/BlockedByListDialog.tsx index 2532122087..3fabf12ecf 100644 --- a/src/components/dms/BlockedByListDialog.tsx +++ b/src/components/dms/BlockedByListDialog.tsx @@ -1,6 +1,6 @@ import {Fragment} from 'react' import {View} from 'react-native' -import {type ModerationCause} from '@atproto/api' +import {type ModerationCause} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' 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 bac128c819..d5ae8698ee 100644 --- a/src/components/dms/ChatProfileTabs.tsx +++ b/src/components/dms/ChatProfileTabs.tsx @@ -1,7 +1,7 @@ import {useCallback, useEffect} from 'react' import {type ScrollView, View} from 'react-native' import Animated, {useAnimatedRef, useSharedValue} from 'react-native-reanimated' -import {moderateProfile} from '@atproto/api' +import {moderateProfile} from '@bsky/sdk/moderation' import {useLingui} from '@lingui/react/macro' import {HITSLOP_10} from '#/lib/constants' diff --git a/src/components/dms/ConvoMenu.tsx b/src/components/dms/ConvoMenu.tsx index 2eac8cdb3a..e3414fd070 100644 --- a/src/components/dms/ConvoMenu.tsx +++ b/src/components/dms/ConvoMenu.tsx @@ -1,6 +1,6 @@ import {memo, useCallback} from 'react' import {Keyboard, View} from 'react-native' -import {type ModerationCause} from '@atproto/api' +import {type ModerationCause} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' diff --git a/src/components/dms/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 4e0e71c2f4..4b86eb5f0e 100644 --- a/src/components/dms/InitiateChatFlow.tsx +++ b/src/components/dms/InitiateChatFlow.tsx @@ -7,7 +7,7 @@ import { useState, } from 'react' import {LayoutAnimation, type TextInput, View} from 'react-native' -import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@bsky/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {MAX_GROUP_NAME_GRAPHEME_LENGTH} from '#/lib/constants' diff --git a/src/components/dms/LeaveConvoPrompt.tsx b/src/components/dms/LeaveConvoPrompt.tsx index 5e9943e53f..a69dfcf845 100644 --- a/src/components/dms/LeaveConvoPrompt.tsx +++ b/src/components/dms/LeaveConvoPrompt.tsx @@ -1,14 +1,15 @@ -import {ChatBskyConvoLeaveConvo} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {StackActions, useNavigation} from '@react-navigation/native' import {type NavigationProp} from '#/lib/routes/types' import {isNetworkError} from '#/lib/strings/errors' +import {matchXrpcError} from '#/lib/xrpc-error' import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' import {type DialogOuterProps} from '#/components/Dialog' import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {IS_NATIVE} from '#/env' +import {chat} from '#/lexicons' export function LeaveConvoPrompt({ control, @@ -36,12 +37,15 @@ export function LeaveConvoPrompt({ let errorMessage = l`Could not leave chat` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if (error instanceof ChatBskyConvoLeaveConvo.InvalidConvoError) { - errorMessage = l`Conversation not found.` - } else if ( - error instanceof ChatBskyConvoLeaveConvo.OwnerCannotLeaveError - ) { - errorMessage = l`Owner must lock the group before leaving.` + } else { + switch (matchXrpcError(error, chat.bsky.convo.leaveConvo)) { + case 'InvalidConvo': + errorMessage = l`Conversation not found.` + break + case 'OwnerCannotLeave': + errorMessage = l`Owner must lock the group before leaving.` + break + } } Toast.show(errorMessage, {type: 'error'}) }, diff --git a/src/components/dms/MessageContextMenu.tsx b/src/components/dms/MessageContextMenu.tsx index 1640ecff6d..e16af86cf6 100644 --- a/src/components/dms/MessageContextMenu.tsx +++ b/src/components/dms/MessageContextMenu.tsx @@ -2,11 +2,8 @@ import {memo, useCallback} from 'react' import {Platform} from 'react-native' import {type GestureType} from 'react-native-gesture-handler' import * as Clipboard from 'expo-clipboard' -import { - type ChatBskyConvoDefs, - type ModerationOpts, - RichText, -} from '@atproto/api' +import {type ModerationOpts} from '@bsky/sdk/moderation' +import {RichText} from '@bsky/sdk/richtext' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' @@ -30,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' @@ -41,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'] diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx index 7cf3123cea..ceebea16a6 100644 --- a/src/components/dms/MessageItem.tsx +++ b/src/components/dms/MessageItem.tsx @@ -22,14 +22,8 @@ import Animated, { ZoomIn, ZoomOut, } from 'react-native-reanimated' -import { - AppBskyEmbedRecord, - type ChatBskyActorDefs, - ChatBskyConvoDefs, - ChatBskyEmbedJoinLink, - moderateProfile, - RichText as RichTextAPI, -} from '@atproto/api' +import {moderateProfile} from '@bsky/sdk/moderation' +import {RichText as RichTextAPI} from '@bsky/sdk/richtext' import {plural} from '@lingui/core/macro' import {Trans, useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' @@ -58,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' @@ -76,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, + )) ) } @@ -97,7 +96,7 @@ function isWithinClusterBoundary({ direction, }: { isPending: boolean - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView adjacentMessage: MessageItemNeighbor isFromSameSender: boolean direction: 'prev' | 'next' @@ -109,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) @@ -136,7 +135,7 @@ let MessageItem = ({ isGroupChat?: boolean prevMessage: MessageItemNeighbor nextMessage: MessageItemNeighbor - relatedProfiles: Map + relatedProfiles: Map }): React.ReactNode => { const t = useTheme() const {currentAccount} = useSession() @@ -154,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 @@ -174,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 && @@ -203,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 @@ -245,13 +254,16 @@ let MessageItem = ({ ? t.palette.primary_300 : t.palette.primary_100 - const rt = new RichTextAPI({text: message.text, facets: message.facets}) + const rt = new RichTextAPI({ + text: message.text, + 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 @@ -512,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() @@ -772,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() @@ -787,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) @@ -861,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() @@ -873,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( @@ -904,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/MessagesListBlockedFooter.tsx b/src/components/dms/MessagesListBlockedFooter.tsx index 1fbee421b0..6c3b447d4c 100644 --- a/src/components/dms/MessagesListBlockedFooter.tsx +++ b/src/components/dms/MessagesListBlockedFooter.tsx @@ -1,6 +1,6 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' -import {type ModerationDecision} from '@atproto/api' +import {type ModerationDecision} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useProfileShadow} from '#/state/cache/profile-shadow' diff --git a/src/components/dms/MessagesListHeader.tsx b/src/components/dms/MessagesListHeader.tsx index dc42965dac..e691486554 100644 --- a/src/components/dms/MessagesListHeader.tsx +++ b/src/components/dms/MessagesListHeader.tsx @@ -1,6 +1,6 @@ import {useMemo} from 'react' import {View} from 'react-native' -import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@bsky/sdk/moderation' import {useLingui} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' 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 1d704766c2..a4e94c668d 100644 --- a/src/components/dms/components/GroupChatProfileCard.tsx +++ b/src/components/dms/components/GroupChatProfileCard.tsx @@ -1,5 +1,5 @@ import {View} from 'react-native' -import {moderateProfile, type ModerationOpts} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@bsky/sdk/moderation' import {Trans} from '@lingui/react/macro' import {sanitizeDisplayName} from '#/lib/strings/display-names' diff --git a/src/components/dms/dialogs/NewChatDialog.tsx b/src/components/dms/dialogs/NewChatDialog.tsx index fd9eaa8880..7ee103ef9b 100644 --- a/src/components/dms/dialogs/NewChatDialog.tsx +++ b/src/components/dms/dialogs/NewChatDialog.tsx @@ -1,12 +1,9 @@ import {useCallback} from 'react' -import { - ChatBskyConvoGetConvoForMembers, - ChatBskyGroupCreateGroup, -} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification' import {isNetworkError} from '#/lib/strings/errors' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useCreateGroupChat} from '#/state/queries/messages/create-group-chat' import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members' @@ -19,6 +16,7 @@ import {InitiateChatFlow} from '#/components/dms/InitiateChatFlow' import {MessagePlus_Stroke2_Corner0_Rounded as NewChatIcon} from '#/components/icons/Message' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {chat} from '#/lexicons' export function NewChat({ control, @@ -54,27 +52,24 @@ export function NewChat({ let errorMessage = l`An issue occurred starting the chat, please try again.` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.AccountSuspendedError - ) { - errorMessage = l`Suspended accounts cannot participate in chat.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.BlockedActorError - ) { - errorMessage = l`This user has blocked you and cannot be messaged.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.MessagesDisabledError - ) { - errorMessage = l`This user has disabled chat and cannot be messaged.` - } else if ( - error instanceof - ChatBskyConvoGetConvoForMembers.NotFollowedBySenderError - ) { - errorMessage = l`Chat recipient is not followed by the sender.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.RecipientNotFoundError - ) { - errorMessage = l`Unable to find the selected recipient.` + } else { + switch (matchXrpcError(error, chat.bsky.convo.getConvoForMembers)) { + case 'AccountSuspended': + errorMessage = l`Suspended accounts cannot participate in chat.` + break + case 'BlockedActor': + errorMessage = l`This user has blocked you and cannot be messaged.` + break + case 'MessagesDisabled': + errorMessage = l`This user has disabled chat and cannot be messaged.` + break + case 'NotFollowedBySender': + errorMessage = l`Chat recipient is not followed by the sender.` + break + case 'RecipientNotFound': + errorMessage = l`Unable to find the selected recipient.` + break + } } Toast.show(errorMessage, { type: 'error', @@ -92,29 +87,27 @@ export function NewChat({ let errorMessage = l`An issue occurred starting the group chat, please try again.` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if ( - error instanceof ChatBskyGroupCreateGroup.AccountSuspendedError - ) { - errorMessage = l`Suspended accounts cannot participate in a group chat.` - } else if (error instanceof ChatBskyGroupCreateGroup.BlockedActorError) { - errorMessage = l`One of the selected recipients has blocked you and cannot be messaged.` - } else if ( - error instanceof - ChatBskyGroupCreateGroup.NewAccountCannotCreateGroupError - ) { - errorMessage = l`You cannot create a group chat yet.` - } else if ( - error instanceof ChatBskyGroupCreateGroup.NotFollowedBySenderError - ) { - errorMessage = l`A selected recipient is not followed by the sender.` - } else if ( - error instanceof ChatBskyGroupCreateGroup.RecipientNotFoundError - ) { - errorMessage = l`Unable to find a selected recipient.` - } else if ( - error instanceof ChatBskyGroupCreateGroup.UserForbidsGroupsError - ) { - errorMessage = l`One of the selected recipients does not allow group chats.` + } else { + switch (matchXrpcError(error, chat.bsky.group.createGroup)) { + case 'AccountSuspended': + errorMessage = l`Suspended accounts cannot participate in a group chat.` + break + case 'BlockedActor': + errorMessage = l`One of the selected recipients has blocked you and cannot be messaged.` + break + case 'NewAccountCannotCreateGroup': + errorMessage = l`You cannot create a group chat yet.` + break + case 'NotFollowedBySender': + errorMessage = l`A selected recipient is not followed by the sender.` + break + case 'RecipientNotFound': + errorMessage = l`Unable to find a selected recipient.` + break + case 'UserForbidsGroups': + errorMessage = l`One of the selected recipients does not allow group chats.` + break + } } Toast.show(errorMessage, { type: 'error', diff --git a/src/components/dms/dialogs/ShareViaChatDialog.tsx b/src/components/dms/dialogs/ShareViaChatDialog.tsx index 0463cc694d..f26dd47a57 100644 --- a/src/components/dms/dialogs/ShareViaChatDialog.tsx +++ b/src/components/dms/dialogs/ShareViaChatDialog.tsx @@ -1,11 +1,8 @@ import {useCallback, useState} from 'react' -import { - ChatBskyConvoGetConvoForMembers, - ChatBskyGroupCreateGroup, -} from '@atproto/api' import {useLingui} from '@lingui/react/macro' import {isNetworkError} from '#/lib/strings/errors' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useCreateGroupChat} from '#/state/queries/messages/create-group-chat' import {useGetConvoForMembers} from '#/state/queries/messages/get-convo-for-members' @@ -14,6 +11,7 @@ import {SearchablePeopleList} from '#/components/dialogs/SearchablePeopleList' import {InitiateChatFlow} from '#/components/dms/InitiateChatFlow' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' +import {chat} from '#/lexicons' export function SendViaChatDialog({ control, @@ -69,27 +67,24 @@ function SendViaChatDialogInner({ let errorMessage = l`An issue occurred starting the chat, please try again.` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.AccountSuspendedError - ) { - errorMessage = l`Suspended accounts cannot participate in chat.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.BlockedActorError - ) { - errorMessage = l`This user has blocked you and cannot be messaged.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.MessagesDisabledError - ) { - errorMessage = l`This user has disabled chat and cannot be messaged.` - } else if ( - error instanceof - ChatBskyConvoGetConvoForMembers.NotFollowedBySenderError - ) { - errorMessage = l`Chat recipient is not followed by the sender.` - } else if ( - error instanceof ChatBskyConvoGetConvoForMembers.RecipientNotFoundError - ) { - errorMessage = l`Unable to find the selected recipient.` + } else { + switch (matchXrpcError(error, chat.bsky.convo.getConvoForMembers)) { + case 'AccountSuspended': + errorMessage = l`Suspended accounts cannot participate in chat.` + break + case 'BlockedActor': + errorMessage = l`This user has blocked you and cannot be messaged.` + break + case 'MessagesDisabled': + errorMessage = l`This user has disabled chat and cannot be messaged.` + break + case 'NotFollowedBySender': + errorMessage = l`Chat recipient is not followed by the sender.` + break + case 'RecipientNotFound': + errorMessage = l`Unable to find the selected recipient.` + break + } } Toast.show(errorMessage, { type: 'error', @@ -108,29 +103,27 @@ function SendViaChatDialogInner({ let errorMessage = l`An issue occurred starting the group chat, please try again.` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if ( - error instanceof ChatBskyGroupCreateGroup.AccountSuspendedError - ) { - errorMessage = l`Suspended accounts cannot participate in a group chat.` - } else if (error instanceof ChatBskyGroupCreateGroup.BlockedActorError) { - errorMessage = l`One of the selected recipients has blocked you and cannot be messaged.` - } else if ( - error instanceof - ChatBskyGroupCreateGroup.NewAccountCannotCreateGroupError - ) { - errorMessage = l`You cannot create a group chat yet.` - } else if ( - error instanceof ChatBskyGroupCreateGroup.NotFollowedBySenderError - ) { - errorMessage = l`A selected recipient is not followed by the sender.` - } else if ( - error instanceof ChatBskyGroupCreateGroup.RecipientNotFoundError - ) { - errorMessage = l`Unable to find a selected recipient.` - } else if ( - error instanceof ChatBskyGroupCreateGroup.UserForbidsGroupsError - ) { - errorMessage = l`One of the selected recipients does not allow group chats.` + } else { + switch (matchXrpcError(error, chat.bsky.group.createGroup)) { + case 'AccountSuspended': + errorMessage = l`Suspended accounts cannot participate in a group chat.` + break + case 'BlockedActor': + errorMessage = l`One of the selected recipients has blocked you and cannot be messaged.` + break + case 'NewAccountCannotCreateGroup': + errorMessage = l`You cannot create a group chat yet.` + break + case 'NotFollowedBySender': + errorMessage = l`A selected recipient is not followed by the sender.` + break + case 'RecipientNotFound': + errorMessage = l`Unable to find a selected recipient.` + break + case 'UserForbidsGroups': + errorMessage = l`One of the selected recipients does not allow group chats.` + break + } } Toast.show(errorMessage, { type: 'error', 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..91a736f269 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 @@ -16,18 +11,16 @@ import {BSKY_APP_HOST, toShortUrl} from '#/lib/strings/url-helpers' * record (post/feed/list/etc.) so the caller can render a translated label. */ type ReplyEmbedSummary = - | {type: 'external'; uri: string} - | {type: 'post'} - | {type: 'unknown'} + {type: 'external'; uri: string} | {type: 'post'} | {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 +38,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 +72,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 ce0d30a8fd..2618a84db8 100644 --- a/src/components/dms/util.ts +++ b/src/components/dms/util.ts @@ -1,10 +1,5 @@ -import { - type $Typed, - ChatBskyActorDefs, - ChatBskyConvoDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {type $Typed} from '@atproto/lex' +import {moderateProfile, type ModerationOpts} from '@bsky/sdk/moderation' import {EMOJI_REACTION_LIMIT} from '#/lib/constants' import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted' @@ -13,6 +8,7 @@ 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 @@ -66,9 +62,7 @@ export function resolveAllowGroupInvites( chat: {allowIncoming?: string; allowGroupInvites?: string} | undefined, ): 'all' | 'none' | 'following' { return (chat?.allowGroupInvites ?? chat?.allowIncoming ?? 'following') as - | 'all' - | 'none' - | 'following' + 'all' | 'none' | 'following' } export function localDateString(date: Date) { @@ -81,7 +75,7 @@ export function localDateString(date: Date) { } export function hasAlreadyReacted( - message: ChatBskyConvoDefs.MessageView, + message: chat.bsky.convo.defs.MessageView, myDid: string | undefined, emoji: string, ): boolean { @@ -102,9 +96,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) @@ -113,7 +107,7 @@ export function filterBlockedReactions( } export function hasReachedReactionLimit( - message: ChatBskyConvoDefs.MessageView, + message: chat.bsky.convo.defs.MessageView, myDid: string | undefined, ): boolean { if (!message.reactions) { @@ -174,25 +168,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 } @@ -203,24 +197,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} @@ -242,12 +226,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) { @@ -290,7 +269,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/hooks/useRichText.ts b/src/components/hooks/useRichText.ts index a1334bb635..a2d25055b5 100644 --- a/src/components/hooks/useRichText.ts +++ b/src/components/hooks/useRichText.ts @@ -1,13 +1,18 @@ import {useEffect, useState} from 'react' -import {RichText as RichTextAPI} from '@atproto/api' +import {RichText as RichTextAPI} from '@bsky/sdk/richtext' -import {useAgent} from '#/state/session' +import {useAppviewClient} from '#/state/session' export function useRichText(text: string): [RichTextAPI, boolean] { const [prevText, setPrevText] = useState(text) const [rawRT, setRawRT] = useState(() => new RichTextAPI({text})) const [resolvedRT, setResolvedRT] = useState(null) - const agent = useAgent() + /* + * Facet/mention resolution is an appview job - it resolves handles via + * `com.atproto.identity.resolveHandle` through the appview. The public + * fallback keeps mentions working on logged-out surfaces. + */ + const client = useAppviewClient() if (text !== prevText) { setPrevText(text) setRawRT(new RichTextAPI({text})) @@ -19,7 +24,7 @@ export function useRichText(text: string): [RichTextAPI, boolean] { async function resolveRTFacets() { // new each time const resolvedRT = new RichTextAPI({text}) - await resolvedRT.detectFacets(agent) + await resolvedRT.detectFacets(client) if (!ignore) { setResolvedRT(resolvedRT) } @@ -28,7 +33,7 @@ export function useRichText(text: string): [RichTextAPI, boolean] { return () => { ignore = true } - }, [text, agent]) + }, [text, client]) const isResolving = resolvedRT === null return [resolvedRT ?? rawRT, isResolving] } 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 eea54b5ed9..8634c2eb6d 100644 --- a/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts +++ b/src/components/images/Gallery/maybeApplyGalleryOffsetStyles.ts @@ -1,16 +1,9 @@ -import { - AppBskyEmbedGallery, - AppBskyEmbedImages, - AppBskyEmbedRecordWithMedia, - type AppBskyFeedDefs, - AppBskyFeedPost, - type ModerationCause, - type ModerationUI, -} from '@atproto/api' +import {type ModerationCause, type ModerationUI} from '@bsky/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} @@ -23,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 } @@ -48,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 @@ -79,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 7c506a634d..9266eb7a91 100644 --- a/src/components/intents/GroupChatJoinDialog.tsx +++ b/src/components/intents/GroupChatJoinDialog.tsx @@ -1,11 +1,6 @@ import {useEffect} from 'react' import {View} from 'react-native' -import { - ChatBskyGroupDefs, - ChatBskyGroupRequestJoin, - ChatBskyGroupWithdrawJoinRequest, - moderateProfile, -} from '@atproto/api' +import {moderateProfile} from '@bsky/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' @@ -16,6 +11,7 @@ import {makeProfileLink} from '#/lib/routes/links' import {type NavigationProp} from '#/lib/routes/types' import {isNetworkError} from '#/lib/strings/errors' import {sanitizeHandle} from '#/lib/strings/handles' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useModerationOpts} from '#/state/preferences/moderation-opts' import { @@ -49,6 +45,8 @@ import {Loader} from '#/components/Loader' 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() { @@ -147,33 +145,40 @@ function GroupChatJoinDialogContent({code}: {code?: string}) { let errorMessage = l`Failed to join the group chat. Please try again.` if (isNetworkError(error)) { errorMessage = l`There was a problem with your internet connection, please try again` - } else if (error instanceof ChatBskyGroupRequestJoin.ConvoLockedError) { - errorMessage = l`This conversation is locked.` - } else if ( - error instanceof ChatBskyGroupRequestJoin.FollowRequiredError - ) { - errorMessage = l`Only followers can join this group chat.` - } else if (error instanceof ChatBskyGroupRequestJoin.InvalidCodeError) { - errorMessage = l`Invalid group chat code.` - } else if ( - error instanceof ChatBskyGroupRequestJoin.LinkDisabledError - ) { - errorMessage = l`This invite link has been disabled.` - } else if ( - error instanceof ChatBskyGroupRequestJoin.MemberLimitReachedError - ) { - errorMessage = l`The member limit has been reached.` - const preview = data?.joinLinkPreviews[0] - if ( - ChatBskyGroupDefs.isJoinLinkPreviewView(preview) && - preview.convo?.id - ) { - ax.metric('groupchat:join:memberLimitReached', { - convoId: preview.convo.id, - }) + } else { + switch (matchXrpcError(error, chat.bsky.group.requestJoin)) { + case 'ConvoLocked': + errorMessage = l`This conversation is locked.` + break + case 'FollowRequired': + errorMessage = l`Only followers can join this group chat.` + break + case 'InvalidCode': + errorMessage = l`Invalid group chat code.` + break + case 'LinkDisabled': + errorMessage = l`This invite link has been disabled.` + break + case 'MemberLimitReached': { + errorMessage = l`The member limit has been reached.` + const preview = data?.joinLinkPreviews[0] + if ( + bsky.isType( + chat.bsky.group.defs.joinLinkPreviewView, + preview, + ) && + preview.convo?.id + ) { + ax.metric('groupchat:join:memberLimitReached', { + convoId: preview.convo.id, + }) + } + break + } + case 'UserKicked': + errorMessage = l`You have been previously removed from this group and can’t join it using this link.` + break } - } else if (error instanceof ChatBskyGroupRequestJoin.UserKickedError) { - errorMessage = l`You have been previously removed from this group and can’t join it using this link.` } Toast.show(errorMessage) }, @@ -194,8 +199,8 @@ function GroupChatJoinDialogContent({code}: {code?: string}) { if (isNetworkError(error)) { errorMessage = l`There was a problem with your internet connection, please try again` } else if ( - error instanceof - ChatBskyGroupWithdrawJoinRequest.InvalidJoinRequestError + matchXrpcError(error, chat.bsky.group.withdrawJoinRequest) === + 'InvalidJoinRequest' ) { errorMessage = l`Invalid rescind request.` } @@ -252,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/intents/VerifyEmailIntentDialog.tsx b/src/components/intents/VerifyEmailIntentDialog.tsx index b3504766f8..272804af0e 100644 --- a/src/components/intents/VerifyEmailIntentDialog.tsx +++ b/src/components/intents/VerifyEmailIntentDialog.tsx @@ -4,7 +4,7 @@ import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' -import {useAgent, useSession} from '#/state/session' +import {usePdsClient, useSession} from '#/state/session' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' @@ -16,6 +16,7 @@ import {useIntentDialogs} from '#/components/intents/IntentDialogs' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {IS_NATIVE} from '#/env' +import {com} from '#/lexicons' export function VerifyEmailIntentDialog() { const {verifyEmailDialogControl: control} = useIntentDialogs() @@ -37,7 +38,7 @@ function Inner({}: {control: DialogControlProps}) { 'loading' | 'success' | 'failure' | 'resent' >('loading') const [sending, setSending] = useState(false) - const agent = useAgent() + const client = usePdsClient() const {currentAccount} = useSession() const {mutate: confirmEmail} = useConfirmEmail({ onSuccess: () => setStatus('success'), @@ -52,7 +53,7 @@ function Inner({}: {control: DialogControlProps}) { const onPressResendEmail = async () => { setSending(true) - await agent.com.atproto.server.requestEmailConfirmation() + await client.call(com.atproto.server.requestEmailConfirmation) setSending(false) setStatus('resent') } diff --git a/src/components/interstitials/FeedTrendingTopics.tsx b/src/components/interstitials/FeedTrendingTopics.tsx index 4d45001f1c..4d3d9fb0fa 100644 --- a/src/components/interstitials/FeedTrendingTopics.tsx +++ b/src/components/interstitials/FeedTrendingTopics.tsx @@ -1,6 +1,6 @@ import {useMemo} from 'react' import {Pressable, View} from 'react-native' -import {type AppBskyUnspeccedDefs, moderateProfile} from '@atproto/api' +import {moderateProfile} from '@bsky/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -29,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 @@ -182,7 +183,7 @@ function TrendRow({ recId, onPress, }: ViewStyleProp & { - trend: AppBskyUnspeccedDefs.TrendView + trend: app.bsky.unspecced.defs.TrendView rank: number feedSliceIndex: number recId?: string @@ -290,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 5432672d1e..f40740f179 100644 --- a/src/components/moderation/AppealForm.tsx +++ b/src/components/moderation/AppealForm.tsx @@ -1,7 +1,7 @@ import {useState} from 'react' import {View} from 'react-native' -import {type ComAtprotoLabelDefs, ToolsOzoneReportDefs} from '@atproto/api' -import {XRPCError} from '@atproto/api' +import {XrpcResponseError} from '@atproto/lex' +import {type AtUriString, type DidString} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -12,7 +12,7 @@ import {useLabelInfo} from '#/lib/moderation/useLabelInfo' import {makeProfileLink} from '#/lib/routes/links' import {sanitizeHandle} from '#/lib/strings/handles' import {logger} from '#/logger' -import {useAgent} from '#/state/session' +import {useAppviewClient} from '#/state/session' import {atoms as a, useBreakpoints} from '#/alf' import {Admonition} from '#/components/Admonition' import {Button, ButtonIcon, ButtonText} from '#/components/Button' @@ -22,13 +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, tools} from '#/lexicons' export function AppealForm({ label, control, onPressBack, }: { - label: ComAtprotoLabelDefs.Label + label: com.atproto.label.defs.Label control: Dialog.DialogOuterProps['control'] onPressBack: () => void }) { @@ -38,7 +39,7 @@ export function AppealForm({ const [details, setDetails] = useState('') const {subject} = useLabelSubject({label}) const isAccountReport = 'did' in subject - const agent = useAgent() + const client = useAppviewClient() const sourceName = labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src @@ -46,28 +47,38 @@ export function AppealForm({ const {mutate, isPending} = useMutation({ mutationFn: async () => { - const $type = !isAccountReport - ? 'com.atproto.repo.strongRef' - : 'com.atproto.admin.defs#repoRef' - await agent.createModerationReport( + await client.call( + com.atproto.moderation.createReport, { - reasonType: ToolsOzoneReportDefs.REASONAPPEAL, - subject: { - $type, - ...subject, - }, + 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 + * as the account did. + */ + subject: isAccountReport + ? { + $type: 'com.atproto.admin.defs#repoRef', + did: subject.did as DidString, + } + : { + $type: 'com.atproto.repo.strongRef', + uri: subject.uri as AtUriString, + cid: subject.cid, + }, reason: details, }, - { - encoding: 'application/json', - headers: { - 'atproto-proxy': `${label.src}#atproto_labeler`, - }, - }, + // the appeal goes to the labeler that applied the label + {service: `${label.src}#atproto_labeler`}, ) }, onError: err => { - if (err instanceof XRPCError && err.error === 'AlreadyAppealed') { + /* + * `AlreadyAppealed` is real server behavior that createReport's lexicon + * does NOT declare, so `matchXrpcError` cannot see it and the raw error + * code is checked instead. Worth an upstream PR to declare it. + */ + if (err instanceof XrpcResponseError && err.error === 'AlreadyAppealed') { setError( _( msg`You've already appealed this label and it's being reviewed by our moderation team.`, diff --git a/src/components/moderation/BlockDialog.tsx b/src/components/moderation/BlockDialog.tsx index a575cd7433..b49f6444c6 100644 --- a/src/components/moderation/BlockDialog.tsx +++ b/src/components/moderation/BlockDialog.tsx @@ -1,14 +1,10 @@ import {useState} from 'react' import {View} from 'react-native' -import { - type ChatBskyConvoDefs, - ChatBskyConvoLeaveConvo, - ChatBskyGroupRemoveMembers, -} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' import {isNetworkError} from '#/lib/strings/errors' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {type Shadow} from '#/state/cache/types' import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' @@ -27,9 +23,10 @@ import {parseConvoView} from '#/components/dms/util' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' 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 @@ -254,7 +251,7 @@ function MutualGroupChat({ onOptimisticallyRemoveConvo, onRestoreConvo, }: { - view: ChatBskyConvoDefs.ConvoView + view: chat.bsky.convo.defs.ConvoView profileDid: string currentConvoId?: string onOptimisticallyRemoveConvo: (convoId: string) => void @@ -282,12 +279,15 @@ function MutualGroupChat({ let errorMessage = l`Could not leave chat.` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if (error instanceof ChatBskyConvoLeaveConvo.InvalidConvoError) { - errorMessage = l`Chat not found.` - } else if ( - error instanceof ChatBskyConvoLeaveConvo.OwnerCannotLeaveError - ) { - errorMessage = l`Chat owners cannot leave a group chat.` + } else { + switch (matchXrpcError(error, chat.bsky.convo.leaveConvo)) { + case 'InvalidConvo': + errorMessage = l`Chat not found.` + break + case 'OwnerCannotLeave': + errorMessage = l`Chat owners cannot leave a group chat.` + break + } } Toast.show(errorMessage, {type: 'error'}) }, @@ -308,14 +308,15 @@ function MutualGroupChat({ let errorMessage = l`Could not remove member.` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if ( - error instanceof ChatBskyGroupRemoveMembers.InvalidConvoError - ) { - errorMessage = l`Chat not found.` - } else if ( - error instanceof ChatBskyGroupRemoveMembers.InsufficientRoleError - ) { - errorMessage = l`You must be a chat owner to remove a member.` + } else { + switch (matchXrpcError(error, chat.bsky.group.removeMembers)) { + case 'InvalidConvo': + errorMessage = l`Chat not found.` + break + case 'InsufficientRole': + errorMessage = l`You must be a chat owner to remove a member.` + break + } } Toast.show(errorMessage, {type: 'error'}) }, diff --git a/src/components/moderation/ContentHider.tsx b/src/components/moderation/ContentHider.tsx index cfdc6c7e12..c33c17a881 100644 --- a/src/components/moderation/ContentHider.tsx +++ b/src/components/moderation/ContentHider.tsx @@ -5,7 +5,7 @@ import { View, type ViewStyle, } from 'react-native' -import {type ModerationUI} from '@atproto/api' +import {type ModerationUI} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import { diff --git a/src/components/moderation/Hider.tsx b/src/components/moderation/Hider.tsx index 74cc9bd1c8..afe43e7000 100644 --- a/src/components/moderation/Hider.tsx +++ b/src/components/moderation/Hider.tsx @@ -1,5 +1,5 @@ import {createContext, useContext, useState} from 'react' -import {type ModerationUI} from '@atproto/api' +import {type ModerationUI} from '@bsky/sdk/moderation' import { type ModerationCauseDescription, diff --git a/src/components/moderation/LabelPreference.tsx b/src/components/moderation/LabelPreference.tsx index a654b46e24..227ccae36e 100644 --- a/src/components/moderation/LabelPreference.tsx +++ b/src/components/moderation/LabelPreference.tsx @@ -2,7 +2,7 @@ import {View} from 'react-native' import { type InterpretedLabelValueDefinition, type LabelPreference, -} from '@atproto/api' +} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/components/moderation/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/ModerationDetailsDialog.tsx b/src/components/moderation/ModerationDetailsDialog.tsx index bf19dc0363..d4b816e5cd 100644 --- a/src/components/moderation/ModerationDetailsDialog.tsx +++ b/src/components/moderation/ModerationDetailsDialog.tsx @@ -1,6 +1,6 @@ import {useState} from 'react' import {View} from 'react-native' -import {type ModerationCause} from '@atproto/api' +import {type ModerationCause} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/components/moderation/PostAlerts.tsx b/src/components/moderation/PostAlerts.tsx index af8329bec5..c7a00e76eb 100644 --- a/src/components/moderation/PostAlerts.tsx +++ b/src/components/moderation/PostAlerts.tsx @@ -1,10 +1,5 @@ import {type StyleProp, View, type ViewStyle} from 'react-native' -import { - type AppBskyFeedDefs, - type ComAtprotoLabelDefs, - type ModerationCause, - type ModerationUI, -} from '@atproto/api' +import {type ModerationCause, type ModerationUI} from '@bsky/sdk/moderation' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' @@ -20,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, @@ -28,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 @@ -52,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 ?? []), @@ -138,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 5019b7c3e3..178abd8e18 100644 --- a/src/components/moderation/PostHider.tsx +++ b/src/components/moderation/PostHider.tsx @@ -7,11 +7,7 @@ import { View, type ViewStyle, } from 'react-native' -import { - type AppBskyActorDefs, - type ModerationCause, - type ModerationUI, -} from '@atproto/api' +import {type ModerationCause, type ModerationUI} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -28,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/ProfileHeaderAlerts.tsx b/src/components/moderation/ProfileHeaderAlerts.tsx index e6a13e2944..8b335fa73c 100644 --- a/src/components/moderation/ProfileHeaderAlerts.tsx +++ b/src/components/moderation/ProfileHeaderAlerts.tsx @@ -1,5 +1,5 @@ import {type StyleProp, type ViewStyle} from 'react-native' -import {type ModerationDecision} from '@atproto/api' +import {type ModerationDecision} from '@bsky/sdk/moderation' import {getModerationCauseKey, unique} from '#/lib/moderation' import * as Pills from '#/components/Pills' diff --git a/src/components/moderation/ReportDialog/action.ts b/src/components/moderation/ReportDialog/action.ts index 20303ddef5..da94e5dde2 100644 --- a/src/components/moderation/ReportDialog/action.ts +++ b/src/components/moderation/ReportDialog/action.ts @@ -1,22 +1,21 @@ -import { - type $Typed, - BSKY_LABELER_DID, - type ChatBskyConvoDefs, - type ComAtprotoModerationCreateReport, -} from '@atproto/api' +import {type AtUriString, type DidString} from '@atproto/syntax' +import {api} from '@bsky/sdk' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useMutation} from '@tanstack/react-query' import {logger} from '#/logger' -import {useAgent} from '#/state/session' +import {useAppviewClient} from '#/state/session' +import {com} from '#/lexicons' import {NEW_TO_OLD_REASONS_MAP, REPORT_MOD_TOOL_NAME} from './const' import {type ReportState} from './state' import {type ParsedReportSubject} from './types' +type ReportInput = com.atproto.moderation.createReport.$InputBody + export function useSubmitReportMutation() { const {_} = useLingui() - const agent = useAgent() + const client = useAppviewClient() return useMutation({ async mutationFn({ @@ -59,13 +58,7 @@ export function useSubmitReportMutation() { reasonType = backwardsCompatibleReasonType } - let report: - | ComAtprotoModerationCreateReport.InputSchema - | (Omit & { - subject: - | $Typed - | $Typed - }) + let report: ReportInput switch (subject.type) { case 'account': { @@ -74,7 +67,8 @@ export function useSubmitReportMutation() { reason: state.details, subject: { $type: 'com.atproto.admin.defs#repoRef', - did: subject.did, + // the parsed subject holds the did as a plain string + did: subject.did as DidString, }, } break @@ -89,7 +83,8 @@ export function useSubmitReportMutation() { reason: state.details, subject: { $type: 'com.atproto.repo.strongRef', - uri: subject.uri, + // the parsed subject carries an at-uri read off a view + uri: subject.uri as AtUriString, cid: subject.cid, }, } @@ -99,12 +94,12 @@ export function useSubmitReportMutation() { report = { reasonType, reason: state.details, - subject: { + subject: toOpenSubject({ $type: 'chat.bsky.convo.defs#messageRef', messageId: subject.message.id, convoId: subject.convoId, did: subject.message.sender.did, - }, + }), } break } @@ -112,11 +107,11 @@ export function useSubmitReportMutation() { report = { reasonType, reason: state.details, - subject: { + subject: toOpenSubject({ $type: 'chat.bsky.convo.defs#convoRef', convoId: subject.convoId, did: subject.did, - }, + }), } break } @@ -126,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 @@ -145,13 +140,30 @@ export function useSubmitReportMutation() { report, }) } else { - await agent.createModerationReport(report, { - encoding: 'application/json', - headers: { - 'atproto-proxy': `${labeler.creator.did}#atproto_labeler`, - }, + /* + * Reports go to the labeler the user selected rather than Bluesky's, so + * the proxy target is built per call from that labeler's creator did. + */ + await client.call(com.atproto.moderation.createReport, report, { + service: `${labeler.creator.did}#atproto_labeler`, }) } }, }) } + +/** + * Widen a chat convo ref into `createReport`'s subject union. + * + * The lexicon declares only `com.atproto.admin.defs#repoRef` and + * `com.atproto.repo.strongRef`, but leaves the union OPEN, and the chat service + * accepts its own refs there. An open union types its unknown arm as + * `{$type: Unknown$Type}`, which a concrete chat ref does not structurally + * satisfy, so the widening is asserted here once rather than at each call site. + */ +function toOpenSubject(ref: { + $type: string + [key: string]: unknown +}): ReportInput['subject'] { + return ref as unknown as ReportInput['subject'] +} 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 7c63d04a47..c5c60ccba4 100644 --- a/src/components/moderation/ReportDialog/index.tsx +++ b/src/components/moderation/ReportDialog/index.tsx @@ -7,7 +7,7 @@ import { useState, } from 'react' import {Pressable, type ScrollView, View} from 'react-native' -import {type AppBskyLabelerDefs, BSKY_LABELER_DID} from '@atproto/api' +import {api} from '@bsky/sdk' import {Trans, useLingui} from '@lingui/react/macro' import {wait} from '#/lib/async/wait' @@ -38,6 +38,7 @@ import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' import {useSubmitReportMutation} from './action' import { BSKY_LABELER_ONLY_REPORT_REASONS, @@ -222,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 @@ -643,7 +644,7 @@ function Inner( {videoTimestampSeconds !== undefined && - state.selectedLabeler?.creator.did === BSKY_LABELER_DID && ( + state.selectedLabeler?.creator.did === api.moderation.did && ( void + labeler: app.bsky.labeler.defs.LabelerViewDetailed + onSelect?: (option: app.bsky.labeler.defs.LabelerViewDetailed) => void }) { const t = useTheme() const {t: l} = useLingui() diff --git a/src/components/moderation/ReportDialog/state.test.ts b/src/components/moderation/ReportDialog/state.test.ts index e6f399ddf1..6f64c1a7c3 100644 --- a/src/components/moderation/ReportDialog/state.test.ts +++ b/src/components/moderation/ReportDialog/state.test.ts @@ -1,8 +1,4 @@ -import { - type AppBskyLabelerDefs, - ToolsOzoneReportDefs as OzoneReportDefs, -} from '@atproto/api' - +import {type app, tools} from '#/lexicons' import { getNciiQualificationOutcome, initialState, @@ -12,12 +8,12 @@ import { const nciiOption = { title: 'Non-consensual intimate imagery', - reason: OzoneReportDefs.REASONSEXUALNCII, + reason: tools.ozone.report.defs.reasonSexualNCII.value, } const otherOption = { title: 'Unlabeled adult content', - reason: OzoneReportDefs.REASONSEXUALUNLABELED, + reason: tools.ozone.report.defs.reasonSexualUnlabeled.value, } function selectNciiOption(state: ReportState = initialState) { @@ -83,7 +79,7 @@ describe('reducer NCII qualification', () => { let state = selectNciiOption() state = reducer(state, { type: 'selectLabeler', - labeler: {} as AppBskyLabelerDefs.LabelerViewDetailed, + labeler: {} as app.bsky.labeler.defs.LabelerViewDetailed, }) expect(state.activeStepIndex1).toBe(2) }) @@ -92,7 +88,7 @@ describe('reducer NCII qualification', () => { let state = selectNciiOption() state = reducer(state, { type: 'selectLabeler', - labeler: {} as AppBskyLabelerDefs.LabelerViewDetailed, + labeler: {} as app.bsky.labeler.defs.LabelerViewDetailed, }) state = reducer(state, { type: 'answerNciiQuestion', @@ -110,7 +106,7 @@ describe('reducer NCII qualification', () => { }) describe('reducer video timestamp', () => { - const labeler = {} as AppBskyLabelerDefs.LabelerViewDetailed + const labeler = {} as app.bsky.labeler.defs.LabelerViewDetailed const opted: ReportState = {...initialState, includeVideoTimestamp: true} it('is off by default', () => { diff --git a/src/components/moderation/ReportDialog/state.ts b/src/components/moderation/ReportDialog/state.ts index 921074952d..9be5145ce7 100644 --- a/src/components/moderation/ReportDialog/state.ts +++ b/src/components/moderation/ReportDialog/state.ts @@ -1,13 +1,9 @@ -import { - type AppBskyLabelerDefs, - ToolsOzoneReportDefs as OzoneReportDefs, -} from '@atproto/api' - import {OTHER_REPORT_REASONS} from '#/components/moderation/ReportDialog/const' import { type ReportCategoryConfig, type ReportOption, } from '#/components/moderation/ReportDialog/utils/useReportOptions' +import {type app, tools} from '#/lexicons' export type NciiQualification = { isDepicted?: boolean @@ -16,7 +12,7 @@ export type NciiQualification = { export type ReportState = { selectedCategory?: ReportCategoryConfig selectedOption?: ReportOption - selectedLabeler?: AppBskyLabelerDefs.LabelerViewDetailed + selectedLabeler?: app.bsky.labeler.defs.LabelerViewDetailed details?: string detailsOpen: boolean activeStepIndex1: number @@ -71,7 +67,7 @@ export type ReportAction = } | { type: 'selectLabeler' - labeler: AppBskyLabelerDefs.LabelerViewDetailed + labeler: app.bsky.labeler.defs.LabelerViewDetailed } | { type: 'clearLabeler' @@ -127,7 +123,8 @@ export function reducer(state: ReportState, action: ReportAction): ReportState { includeVideoTimestamp: false, } case 'selectOption': { - const isNcii = action.option.reason === OzoneReportDefs.REASONSEXUALNCII + const isNcii = + action.option.reason === tools.ozone.report.defs.reasonSexualNCII.value return { ...state, selectedOption: action.option, diff --git a/src/components/moderation/ReportDialog/types.ts b/src/components/moderation/ReportDialog/types.ts index 57ed3cd279..9b66cb72ce 100644 --- a/src/components/moderation/ReportDialog/types.ts +++ b/src/components/moderation/ReportDialog/types.ts @@ -1,17 +1,12 @@ -import { - type $Typed, - type AppBskyActorDefs, - type AppBskyFeedDefs, - type AppBskyGraphDefs, - type ChatBskyConvoDefs, -} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import type * as Dialog from '#/components/Dialog' +import {type app, type chat} from '#/lexicons' export type ReportSubjectConvoMessage = { view: 'convo' | 'message' convoId: string - message: ChatBskyConvoDefs.MessageView + message: chat.bsky.convo.defs.MessageView } export type ReportSubjectConvo = { @@ -20,14 +15,14 @@ export type ReportSubjectConvo = { } export type ReportSubject = - | $Typed - | $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/moderation/ScreenHider.tsx b/src/components/moderation/ScreenHider.tsx index 3c0d4584b8..e023b03c48 100644 --- a/src/components/moderation/ScreenHider.tsx +++ b/src/components/moderation/ScreenHider.tsx @@ -5,7 +5,7 @@ import { View, type ViewStyle, } from 'react-native' -import {type ModerationUI} from '@atproto/api' +import {type ModerationUI} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' 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..bd56d6cc53 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' @@ -14,11 +14,7 @@ export const RELEASE_VERSION: string = * The env the app is running in e.g. development, testflight, production, e2e */ export const ENV: string = process.env.EXPO_PUBLIC_ENV as - | 'production' - | 'testflight' - | 'development' - | 'e2e' - | (string & {}) + 'production' | 'testflight' | 'development' | 'e2e' | (string & {}) /** * Indicates whether the app is running in TestFlight @@ -62,10 +58,7 @@ export const BUNDLE_DATE: number = * The log level for the app. */ export const LOG_LEVEL = (process.env.EXPO_PUBLIC_LOG_LEVEL || 'info') as - | 'debug' - | 'info' - | 'warn' - | 'error' + 'debug' | 'info' | 'warn' | 'error' /** * Enable debug logs for specific logger instances @@ -75,13 +68,13 @@ 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 = +export const BLUESKY_PROXY_DID: DidString = process.env.EXPO_PUBLIC_BLUESKY_PROXY_DID || 'did:web:api.bsky.app' /** * The DID of the chat service to proxy to */ -export const CHAT_PROXY_DID: Did = +export const CHAT_PROXY_DID: DidString = process.env.EXPO_PUBLIC_CHAT_PROXY_DID || 'did:web:api.bsky.chat' /** diff --git a/src/features/inviteFriends/themes.ts b/src/features/inviteFriends/themes.ts index 7cb64131e1..9834adfc1b 100644 --- a/src/features/inviteFriends/themes.ts +++ b/src/features/inviteFriends/themes.ts @@ -6,12 +6,7 @@ * follows handleColor. */ export type InviteThemeKey = - | 'dawn' - | 'sunlight' - | 'day' - | 'dusk' - | 'twilight' - | 'night' + 'dawn' | 'sunlight' | 'day' | 'dusk' | 'twilight' | 'night' export type InviteThemeVariant = { /** QR data + eye color */ 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/liveEvents/context.tsx b/src/features/liveEvents/context.tsx index 3de2534a8e..b7bdfedb90 100644 --- a/src/features/liveEvents/context.tsx +++ b/src/features/liveEvents/context.tsx @@ -1,5 +1,5 @@ import {createContext, useContext, useMemo} from 'react' -import {hasMutedWord} from '@atproto/api' +import {hasMutedWord} from '@bsky/sdk/moderation' import {QueryClient, useQuery} from '@tanstack/react-query' import {useOnAppStateChange} from '#/lib/appState' diff --git a/src/features/liveEvents/preferences.ts b/src/features/liveEvents/preferences.ts index 6fb2ac6d11..22265ac36c 100644 --- a/src/features/liveEvents/preferences.ts +++ b/src/features/liveEvents/preferences.ts @@ -1,12 +1,12 @@ import {useEffect} from 'react' -import {type Agent, AppBskyActorDefs, asPredicate} from '@atproto/api' +import {getPreferences, updateLiveEventPreferences} from '@bsky/sdk' import {useMutation, useQueryClient} from '@tanstack/react-query' import { preferencesQueryKey, usePreferencesQuery, } from '#/state/queries/preferences' -import {useAgent} from '#/state/session' +import {usePdsClient} from '#/state/session' import {useAnalytics} from '#/analytics' import * as env from '#/env' import {IS_WEB} from '#/env' @@ -14,10 +14,11 @@ import { type LiveEventFeed, type LiveEventFeedMetricContext, } from '#/features/liveEvents/types' +import {type app} from '#/lexicons' export type LiveEventPreferencesAction = Parameters< - Agent['updateLiveEventPreferences'] ->[0] & { + typeof updateLiveEventPreferences +>[1] & { /** * Flag that is internal to this hook, do not set when updating prefs */ @@ -38,7 +39,7 @@ export function useLiveEventPreferences() { function useWebOnlyDebugLiveEventPreferences() { const queryClient = useQueryClient() - const agent = useAgent() + const pdsClient = usePdsClient() useEffect(() => { if (env.IS_DEV && IS_WEB && typeof window !== 'undefined') { @@ -46,14 +47,14 @@ function useWebOnlyDebugLiveEventPreferences() { window.__updateLiveEventPreferences = async ( action: LiveEventPreferencesAction, ) => { - await agent.updateLiveEventPreferences(action) + await pdsClient.call(updateLiveEventPreferences, action) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) } } - }, [agent, queryClient]) + }, [pdsClient, queryClient]) } export function useUpdateLiveEventPreferences(props: { @@ -65,10 +66,10 @@ export function useUpdateLiveEventPreferences(props: { }) { const ax = useAnalytics() const queryClient = useQueryClient() - const agent = useAgent() + const pdsClient = usePdsClient() return useMutation< - AppBskyActorDefs.LiveEventPreferences, + app.bsky.actor.defs.LiveEventPreferences, Error, LiveEventPreferencesAction, {undoAction: LiveEventPreferencesAction | null} @@ -108,10 +109,14 @@ export function useUpdateLiveEventPreferences(props: { } }, mutationFn: async action => { - const updated = await agent.updateLiveEventPreferences(action) - const prefs = updated.find(p => - asPredicate(AppBskyActorDefs.validateLiveEventPreferences)(p), - ) + /* + * The SDK action returns void, so after applying the update we read the + * fresh, interpreted preferences back to obtain the updated + * `liveEventPreferences` (the SDK extracts it from the raw prefs array for + * us, replacing the old `asPredicate(...).find(...)` lookup). + */ + await pdsClient.call(updateLiveEventPreferences, action) + const {liveEventPreferences: prefs} = await pdsClient.call(getPreferences) switch (action.type) { case 'hideFeed': @@ -138,7 +143,7 @@ export function useUpdateLiveEventPreferences(props: { break } case 'toggleHideAllFeeds': { - if (prefs!.hideAllFeeds) { + if (prefs.hideAllFeeds) { ax.metric('liveEvents:hideAllFeedBanners', { context: props.metricContext, }) @@ -156,7 +161,7 @@ export function useUpdateLiveEventPreferences(props: { queryKey: preferencesQueryKey, }) - return prefs! + return prefs }, }) } diff --git a/src/features/liveEvents/types.ts b/src/features/liveEvents/types.ts index 8bc88c448c..95a9179d20 100644 --- a/src/features/liveEvents/types.ts +++ b/src/features/liveEvents/types.ts @@ -21,7 +21,4 @@ export type LiveEventsWorkerResponse = { } export type LiveEventFeedMetricContext = - | 'explore' - | 'discover' - | 'sidebar' - | 'settings' + 'explore' | 'discover' | 'sidebar' | 'settings' 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 7eeccdc22b..3de918d0bd 100644 --- a/src/features/liveNow/components/GoLiveDisabledDialog.tsx +++ b/src/features/liveNow/components/GoLiveDisabledDialog.tsx @@ -1,27 +1,27 @@ import {useCallback, useState} from 'react' import {View} from 'react-native' -import {type AppBskyActorDefs, ToolsOzoneReportDefs} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {useMutation} from '@tanstack/react-query' -import {BLUESKY_MOD_SERVICE_HEADERS} from '#/lib/constants' +import {MOD_PROXY_SERVICE} from '#/lib/constants' import {logger} from '#/logger' -import {useAgent} from '#/state/session' +import {useAppviewClient} from '#/state/session' import {atoms as a, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +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 ( @@ -36,15 +36,15 @@ export function DialogInner({ status, }: { control: Dialog.DialogControlProps - status: AppBskyActorDefs.StatusView + status: app.bsky.actor.defs.StatusView }) { const {_} = useLingui() - const agent = useAgent() + const client = useAppviewClient() const [details, setDetails] = useState('') const {mutate, isPending} = useMutation({ mutationFn: async () => { - if (!agent.session?.did) { + if (!client.did) { throw new Error('Not logged in') } if (!status.uri || !status.cid) { @@ -56,20 +56,19 @@ export function DialogInner({ details, }) } else { - await agent.createModerationReport( + 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, cid: status.cid, }, reason: details, }, - { - encoding: 'application/json', - headers: BLUESKY_MOD_SERVICE_HEADERS, - }, + {service: MOD_PROXY_SERVICE}, ) } }, diff --git a/src/features/liveNow/components/LiveStatusDialog.tsx b/src/features/liveNow/components/LiveStatusDialog.tsx index 0d3598451c..18b6982692 100644 --- a/src/features/liveNow/components/LiveStatusDialog.tsx +++ b/src/features/liveNow/components/LiveStatusDialog.tsx @@ -1,11 +1,7 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' -import { - type AppBskyActorDefs, - type AppBskyEmbedExternal, - moderateStatus, -} from '@atproto/api' +import {moderateStatus} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' @@ -30,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({ @@ -40,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 ( @@ -64,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() @@ -102,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 23f33a5ffc..0959b068bd 100644 --- a/src/features/liveNow/index.tsx +++ b/src/features/liveNow/index.tsx @@ -1,14 +1,8 @@ import {useMemo} from 'react' -import { - type $Typed, - type AppBskyActorDefs, - type AppBskyActorStatus, - AppBskyEmbedExternal, - AtUri, - ComAtprotoRepoPutRecord, - moderateStatus, -} from '@atproto/api' import {retry} from '@atproto/common-web' +import {type $Typed, type l, type UriString} from '@atproto/lex' +import {AtUri, toDatetimeString} from '@atproto/syntax' +import {moderateStatus} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' @@ -17,19 +11,21 @@ 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 {matchXrpcError} from '#/lib/xrpc-error' import {useAppConfig} from '#/state/appConfig' import { updateProfileShadow, useMaybeProfileShadow, } from '#/state/cache/profile-shadow' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {useAgent, useSession} from '#/state/session' +import {usePdsClient, useSession} from '#/state/session' import {useTickEveryMinute} from '#/state/shell' import {useDialogContext} from '#/components/Dialog' import * as Toast from '#/components/Toast' import {useAnalytics} from '#/analytics' import {getLiveNowHost, getLiveServiceNames} from '#/features/liveNow/utils' -import type * as bsky from '#/types/bsky' +import {app, com} from '#/lexicons' +import * as bsky from '#/types/bsky' export * from '#/features/liveNow/utils' @@ -47,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 @@ -134,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, @@ -145,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 } @@ -168,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 @@ -193,7 +189,6 @@ export function useLiveLinkMetaQuery(url: string | null) { const liveNowConfig = useLiveNowConfig() const {_} = useLingui() - const agent = useAgent() return useQuery({ enabled: !!url, queryKey: ['link-meta', url], @@ -211,7 +206,7 @@ export function useLiveLinkMetaQuery(url: string | null) { ) } - return await getLinkMeta(agent, url) + return await getLinkMeta(url) }, }) } @@ -223,7 +218,7 @@ export function useUpsertLiveStatusMutation( ) { const ax = useAnalytics() const {currentAccount} = useSession() - const agent = useAgent() + const pdsClient = usePdsClient() const queryClient = useQueryClient() const control = useDialogContext() const {_} = useLingui() @@ -232,21 +227,21 @@ export function useUpsertLiveStatusMutation( mutationFn: async () => { if (!currentAccount) throw new Error('Not logged in') - let embed: $Typed | undefined + let embed: $Typed | undefined if (linkMeta) { - let thumb + let thumb: l.BlobRef | undefined if (linkMeta.image) { try { const img = await imageToThumb(linkMeta.image) if (img) { const blob = await uploadBlob( - agent, + pdsClient, img.source.path, img.source.mime, ) - thumb = blob.data.blob + thumb = blob.blob } } catch (e: any) { ax.logger.error(`Failed to upload thumbnail for live status`, { @@ -263,7 +258,8 @@ export function useUpsertLiveStatusMutation( $type: 'app.bsky.embed.external#external', title: linkMeta.title ?? '', description: linkMeta.description ?? '', - uri: linkMeta.url, + // `getLinkMeta` returns a plain url string + uri: linkMeta.url as l.UriString, thumb, }, } @@ -271,32 +267,40 @@ export function useUpsertLiveStatusMutation( const record = { $type: 'app.bsky.actor.status', - createdAt: createdAt ?? new Date().toISOString(), + createdAt: toDatetimeString( + createdAt ? new Date(createdAt) : new Date(), + ), status: 'app.bsky.actor.status#live', durationMinutes: duration, embed, - } satisfies AppBskyActorStatus.Record + } satisfies app.bsky.actor.status.Main const upsert = async () => { const repo = currentAccount.did const collection = 'app.bsky.actor.status' - const existing = await agent.com.atproto.repo - .getRecord({repo, collection, rkey: 'self'}) + const existing = await pdsClient + .call(com.atproto.repo.getRecord, {repo, collection, rkey: 'self'}) .catch(_e => undefined) - await agent.com.atproto.repo.putRecord({ + /* + * Stays on the raw `putRecord`, not `pdsClient.put`: the lexicon lets + * `swapRecord` be null (meaning "must not already exist"), while the + * record-helper option type is `string | undefined`. + */ + await pdsClient.call(com.atproto.repo.putRecord, { repo, collection, rkey: 'self', record, - swapRecord: existing?.data.cid || null, + swapRecord: existing?.cid || null, }) } await retry(upsert, { maxRetries: 5, - retryable: e => e instanceof ComAtprotoRepoPutRecord.InvalidSwapError, + retryable: e => + matchXrpcError(e, com.atproto.repo.putRecord) === 'InvalidSwap', }) return { @@ -330,7 +334,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 ? { @@ -338,7 +342,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, @@ -353,7 +358,7 @@ export function useUpsertLiveStatusMutation( export function useRemoveLiveStatusMutation() { const ax = useAnalytics() const {currentAccount} = useSession() - const agent = useAgent() + const pdsClient = usePdsClient() const queryClient = useQueryClient() const control = useDialogContext() const {_} = useLingui() @@ -362,7 +367,7 @@ export function useRemoveLiveStatusMutation() { mutationFn: async () => { if (!currentAccount) throw new Error('Not logged in') - await agent.app.bsky.actor.status.delete({ + await pdsClient.delete(app.bsky.actor.status, { repo: currentAccount.did, rkey: 'self', }) 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/ThemeContext.tsx b/src/lib/ThemeContext.tsx index 19abb9a7f8..97cd8c2a21 100644 --- a/src/lib/ThemeContext.tsx +++ b/src/lib/ThemeContext.tsx @@ -8,11 +8,7 @@ import {darkTheme, defaultTheme, dimTheme} from './themes' export type ColorScheme = 'light' | 'dark' export type PaletteColorName = - | 'default' - | 'primary' - | 'secondary' - | 'inverted' - | 'error' + 'default' | 'primary' | 'secondary' | 'inverted' | 'error' export type PaletteColor = { background: string backgroundLight: string diff --git a/src/lib/__tests__/xrpc-error.test.ts b/src/lib/__tests__/xrpc-error.test.ts new file mode 100644 index 0000000000..5c09fb4f08 --- /dev/null +++ b/src/lib/__tests__/xrpc-error.test.ts @@ -0,0 +1,91 @@ +import { + getMain, + type Procedure, + type Query, + XrpcInternalError, + XrpcResponseError, +} from '@atproto/lex' +import {describe, expect, it} from '@jest/globals' + +import {app, com} from '#/lexicons' +import {matchXrpcError} from '../xrpc-error' + +const createAccount = com.atproto.server.createAccount +const getTrends = app.bsky.unspecced.getTrends + +/** + * An `XrpcResponseError` as a lex `Client` would construct it: the method + * schema it was thrown for, plus the server's error response and its parsed + * payload. + */ +function responseError(method: Procedure | Query, error: string, status = 400) { + return new XrpcResponseError( + method, + new Response(JSON.stringify({error}), { + status, + headers: {'content-type': 'application/json'}, + }), + {encoding: 'application/json', body: {error}}, + ) +} + +describe('matchXrpcError', () => { + it('returns a code declared by the method', () => { + const e = responseError(getMain(createAccount), 'InvalidHandle') + expect(matchXrpcError(e, createAccount)).toBe('InvalidHandle') + }) + + it('accepts the .main schema as well as the namespace', () => { + const e = responseError(getMain(createAccount), 'InvalidInviteCode') + expect(matchXrpcError(e, createAccount.main)).toBe('InvalidInviteCode') + }) + + it('returns undefined for a code the method does not declare', () => { + const e = responseError(getMain(createAccount), 'RateLimitExceeded') + expect(matchXrpcError(e, createAccount)).toBeUndefined() + }) + + it('returns undefined for a method that declares no errors at all', () => { + const e = responseError(getMain(getTrends), 'InvalidHandle') + expect(matchXrpcError(e, getTrends)).toBeUndefined() + }) + + it('does not match a declared code thrown for a different method', () => { + /* + * `InvalidHandle` is declared by createAccount but this error came from a + * getTrends call, so scoping must reject it. + */ + const e = responseError(getMain(getTrends), 'InvalidHandle') + expect(matchXrpcError(e, createAccount)).toBeUndefined() + }) + + it('returns undefined for a lex error carrying no server code', () => { + const e = new XrpcInternalError(getMain(createAccount), 'boom') + expect(matchXrpcError(e, createAccount)).toBeUndefined() + }) + + it('returns undefined for non-lex errors and non-errors', () => { + expect(matchXrpcError(new Error('InvalidHandle'), createAccount)).toBe( + undefined, + ) + expect(matchXrpcError('InvalidHandle', createAccount)).toBeUndefined() + expect(matchXrpcError(undefined, createAccount)).toBeUndefined() + }) + + it('narrows the result to the declared-errors union', () => { + const e = responseError(getMain(createAccount), 'InvalidHandle') + const code = matchXrpcError(e, createAccount) + + /* + * A misspelled or undeclared code is not comparable to the narrowed union, + * which is what makes a typo'd `switch` case a compile error (TS2678 / + * TS2367) rather than a branch that never runs. + */ + // @ts-expect-error 'InvalidHandel' is not a declared createAccount error + expect(code === 'InvalidHandel').toBe(false) + // @ts-expect-error 'RateLimitExceeded' is not declared by createAccount + expect(code === 'RateLimitExceeded').toBe(false) + + expect(code === 'UnsupportedDomain').toBe(false) + }) +}) diff --git a/src/lib/api/__tests__/computeCid.test.ts b/src/lib/api/__tests__/computeCid.test.ts new file mode 100644 index 0000000000..d5f8b50726 --- /dev/null +++ b/src/lib/api/__tests__/computeCid.test.ts @@ -0,0 +1,108 @@ +/* + * The jest suite ships global manual mocks for `multiformats/cid` and + * `multiformats/hashes/hasher` (in root `__mocks__/`) so unrelated tests don't + * pull in real crypto. This test is precisely about the real CID hashing, so we + * opt back into the actual implementations here. + */ +jest.unmock('multiformats/cid') +jest.unmock('multiformats/hashes/hasher') + +import {CID} from 'multiformats/cid' + +import {computeCid} from '#/lib/api/computeCid' +import {type app, type com} from '#/lexicons' + +/* + * Golden-CID regression test for the composer post pipeline. + * + * `computeCid` hashes a post record in the client so a thread's later posts can + * reference earlier posts by CID before the server assigns them. The hash is + * byte-sensitive: any drift in how records (especially blobs) are serialized to + * DAG-CBOR silently produces the wrong CID and breaks reply chains with NO type + * error. These golden values MUST remain byte-identical - they gate the + * structural lex-blob shape check. + * + * The blob CID below is a fixed, deterministic CIDv1/raw/sha256 used purely as a + * stable fixture - it is not derived from any real upload. + */ +const BLOB_CID = 'bafkreieq5jui4j25lacwomsqgjeswwl3y5zcdrresptwgmfylxo2depppq' + +/** + * Build a post record with an image embed whose blob is the given value. + */ +function postWithImageBlob(blob: unknown): app.bsky.feed.post.Main { + return { + $type: 'app.bsky.feed.post', + createdAt: '2024-01-01T00:00:00.001Z', + text: 'post with image', + embed: { + $type: 'app.bsky.embed.images', + images: [ + { + image: blob, + alt: 'alt text', + aspectRatio: {width: 100, height: 200}, + }, + ], + }, + } as app.bsky.feed.post.Main +} + +describe('computeCid', () => { + it('case 1: plain post record with no blob', async () => { + const record: app.bsky.feed.post.Main = { + $type: 'app.bsky.feed.post', + createdAt: '2024-01-01T00:00:00.000Z', + text: 'hello world', + } + expect(await computeCid(record)).toBe( + 'bafyreieawtmh7hwfrqpamqkodza5r62bbfhsepe2iyustgxhgbhi6b2lfi', + ) + }) + + it('case 2: record whose embed carries a plain-JSON lex blob', async () => { + /* + * The blob shape lex `uploadBlob` returns: a plain object + * `{$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 legacy blob + * class instance produced via `.ipld()`. + */ + const blob = { + $type: 'blob' as const, + ref: CID.parse(BLOB_CID), + mimeType: 'image/jpeg', + size: 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') + const golden = [ + 'bafyreig62rxs34h5rvznfrracwkjlfgad5b25qxglp2hcziqdfas2nw2ee', + 'bafyreicxcj2tq5jrh5jcaczg3eli5cvxitgzu7kpu3fm5v3njq2byjxirq', + 'bafyreigvaswuhlpd7dllja2xrqswhqbruyv2kar7mvbn7gdm5ldzu6vkti', + ] + + let reply: app.bsky.feed.post.Main['reply'] | undefined + for (let i = 0; i < 3; i++) { + const now = new Date(base.getTime() + i) + const uri = `at://${did}/app.bsky.feed.post/rkey${i}` + const record = { + $type: 'app.bsky.feed.post', + createdAt: now.toISOString(), + text: `post ${i}`, + reply, + } as app.bsky.feed.post.Main + const cid = await computeCid(record) + expect(cid).toBe(golden[i]) + const ref = {cid, uri} as com.atproto.repo.strongRef.Main + reply = {root: reply?.root ?? ref, parent: ref} + } + }) +}) diff --git a/src/lib/api/computeCid.ts b/src/lib/api/computeCid.ts new file mode 100644 index 0000000000..c5bbb9830e --- /dev/null +++ b/src/lib/api/computeCid.ts @@ -0,0 +1,131 @@ +import {sha256} from 'js-sha256' +import {CID} from 'multiformats/cid' +import * as Hasher from 'multiformats/hashes/hasher' + +import {type app} from '#/lexicons' + +/* + * Client-side CID computation for post records, extracted from the post + * pipeline so it can be unit-tested in isolation (importing the pipeline pulls + * in the native gallery/media chain). See `computeCid.test.ts` for the + * golden-CID regression fixtures that gate any change to this serialization. + */ + +// The built-in hashing functions from multiformats (`multiformats/hashes/sha2`) +// are meant for Node.js, this is the cross-platform equivalent. +const mf_sha256 = Hasher.from({ + name: 'sha2-256', + code: 0x12, + encode: input => { + const digest = sha256.arrayBuffer(input) + return new Uint8Array(digest) + }, +}) + +export async function computeCid( + record: app.bsky.feed.post.Main, +): Promise { + /* + * Lazily loaded since it's only needed when posting a thread, and its + * `cborg` dependency is ~190KB that would otherwise be in the initial + * web bundle. + */ + const dcbor = await importDagCbor() + /* + * IMPORTANT: `prepareForHashing` prepares the record to be hashed by + * removing fields with undefined value, and converting blobs to the right + * IPLD representation. + */ + const prepared = prepareForHashing(record) + // 1. Encode the record into DAG-CBOR format + const encoded = dcbor.encode(prepared) + // 2. Hash the record in SHA-256 (code 0x12) + const digest = await mf_sha256.digest(encoded) + // 3. Create a CIDv1, specifying DAG-CBOR as content (code 0x71) + const cid = CID.createV1(0x71, digest) + // 4. Get the Base32 representation of the CID (`b` prefix) + return cid.toString() +} + +/** + * True for a plain-JSON lexicon blob, the shape lex `uploadBlob` returns + * (`{$type: 'blob', ref, mimeType, size}` with `ref` a parsed CID). Lex blobs + * are plain objects, not class instances. + */ +function isLexBlob(v: unknown): boolean { + if (v == null || typeof v !== 'object') return false + const o = v as Record + return o.$type === 'blob' && 'ref' in o && 'mimeType' in o +} + +/* + * Returns a transformed version of the object for use in DAG-CBOR. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function prepareForHashing(v: any): any { + /* + * A plain-JSON lex blob is already in the right IPLD shape (its `ref` is a + * parsed CID that DAG-CBOR encodes as a CID link), so pass it through + * untouched. + */ + if (isLexBlob(v)) { + return v + } + + // Walk through arrays + if (Array.isArray(v)) { + let pure = true + const mapped = v.map(value => { + if (value !== (value = prepareForHashing(value))) { + pure = false + } + return value + }) + return pure ? v : mapped + } + + // Walk through plain objects + if (isPlainObject(v)) { + const rec = v as Record + const obj: Record = {} + let pure = true + for (const key in rec) { + let value = rec[key] + // `value` is undefined + if (value === undefined) { + pure = false + continue + } + /* + * `prepareForHashing` returned a value that's different from what we had + * before + */ + if (value !== (value = prepareForHashing(value))) { + pure = false + } + obj[key] = value + } + // Return as is if we haven't needed to tamper with anything + return pure ? v : obj + } + return v +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function isPlainObject(v: any): boolean { + if (typeof v !== 'object' || v === null) { + return false + } + const proto = Object.getPrototypeOf(v) + return proto === Object.prototype || proto === null +} + +/** + * Load `@ipld/dag-cbor` on demand. The dynamic `import()` lets web bundlers + * emit it (and its ~190KB `cborg` dependency) as a separate chunk that only + * loads when posting a thread. Under jest this compiles down to a require + * that resolves through the test moduleNameMapper. + */ +function importDagCbor(): Promise { + return import('@ipld/dag-cbor') +} 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 3b97b8ef73..76e6433f69 100644 --- a/src/lib/api/feed/author.ts +++ b/src/lib/api/feed/author.ts @@ -1,23 +1,25 @@ -import { - AppBskyFeedDefs, - type AppBskyFeedGetAuthorFeed as GetAuthorFeed, - type AtpAgent, -} 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< + typeof app.bsky.feed.getAuthorFeed.main +> + export class AuthorFeedAPI implements FeedAPI { - agent: AtpAgent - _params: GetAuthorFeed.QueryParams + client: Client + _params: GetAuthorFeedParams constructor({ - agent, + client, feedParams, }: { - agent: AtpAgent - feedParams: GetAuthorFeed.QueryParams + client: Client + feedParams: GetAuthorFeedParams }) { - this.agent = agent + this.client = client this._params = feedParams } @@ -27,12 +29,12 @@ export class AuthorFeedAPI implements FeedAPI { return params } - async peekLatest(): Promise { - const res = await this.agent.getAuthorFeed({ + async peekLatest(): Promise { + const data = await this.client.call(app.bsky.feed.getAuthorFeed, { ...this.params, limit: 1, }) - return res.data.feed[0] + return data.feed[0] } async fetch({ @@ -42,28 +44,32 @@ export class AuthorFeedAPI implements FeedAPI { cursor: string | undefined limit: number }): Promise { - const res = await this.agent.getAuthorFeed({ + /* + * A failed request rejects rather than resolving, so the error propagates + * to the query and drives the feed error UI (blocked actor, rate limit). + * The agent behaved the same way - its `success` flag was only ever true - + * so the empty-page branch this replaces was unreachable. + */ + const data = await this.client.call(app.bsky.feed.getAuthorFeed, { ...this.params, cursor, limit, }) - if (res.success) { - return { - cursor: res.data.cursor, - feed: this._filter(res.data.feed), - } - } return { - feed: [], + cursor: data.cursor, + feed: this._filter(data.feed), } } - _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) @@ -76,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 54d9dc9067..f4a5d4862f 100644 --- a/src/lib/api/feed/custom.ts +++ b/src/lib/api/feed/custom.ts @@ -1,46 +1,51 @@ import { - type AppBskyFeedDefs, - type AppBskyFeedGetFeed as GetCustomFeed, - AtpAgent, - jsonStringToLex, -} from '@atproto/api' + type Client, + type XrpcRequestParams, + XrpcResponseError, +} from '@atproto/lex' +import {PUBLIC_APPVIEW} from '#/lib/constants' +import {createLexClient} from '#/lib/lexClient' import { getAppLanguageAsContentLanguage, getContentLanguages, } from '#/state/preferences/languages' +import {app} from '#/lexicons' import {type FeedAPI, type FeedAPIResponse} from './types' import {createBskyTopicsHeader, isBlueskyOwnedFeed} from './utils' +type GetCustomFeedParams = XrpcRequestParams + export class CustomFeedAPI implements FeedAPI { - agent: AtpAgent - params: GetCustomFeed.QueryParams + client: Client + params: GetCustomFeedParams userInterests?: string constructor({ - agent, + client, feedParams, userInterests, }: { - agent: AtpAgent - feedParams: GetCustomFeed.QueryParams + client: Client + feedParams: GetCustomFeedParams userInterests?: string }) { - this.agent = agent + this.client = client this.params = feedParams this.userInterests = userInterests } - async peekLatest(): Promise { + async peekLatest(): Promise { const contentLangs = getContentLanguages().join(',') - const res = await this.agent.app.bsky.feed.getFeed( + const data = await this.client.call( + app.bsky.feed.getFeed, { ...this.params, limit: 1, }, {headers: {'Accept-Language': contentLangs}}, ) - return res.data.feed[0] + return data.feed[0] } async fetch({ @@ -51,11 +56,17 @@ export class CustomFeedAPI implements FeedAPI { limit: number }): Promise { const contentLangs = getContentLanguages().join(',') - const agent = this.agent const isBlueskyOwned = isBlueskyOwnedFeed(this.params.feed) - const res = agent.did - ? await this.agent.app.bsky.feed.getFeed( + /* + * The authed branch rejects on failure, so the error propagates to the + * query and drives the feed error UI (feedgen offline, misconfigured, rate + * limited). Only the logged-out branch can resolve without data, and it + * signals that with a null body. + */ + const data = this.client.did + ? await this.client.call( + app.bsky.feed.getFeed, { ...this.params, cursor, @@ -71,92 +82,107 @@ export class CustomFeedAPI implements FeedAPI { }, ) : await loggedOutFetch({...this.params, cursor, limit}) - if (res.success) { - // NOTE - // some custom feeds fail to enforce the pagination limit - // so we manually truncate here - // -prf - if (res.data.feed.length > limit) { - res.data.feed = res.data.feed.slice(0, limit) - } + + if (!data) { return { - cursor: res.data.feed.length ? res.data.cursor : undefined, - feed: res.data.feed, + feed: [], } } + + // NOTE + // some custom feeds fail to enforce the pagination limit + // so we manually truncate here + // -prf + const feed = + data.feed.length > limit ? data.feed.slice(0, limit) : data.feed return { - feed: [], + cursor: feed.length ? data.cursor : undefined, + feed, } } } -// HACK -// we want feeds to give language-specific results immediately when a -// logged-out user changes their language. this comes with two problems: -// 1. not all languages have content, and -// 2. our public caching layer isnt correctly busting against the accept-language header -// for now we handle both of these with a manual workaround -// -prf -async function loggedOutFetch({ - feed, - limit, - cursor, -}: { - feed: string - limit: number - cursor?: string -}) { - let contentLangs = getAppLanguageAsContentLanguage() +let loggedOutAppviewClient: Client | undefined - /** - * Copied from our root `Agent` class - * @see https://github.com/bluesky-social/atproto/blob/60df3fc652b00cdff71dd9235d98a7a4bb828f05/packages/api/src/agent.ts#L120 - */ - const labelersHeader = { - 'atproto-accept-labelers': AtpAgent.appLabelers - .map(l => `${l};redact`) - .join(', '), - } +/** + * The unauthenticated {@link Client} for logged-out feed reads, pointed at the + * direct appview ({@link PUBLIC_APPVIEW}, `api.bsky.app`). + * + * Deliberately NOT the public appview client (`public.api.bsky.app`): that host + * fronts a cache which does not vary on `Accept-Language`, so it would answer a + * language-filtered read from another language's cached body. The direct + * appview respects the header (verified 2026-08-04), at the cost of not being + * cached. See {@link loggedOutFetch}. + * + * A single module-level instance, because there is no session to scope it to. + * Like the public chat client, it uses plain `fetch` rather than + * `networkAwareFetch`, matching the ad-hoc fetch it replaces: this read has its + * own failure handling and should not move the app-wide network signal. + */ +function getLoggedOutAppviewClient(): Client { + return (loggedOutAppviewClient ??= createLexClient({ + service: PUBLIC_APPVIEW, + })) +} - // manually construct fetch call so we can add the `lang` cache-busting param - let res = await fetch( - `https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=${feed}${ - cursor ? `&cursor=${cursor}` : '' - }&limit=${limit}&lang=${contentLangs}`, - { - method: 'GET', - headers: {'Accept-Language': contentLangs, ...labelersHeader}, - }, - ) - let data = res.ok - ? (jsonStringToLex(await res.text()) as GetCustomFeed.OutputSchema) - : null +/* + * HACK + * We want feeds to give language-specific results immediately when a logged-out + * user changes their language. That comes with two problems: + * 1. not all languages have content, and + * 2. our public caching layer does not bust against the `Accept-Language` + * header. + * -prf + * + * Problem 2 is why this uses its own client rather than the app's public + * appview one: it talks to the direct appview, which honors the header, instead + * of the cached `public.api.bsky.app`, which does not vary on it. That trades + * CDN caching for language correctness on logged-out feed traffic. + * + * Problem 1 is host-independent, so it is still handled here: an empty + * language-filtered feed is retried once with the language constraint removed. + */ +async function loggedOutFetch( + params: GetCustomFeedParams, +): Promise { + const contentLangs = getAppLanguageAsContentLanguage() + + let data = await getFeedOrNull(params, contentLangs) if (data?.feed?.length) { - return { - success: true, - data, - } + return data } // no data, try again with language headers removed - res = await fetch( - `https://api.bsky.app/xrpc/app.bsky.feed.getFeed?feed=${feed}${ - cursor ? `&cursor=${cursor}` : '' - }&limit=${limit}`, - {method: 'GET', headers: {'Accept-Language': '', ...labelersHeader}}, - ) - data = res.ok - ? (jsonStringToLex(await res.text()) as GetCustomFeed.OutputSchema) - : null + data = await getFeedOrNull(params, '') if (data?.feed?.length) { - return { - success: true, - data, - } + return data } - return { - success: false, - data: {feed: []}, + return null +} + +/** + * A logged-out `getFeed` read that resolves to null on a response error. + * + * The pre-client code only guarded `res.ok`, so a failed RESPONSE fell through + * to the next attempt while a failed REQUEST rejected. Catching + * `XrpcResponseError` preserves that split: every other lex error - the fetch + * and validation ones - still propagates. + */ +async function getFeedOrNull( + params: GetCustomFeedParams, + contentLangs: string, +): Promise { + try { + return await getLoggedOutAppviewClient().call( + app.bsky.feed.getFeed, + params, + {headers: {'Accept-Language': contentLangs}}, + ) + } catch (e) { + if (e instanceof XrpcResponseError) { + return null + } + throw e } } diff --git a/src/lib/api/feed/demo.ts b/src/lib/api/feed/demo.ts index 42d1046bdc..5d549c69b7 100644 --- a/src/lib/api/feed/demo.ts +++ b/src/lib/api/feed/demo.ts @@ -1,16 +1,17 @@ -import {type AppBskyFeedDefs, type AtpAgent} 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 { - agent: AtpAgent + client: Client - constructor({agent}: {agent: AtpAgent}) { - this.agent = agent + constructor({client}: {client: Client}) { + 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 17e96d8e1b..27a08c6985 100644 --- a/src/lib/api/feed/following.ts +++ b/src/lib/api/feed/following.ts @@ -1,19 +1,20 @@ -import {type AppBskyFeedDefs, type AtpAgent} from '@atproto/api' +import {type Client} from '@atproto/lex' +import {app} from '#/lexicons' import {type FeedAPI, type FeedAPIResponse} from './types' export class FollowingFeedAPI implements FeedAPI { - agent: AtpAgent + client: Client - constructor({agent}: {agent: AtpAgent}) { - this.agent = agent + constructor({client}: {client: Client}) { + this.client = client } - async peekLatest(): Promise { - const res = await this.agent.getTimeline({ + async peekLatest(): Promise { + const data = await this.client.call(app.bsky.feed.getTimeline, { limit: 1, }) - return res.data.feed[0] + return data.feed[0] } async fetch({ @@ -23,18 +24,19 @@ export class FollowingFeedAPI implements FeedAPI { cursor: string | undefined limit: number }): Promise { - const res = await this.agent.getTimeline({ + /* + * A failed request rejects rather than resolving, so the error propagates + * to the query and drives the feed error UI. The agent behaved the same + * way - its `success` flag was only ever true - so the empty-page branch + * this replaces was unreachable. + */ + const data = await this.client.call(app.bsky.feed.getTimeline, { cursor, limit, }) - if (res.success) { - return { - cursor: res.data.cursor, - feed: res.data.feed, - } - } return { - feed: [], + cursor: data.cursor, + feed: data.feed, } } } diff --git a/src/lib/api/feed/home.ts b/src/lib/api/feed/home.ts index aa13c70bf0..9c90b213aa 100644 --- a/src/lib/api/feed/home.ts +++ b/src/lib/api/feed/home.ts @@ -1,6 +1,8 @@ -import {type AppBskyFeedDefs, type AtpAgent} 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' @@ -13,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', @@ -24,10 +31,10 @@ export const FALLBACK_MARKER_POST: AppBskyFeedDefs.FeedViewPost = { }, indexedAt: new Date().toISOString(), }, -} +} as unknown as app.bsky.feed.defs.FeedViewPost export class HomeFeedAPI implements FeedAPI { - agent: AtpAgent + client: Client following: FollowingFeedAPI discover: CustomFeedAPI usingDiscover = false @@ -36,32 +43,32 @@ export class HomeFeedAPI implements FeedAPI { constructor({ userInterests, - agent, + client, }: { userInterests?: string - agent: AtpAgent + client: Client }) { - this.agent = agent - this.following = new FollowingFeedAPI({agent}) + this.client = client + this.following = new FollowingFeedAPI({client}) this.discover = new CustomFeedAPI({ - agent, - feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')}, + client, + feedParams: {feed: PROD_DEFAULT_FEED('whats-hot') as AtUriString}, }) this.userInterests = userInterests } reset() { - this.following = new FollowingFeedAPI({agent: this.agent}) + this.following = new FollowingFeedAPI({client: this.client}) this.discover = new CustomFeedAPI({ - agent: this.agent, - feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')}, + client: this.client, + feedParams: {feed: PROD_DEFAULT_FEED('whats-hot') as AtUriString}, userInterests: this.userInterests, }) this.usingDiscover = false this.itemCursor = 0 } - async peekLatest(): Promise { + async peekLatest(): Promise { if (this.usingDiscover) { return this.discover.peekLatest() } @@ -80,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 1511dc833a..d8bba416ac 100644 --- a/src/lib/api/feed/likes.ts +++ b/src/lib/api/feed/likes.ts @@ -1,32 +1,33 @@ -import { - type AppBskyFeedDefs, - type AppBskyFeedGetActorLikes as GetActorLikes, - type AtpAgent, -} from '@atproto/api' +import {type Client, type XrpcRequestParams} from '@atproto/lex' +import {app} from '#/lexicons' import {type FeedAPI, type FeedAPIResponse} from './types' +type GetActorLikesParams = XrpcRequestParams< + typeof app.bsky.feed.getActorLikes.main +> + export class LikesFeedAPI implements FeedAPI { - agent: AtpAgent - params: GetActorLikes.QueryParams + client: Client + params: GetActorLikesParams constructor({ - agent, + client, feedParams, }: { - agent: AtpAgent - feedParams: GetActorLikes.QueryParams + client: Client + feedParams: GetActorLikesParams }) { - this.agent = agent + this.client = client this.params = feedParams } - async peekLatest(): Promise { - const res = await this.agent.getActorLikes({ + async peekLatest(): Promise { + const data = await this.client.call(app.bsky.feed.getActorLikes, { ...this.params, limit: 1, }) - return res.data.feed[0] + return data.feed[0] } async fetch({ @@ -36,21 +37,22 @@ export class LikesFeedAPI implements FeedAPI { cursor: string | undefined limit: number }): Promise { - const res = await this.agent.getActorLikes({ + /* + * A failed request rejects rather than resolving, so the error propagates + * to the query and drives the feed error UI. The agent behaved the same + * way - its `success` flag was only ever true - so the empty-page branch + * this replaces was unreachable. + */ + const data = await this.client.call(app.bsky.feed.getActorLikes, { ...this.params, cursor, limit, }) - if (res.success) { - // HACKFIX: the API incorrectly returns a cursor when there are no items -sfn - const isEmptyPage = res.data.feed.length === 0 - return { - cursor: isEmptyPage ? undefined : res.data.cursor, - feed: res.data.feed, - } - } + // HACKFIX: the API incorrectly returns a cursor when there are no items -sfn + const isEmptyPage = data.feed.length === 0 return { - feed: [], + cursor: isEmptyPage ? undefined : data.cursor, + feed: data.feed, } } } diff --git a/src/lib/api/feed/list.ts b/src/lib/api/feed/list.ts index 9697b0aaf3..51e67bb4e8 100644 --- a/src/lib/api/feed/list.ts +++ b/src/lib/api/feed/list.ts @@ -1,32 +1,33 @@ -import { - type Agent, - type AppBskyFeedDefs, - type AppBskyFeedGetListFeed as GetListFeed, -} from '@atproto/api' +import {type Client, type XrpcRequestParams} from '@atproto/lex' +import {app} from '#/lexicons' import {type FeedAPI, type FeedAPIResponse} from './types' +type GetListFeedParams = XrpcRequestParams< + typeof app.bsky.feed.getListFeed.main +> + export class ListFeedAPI implements FeedAPI { - agent: Agent - params: GetListFeed.QueryParams + client: Client + params: GetListFeedParams constructor({ - agent, + client, feedParams, }: { - agent: Agent - feedParams: GetListFeed.QueryParams + client: Client + feedParams: GetListFeedParams }) { - this.agent = agent + this.client = client this.params = feedParams } - async peekLatest(): Promise { - const res = await this.agent.app.bsky.feed.getListFeed({ + async peekLatest(): Promise { + const data = await this.client.call(app.bsky.feed.getListFeed, { ...this.params, limit: 1, }) - return res.data.feed[0] + return data.feed[0] } async fetch({ @@ -36,19 +37,20 @@ export class ListFeedAPI implements FeedAPI { cursor: string | undefined limit: number }): Promise { - const res = await this.agent.app.bsky.feed.getListFeed({ + /* + * A failed request rejects rather than resolving, so the error propagates + * to the query and drives the feed error UI. The agent behaved the same + * way - its `success` flag was only ever true - so the empty-page branch + * this replaces was unreachable. + */ + const data = await this.client.call(app.bsky.feed.getListFeed, { ...this.params, cursor, limit, }) - if (res.success) { - return { - cursor: res.data.cursor, - feed: res.data.feed, - } - } return { - feed: [], + cursor: data.cursor, + feed: data.feed, } } } diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts index c341dd53a0..91d300afb8 100644 --- a/src/lib/api/feed/merge.ts +++ b/src/lib/api/feed/merge.ts @@ -1,8 +1,5 @@ -import { - type AppBskyFeedDefs, - type AppBskyFeedGetTimeline, - type AtpAgent, -} from '@atproto/api' +import {type Client} from '@atproto/lex' +import {type AtUriString} from '@atproto/syntax' import shuffle from 'lodash.shuffle' import {bundleAsync} from '#/lib/async/bundle' @@ -10,6 +7,7 @@ import {timeout} from '#/lib/async/timeout' import {feedUriToHref} from '#/lib/strings/url-helpers' import {getContentLanguages} from '#/state/preferences/languages' import {type FeedParams} from '#/state/queries/post-feed' +import {app} from '#/lexicons' import {FeedTuner} from '../feed-manip' import {type FeedTunerFn} from '../feed-manip' import { @@ -22,9 +20,19 @@ import {createBskyTopicsHeader, isBlueskyOwnedFeed} from './utils' const REQUEST_WAIT_MS = 500 // 500ms const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours +/** + * A page of feed items, or `null` when the source could not produce one. Only + * sources that deliberately swallow their own errors return `null`; the rest + * reject so the error reaches the caller. + */ +type MergeFeedPage = { + cursor?: string + feed: app.bsky.feed.defs.FeedViewPost[] +} | null + export class MergeFeedAPI implements FeedAPI { userInterests?: string - agent: AtpAgent + client: Client params: FeedParams feedTuners: FeedTunerFn[] following: MergeFeedSource_Following @@ -34,29 +42,29 @@ export class MergeFeedAPI implements FeedAPI { sampleCursor = 0 constructor({ - agent, + client, feedParams, feedTuners, userInterests, }: { - agent: AtpAgent + client: Client feedParams: FeedParams feedTuners: FeedTunerFn[] userInterests?: string }) { - this.agent = agent + this.client = client this.params = feedParams this.feedTuners = feedTuners this.userInterests = userInterests this.following = new MergeFeedSource_Following({ - agent: this.agent, + client: this.client, feedTuners: this.feedTuners, }) } reset() { this.following = new MergeFeedSource_Following({ - agent: this.agent, + client: this.client, feedTuners: this.feedTuners, }) this.customFeeds = [] @@ -68,7 +76,7 @@ export class MergeFeedAPI implements FeedAPI { this.params.mergeFeedSources.map( feedUri => new MergeFeedSource_Custom({ - agent: this.agent, + client: this.client, feedUri, feedTuners: this.feedTuners, userInterests: this.userInterests, @@ -80,11 +88,11 @@ export class MergeFeedAPI implements FeedAPI { } } - async peekLatest(): Promise { - const res = await this.agent.getTimeline({ + async peekLatest(): Promise { + const data = await this.client.call(app.bsky.feed.getTimeline, { limit: 1, }) - return res.data.feed[0] + return data.feed[0] } async fetch({ @@ -127,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]) { @@ -175,21 +183,21 @@ export class MergeFeedAPI implements FeedAPI { } class MergeFeedSource { - agent: AtpAgent + client: Client feedTuners: FeedTunerFn[] sourceInfo: ReasonFeedSource | undefined cursor: string | undefined = undefined - queue: AppBskyFeedDefs.FeedViewPost[] = [] + queue: app.bsky.feed.defs.FeedViewPost[] = [] hasMore = true constructor({ - agent, + client, feedTuners, }: { - agent: AtpAgent + client: Client feedTuners: FeedTunerFn[] }) { - this.agent = agent + this.client = client this.feedTuners = feedTuners } @@ -201,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) } @@ -210,11 +218,11 @@ class MergeFeedSource { } _fetchNextInner = bundleAsync(async (n: number) => { - const res = await this._getFeed(this.cursor, n) - if (res.success) { - this.cursor = res.data.cursor - if (res.data.feed.length) { - this.queue = this.queue.concat(res.data.feed) + const page = await this._getFeed(this.cursor, n) + if (page) { + this.cursor = page.cursor + if (page.feed.length) { + this.queue = this.queue.concat(page.feed) } else { this.hasMore = false } @@ -226,7 +234,7 @@ class MergeFeedSource { protected _getFeed( _cursor: string | undefined, _limit: number, - ): Promise { + ): Promise { throw new Error('Must be overridden') } } @@ -238,42 +246,49 @@ class MergeFeedSource_Following extends MergeFeedSource { return this._fetchNextInner(n) } + /* + * No error handling: a failed timeline read rejects, which is what the agent + * did too, so the error still reaches `MergeFeedAPI.fetch` and the query. + */ protected async _getFeed( cursor: string | undefined, limit: number, - ): Promise { - const res = await this.agent.getTimeline({cursor, limit}) + ): Promise { + const data = await this.client.call(app.bsky.feed.getTimeline, { + cursor, + limit, + }) // run the tuner pre-emptively to ensure better mixing - const slices = this.tuner.tune(res.data.feed, { + const slices = this.tuner.tune(data.feed, { dryRun: false, }) - res.data.feed = slices.map(slice => slice._feedPost) - return res + return { + cursor: data.cursor, + feed: slices.map(slice => slice._feedPost), + } } } class MergeFeedSource_Custom extends MergeFeedSource { - agent: AtpAgent minDate: Date feedUri: string userInterests?: string constructor({ - agent, + client, feedUri, feedTuners, userInterests, }: { - agent: AtpAgent + client: Client feedUri: string feedTuners: FeedTunerFn[] userInterests?: string }) { super({ - agent, + client, feedTuners, }) - this.agent = agent this.feedUri = feedUri this.userInterests = userInterests this.sourceInfo = { @@ -287,15 +302,16 @@ class MergeFeedSource_Custom extends MergeFeedSource { protected async _getFeed( cursor: string | undefined, limit: number, - ): Promise { + ): Promise { try { const contentLangs = getContentLanguages().join(',') const isBlueskyOwned = isBlueskyOwnedFeed(this.feedUri) - const res = await this.agent.app.bsky.feed.getFeed( + const data = await this.client.call( + app.bsky.feed.getFeed, { cursor, limit, - feed: this.feedUri, + feed: this.feedUri as AtUriString, }, { headers: { @@ -310,22 +326,24 @@ class MergeFeedSource_Custom extends MergeFeedSource { // some custom feeds fail to enforce the pagination limit // so we manually truncate here // -prf - if (limit && res.data.feed.length > limit) { - res.data.feed = res.data.feed.slice(0, limit) - } + let feed: app.bsky.feed.defs.FeedViewPost[] = + limit && data.feed.length > limit + ? data.feed.slice(0, limit) + : data.feed // filter out older posts - res.data.feed = res.data.feed.filter( - post => new Date(post.post.indexedAt) > this.minDate, - ) + feed = feed.filter(post => new Date(post.post.indexedAt) > this.minDate) // attach source info - for (const post of res.data.feed) { + for (const post of feed) { // @ts-ignore post.__source = this.sourceInfo } - return res + return { + cursor: data.cursor, + feed, + } } catch { // dont bubble custom-feed errors - return {success: false, headers: {}, data: {feed: []}} + return null } } } diff --git a/src/lib/api/feed/posts.ts b/src/lib/api/feed/posts.ts index 33eff50997..18835ecbd2 100644 --- a/src/lib/api/feed/posts.ts +++ b/src/lib/api/feed/posts.ts @@ -1,25 +1,24 @@ -import { - type Agent, - type AppBskyFeedDefs, - type AppBskyFeedGetPosts, -} from '@atproto/api' +import {type Client, type XrpcRequestParams} from '@atproto/lex' import {logger} from '#/logger' +import {app} from '#/lexicons' import {type FeedAPI, type FeedAPIResponse} from './types' +type GetPostsParams = XrpcRequestParams + export class PostListFeedAPI implements FeedAPI { - agent: Agent - params: AppBskyFeedGetPosts.QueryParams - peek: AppBskyFeedDefs.FeedViewPost | null = null + client: Client + params: GetPostsParams + peek: app.bsky.feed.defs.FeedViewPost | null = null constructor({ - agent, + client, feedParams, }: { - agent: Agent - feedParams: AppBskyFeedGetPosts.QueryParams + client: Client + feedParams: GetPostsParams }) { - this.agent = agent + this.client = client if (feedParams.uris.length > 25) { logger.warn( `Too many URIs provided - expected 25, got ${feedParams.uris.length}`, @@ -30,23 +29,24 @@ export class PostListFeedAPI implements FeedAPI { } } - async peekLatest(): Promise { + async peekLatest(): Promise { if (this.peek) return this.peek throw new Error('Has not fetched yet') } async fetch({}: {}): Promise { - const res = await this.agent.app.bsky.feed.getPosts({ + /* + * A failed request rejects rather than resolving, so the error propagates + * to the query and drives the feed error UI. The agent behaved the same + * way - its `success` flag was only ever true - so the empty-page branch + * this replaces was unreachable. + */ + const data = await this.client.call(app.bsky.feed.getPosts, { ...this.params, }) - if (res.success) { - this.peek = {post: res.data.posts[0]} - return { - feed: res.data.posts.map(post => ({post})), - } - } + this.peek = {post: data.posts[0]} return { - feed: [], + feed: data.posts.map(post => ({post})), } } } 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 69cc2bf03e..55b0fdecb6 100644 --- a/src/lib/api/index.ts +++ b/src/lib/api/index.ts @@ -1,27 +1,15 @@ -import { - type $Typed, - type AppBskyEmbedExternal, - type AppBskyEmbedGallery, - type AppBskyEmbedImages, - type AppBskyEmbedRecord, - type AppBskyEmbedRecordWithMedia, - type AppBskyEmbedVideo, - AppBskyFeedPost, - type AtpAgent, - BlobRef, - ChatBskyGroupDefs, - type ComAtprotoLabelDefs, - type ComAtprotoRepoApplyWrites, - type ComAtprotoRepoStrongRef, - RichText, -} from '@atproto/api' import {TID} from '@atproto/common-web' +import {type $Typed, type Client} from '@atproto/lex' +import { + type AtUriString, + toDatetimeString, + type UriString, +} from '@atproto/syntax' +import {RichText} from '@bsky/sdk/richtext' import {t} from '@lingui/core/macro' import {type QueryClient} from '@tanstack/react-query' -import {sha256} from 'js-sha256' -import {CID} from 'multiformats/cid' -import * as Hasher from 'multiformats/hashes/hasher' +import {type LinkResolvers} from '#/lib/api/resolve' import {IMAGE_SIZE_CONFIG_POSTS} from '#/lib/constants' import {isNetworkError} from '#/lib/strings/errors' import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip' @@ -40,8 +28,10 @@ import { type PostDraft, type ThreadDraft, } from '#/view/com/composer/state/composer' +import {app, chat, com} from '#/lexicons' import * as bsky from '#/types/bsky' import {createGIFDescription} from '../gif-alt-text' +import {computeCid} from './computeCid' import {uploadBlob} from './upload-blob' export {uploadBlob} @@ -51,23 +41,36 @@ interface PostOpts { replyTo?: string onStateChange?: (state: string) => void langs?: string[] + /* + * Facet/mention resolution and reply-root lookup are appview jobs - they + * resolve handles and read posts through the appview, and the public + * fallback keeps facet detection working when logged out. + */ + appviewClient: Client + /* + * A quoted chat invite link resolves its preview through the chat service, so + * the embed resolver needs the chat client alongside the appview one. + */ + chatClient: Client + /* + * The repo write itself (applyWrites) plus every record blob (images, + * gallery items, link thumbnails, video captions) goes to the account's own + * PDS, never the appview. + */ + pdsClient: Client } -export async function post( - agent: AtpAgent, - queryClient: QueryClient, - opts: PostOpts, -) { +export async function post(queryClient: QueryClient, opts: PostOpts) { const thread = opts.thread opts.onStateChange?.(t`Processing...`) let replyPromise: - | Promise - | AppBskyFeedPost.Record['reply'] + | Promise + | app.bsky.feed.post.Main['reply'] | undefined if (opts.replyTo) { // Not awaited to avoid waterfalls. - replyPromise = resolveReply(agent, opts.replyTo) + replyPromise = resolveReply(opts.appviewClient, opts.replyTo) } // add top 3 languages from user preferences if langs is provided @@ -76,8 +79,8 @@ export async function post( langs = opts.langs.slice(0, 3) } - const did = agent.assertDid - const writes: $Typed[] = [] + const did = opts.pdsClient.assertDid + const writes: com.atproto.repo.applyWrites.$InputBody['writes'] = [] const uris: string[] = [] let now = new Date() @@ -87,14 +90,16 @@ export async function post( const draft = thread.posts[i] // Not awaited to avoid waterfalls. - const rtPromise = resolveRT(agent, draft.richtext) + const rtPromise = resolveRT(opts.appviewClient, draft.richtext) const embedPromise = resolveEmbed( - agent, + opts.appviewClient, + opts.chatClient, + opts.pdsClient, queryClient, draft, opts.onStateChange, ) - let labels: $Typed | undefined + let labels: $Typed | undefined if (draft.labels.length) { labels = { $type: 'com.atproto.label.defs#selfLabels', @@ -107,17 +112,17 @@ export async function post( now.setMilliseconds(now.getMilliseconds() + 1) tid = TID.next(tid) const rkey = tid.toString() - const uri = `at://${did}/app.bsky.feed.post/${rkey}` + const uri = `at://${did}/app.bsky.feed.post/${rkey}` as AtUriString uris.push(uri) const rt = await rtPromise const embed = await embedPromise const reply = await replyPromise - const record: AppBskyFeedPost.Record = { + const record: app.bsky.feed.post.Main = { // IMPORTANT: $type has to exist, CID is calculated with the `$type` field // present and will produce the wrong CID if you omit it. $type: 'app.bsky.feed.post', - createdAt: now.toISOString(), + createdAt: toDatetimeString(now), text: rt.text, facets: rt.facets, reply, @@ -138,7 +143,7 @@ export async function post( collection: 'app.bsky.feed.threadgate', rkey: rkey, value: createThreadgateRecord({ - createdAt: now.toISOString(), + createdAt: toDatetimeString(now), post: uri, allow: threadgateAllowUISettingToAllowRecordValue(thread.threadgate), }), @@ -156,7 +161,7 @@ export async function post( value: { ...thread.postgate, $type: 'app.bsky.feed.postgate', - createdAt: now.toISOString(), + createdAt: toDatetimeString(now), post: uri, }, }) @@ -174,8 +179,8 @@ export async function post( } try { - await agent.com.atproto.repo.applyWrites({ - repo: agent.assertDid, + await opts.pdsClient.call(com.atproto.repo.applyWrites, { + repo: did, writes: writes, validate: true, }) @@ -196,14 +201,14 @@ export async function post( return {uris} } -async function resolveRT(agent: AtpAgent, richtext: RichText) { +async function resolveRT(appviewClient: Client, richtext: RichText) { const trimmedText = richtext.text // Trim leading whitespace-only lines (but don't break ASCII art). .replace(/^(\s*\n)+/, '') // Trim any trailing whitespace. .trimEnd() let rt = new RichText({text: trimmedText}, {cleanNewlines: true}) - await rt.detectFacets(agent) + await rt.detectFacets(appviewClient) rt = shortenLinks(rt) rt = stripInvalidMentions(rt) @@ -216,9 +221,9 @@ export class ReplyDeletedError extends Error { } } -async function resolveReply(agent: AtpAgent, replyTo: string) { - const {data} = await agent.app.bsky.feed.getPosts({ - uris: [replyTo], +async function resolveReply(appviewClient: Client, replyTo: string) { + const data = await appviewClient.call(app.bsky.feed.getPosts, { + uris: [replyTo as AtUriString], }) const parentPost = data.posts[0] if (!parentPost) { @@ -229,14 +234,9 @@ async function resolveReply(agent: AtpAgent, replyTo: string) { uri: parentPost.uri, cid: parentPost.cid, } - let rootRef = parentRef + let rootRef: com.atproto.repo.strongRef.Main = parentRef - if ( - bsky.dangerousIsType( - parentPost.record, - AppBskyFeedPost.isRecord, - ) - ) { + if (bsky.isType(app.bsky.feed.post, parentPost.record)) { if (parentPost.record.reply) { rootRef = parentPost.record.reply.root } @@ -249,23 +249,28 @@ async function resolveReply(agent: AtpAgent, replyTo: string) { } async function resolveEmbed( - agent: AtpAgent, + appviewClient: Client, + chatClient: Client, + pdsClient: Client, queryClient: QueryClient, draft: PostDraft, onStateChange: ((state: string) => void) | undefined, -): Promise< - | $Typed - | $Typed - | $Typed - | $Typed - | $Typed - | $Typed - | undefined -> { +): Promise { if (draft.embed.quote) { const [resolvedMedia, resolvedQuote] = await Promise.all([ - resolveMedia(agent, queryClient, draft.embed, onStateChange), - resolveRecord(agent, queryClient, draft.embed.quote.uri), + resolveMedia( + appviewClient, + chatClient, + pdsClient, + queryClient, + draft.embed, + onStateChange, + ), + resolveRecord( + {appviewClient, chatClient}, + queryClient, + draft.embed.quote.uri, + ), ]) if (resolvedMedia) { return { @@ -283,7 +288,9 @@ async function resolveEmbed( } } const resolvedMedia = await resolveMedia( - agent, + appviewClient, + chatClient, + pdsClient, queryClient, draft.embed, onStateChange, @@ -294,7 +301,7 @@ async function resolveEmbed( if (draft.embed.link) { const resolvedLink = await fetchResolveLinkQuery( queryClient, - agent, + {appviewClient, chatClient}, draft.embed.link.uri, ) if (resolvedLink.type === 'record') { @@ -308,15 +315,17 @@ async function resolveEmbed( } async function resolveMedia( - agent: AtpAgent, + appviewClient: Client, + chatClient: Client, + pdsClient: Client, queryClient: QueryClient, embedDraft: EmbedDraft, onStateChange: ((state: string) => void) | undefined, ): Promise< - | $Typed - | $Typed - | $Typed - | $Typed + | $Typed + | $Typed + | $Typed + | $Typed | undefined > { if (embedDraft.media?.type === 'images') { @@ -325,7 +334,7 @@ async function resolveMedia( count: imagesDraft.length, }) onStateChange?.(t`Uploading images...`) - const images: AppBskyEmbedImages.Image[] = await Promise.all( + const images: app.bsky.embed.images.Image[] = await Promise.all( imagesDraft.map(async (image, i) => { logger.debug(`Compressing image #${i}`) const {path, width, height, mime} = await compressImage( @@ -333,9 +342,9 @@ async function resolveMedia( IMAGE_SIZE_CONFIG_POSTS, ) logger.debug(`Uploading image #${i}`) - const res = await uploadBlob(agent, path, mime) + const res = await uploadBlob(pdsClient, path, mime) return { - image: res.data.blob, + image: res.blob, alt: image.alt, aspectRatio: {width, height}, } @@ -352,7 +361,7 @@ async function resolveMedia( count: imagesDraft.length, }) onStateChange?.(t`Uploading images...`) - const items: $Typed[] = await Promise.all( + const items: $Typed[] = await Promise.all( imagesDraft.map(async (image, i) => { logger.debug(`Compressing image #${i}`) const {path, width, height, mime} = await compressImage( @@ -360,10 +369,10 @@ async function resolveMedia( IMAGE_SIZE_CONFIG_POSTS, ) logger.debug(`Uploading image #${i}`) - const res = await uploadBlob(agent, path, mime) + const res = await uploadBlob(pdsClient, path, mime) return { $type: 'app.bsky.embed.gallery#image' as const, - image: res.data.blob, + image: res.blob, alt: image.alt, aspectRatio: {width, height}, } @@ -383,10 +392,10 @@ async function resolveMedia( videoDraft.captions .filter(caption => caption.lang !== '') .map(async caption => { - const {data} = await agent.uploadBlob(caption.file, { + const res = await pdsClient.uploadBlob(caption.file, { encoding: 'text/vtt', }) - return {lang: caption.lang, file: data.blob} + return {lang: caption.lang, file: res.body.blob} }), ) @@ -406,6 +415,11 @@ async function resolveMedia( return { $type: 'app.bsky.embed.video', + /* + * The video blob is a plain lex blob from the video pipeline + * (getJobStatus, in composer state/video). Its structural shape matches + * the lexicon blob field and the CID hasher (see computeCid). + */ video: videoDraft.pendingPublish.blobRef, alt: videoDraft.altText || undefined, captions: captions.length === 0 ? undefined : captions, @@ -416,22 +430,18 @@ async function resolveMedia( } if (embedDraft.media?.type === 'gif') { const gifDraft = embedDraft.media - const resolvedGif = await fetchResolveGifQuery( - queryClient, - agent, - gifDraft.gif, - ) - let blob: BlobRef | undefined + const resolvedGif = await fetchResolveGifQuery(queryClient, gifDraft.gif) + let blob: app.bsky.embed.external.External['thumb'] if (resolvedGif.thumb) { onStateChange?.(t`Uploading link thumbnail...`) const {path, mime} = resolvedGif.thumb.source - const response = await uploadBlob(agent, path, mime) - blob = response.data.blob + const response = await uploadBlob(pdsClient, path, mime) + blob = response.blob } return { $type: 'app.bsky.embed.external', external: { - uri: resolvedGif.uri, + uri: resolvedGif.uri as UriString, title: resolvedGif.title, description: createGIFDescription(resolvedGif.title, gifDraft.alt), thumb: blob, @@ -441,21 +451,21 @@ async function resolveMedia( if (embedDraft.link) { const resolvedLink = await fetchResolveLinkQuery( queryClient, - agent, + {appviewClient, chatClient}, embedDraft.link.uri, ) if (resolvedLink.type === 'external') { - let blob: BlobRef | undefined + let blob: app.bsky.embed.external.External['thumb'] if (resolvedLink.thumb) { onStateChange?.(t`Uploading link thumbnail...`) const {path, mime} = resolvedLink.thumb.source - const response = await uploadBlob(agent, path, mime) - blob = response.data.blob + const response = await uploadBlob(pdsClient, path, mime) + blob = response.blob } return { $type: 'app.bsky.embed.external', external: { - uri: resolvedLink.uri, + uri: resolvedLink.uri as UriString, title: resolvedLink.title, description: resolvedLink.description, thumb: blob, @@ -465,12 +475,12 @@ async function resolveMedia( } if ( resolvedLink.type === 'chat-invite' && - ChatBskyGroupDefs.isJoinLinkPreviewView(resolvedLink.view) + bsky.isType(chat.bsky.group.defs.joinLinkPreviewView, resolvedLink.view) ) { return { $type: 'app.bsky.embed.external', external: { - uri: resolvedLink.uri, + uri: resolvedLink.uri as UriString, title: resolvedLink.view.name, description: `${resolvedLink.view.memberCount}/${resolvedLink.view.memberLimit}`, }, @@ -481,100 +491,13 @@ async function resolveMedia( } async function resolveRecord( - agent: AtpAgent, + clients: LinkResolvers, queryClient: QueryClient, uri: string, -): Promise { - const resolvedLink = await fetchResolveLinkQuery(queryClient, agent, uri) +): Promise { + const resolvedLink = await fetchResolveLinkQuery(queryClient, clients, uri) if (resolvedLink.type !== 'record') { throw Error(t`Expected uri to resolve to a record`) } return resolvedLink.record } - -// The built-in hashing functions from multiformats (`multiformats/hashes/sha2`) -// are meant for Node.js, this is the cross-platform equivalent. -const mf_sha256 = Hasher.from({ - name: 'sha2-256', - code: 0x12, - encode: input => { - const digest = sha256.arrayBuffer(input) - return new Uint8Array(digest) - }, -}) - -async function computeCid(record: AppBskyFeedPost.Record): Promise { - /* - * Lazily loaded since it's only needed when posting a thread, and its - * `cborg` dependency is ~190KB that would otherwise be in the initial - * web bundle. - */ - const dcbor = await import('@ipld/dag-cbor') - // IMPORTANT: `prepareObject` prepares the record to be hashed by removing - // fields with undefined value, and converting BlobRef instances to the - // right IPLD representation. - const prepared = prepareForHashing(record) - // 1. Encode the record into DAG-CBOR format - const encoded = dcbor.encode(prepared) - // 2. Hash the record in SHA-256 (code 0x12) - const digest = await mf_sha256.digest(encoded) - // 3. Create a CIDv1, specifying DAG-CBOR as content (code 0x71) - const cid = CID.createV1(0x71, digest) - // 4. Get the Base32 representation of the CID (`b` prefix) - return cid.toString() -} - -// Returns a transformed version of the object for use in DAG-CBOR. -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function prepareForHashing(v: any): any { - // IMPORTANT: BlobRef#ipld() returns the correct object we need for hashing, - // the API client will convert this for you but we're hashing in the client, - // so we need it *now*. - if (v instanceof BlobRef) { - return v.ipld() - } - - // Walk through arrays - if (Array.isArray(v)) { - let pure = true - const mapped = v.map(value => { - if (value !== (value = prepareForHashing(value))) { - pure = false - } - return value - }) - return pure ? v : mapped - } - - // Walk through plain objects - if (isPlainObject(v)) { - const obj: Record = {} - let pure = true - for (const key in v) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - let value = v[key] - // `value` is undefined - if (value === undefined) { - pure = false - continue - } - // `prepareObject` returned a value that's different from what we had before - if (value !== (value = prepareForHashing(value))) { - pure = false - } - obj[key] = value - } - // Return as is if we haven't needed to tamper with anything - return pure ? v : obj - } - return v -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function isPlainObject(v: any): boolean { - if (typeof v !== 'object' || v === null) { - return false - } - const proto = Object.getPrototypeOf(v) - return proto === Object.prototype || proto === null -} diff --git a/src/lib/api/resolve.ts b/src/lib/api/resolve.ts index 476c0f1a20..4d9ec3e457 100644 --- a/src/lib/api/resolve.ts +++ b/src/lib/api/resolve.ts @@ -1,12 +1,7 @@ -import { - type AppBskyFeedDefs, - type AppBskyGraphDefs, - type AtpAgent, - type ComAtprotoRepoStrongRef, -} from '@atproto/api' -import {AtUri} from '@atproto/api' +import {type Client} from '@atproto/lex' +import {AtUri, type AtUriString, type HandleString} from '@atproto/syntax' -import {DM_SERVICE_HEADERS, IMAGE_SIZE_CONFIG_2K_1MB} from '#/lib/constants' +import {IMAGE_SIZE_CONFIG_2K_1MB} from '#/lib/constants' import {getLinkMeta, type LinkMeta} from '#/lib/link-meta/link-meta' import {resolveShortLink} from '#/lib/link-meta/resolve-short-link' import {downloadAndResize} from '#/lib/media/manip' @@ -29,6 +24,7 @@ import {type ComposerImage} from '#/state/gallery' import {createComposerImage} from '#/state/gallery' import {type ChatInvitePreview} from '#/state/queries/join-links' import {type Gif} from '#/features/gifPicker/types' +import {app, chat, com} from '#/lexicons' import {createGIFDescription} from '../gif-alt-text' type ResolvedExternalLink = { @@ -47,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 = { @@ -94,8 +90,21 @@ export class EmbeddingDisabledError extends Error { } } +/** + * The clients a link resolution may need. + * + * Everything but the chat-invite branch is an appview read, and the chat invite + * preview goes through the chat client so it is proxied to the chat service. + * Both are passed together because the caller cannot know which branch a URL + * will take until it is parsed. + */ +export type LinkResolvers = { + appviewClient: Client + chatClient: Client +} + export async function resolveLink( - agent: AtpAgent, + {appviewClient, chatClient}: LinkResolvers, uri: string, ): Promise { if (isShortLink(uri)) { @@ -124,15 +133,17 @@ export async function resolveLink( const [_0, handleOrDid, _1, rkey] = uri.split('/').filter(Boolean) const did = await fetchDid(handleOrDid) const feed = makeRecordUri(did, 'app.bsky.feed.generator', rkey) - const res = await agent.app.bsky.feed.getFeedGenerator({feed}) + const data = await appviewClient.call(app.bsky.feed.getFeedGenerator, { + feed: feed, + }) return { type: 'record', record: { - uri: res.data.view.uri, - cid: res.data.view.cid, + uri: data.view.uri, + cid: data.view.cid, }, kind: 'feed', - view: res.data.view, + view: data.view, } } if (isBskyListUrl(uri)) { @@ -140,28 +151,29 @@ export async function resolveLink( const [_0, handleOrDid, _1, rkey] = uri.split('/').filter(Boolean) const did = await fetchDid(handleOrDid) const list = makeRecordUri(did, 'app.bsky.graph.list', rkey) - const res = await agent.app.bsky.graph.getList({list}) + const data = await appviewClient.call(app.bsky.graph.getList, { + list: list, + }) return { type: 'record', record: { - uri: res.data.list.uri, - cid: res.data.list.cid, + uri: data.list.uri, + cid: data.list.cid, }, kind: 'list', - view: res.data.list, + view: data.list, } } const chatInviteCode = getChatInviteCodeFromUrl(uri) if (chatInviteCode) { - const res = await agent.chat.bsky.group.getJoinLinkPreviews( - {codes: [chatInviteCode]}, - {headers: DM_SERVICE_HEADERS}, - ) + const data = await chatClient.call(chat.bsky.group.getJoinLinkPreviews, { + codes: [chatInviteCode], + }) return { type: 'chat-invite', uri, code: chatInviteCode, - view: res.data.joinLinkPreviews[0], + view: data.joinLinkPreviews[0], } } if (isBskyStartUrl(uri) || isBskyStarterPackUrl(uri)) { @@ -173,34 +185,36 @@ export async function resolveLink( } const did = await fetchDid(parsed.name) const starterPack = createStarterPackUri({did, rkey: parsed.rkey}) - const res = await agent.app.bsky.graph.getStarterPack({starterPack}) + const data = await appviewClient.call(app.bsky.graph.getStarterPack, { + starterPack: starterPack as AtUriString, + }) return { type: 'record', record: { - uri: res.data.starterPack.uri, - cid: res.data.starterPack.cid, + uri: data.starterPack.uri, + cid: data.starterPack.cid, }, kind: 'starter-pack', - view: res.data.starterPack, + view: data.starterPack, } } - return resolveExternal(agent, uri) + return resolveExternal(uri) // Forked from useGetPost. TODO: move into RQ. async function getPost({uri}: {uri: string}) { const urip = new AtUri(uri) if (!urip.host.startsWith('did:')) { - const res = await agent.resolveHandle({ - handle: urip.host, - }) - // @ts-expect-error TODO new-sdk-migration - urip.host = res.data.did + const data = await appviewClient.call( + com.atproto.identity.resolveHandle, + {handle: urip.host as HandleString}, + ) + urip.host = data.did } - const res = await agent.getPosts({ + const data = await appviewClient.call(app.bsky.feed.getPosts, { uris: [urip.toString()], }) - if (res.success && res.data.posts[0]) { - return res.data.posts[0] + if (data.posts[0]) { + return data.posts[0] } throw new Error('getPost: post not found') } @@ -209,17 +223,17 @@ export async function resolveLink( async function fetchDid(handleOrDid: string) { let identifier = handleOrDid if (!identifier.startsWith('did:')) { - const res = await agent.resolveHandle({handle: identifier}) - identifier = res.data.did + const data = await appviewClient.call( + com.atproto.identity.resolveHandle, + {handle: identifier as HandleString}, + ) + identifier = data.did } return identifier } } -export async function resolveGif( - agent: AtpAgent, - gif: Gif, -): Promise { +export async function resolveGif(gif: Gif): Promise { const gifUrl = gif.media_formats.gif.url const params = new URLSearchParams() params.set('hh', String(gif.media_formats.gif.dims[1])) @@ -258,11 +272,8 @@ function getFileSlug(url: string | undefined): string | undefined { return dotIndex > 0 ? filename.slice(0, dotIndex) : undefined } -async function resolveExternal( - agent: AtpAgent, - uri: string, -): Promise { - const result = await getLinkMeta(agent, uri) +async function resolveExternal(uri: string): Promise { + const result = await getLinkMeta(uri) return { type: 'external', uri: result.url, diff --git a/src/lib/api/upload-blob.ts b/src/lib/api/upload-blob.ts index 0bee39b9b1..c679e15d84 100644 --- a/src/lib/api/upload-blob.ts +++ b/src/lib/api/upload-blob.ts @@ -1,38 +1,62 @@ import {copyAsync} from 'expo-file-system/legacy' -import {type AtpAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api' +import {type BlobRef, type Client, type EncodingString} from '@atproto/lex' import {safeDeleteAsync} from '#/lib/media/manip' /** - * @param encoding Allows overriding the blob's type + * The blob-upload response body: `{blob}`. lex `Client.uploadBlob` returns the + * full XRPC response, so this helper unwraps `res.body` for callers. + */ +type UploadBlobResult = {blob: BlobRef} + +/** + * @param encoding Allows overriding the blob's type. Passed as the lex upload + * option (NEVER a content-type header - lex-client throws if the encoding is + * set via headers). */ export async function uploadBlob( - agent: AtpAgent, + client: Client, input: string | Blob, encoding?: string, -): Promise { +): Promise { if (typeof input === 'string' && input.startsWith('file:')) { const blob = await asBlob(input) - return agent.uploadBlob(blob, {encoding}) + return uploadBlobResult(client, blob, encoding) } if (typeof input === 'string' && input.startsWith('/')) { const blob = await asBlob(`file://${input}`) - return agent.uploadBlob(blob, {encoding}) + return uploadBlobResult(client, blob, encoding) } if (typeof input === 'string' && input.startsWith('data:')) { const blob = await fetch(input).then(r => r.blob()) - return agent.uploadBlob(blob, {encoding}) + return uploadBlobResult(client, blob, encoding) } if (input instanceof Blob) { - return agent.uploadBlob(input, {encoding}) + return uploadBlobResult(client, input, encoding) } throw new TypeError(`Invalid uploadBlob input: ${typeof input}`) } +async function uploadBlobResult( + client: Client, + blob: Blob, + encoding?: string, +): Promise { + const res = await client.uploadBlob(blob, { + /* + * The lex encoding option is a branded mime string + * (`${string}/${string}`); callers pass a plain mime string, so assert the + * brand here. + */ + encoding: encoding as EncodingString | undefined, + }) + return {blob: res.body.blob} +} + async function asBlob(uri: string): Promise { return withSafeFile(uri, async safeUri => { // Note diff --git a/src/lib/api/upload-blob.web.ts b/src/lib/api/upload-blob.web.ts index d74e834647..0a58ad42f1 100644 --- a/src/lib/api/upload-blob.web.ts +++ b/src/lib/api/upload-blob.web.ts @@ -1,28 +1,42 @@ -import {type AtpAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api' +import {type BlobRef, type Client, type EncodingString} from '@atproto/lex' + +/** + * The blob-upload response body: `{blob}`. lex `Client.uploadBlob` returns the + * full XRPC response, so this helper unwraps `res.body` for callers. + */ +type UploadBlobResult = {blob: BlobRef} /** * @note It is recommended, on web, to use the `file` instance of the file * selector input element, rather than a `data:` URL, to avoid * loading the file into memory. `File` extends `Blob` "file" instances can * be passed directly to this function. + * + * @param encoding Passed as the lex upload option (NEVER a content-type header + * - lex-client throws if the encoding is set via headers). */ export async function uploadBlob( - agent: AtpAgent, + client: Client, input: string | Blob, encoding?: string, -): Promise { +): Promise { + /* + * The lex encoding option is a branded mime string (`${string}/${string}`); + * callers pass a plain mime string, so assert the brand here. + */ + const enc = encoding as EncodingString | undefined if ( typeof input === 'string' && (input.startsWith('data:') || input.startsWith('blob:')) ) { const blob = await fetch(input).then(r => r.blob()) - return agent.uploadBlob(blob, {encoding}) + const res = await client.uploadBlob(blob, {encoding: enc}) + return {blob: res.body.blob} } if (input instanceof Blob) { - return agent.uploadBlob(input, { - encoding, - }) + const res = await client.uploadBlob(input, {encoding: enc}) + return {blob: res.body.blob} } throw new TypeError(`Invalid uploadBlob input: ${typeof input}`) diff --git a/src/lib/constants.ts b/src/lib/constants.ts index bc079c7af5..514312aeb9 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/sdk' -import {type ProxyHeaderValue} from '#/state/session/agent' 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] @@ -240,24 +241,45 @@ export const DEV_ENV_APPVIEW_DID = `did:plc:dw4kbjf5mn7nhenabiqpkyh3` // always export const BLUESKY_PROXY_HEADER = { value: `${BLUESKY_PROXY_DID}#bsky_appview`, get() { - return this.value as ProxyHeaderValue + return this.value as Service }, set(value: string) { this.value = value }, } -export const DM_SERVICE_HEADERS = { - 'atproto-proxy': `${CHAT_PROXY_DID}#bsky_chat`, -} +/** + * The chat service's proxy target, in the `did#service_id` form a lex client's + * `service` option takes. A client constructed with it emits `atproto-proxy: + * ` on every request, which is what routes `chat.bsky.*` calls to + * the chat service. + * + * The DID comes from the env-configurable `CHAT_PROXY_DID` (via + * `EXPO_PUBLIC_CHAT_PROXY_DID`) rather than a hard-coded constant, so the + * target can be retargeted per environment. + */ +export const CHAT_PROXY_SERVICE: Service = `${CHAT_PROXY_DID}#bsky_chat` -export const BLUESKY_MOD_SERVICE_HEADERS = { - 'atproto-proxy': `${BSKY_LABELER_DID}#atproto_labeler`, -} +/** + * Bluesky's own moderation service, in the `did#service_id` form a lex client's + * per-call `service` option takes. Passing it emits `atproto-proxy: ` on that one request, routing a `com.atproto.moderation.*` call to + * Bluesky's labeler. + * + * Reports and appeals aimed at a DIFFERENT labeler build their own value from + * 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 = `${api.moderation.did}#atproto_labeler` -export const BLUESKY_NOTIF_SERVICE_HEADERS = { - 'atproto-proxy': `${BLUESKY_PROXY_DID}#bsky_notif`, -} +/** + * The notification service's proxy target, in the `did#service_id` form a lex + * client's per-call `service` option takes. Passing it emits `atproto-proxy: + * ` on that one request, which is what routes push registration to + * the notification service (replaces the old + * `BLUESKY_NOTIF_SERVICE_HEADERS`). + */ +export const NOTIF_SERVICE: Service = `${BLUESKY_PROXY_DID}#bsky_notif` export const webLinks = { tos: `https://bsky.social/about/support/tos`, 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/generate-starterpack.ts b/src/lib/generate-starterpack.ts index a53e95a6a3..a786fb47ad 100644 --- a/src/lib/generate-starterpack.ts +++ b/src/lib/generate-starterpack.ts @@ -1,11 +1,5 @@ -import { - type $Typed, - type AppBskyActorDefs, - type AppBskyGraphGetStarterPack, - type AtpAgent, - type ComAtprotoRepoApplyWrites, - type Facet, -} from '@atproto/api' +import {type Client} from '@atproto/lex' +import {type AtUriString, toDatetimeString} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {useMutation} from '@tanstack/react-query' @@ -14,7 +8,8 @@ import {until} from '#/lib/async/until' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {enforceLen} from '#/lib/strings/helpers' -import {useAgent} from '#/state/session' +import {useAppviewClient, usePdsClient} from '#/state/session' +import {app, com} from '#/lexicons' import type * as bsky from '#/types/bsky' export const createStarterPackList = async ({ @@ -22,30 +17,27 @@ export const createStarterPackList = async ({ description, descriptionFacets, profiles, - agent, + client, }: { name: string description?: string - descriptionFacets?: Facet[] + descriptionFacets?: app.bsky.richtext.facet.Main[] profiles: bsky.profile.AnyProfileView[] - agent: AtpAgent + client: Client }): Promise<{uri: string; cid: string}> => { if (profiles.length === 0) throw new Error('No profiles given') - const list = await agent.app.bsky.graph.list.create( - {repo: agent.session!.did}, - { - name, - description, - descriptionFacets, - avatar: undefined, - createdAt: new Date().toISOString(), - purpose: 'app.bsky.graph.defs#referencelist', - }, - ) + const list = await client.create(app.bsky.graph.list, { + name, + description, + descriptionFacets, + avatar: undefined, + createdAt: toDatetimeString(new Date()), + purpose: 'app.bsky.graph.defs#referencelist', + }) if (!list) throw new Error('List creation failed') - await agent.com.atproto.repo.applyWrites({ - repo: agent.session!.did, + await client.call(com.atproto.repo.applyWrites, { + repo: client.assertDid, writes: profiles.map(p => createListItem({did: p.did, listUri: list.uri})), }) @@ -60,28 +52,27 @@ export function useGenerateStarterPackMutation({ onError: (e: Error) => void }) { const {_} = useLingui() - const agent = useAgent() + const appviewClient = useAppviewClient() + const pdsClient = usePdsClient() return useMutation<{uri: string; cid: string}, Error, void>({ mutationFn: async () => { - let profile: AppBskyActorDefs.ProfileViewDetailed | undefined - let profiles: AppBskyActorDefs.ProfileView[] | undefined + let profile: app.bsky.actor.defs.ProfileViewDetailed | undefined + let profiles: app.bsky.actor.defs.ProfileView[] | undefined await Promise.all([ (async () => { - profile = ( - await agent.app.bsky.actor.getProfile({ - actor: agent.session!.did, - }) - ).data + profile = await appviewClient.call(app.bsky.actor.getProfile, { + actor: pdsClient.assertDid, + }) })(), (async () => { profiles = ( - await agent.app.bsky.actor.searchActors({ + await appviewClient.call(app.bsky.actor.searchActors, { q: encodeURIComponent('*'), limit: 49, }) - ).data.actors.filter(p => p.viewer?.following) + ).actors.filter(p => p.viewer?.following) })(), ]) @@ -106,23 +97,19 @@ export function useGenerateStarterPackMutation({ const list = await createStarterPackList({ name: starterPackName, profiles, - agent, + client: pdsClient, }) - return await agent.app.bsky.graph.starterpack.create( - { - repo: agent.session!.did, - }, - { - name: starterPackName, - list: list.uri, - createdAt: new Date().toISOString(), - }, - ) + return await pdsClient.create(app.bsky.graph.starterpack, { + name: starterPackName, + // `create` returns a plain string uri + list: list.uri as AtUriString, + createdAt: toDatetimeString(new Date()), + }) }, onSuccess: async data => { - await whenAppViewReady(agent, data.uri, v => { - return typeof v?.data.starterPack.uri === 'string' + await whenAppViewReady(appviewClient, data.uri, v => { + return typeof v?.starterPack.uri === 'string' }) onSuccess(data) }, @@ -138,7 +125,7 @@ function createListItem({ }: { did: string listUri: string -}): $Typed { +}): com.atproto.repo.applyWrites.$InputBody['writes'][number] { return { $type: 'com.atproto.repo.applyWrites#create', collection: 'app.bsky.graph.listitem', @@ -152,14 +139,17 @@ function createListItem({ } async function whenAppViewReady( - agent: AtpAgent, + client: Client, uri: string, - fn: (res?: AppBskyGraphGetStarterPack.Response) => boolean, + fn: (res?: app.bsky.graph.getStarterPack.$OutputBody) => boolean, ) { await until( 5, // 5 tries 1e3, // 1s delay between tries fn, - () => agent.app.bsky.graph.getStarterPack({starterPack: uri}), + () => + client.call(app.bsky.graph.getStarterPack, { + starterPack: uri as AtUriString, + }), ) } 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/useOTAUpdates.ts b/src/lib/hooks/useOTAUpdates.ts index 180102c2cd..143eb5c619 100644 --- a/src/lib/hooks/useOTAUpdates.ts +++ b/src/lib/hooks/useOTAUpdates.ts @@ -60,8 +60,7 @@ function getRunningChannel( * entirely from embedded manifests, so narrow it ourselves. */ const manifest = currentlyRunning?.manifest as - | {metadata?: {channel?: unknown}} - | undefined + {metadata?: {channel?: unknown}} | undefined const channel = manifest?.metadata?.channel if (typeof channel === 'string' && channel) { return channel 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/lexClient.ts b/src/lib/lexClient.ts new file mode 100644 index 0000000000..568f9c142f --- /dev/null +++ b/src/lib/lexClient.ts @@ -0,0 +1,51 @@ +import { + type Agent, + type AgentOptions, + Client, + type ClientOptions, +} from '@atproto/lex' + +/** + * App-standard factory for lex {@link Client}s. Use this instead of `new + * Client(...)` so every client shares the same lenient response processing. + * + * lex-client defaults to strict Lex processing, which rejects responses + * containing the LEGACY blob reference format (objects with `cid` and + * `mimeType` properties instead of `$type: 'blob'`). Older records on the + * network still carry these, and rejecting them would drop records the app + * currently renders. Lenient mode also relaxes datetime format checks (e.g. + * missing timezones) and blob MIME/size constraints. `Client.configure` only + * accepts `appLabelers` globally, so the option is defaulted here, per + * constructed client. + */ +export function createLexClient( + agent: Agent | AgentOptions, + options?: ClientOptions, +): Client { + return new Client(agent, {strictResponseProcessing: false, ...options}) +} + +/** + * An unauthenticated {@link Client} pointed at an arbitrary service URL, for + * the PRE-AUTH flows that talk to a host the user typed or picked (hosting + * provider description, password reset, handle availability). Those requests + * cannot go through a session-scoped client, because there is no session yet. + * + * Deliberately NOT memoized: `service` is user-supplied and changes as the user + * edits it, so there is no stable key to cache on. Each call builds a fresh + * client, mirroring the ad-hoc agents these call sites constructed inline. That + * keeps client identity per-render, so callers must not put the returned client + * in a React Query key or a dependency array. + * + * Requests use PLAIN `fetch`, not `networkAwareFetch`: the host is untrusted + * input, and a typo'd or dead service must not be reported as the app losing + * network reachability. + * + * `appLabelers: null` suppresses the global `Client.appLabelers` static: these + * are `com.atproto.server` calls to a host the user typed, which have no use + * for moderation labels, and the header would disclose the app's configured + * moderation authorities to an arbitrary third-party server. + */ +export function createServiceClient(service: string): Client { + return createLexClient({service}, {appLabelers: null}) +} diff --git a/src/lib/link-meta/link-meta.ts b/src/lib/link-meta/link-meta.ts index c282a0c4c3..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, type AtpAgent} 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,12 +25,11 @@ 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( - agent: AtpAgent, url: string, timeout = 15e3, ): Promise { @@ -80,9 +78,7 @@ export async function getLinkMeta( try { const response = await fetch( - `${LINK_META_PROXY(agent.serviceUrl.toString() || '')}${encodeURIComponent( - url, - )}`, + `${LINK_META_PROXY('')}${encodeURIComponent(url)}`, {signal: controller.signal}, ) diff --git a/src/lib/media/video/multipart/types.ts b/src/lib/media/video/multipart/types.ts index 16817778c3..68901d565b 100644 --- a/src/lib/media/video/multipart/types.ts +++ b/src/lib/media/video/multipart/types.ts @@ -1,3 +1,5 @@ +import {type app} from '#/lexicons' + /** * One part of a multipart upload. `partNumber` is 1-indexed to match the S3 * convention the backend uses. @@ -42,12 +44,7 @@ export type StartUploadResponse = { } export type UploadState = - | 'created' - | 'finishing' - | 'completed' - | 'failed' - | 'aborted' - | 'expired' + 'created' | 'finishing' | 'completed' | 'failed' | 'aborted' | 'expired' export type UploadStatusResponse = { jobId: string @@ -57,13 +54,13 @@ export type UploadStatusResponse = { expiresAt: string state: UploadState completedJobId?: string - jobStatus?: import('@atproto/api').AppBskyVideoDefs.JobStatus + jobStatus?: app.bsky.video.defs.JobStatus failureReason?: string } export type FinishUploadResponse = { completedJobId: string - jobStatus: import('@atproto/api').AppBskyVideoDefs.JobStatus + jobStatus: app.bsky.video.defs.JobStatus } export type AbortUploadResponse = Pick< diff --git a/src/lib/media/video/multipart/upload.ts b/src/lib/media/video/multipart/upload.ts index 2b45e6d2c6..d3e5210873 100644 --- a/src/lib/media/video/multipart/upload.ts +++ b/src/lib/media/video/multipart/upload.ts @@ -1,9 +1,10 @@ -import {type AppBskyVideoDefs, type AtpAgent} from '@atproto/api' +import {type Client} from '@atproto/lex' import {nanoid} from 'nanoid/non-secure' import {AbortError} from '#/lib/async/cancelable' import {type CompressedVideo} from '#/lib/media/video/types' import {shouldRetryError} from '#/lib/strings/errors' +import {type app} from '#/lexicons' import {getServiceAuthToken} from '../upload.shared' import {mimeToExt} from '../util' import { @@ -29,19 +30,22 @@ export class MultipartFallbackError extends Error {} export async function uploadVideoMultipart({ video, - agent, + client, + dispatchUrl, setProgress, signal, onStarted, }: { video: CompressedVideo - agent: AtpAgent + client: Client + /** The account's PDS/dispatch URL, for the uploadBlob service-auth token. */ + dispatchUrl: string | URL setProgress: (progress: number) => void signal: AbortSignal onStarted?: () => void -}): Promise { +}): Promise { throwIfAborted(signal) - const tokenProvider = createTokenProvider(agent, signal) + const tokenProvider = createTokenProvider(client, dispatchUrl, signal) const token = await tokenProvider.get() const name = `${nanoid(12)}.${mimeToExt(video.mimeType)}` let session @@ -134,7 +138,7 @@ async function finishAndRecover({ getToken: (forceRefresh?: boolean) => Promise signal: AbortSignal resendMissingParts: (receivedPartNumbers: number[]) => Promise -}): Promise { +}): Promise { let createdFailures = 0 let forceTokenRefresh = true while (true) { @@ -224,7 +228,7 @@ async function abortThenFallbackOrResolve( jobId: string, token: string, cause: unknown, -): Promise { +): Promise { const result = await abortUploadWithRetry(jobId, token) if (result.state === 'aborted') { throw new MultipartFallbackError( @@ -264,7 +268,11 @@ async function abortUploadWithRetry(jobId: string, token: string) { throw lastError } -function createTokenProvider(agent: AtpAgent, signal: AbortSignal) { +function createTokenProvider( + client: Client, + dispatchUrl: string | URL, + signal: AbortSignal, +) { let token: string | undefined let expiresAt = 0 let refresh: Promise | undefined @@ -273,7 +281,7 @@ function createTokenProvider(agent: AtpAgent, signal: AbortSignal) { if (!forceRefresh && token && Date.now() < expiresAt - 60_000) return token if (!refresh) { const exp = Math.floor(Date.now() / 1000) + 60 * 30 - refresh = getServiceAuthTokenWithRetry(agent, exp, signal) + refresh = getServiceAuthTokenWithRetry(client, dispatchUrl, exp, signal) .then(nextToken => { token = nextToken expiresAt = exp * 1000 @@ -290,7 +298,8 @@ function createTokenProvider(agent: AtpAgent, signal: AbortSignal) { } async function getServiceAuthTokenWithRetry( - agent: AtpAgent, + client: Client, + dispatchUrl: string | URL, exp: number, signal: AbortSignal, ) { @@ -299,7 +308,8 @@ async function getServiceAuthTokenWithRetry( throwIfAborted(signal) try { return await getServiceAuthToken({ - agent, + client, + dispatchUrl, lxm: 'com.atproto.repo.uploadBlob', exp, }) diff --git a/src/lib/media/video/types.ts b/src/lib/media/video/types.ts index 6431c567fa..f929c06664 100644 --- a/src/lib/media/video/types.ts +++ b/src/lib/media/video/types.ts @@ -3,10 +3,7 @@ // on the `video:upload:compressSkipped` analytics event, so the two stay in // sync. export type VideoCompressSkipReason = - | 'gif' - | 'below-byte-threshold' - | 'no-webcodecs' - | 'compress-error-fallback' + 'gif' | 'below-byte-threshold' | 'no-webcodecs' | 'compress-error-fallback' export type VideoUploadTransport = 'multipart' | 'legacy' | 'legacy-fallback' diff --git a/src/lib/media/video/upload.shared.ts b/src/lib/media/video/upload.shared.ts index f8aaa1249b..9e8d99a185 100644 --- a/src/lib/media/video/upload.shared.ts +++ b/src/lib/media/video/upload.shared.ts @@ -1,44 +1,62 @@ -import {type AtpAgent} from '@atproto/api' +import {type Client} from '@atproto/lex' +import {type DidString, type NsidString} from '@atproto/syntax' import {type I18n} from '@lingui/core' import {msg} from '@lingui/core/macro' import {VIDEO_SERVICE_DID} from '#/lib/constants' import {UploadLimitError} from '#/lib/media/video/errors' import {getServiceAuthAudFromUrl} from '#/lib/strings/url-helpers' -import {createVideoAgent} from './util' +import {app, com} from '#/lexicons' +import {createVideoServiceClient} from './util' export async function getServiceAuthToken({ - agent, + client, + dispatchUrl, aud, lxm, exp, }: { - agent: AtpAgent + client: Client + /** + * The account's dispatch URL (the old `agent.dispatchUrl`: its PDS, falling + * back to the account service). Only needed when `aud` is omitted, so the + * default audience can be derived from the PDS host. A lex {@link Client} does + * not expose this - it resolves the PDS per request internally - so the caller, + * which holds the session, passes it in. + */ + dispatchUrl?: string | URL aud?: string - lxm: string + lxm: NsidString exp?: number }) { - const pdsAud = getServiceAuthAudFromUrl(agent.dispatchUrl) - if (!pdsAud) { - throw new Error('Agent does not have a PDS URL') + let resolvedAud = aud + if (!resolvedAud) { + if (!dispatchUrl) { + throw new Error('Missing service auth audience: no aud or dispatchUrl') + } + const pdsAud = getServiceAuthAudFromUrl(dispatchUrl) + if (!pdsAud) { + throw new Error('Agent does not have a PDS URL') + } + resolvedAud = pdsAud } - const {data: serviceAuth} = await agent.com.atproto.server.getServiceAuth({ - aud: aud ?? pdsAud, + const {token} = await client.call(com.atproto.server.getServiceAuth, { + aud: resolvedAud as DidString, lxm, exp, }) - return serviceAuth.token + return token } -export async function getVideoUploadLimits(agent: AtpAgent, i18n: I18n) { +export async function getVideoUploadLimits(client: Client, i18n: I18n) { const token = await getServiceAuthToken({ - agent, + client, lxm: 'app.bsky.video.getUploadLimits', aud: VIDEO_SERVICE_DID, }) - const videoAgent = createVideoAgent() - const {data: limits} = await videoAgent.app.bsky.video - .getUploadLimits({}, {headers: {Authorization: `Bearer ${token}`}}) + const videoClient = createVideoServiceClient(token) + const limits = await videoClient + .call(app.bsky.video.getUploadLimits) .catch(err => { if (err instanceof Error) { throw new UploadLimitError(err.message) diff --git a/src/lib/media/video/upload.ts b/src/lib/media/video/upload.ts index b91ad7a153..287c8f09c6 100644 --- a/src/lib/media/video/upload.ts +++ b/src/lib/media/video/upload.ts @@ -1,5 +1,5 @@ import {createUploadTask, FileSystemUploadType} from 'expo-file-system/legacy' -import {type AppBskyVideoDefs, type AtpAgent} from '@atproto/api' +import {type Client} from '@atproto/lex' import {type I18n} from '@lingui/core' import {msg} from '@lingui/core/macro' import {nanoid} from 'nanoid/non-secure' @@ -11,13 +11,15 @@ import { type VideoUploadTransport, } from '#/lib/media/video/types' import {Features, features} from '#/analytics/features' +import {type app} from '#/lexicons' import {MultipartFallbackError, uploadVideoMultipart} from './multipart/upload' import {getServiceAuthToken, getVideoUploadLimits} from './upload.shared' import {createVideoEndpointUrl, mimeToExt} from './util' export async function uploadVideo({ video, - agent, + client, + dispatchUrl, did, setProgress, signal, @@ -25,7 +27,9 @@ export async function uploadVideo({ onTransport, }: { video: CompressedVideo - agent: AtpAgent + client: Client + /** The account's PDS/dispatch URL, for the uploadBlob service-auth token. */ + dispatchUrl: string | URL did: string setProgress: (progress: number) => void signal: AbortSignal @@ -35,13 +39,14 @@ export async function uploadVideo({ if (signal.aborted) { throw new AbortError() } - await getVideoUploadLimits(agent, i18n) + await getVideoUploadLimits(client, i18n) if (features.isOn(Features.VideoMultipartUploadEnable)) { try { return await uploadVideoMultipart({ video, - agent, + client, + dispatchUrl, setProgress, signal, onStarted: () => onTransport?.('multipart'), @@ -64,7 +69,8 @@ export async function uploadVideo({ throw new AbortError() } const token = await getServiceAuthToken({ - agent, + client, + dispatchUrl, lxm: 'com.atproto.repo.uploadBlob', exp: Date.now() / 1000 + 60 * 30, // 30 minutes }) @@ -91,7 +97,7 @@ export async function uploadVideo({ throw new Error('No response') } - const responseBody = JSON.parse(res.body) as AppBskyVideoDefs.JobStatus + const responseBody = JSON.parse(res.body) as app.bsky.video.defs.JobStatus if (!responseBody.jobId) { throw new ServerError( diff --git a/src/lib/media/video/upload.web.ts b/src/lib/media/video/upload.web.ts index cfefbd797d..90c10a0684 100644 --- a/src/lib/media/video/upload.web.ts +++ b/src/lib/media/video/upload.web.ts @@ -1,4 +1,4 @@ -import {type AppBskyVideoDefs, type AtpAgent} from '@atproto/api' +import {type Client} from '@atproto/lex' import {type I18n} from '@lingui/core' import {msg} from '@lingui/core/macro' import {nanoid} from 'nanoid/non-secure' @@ -10,13 +10,15 @@ import { type VideoUploadTransport, } from '#/lib/media/video/types' import {Features, features} from '#/analytics/features' +import {type app} from '#/lexicons' import {MultipartFallbackError, uploadVideoMultipart} from './multipart/upload' import {getServiceAuthToken, getVideoUploadLimits} from './upload.shared' import {createVideoEndpointUrl, mimeToExt} from './util' export async function uploadVideo({ video, - agent, + client, + dispatchUrl, did, setProgress, signal, @@ -24,7 +26,9 @@ export async function uploadVideo({ onTransport, }: { video: CompressedVideo - agent: AtpAgent + client: Client + /** The account's PDS/dispatch URL, for the uploadBlob service-auth token. */ + dispatchUrl: string | URL did: string setProgress: (progress: number) => void signal: AbortSignal @@ -34,13 +38,14 @@ export async function uploadVideo({ if (signal.aborted) { throw new AbortError() } - await getVideoUploadLimits(agent, i18n) + await getVideoUploadLimits(client, i18n) if (features.isOn(Features.VideoMultipartUploadEnable)) { try { return await uploadVideoMultipart({ video, - agent, + client, + dispatchUrl, setProgress, signal, onStarted: () => onTransport?.('multipart'), @@ -71,7 +76,8 @@ export async function uploadVideo({ throw new AbortError() } const token = await getServiceAuthToken({ - agent, + client, + dispatchUrl, lxm: 'com.atproto.repo.uploadBlob', exp: Date.now() / 1000 + 60 * 30, // 30 minutes }) @@ -80,7 +86,7 @@ export async function uploadVideo({ throw new AbortError() } const xhr = new XMLHttpRequest() - const res = await new Promise( + const res = await new Promise( (resolve, reject) => { xhr.upload.addEventListener('progress', e => { const progress = e.loaded / e.total @@ -92,7 +98,7 @@ export async function uploadVideo({ } else if (xhr.readyState === 4) { const uploadRes = JSON.parse( xhr.responseText, - ) as AppBskyVideoDefs.JobStatus + ) as app.bsky.video.defs.JobStatus resolve(uploadRes) } else { reject(new ServerError(i18n._(msg`Failed to upload video`))) diff --git a/src/lib/media/video/util.ts b/src/lib/media/video/util.ts index 236f0cff3e..7c9686a7f4 100644 --- a/src/lib/media/video/util.ts +++ b/src/lib/media/video/util.ts @@ -1,6 +1,5 @@ -import {AtpAgent} from '@atproto/api' - import {type SupportedMimeTypes, VIDEO_SERVICE} from '#/lib/constants' +import {createLexClient} from '#/lib/lexClient' export const createVideoEndpointUrl = ( route: string, @@ -16,12 +15,29 @@ export const createVideoEndpointUrl = ( return url.href } -export function createVideoAgent() { - return new AtpAgent({ +/** + * A non-refreshing single-use lex {@link Client} scoped to the video service and + * authenticated by a per-call service-auth token. It has no session, so nothing + * can refresh it: requests go straight to the video service with the token as a + * static `authorization` header, which a raw client - unlike a session-backed + * one - is allowed to preset. Mirrors the scoped client in + * `#/ageAssurance/useBeginAgeAssurance`. + */ +export function createVideoServiceClient(token: string) { + return createLexClient({ service: VIDEO_SERVICE, + headers: {authorization: `Bearer ${token}`}, }) } +/** + * An unauthenticated lex {@link Client} scoped to the video service, for public + * reads like `getJobStatus` polling. + */ +export function createTokenlessVideoServiceClient() { + return createLexClient({service: VIDEO_SERVICE}) +} + export function mimeToExt(mimeType: SupportedMimeTypes | (string & {})) { switch (mimeType) { case 'video/mp4': diff --git a/src/lib/moderation.ts b/src/lib/moderation.ts index 0d2e3c4dbd..c6cd336875 100644 --- a/src/lib/moderation.ts +++ b/src/lib/moderation.ts @@ -1,18 +1,18 @@ import {useMemo} from 'react' +import {Client} from '@atproto/lex' +import {type DidString} from '@atproto/syntax' import { - type AppBskyLabelerDefs, - AtpAgent, - type ComAtprotoLabelDefs, type InterpretedLabelValueDefinition, LABELS, type ModerationCause, type ModerationOpts, type ModerationUI, -} from '@atproto/api' +} from '@bsky/sdk/moderation' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' import {type AppModerationCause} from '#/components/Pills' +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 @@ -53,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) } @@ -63,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('!') && @@ -102,20 +102,20 @@ export function lookupLabelValueDefinition( export function isAppLabeler( labeler: | string - | AppBskyLabelerDefs.LabelerView - | AppBskyLabelerDefs.LabelerViewDetailed, + | app.bsky.labeler.defs.LabelerView + | app.bsky.labeler.defs.LabelerViewDetailed, ): boolean { if (typeof labeler === 'string') { - return AtpAgent.appLabelers.includes(labeler) + return Client.appLabelers.includes(labeler as DidString) } - return AtpAgent.appLabelers.includes(labeler.creator.did) + return Client.appLabelers.includes(labeler.creator.did) } export function isLabelerSubscribed( labeler: | string - | AppBskyLabelerDefs.LabelerView - | AppBskyLabelerDefs.LabelerViewDetailed, + | app.bsky.labeler.defs.LabelerView + | app.bsky.labeler.defs.LabelerViewDetailed, modOpts: ModerationOpts, ) { labeler = typeof labeler === 'string' ? labeler : labeler.creator.did @@ -134,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/create-sanitized-display-name.ts b/src/lib/moderation/create-sanitized-display-name.ts index d15564d385..538f180777 100644 --- a/src/lib/moderation/create-sanitized-display-name.ts +++ b/src/lib/moderation/create-sanitized-display-name.ts @@ -1,4 +1,4 @@ -import {type ModerationUI} from '@atproto/api' +import {type ModerationUI} from '@bsky/sdk/moderation' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' diff --git a/src/lib/moderation/useLabelBehaviorDescription.ts b/src/lib/moderation/useLabelBehaviorDescription.ts index cdded25924..d1939e20fd 100644 --- a/src/lib/moderation/useLabelBehaviorDescription.ts +++ b/src/lib/moderation/useLabelBehaviorDescription.ts @@ -1,7 +1,7 @@ import { type InterpretedLabelValueDefinition, type LabelPreference, -} from '@atproto/api' +} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' diff --git a/src/lib/moderation/useLabelInfo.ts b/src/lib/moderation/useLabelInfo.ts index f398406078..80df2b1cf0 100644 --- a/src/lib/moderation/useLabelInfo.ts +++ b/src/lib/moderation/useLabelInfo.ts @@ -1,10 +1,8 @@ import { - type AppBskyLabelerDefs, - type ComAtprotoLabelDefs, type InterpretedLabelValueDefinition, interpretLabelValueDefinition, LABELS, -} from '@atproto/api' +} from '@bsky/sdk/moderation' import {useLingui} from '@lingui/react' import * as bcp47Match from 'bcp-47-match' @@ -13,15 +11,16 @@ import { useGlobalLabelStrings, } from '#/lib/moderation/useGlobalLabelStrings' import {useLabelDefinitions} from '#/state/preferences' +import {type app, type com} from '#/lexicons' export interface LabelInfo { - label: ComAtprotoLabelDefs.Label + label: com.atproto.label.defs.Label def: InterpretedLabelValueDefinition - strings: ComAtprotoLabelDefs.LabelValueDefinitionStrings - labeler: AppBskyLabelerDefs.LabelerViewDetailed | undefined + 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 d11d16dca3..2f57e38bca 100644 --- a/src/lib/moderation/useModerationCauseDescription.ts +++ b/src/lib/moderation/useModerationCauseDescription.ts @@ -1,9 +1,6 @@ import {useMemo} from 'react' -import { - BSKY_LABELER_DID, - type ModerationCause, - type ModerationCauseSource, -} from '@atproto/api' +import {api} from '@bsky/sdk' +import {type ModerationCause} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' @@ -25,7 +22,7 @@ export interface ModerationCauseDescription { description: string source?: string sourceDisplayName?: string - sourceType?: ModerationCauseSource['type'] + sourceType?: ModerationCause['source']['type'] sourceAvi?: string sourceDid?: string isSubjectAccount?: boolean @@ -138,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/notifications/notifications.ts b/src/lib/notifications/notifications.ts index 6351a8d8e1..5acc6ab06d 100644 --- a/src/lib/notifications/notifications.ts +++ b/src/lib/notifications/notifications.ts @@ -2,33 +2,47 @@ import {useCallback, useEffect} from 'react' import {Platform} from 'react-native' import * as Notifications from 'expo-notifications' import {getBadgeCountAsync, setBadgeCountAsync} from 'expo-notifications' -import {type AppBskyNotificationRegisterPush, type AtpAgent} from '@atproto/api' +import {type Client} from '@atproto/lex' import debounce from 'lodash.debounce' import { - BLUESKY_NOTIF_SERVICE_HEADERS, + NOTIF_SERVICE, PUBLIC_APPVIEW_DID, PUBLIC_STAGING_APPVIEW_DID, } from '#/lib/constants' import {logger as notyLogger} from '#/lib/notifications/util' import {isNetworkError} from '#/lib/strings/errors' -import {type SessionAccount, useAgent, useSession} from '#/state/session' +import {type SessionAccount, usePdsClient, useSession} from '#/state/session' import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler' import {useAgeAssurance} from '#/ageAssurance' import {useAnalytics} from '#/analytics' import {IS_DEV, IS_NATIVE} from '#/env' +import {app} from '#/lexicons' + +/** + * A resumed single-use account client paired with the account's service origin + * and handle. Produced by `createTemporaryClientsAndResume` (session util) and + * consumed by {@link unregisterPushToken}, which needs the service host to pick + * the appview DID and the handle for a debug log line without reaching into the + * session internals. + */ +export type TemporaryPushClient = { + client: Client + service: string + handle: string +} /** * @private * Registers the device's push notification token with the Bluesky server. */ async function _registerPushToken({ - agent, + client, currentAccount, token, extra = {}, }: { - agent: AtpAgent + client: Client currentAccount: SessionAccount token: Notifications.DevicePushToken extra?: { @@ -36,7 +50,7 @@ async function _registerPushToken({ } }) { try { - const payload: AppBskyNotificationRegisterPush.InputSchema = { + const payload: app.bsky.notification.registerPush.$InputBody = { serviceDid: currentAccount.service?.includes('staging') ? PUBLIC_STAGING_APPVIEW_DID : PUBLIC_APPVIEW_DID, @@ -48,8 +62,8 @@ async function _registerPushToken({ notyLogger.debug(`registerPushToken: registering`, {...payload}) - await agent.app.bsky.notification.registerPush(payload, { - headers: BLUESKY_NOTIF_SERVICE_HEADERS, + await client.call(app.bsky.notification.registerPush, payload, { + service: NOTIF_SERVICE, }) notyLogger.debug(`registerPushToken: success`) @@ -74,7 +88,7 @@ const _registerPushTokenDebounced = debounce(_registerPushToken, 100) * `_registerPushTokenDebounced` directly. */ export function useRegisterPushToken() { - const agent = useAgent() + const client = usePdsClient() const {currentAccount} = useSession() return useCallback( @@ -87,7 +101,7 @@ export function useRegisterPushToken() { }) => { if (!currentAccount) return return _registerPushTokenDebounced({ - agent, + client, currentAccount, token, extra: { @@ -95,7 +109,7 @@ export function useRegisterPushToken() { }, }) }, - [agent, currentAccount], + [client, currentAccount], ) } @@ -326,16 +340,17 @@ export async function resetBadgeCount() { await setBadgeCountAsync(0) } -export async function unregisterPushToken(agents: AtpAgent[]) { +export async function unregisterPushToken(clients: TemporaryPushClient[]) { if (!IS_NATIVE) return try { const token = await getPushToken() if (token) { - for (const agent of agents) { - await agent.app.bsky.notification.unregisterPush( + for (const {client, service, handle} of clients) { + await client.call( + app.bsky.notification.unregisterPush, { - serviceDid: agent.serviceUrl.hostname.includes('staging') + serviceDid: service.includes('staging') ? PUBLIC_STAGING_APPVIEW_DID : PUBLIC_APPVIEW_DID, platform: Platform.OS, @@ -343,10 +358,10 @@ export async function unregisterPushToken(agents: AtpAgent[]) { appId: 'xyz.blueskyweb.app', }, { - headers: BLUESKY_NOTIF_SERVICE_HEADERS, + service: NOTIF_SERVICE, }, ) - notyLogger.debug(`Push token unregistered for ${agent.session?.handle}`) + notyLogger.debug(`Push token unregistered for ${handle}`) } } else { notyLogger.debug('Tried to unregister push token, but could not find one') 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/routes/tab-to-nav-item.ts b/src/lib/routes/tab-to-nav-item.ts index 38d58b25a4..49471a1e84 100644 --- a/src/lib/routes/tab-to-nav-item.ts +++ b/src/lib/routes/tab-to-nav-item.ts @@ -1,11 +1,7 @@ import {type Events} from '#/analytics/metrics/types' export type SharedNavTab = - | 'Home' - | 'Search' - | 'Messages' - | 'Notifications' - | 'MyProfile' + 'Home' | 'Search' | 'Messages' | 'Notifications' | 'MyProfile' export const TAB_TO_NAV_ITEM: Record< SharedNavTab, diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index a038af0225..16a7c64c65 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -169,8 +169,7 @@ export type AllNavigatorParams = CommonNavigatorParams & { export type NavigationProp = NativeStackNavigationProp export type State = - | NavigationState - | Omit, 'stale'> + NavigationState | Omit, 'stale'> export type RouteParams = Record export type MatchResult = {params: RouteParams} diff --git a/src/lib/strings/__tests__/errors.test.ts b/src/lib/strings/__tests__/errors.test.ts new file mode 100644 index 0000000000..703cc955fb --- /dev/null +++ b/src/lib/strings/__tests__/errors.test.ts @@ -0,0 +1,116 @@ +import {LexError, XrpcResponseError} from '@atproto/lex' +import {beforeAll, describe, expect, it} from '@jest/globals' +import {i18n} from '@lingui/core' + +import {cleanError} from '../errors' + +/* + * `cleanError` returns translated copy, so a locale has to be active. With no + * catalog loaded, Lingui falls back to the source message. + */ +beforeAll(() => { + i18n.loadAndActivate({locale: 'en', messages: {}}) +}) + +/** + * A stand-in for the method schema an `XrpcResponseError` is built against. + * The generated lexicons are not available yet, and `XrpcResponseError` only + * reads `method` back out for `matchesSchemaErrors()`, which these tests never + * call - so a minimal object is enough. + */ +const method = { + nsid: 'com.atproto.server.createAccount', + type: 'procedure', + errors: ['HandleNotAvailable'], +} as unknown as ConstructorParameters[0] + +/** An error as the lex client builds one from a JSON error response body. */ +function xrpcResponseError( + error: string, + message: string, + status: number = 400, +) { + return new XrpcResponseError(method, new Response(null, {status}), { + encoding: 'application/json', + body: {error, message}, + }) +} + +/** + * An error as the lex client builds one from a response with no XRPC error + * payload: the code is derived from the HTTP status, and the message is a + * generic overview of the response. + */ +function xrpcStatusError(status: number) { + return new XrpcResponseError(method, new Response(null, {status}), undefined) +} + +describe('cleanError', () => { + it('surfaces the clean message of a lex error', () => { + const e = xrpcResponseError('HandleNotAvailable', 'Handle already taken') + // The raw stringification is class- and code-prefixed, so it must not leak. + expect(e.toString()).toBe( + 'XrpcResponseError: [HandleNotAvailable] Handle already taken', + ) + expect(cleanError(e)).toBe('Handle already taken') + }) + + it('falls back to the lexicon code when a lex error has no message', () => { + // `Error` defaults an absent message to the empty string. + const e = new LexError('InvalidRequest') + expect(e.toString()).toBe('LexError: [InvalidRequest] ') + expect(cleanError(e)).toBe('InvalidRequest') + }) + + it('matches the upstream-failure branch on a lex error code', () => { + // 502 maps to the space-free `UpstreamFailure` lexicon code. + const e = xrpcStatusError(502) + expect(e.error).toBe('UpstreamFailure') + expect(cleanError(e)).toBe( + 'The server appears to be experiencing issues. Please try again in a few moments.', + ) + }) + + it('matches NotEnoughResources', () => { + expect(cleanError(xrpcStatusError(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', () => { + const e = xrpcResponseError('InvalidToken', 'Bad token scope') + expect(cleanError(e)).toBe( + 'This feature is not available while using an App Password. Please sign in with your main password.', + ) + }) + + it('matches the network-error branch on a lex error message', () => { + const e = xrpcResponseError('InternalServerError', 'Failed to fetch', 500) + expect(cleanError(e)).toBe( + 'Unable to connect. Please check your internet connection and try again.', + ) + }) + + it('surfaces the authentication-required code of a lex error', () => { + /* + * The lex client derives `AuthenticationRequired` from a 401 with no XRPC + * 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. + */ + const e = xrpcStatusError(401) + expect(e.error).toBe('AuthenticationRequired') + expect(cleanError(e)).toBe('Upstream server responded with a 401 error') + }) + + it('strips a leading "Error: " from a plain error', () => { + expect(cleanError(new Error('Something broke'))).toBe('Something broke') + }) + + it('passes strings through', () => { + expect(cleanError('Something broke')).toBe('Something broke') + expect(cleanError('')).toBe('') + expect(cleanError(undefined)).toBe('') + }) +}) diff --git a/src/lib/strings/display-names.ts b/src/lib/strings/display-names.ts index 612a317eaf..27d90f2e39 100644 --- a/src/lib/strings/display-names.ts +++ b/src/lib/strings/display-names.ts @@ -1,4 +1,4 @@ -import {type ModerationUI} from '@atproto/api' +import {type ModerationUI} from '@bsky/sdk/moderation' // \u2705 = ✅ // \u2713 = ✓ diff --git a/src/lib/strings/errors.ts b/src/lib/strings/errors.ts index 11e7c11362..365a069e2d 100644 --- a/src/lib/strings/errors.ts +++ b/src/lib/strings/errors.ts @@ -1,17 +1,52 @@ -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. + * + * A `LexError` stringifies as `Class: [ErrorCode] message` (for example + * `XrpcResponseError: [HandleNotAvailable] Handle already taken`), so its + * `toString()` is never fit for display. Its `message` is the server's + * human-readable text, so prefer that and fall back to the lexicon code for the + * errors that carry no message. + * + * Everything else keeps the historical behaviour of stringifying and dropping a + * leading `Error: `. + */ +function toDisplayString(e: unknown, str: string): string { + if (e instanceof LexError) { + return e.message || e.error + } + if (str.startsWith('Error: ')) { + return str.slice('Error: '.length) + } + return str +} + export function cleanError(e: unknown): string { if (!e) { return '' } + /* + * Match against the full stringification rather than the display text: it + * contains the error name and the lexicon code as well as the message, so a + * single check covers both error shapes. + */ // oxlint-disable-next-line typescript/no-base-to-string const str = typeof e === 'string' ? e : e.toString() if (isNetworkError(str)) { return t`Unable to connect. Please check your internet connection and try again.` } + /* + * The legacy client named these with spaces ("Upstream Failure"); lexicon error + * codes are space-free ("UpstreamFailure"). Match both while the app throws + * both shapes. + */ if ( str.includes('Upstream Failure') || + str.includes('UpstreamFailure') || str.includes('NotEnoughResources') || str.includes('pipethrough network error') ) { @@ -41,10 +76,7 @@ export function cleanError(e: unknown): string { if (str.includes('Unable to resolve handle')) { return t`Unable to resolve handle` } - if (str.startsWith('Error: ')) { - return str.slice('Error: '.length) - } - return str + return toDisplayString(e, str) } const NETWORK_ERRORS = [ @@ -66,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') @@ -93,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/helpers.ts b/src/lib/strings/helpers.ts index 77ef18cb55..001e059209 100644 --- a/src/lib/strings/helpers.ts +++ b/src/lib/strings/helpers.ts @@ -1,4 +1,4 @@ -import {type RichText} from '@atproto/api' +import {type RichText} from '@bsky/sdk/richtext' import {countGraphemes} from 'unicode-segmenter/grapheme' import {shortenLinks} from './rich-text-manip' diff --git a/src/lib/strings/rich-text-helpers.ts b/src/lib/strings/rich-text-helpers.ts index c2b2ceac52..6d201b7664 100644 --- a/src/lib/strings/rich-text-helpers.ts +++ b/src/lib/strings/rich-text-helpers.ts @@ -1,5 +1,7 @@ -import {AppBskyRichtextFacet, type RichText} from '@atproto/api' +import {type RichText} from '@bsky/sdk/richtext' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {linkRequiresWarning} from './url-helpers' export function richTextToString(rt: RichText, loose: boolean): string { @@ -14,7 +16,7 @@ export function richTextToString(rt: RichText, loose: boolean): string { for (const segment of rt.segments()) { const link = segment.link - if (link && AppBskyRichtextFacet.validateLink(link).success) { + if (link && bsky.matches(app.bsky.richtext.facet.link, link)) { const href = link.uri const text = segment.text diff --git a/src/lib/strings/rich-text-manip.ts b/src/lib/strings/rich-text-manip.ts index 099fbffb0a..067b2f8cd7 100644 --- a/src/lib/strings/rich-text-manip.ts +++ b/src/lib/strings/rich-text-manip.ts @@ -1,5 +1,7 @@ -import {AppBskyRichtextFacet, type RichText, UnicodeString} from '@atproto/api' +import {type RichText, UnicodeString} from '@bsky/sdk/richtext' +import {app} from '#/lexicons' +import * as bsky from '#/types/bsky' import {toShortUrl} from './url-helpers' export function shortenLinks(rt: RichText): RichText { @@ -10,7 +12,9 @@ export function shortenLinks(rt: RichText): RichText { // enumerate the link facets if (rt.facets) { for (const facet of rt.facets) { - const isLink = !!facet.features.find(AppBskyRichtextFacet.isLink) + const isLink = !!facet.features.find(f => + bsky.isType(app.bsky.richtext.facet.link, f), + ) if (!isLink) { continue } @@ -40,7 +44,9 @@ export function stripInvalidMentions(rt: RichText): RichText { rt = rt.clone() if (rt.facets) { rt.facets = rt.facets?.filter(facet => { - const mention = facet.features.find(AppBskyRichtextFacet.isMention) + const mention = facet.features.find(f => + bsky.isType(app.bsky.richtext.facet.mention, f), + ) if (mention && !mention.did) { return false } 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..6a4e3335f8 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 {type AtIdentifierString, AtUri} from '@atproto/syntax' import {parse} from 'psl' import TLDs from 'tlds' @@ -27,6 +27,15 @@ const TRUSTED_REGEX = new RegExp( )})|/|#)`, ) +export function canParseUrl(url: string | URL, base?: string | URL): boolean { + try { + new URL(url, base) + return true + } catch { + return false + } +} + export function isValidDomain(str: string): boolean { return !!TLDs.find(tld => { let i = str.lastIndexOf(tld) @@ -43,8 +52,8 @@ export function makeRecordUri( rkey: string, ) { const urip = new AtUri('at://placeholder.placeholder/') - // @ts-expect-error TODO new-sdk-migration - urip.host = didOrName + // the helper takes the did or handle as a plain string + urip.host = didOrName as AtIdentifierString urip.collection = collection urip.rkey = rkey return urip.toString() diff --git a/src/lib/xrpc-error.ts b/src/lib/xrpc-error.ts new file mode 100644 index 0000000000..ec4fd7e872 --- /dev/null +++ b/src/lib/xrpc-error.ts @@ -0,0 +1,111 @@ +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 + * `matchesSchemaErrors()` narrow `e.error` to M's declared errors. + */ +function isThrownFor( + e: XrpcResponseError, + schema: M, +): e is XrpcResponseError { + return e.method.nsid === schema.nsid +} + +/** + * The lexicon error code carried by `e`, narrowed to the errors DECLARED by + * `method`, or `undefined` when `e` is not such an error. + * + * `XrpcResponseError.error` is the open `LexErrorCode` union, so comparing it + * as a plain string lets a typo silently never match. Narrowing the return type + * to `InferMethodError` makes a `switch` over the result reject an + * undeclared or misspelled `case` at compile time: + * + * ```ts + * switch (matchXrpcError(e, com.atproto.server.createAccount)) { + * case 'InvalidHandle': + * ... + * } + * ``` + * + * Matching is scoped to `method`: `XrpcError` records the method schema it was + * thrown for, so a declared code arriving from a DIFFERENT call does not match. + * Undeclared codes, non-XRPC errors, and the internal/fetch lex errors (which + * carry no server error code) all return `undefined`. + * + * `method` accepts the same value passed to `client.call` - either the + * generated method namespace (`com.atproto.server.createAccount`) or its + * `.main` schema - via lex's `Main`. + */ +export function matchXrpcError( + e: unknown, + method: Main, +): InferMethodError | undefined { + if (!(e instanceof XrpcResponseError)) { + return undefined + } + const schema = getMain(method) + if (isThrownFor(e, schema) && e.matchesSchemaErrors()) { + return e.error + } + return undefined +} + +/** + * Whether `e` is a `com.atproto.repo.getRecord` failure meaning the record is + * absent, so the caller can treat the read as "no record" instead of an error. + * + * `getRecord` declares `RecordNotFound` and both the PDS and the appview throw + * it by that name, so the declared code is the primary check. The message + * substring is kept as a fallback because that is what the pre-SDK call sites + * matched on, and an older PDS in the network may still answer with an + * unnamed error whose message carries the text. Dropping it would silently + * turn a previously-handled absence into a thrown error. + */ +export function isRecordNotFoundError(e: unknown): boolean { + if (matchXrpcError(e, com.atproto.repo.getRecord) === 'RecordNotFound') { + return true + } + return e instanceof Error && e.message.includes('Could not locate record:') +} 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/locale/locales/en/messages.po b/src/locale/locales/en/messages.po index 9bf8887a63..1c7b7ceff6 100644 --- a/src/locale/locales/en/messages.po +++ b/src/locale/locales/en/messages.po @@ -19,37 +19,37 @@ msgid "\"{interestsDisplayName}\" category (active)" msgstr "" #. A reply summary in chat -#: src/components/dms/MessageItem.tsx:902 +#: src/components/dms/MessageItem.tsx:917 msgid "(blocked message hidden)" msgstr "(blocked message hidden)" #. A reply summary in chat -#: src/components/dms/getMessageInfo.ts:123 -#: src/components/dms/replyPreview.ts:85 +#: src/components/dms/getMessageInfo.ts:118 +#: src/components/dms/replyPreview.ts:92 msgid "(chat invite link)" msgstr "(chat invite link)" -#: src/components/dms/getMessageInfo.ts:105 +#: src/components/dms/getMessageInfo.ts:100 msgid "(contains embedded content)" msgstr "" #. A reply summary in chat -#: src/components/dms/MessageItem.tsx:916 +#: src/components/dms/MessageItem.tsx:933 msgid "(deleted message)" msgstr "(deleted message)" #. A reply summary in chat -#: src/components/dms/replyPreview.ts:68 +#: src/components/dms/replyPreview.ts:70 msgid "(disabled chat invite link)" msgstr "(disabled chat invite link)" #. A reply summary in chat -#: src/components/dms/replyPreview.ts:77 +#: src/components/dms/replyPreview.ts:84 msgid "(invalid chat invite link)" msgstr "(invalid chat invite link)" #. A reply summary in chat -#: src/components/dms/MessageItem.tsx:910 +#: src/components/dms/MessageItem.tsx:927 msgid "(message sent before you joined)" msgstr "(message sent before you joined)" @@ -58,12 +58,12 @@ msgid "(no email)" msgstr "" #. A reply summary in chat -#: src/components/dms/replyPreview.ts:107 +#: src/components/dms/replyPreview.ts:114 msgid "(no text)" msgstr "(no text)" #. A reply summary in chat -#: src/components/dms/replyPreview.ts:99 +#: src/components/dms/replyPreview.ts:106 msgid "(quoted post)" msgstr "(quoted post)" @@ -88,7 +88,7 @@ msgid "{0, plural, one {# following} other {# following}}" msgstr "" #. placeholder {0}: item.count -#: src/components/contacts/screens/ViewMatches.tsx:268 +#: src/components/contacts/screens/ViewMatches.tsx:276 msgid "{0, plural, one {# friend found!} other {# friends found!}}" msgstr "" @@ -98,12 +98,12 @@ msgid "{0, plural, one {# hour} other {# hours}}" msgstr "" #. placeholder {0}: labels.length -#: src/components/moderation/PostAlerts.tsx:157 +#: src/components/moderation/PostAlerts.tsx:153 msgid "{0, plural, one {# label applied to your post} other {# labels applied to your post}}" msgstr "{0, plural, one {# label applied to your post} other {# labels applied to your post}}" #. placeholder {0}: labels.length -#: src/components/moderation/PostAlerts.tsx:164 +#: src/components/moderation/PostAlerts.tsx:160 msgid "{0, plural, one {# label applied} other {# labels applied}}" msgstr "{0, plural, one {# label applied} other {# labels applied}}" @@ -135,7 +135,7 @@ msgstr "{0, plural, one {# new join request} other {# new join requests}}" #. placeholder {0}: reactions.length #. placeholder {1}: groupedReactions.map(g => g.value).join(' ') -#: src/components/dms/MessageItem.tsx:371 +#: src/components/dms/MessageItem.tsx:383 msgid "{0, plural, one {# person} other {# people}} reacted – {1}" msgstr "{0, plural, one {# person} other {# people}} reacted – {1}" @@ -164,7 +164,7 @@ msgstr "" #. How long it takes to read an article, in minutes. Displayed in a short form, e.g. "5m" for 5 minutes. #. placeholder {0}: view.readingTime -#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:251 +#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:250 msgid "{0, plural, one {#m} other {#m}}" msgstr "{0, plural, one {#m} other {#m}}" @@ -180,13 +180,13 @@ msgid "{0, plural, one {1 more post} other {# more posts}}" msgstr "" #. placeholder {0}: profile.followersCount || 0 -#: src/components/ProfileHoverCard/index.web.tsx:444 +#: src/components/ProfileHoverCard/index.web.tsx:441 #: src/screens/Profile/Header/Metrics.tsx:22 msgid "{0, plural, one {follower} other {followers}}" msgstr "" #. placeholder {0}: profile.followsCount || 0 -#: src/components/ProfileHoverCard/index.web.tsx:448 +#: src/components/ProfileHoverCard/index.web.tsx:445 #: src/screens/Profile/Header/Metrics.tsx:26 msgid "{0, plural, one {following} other {following}}" msgstr "" @@ -198,12 +198,12 @@ msgstr "" #. Number of users (always at least 25) who have joined Bluesky using a specific starter pack #. placeholder {0}: starterPack.joinedAllTimeCount || 0 -#: src/screens/StarterPack/StarterPackScreen.tsx:504 +#: src/screens/StarterPack/StarterPackScreen.tsx:497 msgid "{0, plural, other {# people have}} joined Bluesky via this starter pack!" msgstr "" #. placeholder {0}: starterPack.list.listItemCount - 4 -#: src/components/dialogs/StarterPackDialog.tsx:362 +#: src/components/dialogs/StarterPackDialog.tsx:354 msgid "{0, plural, other {+# more}}" msgstr "" @@ -226,13 +226,13 @@ msgstr "" #. Pattern: {wordValue} in tags #. placeholder {0}: word.value -#: src/components/dialogs/MutedWords.tsx:495 +#: src/components/dialogs/MutedWords.tsx:497 msgid "{0} <0>in <1>tags" msgstr "" #. Pattern: {wordValue} in text, tags #. placeholder {0}: word.value -#: src/components/dialogs/MutedWords.tsx:484 +#: src/components/dialogs/MutedWords.tsx:486 msgid "{0} <0>in <1>text & tags" msgstr "" @@ -253,12 +253,12 @@ msgid "{0} following" msgstr "" #. placeholder {0}: sanitizeHandle(profile.handle, '@') -#: src/components/dms/MessageItem.tsx:724 +#: src/components/dms/MessageItem.tsx:739 msgid "{0} is blocking you" msgstr "{0} is blocking you" #. placeholder {0}: sanitizeHandle(profile.handle) -#: src/features/liveNow/components/LiveStatusDialog.tsx:84 +#: src/features/liveNow/components/LiveStatusDialog.tsx:81 msgid "{0} is live" msgstr "" @@ -273,27 +273,27 @@ msgid "{0} is not supported by your device." msgstr "{0} is not supported by your device." #. placeholder {0}: action.displayName -#: src/components/dms/getSystemMessageInfo.ts:83 +#: src/components/dms/getSystemMessageInfo.ts:88 msgid "{0} joined" msgstr "{0} joined" #. placeholder {0}: action.displayName -#: src/components/dms/getSystemMessageInfo.ts:84 +#: src/components/dms/getSystemMessageInfo.ts:89 msgid "{0} joined the group" msgstr "{0} joined the group" #. placeholder {0}: formatCount(i18n, JOINED_THIS_WEEK) -#: src/screens/StarterPack/StarterPackLandingScreen.tsx:230 +#: src/screens/StarterPack/StarterPackLandingScreen.tsx:222 msgid "{0} joined this week" msgstr "" #. placeholder {0}: action.displayName -#: src/components/dms/getSystemMessageInfo.ts:96 +#: src/components/dms/getSystemMessageInfo.ts:103 msgid "{0} left" msgstr "{0} left" #. placeholder {0}: action.displayName -#: src/components/dms/getSystemMessageInfo.ts:97 +#: src/components/dms/getSystemMessageInfo.ts:104 msgid "{0} left the group" msgstr "{0} left the group" @@ -311,7 +311,7 @@ msgstr "" #. placeholder {0}: createSanitizedDisplayName(memberSender) #. placeholder {1}: reaction.value -#: src/components/dms/MessageItem.tsx:366 +#: src/components/dms/MessageItem.tsx:378 msgid "{0} reacted {1}" msgstr "" @@ -322,22 +322,22 @@ msgid "{0} unread items" msgstr "{0} unread items" #. placeholder {0}: action.displayName -#: src/components/dms/getSystemMessageInfo.ts:57 +#: src/components/dms/getSystemMessageInfo.ts:58 msgid "{0} was added" msgstr "{0} was added" #. placeholder {0}: action.displayName -#: src/components/dms/getSystemMessageInfo.ts:58 +#: src/components/dms/getSystemMessageInfo.ts:59 msgid "{0} was added to the group" msgstr "{0} was added to the group" #. placeholder {0}: action.displayName -#: src/components/dms/getSystemMessageInfo.ts:70 +#: src/components/dms/getSystemMessageInfo.ts:73 msgid "{0} was removed" msgstr "{0} was removed" #. placeholder {0}: action.displayName -#: src/components/dms/getSystemMessageInfo.ts:71 +#: src/components/dms/getSystemMessageInfo.ts:74 msgid "{0} was removed from the group" msgstr "{0} was removed from the group" @@ -356,13 +356,13 @@ msgstr "{0}, {1} and {memberCount, plural, one {# other} other {# others}} added #. placeholder {0}: feed.displayName #. placeholder {1}: sanitizeHandle(feed.creatorHandle, '@') #. placeholder {2}: feed.likeCount || 0 -#: src/view/com/feeds/FeedSourceCard.tsx:189 +#: src/view/com/feeds/FeedSourceCard.tsx:187 msgid "{0}, a feed by {1}, liked by {2}" msgstr "" #. placeholder {0}: feed.displayName #. placeholder {1}: sanitizeHandle(feed.creatorHandle, '@') -#: src/view/com/feeds/FeedSourceCard.tsx:192 +#: src/view/com/feeds/FeedSourceCard.tsx:190 msgid "{0}, a list by {1}" msgstr "" @@ -376,7 +376,7 @@ msgstr "" #. The number of active group chat members out of the total number allowed. #. placeholder {0}: joinLinkPreview.memberCount #. placeholder {1}: joinLinkPreview.memberLimit -#: src/screens/Messages/JoinRequest.tsx:139 +#: src/screens/Messages/JoinRequest.tsx:147 msgctxt "group-chat-member-count" msgid "{0}/{1}" msgstr "{0}/{1}" @@ -389,8 +389,8 @@ msgstr "{0}/{1}" #. placeholder {1}: preview.memberLimit #. placeholder {2}: joinLinkPreview.memberLimit #. placeholder {2}: preview.memberLimit -#: src/components/dms/ChatInvite/Card.tsx:62 -#: src/components/intents/GroupChatJoinDialog.tsx:341 +#: src/components/dms/ChatInvite/Card.tsx:64 +#: src/components/intents/GroupChatJoinDialog.tsx:346 msgid "{0}/{1} {2, plural, one {member} other {members}}" msgstr "{0}/{1} {2, plural, one {member} other {members}}" @@ -437,7 +437,7 @@ msgstr "" msgid "{0}s" msgstr "" -#: src/screens/Messages/JoinRequests.tsx:433 +#: src/screens/Messages/JoinRequests.tsx:430 msgid "{count, plural, =0 {No requests to join} one {# request to join} other {# requests to join}}" msgstr "{count, plural, =0 {No requests to join} one {# request to join} other {# requests to join}}" @@ -458,7 +458,7 @@ msgid "{count, plural, one {# unread item} other {# unread items}}" msgstr "" #. Displayed when there are more requests to join a group chat than have been loaded -#: src/screens/Messages/JoinRequests.tsx:427 +#: src/screens/Messages/JoinRequests.tsx:424 msgid "{count, plural, other {#+ requests to join}}" msgstr "{count, plural, other {#+ requests to join}}" @@ -466,16 +466,16 @@ msgstr "{count, plural, other {#+ requests to join}}" msgid "{date} at {time}" msgstr "{date} at {time}" -#: src/lib/generate-starterpack.ts:104 -#: src/screens/StarterPack/Wizard/index.tsx:189 +#: src/lib/generate-starterpack.ts:95 +#: src/screens/StarterPack/Wizard/index.tsx:185 msgid "{displayName}'s Starter Pack" msgstr "" -#: src/screens/SignupQueued.tsx:219 +#: src/screens/SignupQueued.tsx:226 msgid "{estimatedTimeHrs, plural, one {hour} other {hours}}" msgstr "" -#: src/screens/SignupQueued.tsx:225 +#: src/screens/SignupQueued.tsx:232 msgid "{estimatedTimeMins, plural, one {minute} other {minutes}}" msgstr "" @@ -483,178 +483,178 @@ msgstr "" msgid "{filterCount, plural, one {+# filter} other {+# filters}}" msgstr "{filterCount, plural, one {+# filter} other {+# filters}}" -#: src/view/com/notifications/NotificationFeedItem.tsx:367 +#: src/view/com/notifications/NotificationFeedItem.tsx:359 msgid "{firstAuthorLink} and <0>{additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} followed you" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:399 +#: src/view/com/notifications/NotificationFeedItem.tsx:391 msgid "{firstAuthorLink} and <0>{additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} liked your custom feed" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:305 +#: src/view/com/notifications/NotificationFeedItem.tsx:297 msgid "{firstAuthorLink} and <0>{additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} liked your post" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:496 +#: src/view/com/notifications/NotificationFeedItem.tsx:488 msgid "{firstAuthorLink} and <0>{additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} liked your repost" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:471 +#: src/view/com/notifications/NotificationFeedItem.tsx:463 msgid "{firstAuthorLink} and <0>{additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} removed their verifications from your account" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:327 +#: src/view/com/notifications/NotificationFeedItem.tsx:319 msgid "{firstAuthorLink} and <0>{additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} reposted your post" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:518 +#: src/view/com/notifications/NotificationFeedItem.tsx:510 msgid "{firstAuthorLink} and <0>{additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} reposted your repost" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:421 +#: src/view/com/notifications/NotificationFeedItem.tsx:413 msgid "{firstAuthorLink} and <0>{additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} signed up with your starter pack" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:448 +#: src/view/com/notifications/NotificationFeedItem.tsx:440 msgid "{firstAuthorLink} and <0>{additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} verified you" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:379 +#: src/view/com/notifications/NotificationFeedItem.tsx:371 msgid "{firstAuthorLink} followed you" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:351 +#: src/view/com/notifications/NotificationFeedItem.tsx:343 msgid "{firstAuthorLink} followed you back" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:411 +#: src/view/com/notifications/NotificationFeedItem.tsx:403 msgid "{firstAuthorLink} liked your custom feed" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:317 +#: src/view/com/notifications/NotificationFeedItem.tsx:309 msgid "{firstAuthorLink} liked your post" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:508 +#: src/view/com/notifications/NotificationFeedItem.tsx:500 msgid "{firstAuthorLink} liked your repost" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:483 +#: src/view/com/notifications/NotificationFeedItem.tsx:475 msgid "{firstAuthorLink} removed their verification from your account" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:339 +#: src/view/com/notifications/NotificationFeedItem.tsx:331 msgid "{firstAuthorLink} reposted your post" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:530 +#: src/view/com/notifications/NotificationFeedItem.tsx:522 msgid "{firstAuthorLink} reposted your repost" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:433 +#: src/view/com/notifications/NotificationFeedItem.tsx:425 msgid "{firstAuthorLink} signed up with your starter pack" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:460 +#: src/view/com/notifications/NotificationFeedItem.tsx:452 msgid "{firstAuthorLink} verified you" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:361 +#: src/view/com/notifications/NotificationFeedItem.tsx:353 msgid "{firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} followed you" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:355 +#: src/view/com/notifications/NotificationFeedItem.tsx:347 msgid "{firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} followed you via starter pack {starterPackName}" msgstr "{firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} followed you via starter pack {starterPackName}" -#: src/view/com/notifications/NotificationFeedItem.tsx:393 +#: src/view/com/notifications/NotificationFeedItem.tsx:385 msgid "{firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} liked your custom feed" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:299 +#: src/view/com/notifications/NotificationFeedItem.tsx:291 msgid "{firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} liked your post" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:490 +#: src/view/com/notifications/NotificationFeedItem.tsx:482 msgid "{firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} liked your repost" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:465 +#: src/view/com/notifications/NotificationFeedItem.tsx:457 msgid "{firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} removed their verifications from your account" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:321 +#: src/view/com/notifications/NotificationFeedItem.tsx:313 msgid "{firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} reposted your post" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:512 +#: src/view/com/notifications/NotificationFeedItem.tsx:504 msgid "{firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} reposted your repost" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:415 +#: src/view/com/notifications/NotificationFeedItem.tsx:407 msgid "{firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} signed up with your starter pack" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:442 +#: src/view/com/notifications/NotificationFeedItem.tsx:434 msgid "{firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} verified you" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:365 +#: src/view/com/notifications/NotificationFeedItem.tsx:357 msgid "{firstAuthorName} followed you" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:350 +#: src/view/com/notifications/NotificationFeedItem.tsx:342 msgid "{firstAuthorName} followed you back" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:349 +#: src/view/com/notifications/NotificationFeedItem.tsx:341 msgid "{firstAuthorName} followed you back via starter pack {starterPackName}" msgstr "{firstAuthorName} followed you back via starter pack {starterPackName}" -#: src/view/com/notifications/NotificationFeedItem.tsx:359 +#: src/view/com/notifications/NotificationFeedItem.tsx:351 msgid "{firstAuthorName} followed you via starter pack {starterPackName}" msgstr "{firstAuthorName} followed you via starter pack {starterPackName}" -#: src/view/com/notifications/NotificationFeedItem.tsx:397 +#: src/view/com/notifications/NotificationFeedItem.tsx:389 msgid "{firstAuthorName} liked your custom feed" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:303 +#: src/view/com/notifications/NotificationFeedItem.tsx:295 msgid "{firstAuthorName} liked your post" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:494 +#: src/view/com/notifications/NotificationFeedItem.tsx:486 msgid "{firstAuthorName} liked your repost" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:469 +#: src/view/com/notifications/NotificationFeedItem.tsx:461 msgid "{firstAuthorName} removed their verification from your account" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:325 +#: src/view/com/notifications/NotificationFeedItem.tsx:317 msgid "{firstAuthorName} reposted your post" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:516 +#: src/view/com/notifications/NotificationFeedItem.tsx:508 msgid "{firstAuthorName} reposted your repost" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:419 +#: src/view/com/notifications/NotificationFeedItem.tsx:411 msgid "{firstAuthorName} signed up with your starter pack" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:446 +#: src/view/com/notifications/NotificationFeedItem.tsx:438 msgid "{firstAuthorName} verified you" msgstr "" -#: src/components/ProfileHoverCard/index.web.tsx:572 +#: src/components/ProfileHoverCard/index.web.tsx:569 msgid "{following} following" msgstr "" #. '{postCount} {posts}', e.g., '1.2K posts' #. '{postCount} {posts}', e.g., '1.2K posts' -#: src/components/interstitials/FeedTrendingTopics.tsx:245 -#: src/screens/Search/modules/ExploreTrendingTopics.tsx:192 +#: src/components/interstitials/FeedTrendingTopics.tsx:246 +#: src/screens/Search/modules/ExploreTrendingTopics.tsx:190 msgid "{formattedPostCount} {postCount, plural, one {post} other {posts}}" msgstr "{formattedPostCount} {postCount, plural, one {post} other {posts}}" @@ -696,7 +696,7 @@ msgid "{JOIN_REQUESTS_THRESHOLD}+ new join requests" msgstr "{JOIN_REQUESTS_THRESHOLD}+ new join requests" #. Number of users (always at least 50) who have joined Bluesky using a specific starter pack -#: src/components/StarterPack/StarterPackCard.tsx:102 +#: src/components/StarterPack/StarterPackCard.tsx:98 msgid "{joinedAllTimeCount, plural, other {# users have}} joined!" msgstr "" @@ -712,7 +712,7 @@ msgstr "{MAX_DESCRIPTION, plural, other {Description is too long. The maximum nu msgid "{MAX_DISPLAY_NAME, plural, other {Display name is too long. The maximum number of characters is #.}}" msgstr "{MAX_DISPLAY_NAME, plural, other {Display name is too long. The maximum number of characters is #.}}" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:395 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:391 msgid "{MAX_HIDDEN_REPLIES, plural, other {You can hide a maximum of # replies.}}" msgstr "" @@ -726,12 +726,12 @@ msgid "{minutes, plural, one {# minute} other {# minutes}}" msgstr "" #. placeholder {0}: reaction.value -#: src/components/dms/getReactionInfo.ts:65 +#: src/components/dms/getReactionInfo.ts:70 msgid "{name} reacted {0} to {target}" msgstr "{name} reacted {0} to {target}" #. When the last message in a group chat came from someone other than you. -#: src/components/dms/getMessageInfo.ts:89 +#: src/components/dms/getMessageInfo.ts:84 msgid "{name}: {message}" msgstr "{name}: {message}" @@ -747,22 +747,22 @@ msgstr "{name}'s starter pack" msgid "{notificationCount, plural, one {# unread item} other {# unread items}}" msgstr "" -#: src/screens/Settings/FindContactsSettings.tsx:457 +#: src/screens/Settings/FindContactsSettings.tsx:461 msgid "{numMatches, plural, one {# contact found} other {# contacts found}}" msgstr "" -#: src/components/NewskieDialog.tsx:115 +#: src/components/NewskieDialog.tsx:116 msgid "{profileName} joined Bluesky {timeAgoString} ago" msgstr "" -#: src/components/NewskieDialog.tsx:112 +#: src/components/NewskieDialog.tsx:113 msgid "{profileName} joined Bluesky using a starter pack {timeAgoString} ago" msgstr "" #. The trending topic rank, i.e. "1. March Madness", "2. The Bachelor" #. The trending topic rank, i.e. "1. March Madness", "2. The Bachelor" -#: src/components/interstitials/FeedTrendingTopics.tsx:230 -#: src/screens/Search/modules/ExploreTrendingTopics.tsx:167 +#: src/components/interstitials/FeedTrendingTopics.tsx:231 +#: src/screens/Search/modules/ExploreTrendingTopics.tsx:165 msgid "{rank}." msgstr "" @@ -770,15 +770,15 @@ msgstr "" msgid "{remaining, plural, one {# character remaining} other {# characters remaining}}" msgstr "{remaining, plural, one {# character remaining} other {# characters remaining}}" -#: src/components/dms/MessageItem.tsx:811 +#: src/components/dms/MessageItem.tsx:826 msgid "{replierDisplayName} replied" msgstr "{replierDisplayName} replied" -#: src/components/dms/MessageItem.tsx:810 +#: src/components/dms/MessageItem.tsx:825 msgid "{replierDisplayName} replied to {originalName}" msgstr "{replierDisplayName} replied to {originalName}" -#: src/components/dms/MessageItem.tsx:808 +#: src/components/dms/MessageItem.tsx:823 msgid "{replierDisplayName} replied to you" msgstr "{replierDisplayName} replied to you" @@ -808,20 +808,20 @@ msgstr "" #. Number of images beyond the first 3 #. placeholder {0}: labels.length #. placeholder {0}: totalNumber - 3 -#: src/components/moderation/PostAlerts.tsx:163 -#: src/view/com/composer/ComposerReplyTo.tsx:278 +#: src/components/moderation/PostAlerts.tsx:159 +#: src/view/com/composer/ComposerReplyTo.tsx:273 msgid "+{0}" msgstr "+{0}" #. Indicates the number of additional profiles are in the Starter Pack e.g. +12 -#: src/screens/Search/components/StarterPackCard.tsx:258 +#: src/screens/Search/components/StarterPackCard.tsx:250 msgid "+{computedTotal}" msgstr "" #. placeholder {0}: getName(items[0]) #. placeholder {1}: getName(items[1]) #. placeholder {2}: items.length - 2 -#: src/screens/StarterPack/Wizard/index.tsx:569 +#: src/screens/StarterPack/Wizard/index.tsx:565 msgctxt "feeds" msgid "<0>{0}, <1>{1}, and {2, plural, one {# other} other {# others}} are included in your starter pack" msgstr "" @@ -829,7 +829,7 @@ msgstr "" #. placeholder {0}: getName(items[1] /* [0] is self, skip it */) #. placeholder {1}: getName(items[2]) #. placeholder {2}: items.length - 2 -#: src/screens/StarterPack/Wizard/index.tsx:516 +#: src/screens/StarterPack/Wizard/index.tsx:512 msgctxt "profiles" msgid "<0>{0}, <1>{1}, and {2, plural, one {# other} other {# others}} are included in your starter pack" msgstr "" @@ -869,25 +869,25 @@ msgstr "" #. Like count display, the <0> tags enclose the number of likes in bold (will never be 0) #. placeholder {0}: formatPostStatCount(likeCount) -#: src/screens/PostThread/components/LikesStat.tsx:44 +#: src/screens/PostThread/components/LikesStat.tsx:46 msgid "<0>{0} {likeCount, plural, one {like} other {likes}}" msgstr "<0>{0} {likeCount, plural, one {like} other {likes}}" #. placeholder {0}: getName(items[0]) #. placeholder {1}: getName(items[1] /* [0] is self, skip it */) #. placeholder {1}: getName(items[1]) -#: src/screens/StarterPack/Wizard/index.tsx:503 -#: src/screens/StarterPack/Wizard/index.tsx:557 +#: src/screens/StarterPack/Wizard/index.tsx:499 +#: src/screens/StarterPack/Wizard/index.tsx:553 msgid "<0>{0} and<1> <2>{1} are included in your starter pack" msgstr "" #. placeholder {0}: getName(items[0]) -#: src/screens/StarterPack/Wizard/index.tsx:550 +#: src/screens/StarterPack/Wizard/index.tsx:546 msgid "<0>{0} is included in your starter pack" msgstr "" #. placeholder {0}: list.name -#: src/components/WhoCanReply.tsx:353 +#: src/components/WhoCanReply.tsx:346 msgid "<0>{0} members" msgstr "" @@ -896,7 +896,7 @@ msgstr "" msgid "<0>{displayName}<1/><2> added you" msgstr "<0>{displayName}<1/><2> added you" -#: src/screens/Hashtag.tsx:230 +#: src/screens/Hashtag.tsx:232 #: src/screens/Search/SearchResults.tsx:412 msgid "<0>Sign in<1> or <2>create an account<3> <4>to search for news, sports, politics, and everything else happening on Bluesky." msgstr "" @@ -906,7 +906,7 @@ msgid "<0>Tap here to update your location with GPS." msgstr "<0>Tap here to update your location with GPS." #. placeholder {0}: getName(items[1] /* [0] is self, skip it */) -#: src/screens/StarterPack/Wizard/index.tsx:494 +#: src/screens/StarterPack/Wizard/index.tsx:490 msgid "<0>You and<1> <2>{0} are included in your starter pack" msgstr "" @@ -915,25 +915,25 @@ msgstr "" msgid "⚠Invalid Handle" msgstr "" -#: src/components/dialogs/MutedWords.tsx:202 -#: src/components/dialogs/MutedWords.tsx:551 -#: src/components/dialogs/MutedWords.tsx:554 +#: src/components/dialogs/MutedWords.tsx:204 +#: src/components/dialogs/MutedWords.tsx:553 +#: src/components/dialogs/MutedWords.tsx:556 msgid "24 hours" msgstr "" -#: src/screens/Login/LoginForm.tsx:436 +#: src/screens/Login/LoginForm.tsx:437 msgid "2FA Confirmation" msgstr "" -#: src/components/dialogs/MutedWords.tsx:241 -#: src/components/dialogs/MutedWords.tsx:565 -#: src/components/dialogs/MutedWords.tsx:568 +#: src/components/dialogs/MutedWords.tsx:243 +#: src/components/dialogs/MutedWords.tsx:567 +#: src/components/dialogs/MutedWords.tsx:570 msgid "30 days" msgstr "" -#: src/components/dialogs/MutedWords.tsx:226 -#: src/components/dialogs/MutedWords.tsx:558 -#: src/components/dialogs/MutedWords.tsx:561 +#: src/components/dialogs/MutedWords.tsx:228 +#: src/components/dialogs/MutedWords.tsx:560 +#: src/components/dialogs/MutedWords.tsx:563 msgid "7 days" msgstr "" @@ -942,29 +942,29 @@ msgid "A collection of popular feeds you can find on Bluesky, including News, Bo msgstr "" #. If last message does not contain text, fall back to "{user} reacted to {a message}" -#: src/components/dms/getReactionInfo.ts:53 +#: src/components/dms/getReactionInfo.ts:58 msgid "a message" msgstr "" -#: src/components/contacts/screens/PhoneInput.tsx:102 +#: src/components/contacts/screens/PhoneInput.tsx:103 msgid "A network error occurred. Please check your internet connection" msgstr "" -#: src/components/contacts/screens/VerifyNumber.tsx:99 -#: src/components/contacts/screens/VerifyNumber.tsx:155 -#: src/components/dms/dialogs/NewChatDialog.tsx:56 -#: src/components/dms/dialogs/NewChatDialog.tsx:94 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:71 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:110 -#: src/components/dms/LeaveConvoPrompt.tsx:38 -#: src/components/moderation/BlockDialog.tsx:284 +#: src/components/contacts/screens/VerifyNumber.tsx:100 +#: src/components/contacts/screens/VerifyNumber.tsx:159 +#: src/components/dms/dialogs/NewChatDialog.tsx:54 +#: src/components/dms/dialogs/NewChatDialog.tsx:89 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:69 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:105 +#: src/components/dms/LeaveConvoPrompt.tsx:39 +#: src/components/moderation/BlockDialog.tsx:281 #: src/components/moderation/BlockDialog.tsx:310 -#: src/screens/Messages/JoinRequests.tsx:192 -#: src/screens/Messages/JoinRequests.tsx:225 +#: src/screens/Messages/JoinRequests.tsx:189 +#: src/screens/Messages/JoinRequests.tsx:221 msgid "A network error occurred. Please check your internet connection." msgstr "" -#: src/components/contacts/screens/VerifyNumber.tsx:143 +#: src/components/contacts/screens/VerifyNumber.tsx:147 msgid "A new code has been sent" msgstr "" @@ -972,7 +972,7 @@ msgstr "" msgid "A new form of verification" msgstr "" -#: src/components/dms/MessageItem.tsx:821 +#: src/components/dms/MessageItem.tsx:836 msgid "A reply to a message sent before you joined" msgstr "A reply to a message sent before you joined" @@ -995,19 +995,19 @@ msgstr "" msgid "A screenshot of the post composer with a new button next to the post button that says \"Drafts\", with a rainbow firework effect. Below, the text in the composer reads \"Hey, did you hear the news? Bluesky has drafts now!!!\"." msgstr "" -#: src/components/dms/dialogs/NewChatDialog.tsx:109 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:125 +#: src/components/dms/dialogs/NewChatDialog.tsx:102 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:118 msgid "A selected recipient is not followed by the sender." msgstr "A selected recipient is not followed by the sender." #: src/Navigation.tsx:483 #: src/screens/Settings/AboutSettings.tsx:85 -#: src/screens/Settings/Settings.tsx:265 -#: src/screens/Settings/Settings.tsx:268 +#: src/screens/Settings/Settings.tsx:267 +#: src/screens/Settings/Settings.tsx:270 msgid "About" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:100 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:101 msgid "Abusive or discriminatory behavior" msgstr "" @@ -1017,7 +1017,7 @@ msgid "Accept" msgstr "" #. Accept a request to join a chat -#: src/screens/Messages/JoinRequests.tsx:464 +#: src/screens/Messages/JoinRequests.tsx:461 msgctxt "button" msgid "Accept" msgstr "Accept" @@ -1026,7 +1026,7 @@ msgstr "Accept" msgid "Accept chat request" msgstr "" -#: src/screens/Messages/JoinRequests.tsx:458 +#: src/screens/Messages/JoinRequests.tsx:455 msgid "Accept join request" msgstr "Accept join request" @@ -1039,13 +1039,13 @@ msgstr "" msgid "Accept this language suggestion" msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:119 +#: src/components/intents/GroupChatJoinDialog.tsx:117 msgid "Access requested! The group owner will review your request." msgstr "Access requested! The group owner will review your request." #: src/screens/Settings/AccessibilitySettings.tsx:45 -#: src/screens/Settings/Settings.tsx:235 -#: src/screens/Settings/Settings.tsx:238 +#: src/screens/Settings/Settings.tsx:237 +#: src/screens/Settings/Settings.tsx:240 msgid "Accessibility" msgstr "" @@ -1055,12 +1055,12 @@ msgstr "" #: src/Navigation.tsx:406 #: src/screens/Settings/AccountSettings.tsx:54 -#: src/screens/Settings/Settings.tsx:175 -#: src/screens/Settings/Settings.tsx:178 +#: src/screens/Settings/Settings.tsx:177 +#: src/screens/Settings/Settings.tsx:180 msgid "Account" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:427 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:423 #: src/screens/Messages/components/RequestButtons.tsx:104 #: src/screens/Messages/ConversationSettings/MemberMenu.tsx:122 #: src/view/com/profile/ProfileMenu.tsx:223 @@ -1073,22 +1073,22 @@ msgctxt "toast" msgid "Account followed" msgstr "" -#: src/lib/strings/errors.ts:33 +#: src/lib/strings/errors.ts:68 msgid "Account has been suspended" msgstr "" -#: src/lib/strings/errors.ts:36 +#: src/lib/strings/errors.ts:71 msgid "Account is deactivated" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:470 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:466 #: src/view/com/profile/ProfileMenu.tsx:159 msgctxt "toast" msgid "Account muted" msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:123 -#: src/lib/moderation/useModerationCauseDescription.ts:99 +#: src/lib/moderation/useModerationCauseDescription.ts:96 msgid "Account Muted" msgstr "" @@ -1096,11 +1096,11 @@ msgstr "" msgid "Account Muted by List" msgstr "" -#: src/screens/Settings/Settings.tsx:656 +#: src/screens/Settings/Settings.tsx:658 msgid "Account options" msgstr "" -#: src/screens/Settings/Settings.tsx:691 +#: src/screens/Settings/Settings.tsx:693 msgid "Account removed from quick access" msgstr "" @@ -1117,7 +1117,7 @@ msgctxt "toast" msgid "Account unfollowed" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:450 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:446 #: src/view/com/profile/ProfileMenu.tsx:146 msgctxt "toast" msgid "Account unmuted" @@ -1137,22 +1137,22 @@ msgstr "" msgid "Activity notifications" msgstr "Activity notifications" -#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:191 +#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:192 #: src/components/dialogs/lists/UserAddRemoveListsDialog.tsx:319 #: src/components/dialogs/lists/UserAddRemoveListsDialog.tsx:326 -#: src/components/dialogs/MutedWords.tsx:337 -#: src/components/dialogs/StarterPackDialog.tsx:376 -#: src/components/dialogs/StarterPackDialog.tsx:388 +#: src/components/dialogs/MutedWords.tsx:339 +#: src/components/dialogs/StarterPackDialog.tsx:368 +#: src/components/dialogs/StarterPackDialog.tsx:380 #: src/components/dms/AddMembersFlow.tsx:410 msgid "Add" msgstr "" #. placeholder {0}: 8 - items.length -#: src/screens/StarterPack/Wizard/index.tsx:605 +#: src/screens/StarterPack/Wizard/index.tsx:601 msgid "Add {0} more to continue" msgstr "" -#: src/components/StarterPack/Wizard/WizardListCard.tsx:63 +#: src/components/StarterPack/Wizard/WizardListCard.tsx:62 msgid "Add {displayName} to starter pack" msgstr "" @@ -1171,7 +1171,7 @@ msgid "Add a user to this list" msgstr "" #: src/components/dialogs/SwitchAccount.tsx:56 -#: src/screens/Deactivated.tsx:184 +#: src/screens/Deactivated.tsx:189 msgid "Add account" msgstr "" @@ -1189,18 +1189,18 @@ msgstr "" msgid "Add alt text (optional)" msgstr "" -#: src/screens/Settings/Settings.tsx:594 -#: src/screens/Settings/Settings.tsx:597 +#: src/screens/Settings/Settings.tsx:596 +#: src/screens/Settings/Settings.tsx:599 #: src/view/shell/desktop/LeftNav.tsx:276 #: src/view/shell/desktop/LeftNav.tsx:279 msgid "Add another account" msgstr "" -#: src/view/com/composer/Composer.tsx:1635 +#: src/view/com/composer/Composer.tsx:1659 msgid "Add another post" msgstr "" -#: src/view/com/composer/Composer.tsx:2304 +#: src/view/com/composer/Composer.tsx:2328 msgid "Add another post to thread" msgstr "" @@ -1218,7 +1218,7 @@ msgstr "" msgid "Add App Password" msgstr "" -#: src/screens/Settings/AutomationLabelSettings.tsx:170 +#: src/screens/Settings/AutomationLabelSettings.tsx:169 msgid "Add automation label to account" msgstr "Add automation label to account" @@ -1249,8 +1249,8 @@ msgstr "" msgid "Add members" msgstr "Add members" -#: src/components/moderation/ReportDialog/index.tsx:605 -#: src/components/moderation/ReportDialog/index.tsx:609 +#: src/components/moderation/ReportDialog/index.tsx:606 +#: src/components/moderation/ReportDialog/index.tsx:610 msgid "Add more details (optional)" msgstr "" @@ -1259,11 +1259,11 @@ msgstr "" msgid "Add more languages…" msgstr "Add more languages…" -#: src/components/dialogs/MutedWords.tsx:330 +#: src/components/dialogs/MutedWords.tsx:332 msgid "Add mute word with chosen settings" msgstr "" -#: src/components/dialogs/MutedWords.tsx:120 +#: src/components/dialogs/MutedWords.tsx:122 msgid "Add muted words and tags" msgstr "" @@ -1274,7 +1274,7 @@ msgstr "" msgid "Add people" msgstr "" -#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:81 +#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:82 msgid "Add people to list" msgstr "" @@ -1290,7 +1290,7 @@ msgstr "" msgid "Add recommended feeds" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:538 +#: src/screens/StarterPack/Wizard/index.tsx:534 msgid "Add some feeds to your starter pack!" msgstr "" @@ -1302,7 +1302,7 @@ msgstr "" msgid "Add the following DNS record to your domain:" msgstr "" -#: src/components/FeedCard.tsx:338 +#: src/components/FeedCard.tsx:335 msgid "Add this feed to your feeds" msgstr "" @@ -1315,13 +1315,13 @@ msgstr "" msgid "Add to saved posts" msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:184 +#: src/components/dialogs/StarterPackDialog.tsx:181 #: src/view/com/profile/ProfileMenu.tsx:384 #: src/view/com/profile/ProfileMenu.tsx:387 msgid "Add to starter packs" msgstr "" -#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:178 +#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:179 msgid "Add user to list" msgstr "" @@ -1338,25 +1338,25 @@ msgstr "Added by {0}" msgid "Added by invite link" msgstr "Added by invite link" -#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:125 +#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:126 #: src/components/dialogs/lists/UserAddRemoveListsDialog.tsx:221 msgid "Added to list" msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:274 +#: src/components/dialogs/StarterPackDialog.tsx:271 msgid "Added to starter pack" msgstr "" -#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:225 -#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:230 +#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:224 +#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:229 msgid "Adding members to list..." msgstr "" -#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:116 +#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:118 msgid "Additional details (limit 1000 characters)" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:623 +#: src/components/moderation/ReportDialog/index.tsx:624 msgid "Additional details (limit 300 characters)" msgstr "" @@ -1369,13 +1369,13 @@ msgstr "Admin" msgid "Adult" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:66 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:67 msgid "Adult content" msgstr "" #: src/components/moderation/ContentHider.tsx:129 #: src/lib/moderation/useGlobalLabelStrings.ts:34 -#: src/lib/moderation/useModerationCauseDescription.ts:149 +#: src/lib/moderation/useModerationCauseDescription.ts:146 #: src/view/com/composer/labels/LabelsBtn.tsx:123 msgid "Adult Content" msgstr "" @@ -1393,7 +1393,7 @@ msgstr "" msgid "Adult Content labels" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:76 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:77 msgid "Adult sexual abuse content" msgstr "" @@ -1405,11 +1405,11 @@ msgstr "" msgid "Age Assurance" msgstr "" -#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:72 +#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:74 msgid "Age assurance inquiry failed to send, please try again." msgstr "" -#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:82 +#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:84 msgctxt "toast" msgid "Age assurance inquiry was submitted" msgstr "" @@ -1428,7 +1428,7 @@ msgstr "" #. the default tab in the interests tab bar #: src/components/dms/ReactionsDialog.tsx:292 -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:201 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:205 #: src/view/screens/Notifications.tsx:86 msgid "All" msgstr "" @@ -1439,12 +1439,12 @@ msgstr "" msgid "All {0}" msgstr "All {0}" -#: src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx:97 -#: src/screens/StarterPack/StarterPackScreen.tsx:400 +#: src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx:98 +#: src/screens/StarterPack/StarterPackScreen.tsx:398 msgid "All accounts have been followed!" msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:144 +#: src/components/contacts/screens/ViewMatches.tsx:150 msgid "All friends followed!" msgstr "" @@ -1469,7 +1469,7 @@ msgstr "" msgid "Allow access to your direct messages" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:451 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:448 msgid "Allow anyone to reply" msgstr "" @@ -1493,24 +1493,24 @@ msgstr "" msgid "Allow others to be notified of your posts" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:504 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:501 msgid "Allow people you follow to reply" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:518 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:515 msgid "Allow people you mention to reply" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:636 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:633 msgid "Allow quote posts" msgstr "" #. placeholder {0}: list.name -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:598 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:595 msgid "Allow users in {0} to reply" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:490 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:487 msgid "Allow your followers to reply" msgstr "" @@ -1518,9 +1518,9 @@ msgstr "" msgid "Allows access to direct messages" msgstr "" -#: src/screens/Login/ForgotPasswordForm.tsx:174 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:237 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:243 +#: src/screens/Login/ForgotPasswordForm.tsx:178 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:241 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:247 msgid "Already have a code?" msgstr "" @@ -1568,7 +1568,7 @@ msgid "Alt text will be truncated. {MAX_ALT_TEXT, plural, other {Limit: {0} char msgstr "" #. placeholder {0}: currentAccount?.email || '(no email)' -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:94 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:102 msgid "An email has been sent to {0}. It includes a confirmation code which you can enter below." msgstr "" @@ -1582,16 +1582,16 @@ msgstr "" msgid "An error occurred" msgstr "" -#: src/view/com/composer/state/video.ts:508 +#: src/view/com/composer/state/video.ts:513 msgid "An error occurred while compressing the video." msgstr "" -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:223 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:227 #: src/screens/Onboarding/StepSuggestedStarterpacks/index.tsx:69 msgid "An error occurred while fetching suggested accounts." msgstr "" -#: src/state/queries/explore-feed-previews.tsx:183 +#: src/state/queries/explore-feed-previews.tsx:181 msgid "An error occurred while fetching the feed." msgstr "" @@ -1600,7 +1600,7 @@ msgid "An error occurred while generating your starter pack. Want to try again?" msgstr "" #. placeholder {0}: cleanError(err) -#: src/components/contacts/screens/ViewMatches.tsx:243 +#: src/components/contacts/screens/ViewMatches.tsx:251 msgid "An error occurred while hiding suggestion. {0}" msgstr "" @@ -1612,7 +1612,7 @@ msgstr "" msgid "An error occurred while loading the video. Please try again." msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:581 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:578 msgid "An error occurred while loading your lists :/" msgstr "" @@ -1621,25 +1621,25 @@ msgstr "" msgid "An error occurred while saving the QR code!" msgstr "" -#: src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx:54 -#: src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx:82 -#: src/screens/StarterPack/StarterPackScreen.tsx:359 -#: src/screens/StarterPack/StarterPackScreen.tsx:386 +#: src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx:55 +#: src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx:83 +#: src/screens/StarterPack/StarterPackScreen.tsx:357 +#: src/screens/StarterPack/StarterPackScreen.tsx:384 msgid "An error occurred while trying to follow all" msgstr "" -#: src/view/com/composer/state/video.ts:560 +#: src/view/com/composer/state/video.ts:565 msgid "An error occurred while uploading the video. {message}" msgstr "" -#: src/view/com/composer/state/video.ts:552 +#: src/view/com/composer/state/video.ts:557 msgid "An error occurred while uploading the video. Please check your internet connection and try again." msgstr "" #. placeholder {0}: cleanError(err) -#: src/components/contacts/screens/PhoneInput.tsx:120 -#: src/components/contacts/screens/VerifyNumber.tsx:131 -#: src/components/contacts/screens/VerifyNumber.tsx:184 +#: src/components/contacts/screens/PhoneInput.tsx:121 +#: src/components/contacts/screens/VerifyNumber.tsx:136 +#: src/components/contacts/screens/VerifyNumber.tsx:188 msgid "An error occurred. {0}" msgstr "" @@ -1661,30 +1661,30 @@ msgstr "An illustration of the Bluesky app with a paper airplane flying out of i msgid "An illustration showing that Bluesky selects trusted verifiers, and trusted verifiers in turn verify individual user accounts." msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:198 +#: src/screens/Messages/components/InviteLinkDialog.tsx:195 msgid "An invite link lets people join this group chat without being added directly. You control who can join the chat. You can disable the link at any time." msgstr "An invite link lets people join this group chat without being added directly. You control who can join the chat. You can disable the link at any time." -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:239 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:241 msgid "An issue not included in these options" msgstr "" -#: src/components/dms/dialogs/NewChatDialog.tsx:54 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:69 +#: src/components/dms/dialogs/NewChatDialog.tsx:52 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:67 msgid "An issue occurred starting the chat, please try again." msgstr "An issue occurred starting the chat, please try again." -#: src/components/dms/dialogs/NewChatDialog.tsx:92 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:108 +#: src/components/dms/dialogs/NewChatDialog.tsx:87 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:103 msgid "An issue occurred starting the group chat, please try again." msgstr "An issue occurred starting the group chat, please try again." #: src/components/hooks/useFollowMethods.ts:35 #: src/components/hooks/useFollowMethods.ts:52 -#: src/components/ProfileCard.tsx:510 -#: src/components/ProfileCard.tsx:532 -#: src/view/com/notifications/NotificationFeedItem.tsx:834 -#: src/view/com/notifications/NotificationFeedItem.tsx:855 +#: src/components/ProfileCard.tsx:507 +#: src/components/ProfileCard.tsx:529 +#: src/view/com/notifications/NotificationFeedItem.tsx:827 +#: src/view/com/notifications/NotificationFeedItem.tsx:848 msgid "An issue occurred, please try again." msgstr "" @@ -1693,19 +1693,19 @@ msgid "An mockup of a iPhone showing the Bluesky app open to the profile of a ve msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:154 -#: src/lib/moderation/useModerationCauseDescription.ts:145 +#: src/lib/moderation/useModerationCauseDescription.ts:142 msgid "an unknown labeler" msgstr "" -#: src/components/WhoCanReply.tsx:374 +#: src/components/WhoCanReply.tsx:367 msgid "and" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:88 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:89 msgid "Animal sexual abuse" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:130 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:131 msgid "Animal welfare" msgstr "" @@ -1730,25 +1730,25 @@ msgstr "" msgid "Any date" msgstr "Any date" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:457 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:454 msgid "Anyone" msgstr "" -#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:135 +#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:136 msgid "Anyone can interact" msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:356 +#: src/components/intents/GroupChatJoinDialog.tsx:361 msgid "Anyone can join" msgstr "Anyone can join" -#: src/screens/Messages/components/InviteLinkDialog.tsx:153 -#: src/screens/Messages/components/InviteLinkDialog.tsx:154 +#: src/screens/Messages/components/InviteLinkDialog.tsx:150 +#: src/screens/Messages/components/InviteLinkDialog.tsx:151 msgid "Anyone can join instantly" msgstr "Anyone can join instantly" -#: src/screens/Messages/components/InviteLinkDialog.tsx:158 -#: src/screens/Messages/components/InviteLinkDialog.tsx:159 +#: src/screens/Messages/components/InviteLinkDialog.tsx:155 +#: src/screens/Messages/components/InviteLinkDialog.tsx:156 msgid "Anyone can request to join" msgstr "Anyone can request to join" @@ -1758,7 +1758,7 @@ msgstr "Anyone can request to join" msgid "Anyone who follows me" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:478 +#: src/screens/Messages/components/InviteLinkDialog.tsx:475 msgid "Anyone who has it will no longer be able to join or request to join. You can always create a new one." msgstr "Anyone who has it will no longer be able to join or request to join. You can always create a new one." @@ -1812,7 +1812,7 @@ msgid "Appeal" msgstr "" #. placeholder {0}: strings.name -#: src/components/moderation/AppealForm.tsx:93 +#: src/components/moderation/AppealForm.tsx:104 msgid "Appeal \"{0}\" label" msgstr "" @@ -1820,30 +1820,30 @@ msgstr "" msgid "Appeal label" msgstr "Appeal label" -#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:93 +#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:92 msgid "Appeal livestream suspension" msgstr "" -#: src/components/moderation/AppealForm.tsx:83 -#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:83 -#: src/screens/Messages/components/ChatDisabled.tsx:125 +#: src/components/moderation/AppealForm.tsx:94 +#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:82 +#: src/screens/Messages/components/ChatDisabled.tsx:123 msgctxt "toast" msgid "Appeal submitted" msgstr "" -#: src/screens/Takendown.tsx:114 -#: src/screens/Takendown.tsx:140 +#: src/screens/Takendown.tsx:112 +#: src/screens/Takendown.tsx:138 msgid "Appeal suspension" msgstr "" -#: src/screens/Takendown.tsx:117 +#: src/screens/Takendown.tsx:115 msgid "Appeal Suspension" msgstr "" #: src/screens/Messages/components/ChatDisabled.tsx:76 #: src/screens/Messages/components/ChatDisabled.tsx:79 +#: src/screens/Messages/components/ChatDisabled.tsx:131 #: src/screens/Messages/components/ChatDisabled.tsx:133 -#: src/screens/Messages/components/ChatDisabled.tsx:135 msgid "Appeal this decision" msgstr "" @@ -1853,8 +1853,8 @@ msgstr "Appeal this label" #: src/Navigation.tsx:398 #: src/screens/Settings/AppearanceSettings.tsx:71 -#: src/screens/Settings/Settings.tsx:227 -#: src/screens/Settings/Settings.tsx:230 +#: src/screens/Settings/Settings.tsx:229 +#: src/screens/Settings/Settings.tsx:232 msgid "Appearance" msgstr "" @@ -1863,22 +1863,22 @@ msgstr "" msgid "Apply default recommended feeds" msgstr "" -#: src/screens/Settings/Settings.tsx:525 #: src/screens/Settings/Settings.tsx:527 +#: src/screens/Settings/Settings.tsx:529 msgid "Apply Pull Request" msgstr "" #. placeholder {0}: niceDate(i18n, createdAt, 'medium') -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:627 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:624 msgid "Archived from {0}" msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:598 -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:636 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:595 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:633 msgid "Archived post" msgstr "" -#: src/screens/Settings/components/DeleteAccountDialog.tsx:327 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:333 msgid "Are you really, really sure?" msgstr "" @@ -1909,11 +1909,11 @@ msgstr "" msgid "Are you sure you want to delete this message? The message will be deleted for you, but not for the other participants." msgstr "Are you sure you want to delete this message? The message will be deleted for you, but not for the other participants." -#: src/screens/StarterPack/StarterPackScreen.tsx:684 +#: src/screens/StarterPack/StarterPackScreen.tsx:677 msgid "Are you sure you want to delete this starter pack?" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:99 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:100 #: src/screens/Profile/Header/EditProfileDialog.tsx:77 msgid "Are you sure you want to discard your changes?" msgstr "" @@ -1923,32 +1923,32 @@ msgstr "" msgid "Are you sure you want to leave {groupName}?" msgstr "Are you sure you want to leave {groupName}?" -#: src/components/dms/LeaveConvoPrompt.tsx:57 +#: src/components/dms/LeaveConvoPrompt.tsx:61 msgid "Are you sure you want to leave this conversation?" msgstr "" -#: src/components/dms/LeaveConvoPrompt.tsx:56 +#: src/components/dms/LeaveConvoPrompt.tsx:60 msgid "Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for the other participants." msgstr "Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for the other participants." -#: src/components/FeedCard.tsx:374 +#: src/components/FeedCard.tsx:371 msgid "Are you sure you want to remove this from your feeds?" msgstr "" #. placeholder {0}: convoView.name -#: src/screens/Messages/components/OutgoingRequestListItem.tsx:116 +#: src/screens/Messages/components/OutgoingRequestListItem.tsx:114 msgid "Are you sure you want to rescind your request to join {0}?" msgstr "Are you sure you want to rescind your request to join {0}?" -#: src/view/com/composer/Composer.tsx:1771 +#: src/view/com/composer/Composer.tsx:1795 msgid "Are you sure you'd like to discard this post?" msgstr "" -#: src/components/dialogs/MutedWords.tsx:452 +#: src/components/dialogs/MutedWords.tsx:454 msgid "Are you sure?" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:928 +#: src/components/moderation/ReportDialog/index.tsx:929 msgid "Are you the person depicted, or an authorized representative acting on behalf of the person depicted?" msgstr "Are you the person depicted, or an authorized representative acting on behalf of the person depicted?" @@ -1964,16 +1964,16 @@ msgstr "" msgid "Artistic or non-erotic nudity." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:623 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:625 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:619 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:621 msgid "Assign topic for algo" msgstr "" -#: src/screens/Settings/components/ChangePasswordDialog.tsx:209 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:213 msgid "At least 8 characters" msgstr "" -#: src/screens/Settings/components/DeleteAccountDialog.tsx:338 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:344 msgid "AT Protocol FAQ" msgstr "AT Protocol FAQ" @@ -2003,7 +2003,7 @@ msgid "Automation label" msgstr "Automation label" #: src/Navigation.tsx:422 -#: src/screens/Settings/AutomationLabelSettings.tsx:103 +#: src/screens/Settings/AutomationLabelSettings.tsx:102 msgid "Automation Label" msgstr "Automation Label" @@ -2029,40 +2029,40 @@ msgstr "Avatar creator" #: src/components/dms/InitiateChatFlow.tsx:645 #: src/components/dms/InitiateChatFlow.tsx:863 #: src/components/dms/InitiateChatFlow.tsx:870 -#: src/components/moderation/AppealForm.tsx:145 -#: src/components/moderation/AppealForm.tsx:146 +#: src/components/moderation/AppealForm.tsx:156 +#: src/components/moderation/AppealForm.tsx:157 #: src/screens/Login/ChooseAccountForm.tsx:96 #: src/screens/Login/ChooseAccountForm.tsx:100 -#: src/screens/Login/ForgotPasswordForm.tsx:119 -#: src/screens/Login/ForgotPasswordForm.tsx:124 -#: src/screens/Login/LoginForm.tsx:511 -#: src/screens/Login/LoginForm.tsx:516 -#: src/screens/Login/SetNewPasswordForm.tsx:157 -#: src/screens/Login/SetNewPasswordForm.tsx:163 +#: src/screens/Login/ForgotPasswordForm.tsx:123 +#: src/screens/Login/ForgotPasswordForm.tsx:128 +#: src/screens/Login/LoginForm.tsx:512 +#: src/screens/Login/LoginForm.tsx:517 +#: src/screens/Login/SetNewPasswordForm.tsx:162 +#: src/screens/Login/SetNewPasswordForm.tsx:168 +#: src/screens/Messages/components/ChatDisabled.tsx:162 #: src/screens/Messages/components/ChatDisabled.tsx:164 -#: src/screens/Messages/components/ChatDisabled.tsx:166 -#: src/screens/Messages/components/InviteLinkDialog.tsx:276 -#: src/screens/Messages/components/InviteLinkDialog.tsx:304 -#: src/screens/Messages/Inbox.tsx:233 -#: src/screens/Profile/Header/Shell.tsx:174 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:273 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:282 +#: src/screens/Messages/components/InviteLinkDialog.tsx:273 +#: src/screens/Messages/components/InviteLinkDialog.tsx:301 +#: src/screens/Messages/Inbox.tsx:232 +#: src/screens/Profile/Header/Shell.tsx:175 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:277 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:286 #: src/screens/Signup/BackNextButtons.tsx:42 -#: src/screens/StarterPack/Wizard/index.tsx:315 +#: src/screens/StarterPack/Wizard/index.tsx:311 #: src/view/com/composer/drafts/DraftsListDialog.tsx:87 #: src/view/com/composer/drafts/DraftsListDialog.tsx:93 msgid "Back" msgstr "" -#: src/screens/Messages/Inbox.tsx:232 +#: src/screens/Messages/Inbox.tsx:231 msgid "Back to Chats" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:216 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:218 msgid "Banned activities or security violations" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:227 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:229 msgid "Banned user returning" msgstr "" @@ -2091,10 +2091,10 @@ msgstr "" msgid "Before creating a post or replying, you must first verify your email." msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:72 +#: src/components/dialogs/StarterPackDialog.tsx:69 #: src/components/StarterPack/ProfileStarterPacks.tsx:267 #: src/components/StarterPack/ProfileStarterPacks.tsx:277 -#: src/view/screens/Profile.tsx:352 +#: src/view/screens/Profile.tsx:349 msgid "Before creating a starter pack, you must first verify your email." msgstr "" @@ -2106,26 +2106,26 @@ msgstr "" msgid "Before you can get notifications for {name}'s posts, you must first verify your email." msgstr "" -#: src/components/dms/dialogs/NewChatDialog.tsx:155 +#: src/components/dms/dialogs/NewChatDialog.tsx:148 #: src/components/dms/MessageProfileButton.tsx:60 #: src/screens/Messages/ChatList.tsx:168 #: src/screens/Messages/ChatList.tsx:186 #: src/screens/Messages/ChatList.tsx:287 #: src/screens/Messages/ChatList.tsx:543 -#: src/screens/Messages/Conversation.tsx:203 +#: src/screens/Messages/Conversation.tsx:206 #: src/screens/Messages/ConversationSettings/MemberMenu.tsx:99 msgid "Before you can message another user, you must first verify your email." msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:319 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:328 msgid "Begin" msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:313 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:322 msgid "Begin age assurance process" msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:51 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:53 msgid "Begin the age assurance process by completing the fields below." msgstr "" @@ -2135,8 +2135,8 @@ msgstr "" #: src/Navigation.tsx:414 #: src/screens/Settings/BetaFeaturesSettings.tsx:116 -#: src/screens/Settings/Settings.tsx:249 -#: src/screens/Settings/Settings.tsx:252 +#: src/screens/Settings/Settings.tsx:251 +#: src/screens/Settings/Settings.tsx:254 msgid "Beta features" msgstr "Beta features" @@ -2164,9 +2164,9 @@ msgstr "" msgid "Birthday" msgstr "" -#: src/components/moderation/BlockDialog.tsx:187 -#: src/components/moderation/BlockDialog.tsx:193 -#: src/components/moderation/BlockDialog.tsx:213 +#: src/components/moderation/BlockDialog.tsx:184 +#: src/components/moderation/BlockDialog.tsx:190 +#: src/components/moderation/BlockDialog.tsx:210 #: src/screens/Messages/ConversationSettings/MemberMenu.tsx:227 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:190 msgid "Block" @@ -2178,8 +2178,8 @@ msgstr "Block {displayName}" #: src/components/dms/ConvoMenu.tsx:284 #: src/components/dms/ConvoMenu.tsx:288 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:772 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:774 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:768 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:770 #: src/screens/Messages/components/RequestButtons.tsx:154 #: src/screens/Messages/components/RequestButtons.tsx:156 #: src/view/com/profile/ProfileMenu.tsx:536 @@ -2187,7 +2187,7 @@ msgstr "Block {displayName}" msgid "Block account" msgstr "" -#: src/components/moderation/BlockDialog.tsx:148 +#: src/components/moderation/BlockDialog.tsx:145 msgid "Block account?" msgstr "Block account?" @@ -2240,7 +2240,7 @@ msgstr "" msgid "Block user and/or leave this conversation" msgstr "Block user and/or leave this conversation" -#: src/components/Post/Embed/index.tsx:213 +#: src/components/Post/Embed/index.tsx:214 msgid "Blocked" msgstr "" @@ -2253,7 +2253,7 @@ msgstr "" msgid "Blocked Accounts" msgstr "" -#: src/components/moderation/BlockDialog.tsx:163 +#: src/components/moderation/BlockDialog.tsx:160 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." msgstr "" @@ -2269,7 +2269,7 @@ msgstr "" msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." msgstr "" -#: src/components/moderation/BlockDialog.tsx:157 +#: src/components/moderation/BlockDialog.tsx:154 msgid "Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you." msgstr "" @@ -2288,7 +2288,7 @@ msgstr "bloomscrolling booksky" msgid "Bluesky" msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:652 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:649 msgid "Bluesky cannot confirm the authenticity of the claimed date." msgstr "" @@ -2297,7 +2297,7 @@ msgctxt "Name of app icon variant" msgid "Bluesky Classic™" msgstr "" -#: src/components/contacts/screens/GetContacts.tsx:235 +#: src/components/contacts/screens/GetContacts.tsx:237 msgid "Bluesky helps friends find each other by creating an encoded digital fingerprint, called a \"hash\", and then looking for matching hashes." msgstr "" @@ -2322,7 +2322,7 @@ msgstr "" msgid "Bluesky is more fun with friends! Import your contacts to see who’s already here." msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:315 +#: src/components/contacts/screens/ViewMatches.tsx:323 msgid "Bluesky is more fun with friends. Do you want to invite some of yours? <0/>" msgstr "" @@ -2330,11 +2330,11 @@ msgstr "" msgid "Bluesky needs camera access to scan QR codes from other profiles." msgstr "Bluesky needs camera access to scan QR codes from other profiles." -#: src/screens/Takendown.tsx:213 +#: src/screens/Takendown.tsx:211 msgid "Bluesky Social Terms of Service" msgstr "" -#: src/screens/Settings/FindContactsSettings.tsx:570 +#: src/screens/Settings/FindContactsSettings.tsx:574 msgid "Bluesky stores your contacts as encoded data. Removing your contacts will immediately delete this data." msgstr "" @@ -2342,7 +2342,7 @@ msgstr "" msgid "Bluesky will choose a set of recommended accounts from people in your network." msgstr "" -#: src/screens/Settings/components/PwiOptOut.tsx:100 +#: src/screens/Settings/components/PwiOptOut.tsx:101 msgid "Bluesky will not show your profile and posts to logged-out users. Other apps may not honor this request. This does not make your account private." msgstr "" @@ -2370,7 +2370,7 @@ msgstr "" msgid "Books" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:215 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:217 msgid "Breaking site rules" msgstr "" @@ -2405,25 +2405,25 @@ msgstr "" msgid "Browse other feeds" msgstr "" -#: src/components/TrendingTopics.tsx:87 +#: src/components/TrendingTopics.tsx:88 msgid "Browse posts about {displayName}" msgstr "" -#: src/components/TrendingTopics.tsx:95 +#: src/components/TrendingTopics.tsx:96 msgid "Browse posts tagged with {displayName}" msgstr "" -#: src/components/TrendingTopics.tsx:104 +#: src/components/TrendingTopics.tsx:105 msgid "Browse starter pack {displayName}" msgstr "" #. placeholder {0}: trend.displayName -#: src/components/interstitials/FeedTrendingTopics.tsx:202 -#: src/screens/Search/modules/ExploreTrendingTopics.tsx:149 +#: src/components/interstitials/FeedTrendingTopics.tsx:203 +#: src/screens/Search/modules/ExploreTrendingTopics.tsx:147 msgid "Browse topic {0}" msgstr "" -#: src/components/TrendingTopics.tsx:141 +#: src/components/TrendingTopics.tsx:142 msgid "Browse topic {displayName}" msgstr "" @@ -2431,7 +2431,7 @@ msgstr "" msgid "Business" msgstr "" -#: src/screens/Bookmarks.tsx:295 +#: src/screens/Bookmarks.tsx:293 msgid "Button to go back to the home timeline" msgstr "" @@ -2440,10 +2440,10 @@ msgstr "" #. placeholder {0}: sanitizeHandle(handle, '@') #. placeholder {0}: sanitizeHandle(item.feed.creator.handle, '@') #: src/components/LabelingServiceCard/index.tsx:62 -#: src/components/moderation/ReportDialog/index.tsx:1092 -#: src/screens/Messages/JoinRequest.tsx:177 -#: src/screens/Search/components/StarterPackCard.tsx:112 -#: src/screens/Search/Explore.tsx:960 +#: src/components/moderation/ReportDialog/index.tsx:1093 +#: src/screens/Messages/JoinRequest.tsx:185 +#: src/screens/Search/components/StarterPackCard.tsx:104 +#: src/screens/Search/Explore.tsx:956 msgid "By {0}" msgstr "" @@ -2455,13 +2455,13 @@ msgstr "by @{0}" #. The group chat creator, in the format 'By {displayName}'. #. placeholder {0}: createSanitizedDisplayName( joinLinkPreview.owner, true, moderateProfile(joinLinkPreview.owner, moderationOpts).ui( 'displayName', ), ) #. placeholder {0}: info.creatorHandle === TRENDING_HANDLE ? l`Bluesky` : sanitizeHandle(info.creatorHandle, '@') -#: src/components/intents/GroupChatJoinDialog.tsx:379 +#: src/components/intents/GroupChatJoinDialog.tsx:384 #: src/screens/CustomFeed/components/CustomFeedHeader.tsx:502 msgid "By <0>{0}" msgstr "" #. The group chat creator, in the format 'By {displayName}'. -#: src/components/dms/ChatInvite/Card.tsx:84 +#: src/components/dms/ChatInvite/Card.tsx:86 msgid "By <0>{ownerDisplayName}" msgstr "By <0>{ownerDisplayName}" @@ -2485,7 +2485,7 @@ msgstr "" msgid "By creating an account you agree to the <0>Terms of Service." msgstr "" -#: src/screens/Search/components/StarterPackCard.tsx:111 +#: src/screens/Search/components/StarterPackCard.tsx:103 msgid "By you" msgstr "" @@ -2497,25 +2497,25 @@ msgstr "" msgid "Camera access needed" msgstr "Camera access needed" -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:213 -#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:133 -#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:139 -#: src/components/contacts/screens/GetContacts.tsx:292 -#: src/components/contacts/screens/GetContacts.tsx:297 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:210 +#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:135 +#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:141 +#: src/components/contacts/screens/GetContacts.tsx:294 +#: src/components/contacts/screens/GetContacts.tsx:299 #: src/components/dialogs/DeviceLocationRequestDialog.tsx:141 #: src/components/dialogs/DeviceLocationRequestDialog.tsx:146 #: src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx:122 #: src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx:128 #: src/components/dialogs/InAppBrowserConsent.tsx:99 #: src/components/dialogs/InAppBrowserConsent.tsx:105 -#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:192 -#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:200 -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:291 +#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:191 +#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:199 #: src/components/dialogs/lists/CreateOrEditListDialog.tsx:299 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:307 #: src/components/dialogs/nuxs/InviteFriendsAnnouncement.tsx:175 #: src/components/dialogs/nuxs/InviteFriendsAnnouncement.tsx:181 #: src/components/Menu/index.tsx:373 -#: src/components/moderation/BlockDialog.tsx:204 +#: src/components/moderation/BlockDialog.tsx:201 #: src/components/PostControls/RepostButton.tsx:210 #: src/components/Prompt.tsx:152 #: src/components/Prompt.tsx:154 @@ -2524,9 +2524,9 @@ msgstr "Camera access needed" #: src/features/liveNow/components/GoLiveDialog.tsx:254 #: src/features/liveNow/components/GoLiveDialog.tsx:260 #: src/lib/media/picker.tsx:38 -#: src/screens/Deactivated.tsx:150 -#: src/screens/Messages/components/InviteLinkDialog.tsx:500 -#: src/screens/Messages/components/InviteLinkDialog.tsx:504 +#: src/screens/Deactivated.tsx:155 +#: src/screens/Messages/components/InviteLinkDialog.tsx:497 +#: src/screens/Messages/components/InviteLinkDialog.tsx:501 #: src/screens/Messages/ConversationSettings/prompts.tsx:108 #: src/screens/Messages/ConversationSettings/prompts.tsx:132 #: src/screens/Messages/ConversationSettings/prompts.tsx:156 @@ -2541,13 +2541,13 @@ msgstr "Camera access needed" #: src/screens/Settings/components/BetaFeaturesFeedbackDialog.tsx:152 #: src/screens/Settings/components/ChangeHandleDialog.tsx:80 #: src/screens/Settings/components/ChangeHandleDialog.tsx:87 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:248 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:254 -#: src/screens/Settings/Settings.tsx:310 -#: src/screens/Takendown.tsx:102 -#: src/screens/Takendown.tsx:105 -#: src/view/com/composer/Composer.tsx:1849 -#: src/view/com/composer/Composer.tsx:1859 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:252 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:258 +#: src/screens/Settings/Settings.tsx:312 +#: src/screens/Takendown.tsx:100 +#: src/screens/Takendown.tsx:103 +#: src/view/com/composer/Composer.tsx:1873 +#: src/view/com/composer/Composer.tsx:1883 #: src/view/com/composer/photos/EditImageDialog.web.tsx:44 #: src/view/com/composer/photos/EditImageDialog.web.tsx:53 #: src/view/shell/desktop/LeftNav.tsx:228 @@ -2558,7 +2558,7 @@ msgstr "" msgid "Cancel quote post" msgstr "" -#: src/screens/Deactivated.tsx:144 +#: src/screens/Deactivated.tsx:149 msgid "Cancel reactivation and sign out" msgstr "" @@ -2574,10 +2574,10 @@ msgstr "" msgid "Cancels signing in so you can check your username" msgstr "Cancels signing in so you can check your username" -#: src/components/PostControls/index.tsx:108 -#: src/components/PostControls/index.tsx:138 -#: src/components/PostControls/index.tsx:166 -#: src/state/shell/composer/index.tsx:105 +#: src/components/PostControls/index.tsx:104 +#: src/components/PostControls/index.tsx:134 +#: src/components/PostControls/index.tsx:162 +#: src/state/shell/composer/index.tsx:101 msgid "Cannot interact with a blocked user" msgstr "" @@ -2603,7 +2603,7 @@ msgstr "" msgid "cats dogs" msgstr "cats dogs" -#: src/components/Post/Translated/index.tsx:439 +#: src/components/Post/Translated/index.tsx:436 #: src/screens/Settings/components/Email2FAToggle.tsx:31 msgid "Change" msgstr "" @@ -2628,36 +2628,36 @@ msgstr "" msgid "Change Handle" msgstr "" -#: src/screens/Login/LoginForm.tsx:644 +#: src/screens/Login/LoginForm.tsx:645 msgid "Change hosting provider" msgstr "Change hosting provider" -#: src/components/moderation/ReportDialog/index.tsx:522 +#: src/components/moderation/ReportDialog/index.tsx:523 msgid "Change moderation service" msgstr "" -#: src/screens/Settings/components/ChangePasswordDialog.tsx:262 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:268 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:266 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:272 msgid "Change password" msgstr "" -#: src/screens/Settings/components/ChangePasswordDialog.tsx:165 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:169 msgid "Change password dialog" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:384 +#: src/components/moderation/ReportDialog/index.tsx:385 msgid "Change report category" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:463 +#: src/components/moderation/ReportDialog/index.tsx:464 msgid "Change report reason" msgstr "" -#: src/components/Post/Translated/index.tsx:424 +#: src/components/Post/Translated/index.tsx:421 msgid "Change the source language" msgstr "Change the source language" -#: src/screens/Settings/components/ChangePasswordDialog.tsx:58 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:60 msgid "Change your password" msgstr "" @@ -2670,11 +2670,11 @@ msgstr "" msgid "Changes hosting provider" msgstr "" -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:179 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:176 msgid "Changes saved" msgstr "" -#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:164 +#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:163 msgid "Changes to the starter pack will not be reflected in the list after creation. The list will be an independent copy." msgstr "" @@ -2692,17 +2692,17 @@ msgctxt "toast" msgid "Chat deleted" msgstr "" -#: src/components/dms/getSystemMessageInfo.ts:108 +#: src/components/dms/getSystemMessageInfo.ts:124 msgid "Chat ended" msgstr "Chat ended" #: src/components/dms/ChatInvite/Unavailable.tsx:25 -#: src/components/intents/GroupChatJoinDialog.tsx:269 -#: src/screens/Messages/JoinRequest.tsx:97 +#: src/components/intents/GroupChatJoinDialog.tsx:274 +#: src/screens/Messages/JoinRequest.tsx:102 msgid "Chat invite link no longer available" msgstr "Chat invite link no longer available" -#: src/components/dms/getSystemMessageInfo.ts:104 +#: src/components/dms/getSystemMessageInfo.ts:113 msgid "Chat locked" msgstr "Chat locked" @@ -2719,7 +2719,7 @@ msgctxt "toast" msgid "Chat muted" msgstr "" -#: src/components/moderation/BlockDialog.tsx:286 +#: src/components/moderation/BlockDialog.tsx:285 #: src/components/moderation/BlockDialog.tsx:314 msgid "Chat not found." msgstr "Chat not found." @@ -2730,12 +2730,12 @@ msgstr "Chat not found." msgid "Chat options" msgstr "Chat options" -#: src/components/moderation/BlockDialog.tsx:290 +#: src/components/moderation/BlockDialog.tsx:288 msgid "Chat owners cannot leave a group chat." msgstr "Chat owners cannot leave a group chat." -#: src/components/dms/dialogs/NewChatDialog.tsx:73 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:88 +#: src/components/dms/dialogs/NewChatDialog.tsx:67 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:82 msgid "Chat recipient is not followed by the sender." msgstr "Chat recipient is not followed by the sender." @@ -2743,8 +2743,8 @@ msgstr "Chat recipient is not followed by the sender." msgid "Chat request inbox" msgstr "" -#: src/screens/Messages/Inbox.tsx:62 -#: src/screens/Messages/Inbox.tsx:97 +#: src/screens/Messages/Inbox.tsx:59 +#: src/screens/Messages/Inbox.tsx:96 msgid "Chat requests" msgstr "" @@ -2762,16 +2762,16 @@ msgstr "" msgid "Chat Settings" msgstr "" -#: src/components/dms/getSystemMessageInfo.ts:115 +#: src/components/dms/getSystemMessageInfo.ts:133 msgid "Chat title changed" msgstr "Chat title changed" #. placeholder {0}: data.newName -#: src/components/dms/getSystemMessageInfo.ts:114 +#: src/components/dms/getSystemMessageInfo.ts:132 msgid "Chat title changed to {0}" msgstr "Chat title changed to {0}" -#: src/components/dms/getSystemMessageInfo.ts:106 +#: src/components/dms/getSystemMessageInfo.ts:117 msgid "Chat unlocked" msgstr "Chat unlocked" @@ -2786,7 +2786,7 @@ msgstr "" msgid "Chats" msgstr "" -#: src/screens/Settings/components/DeleteAccountDialog.tsx:233 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:239 msgid "Check <0>{currentEmail} for an email with the confirmation code to enter below:" msgstr "" @@ -2794,20 +2794,20 @@ msgstr "" msgid "Check back later!" msgstr "Check back later!" -#: src/screens/SignupQueued.tsx:79 -#: src/screens/SignupQueued.tsx:83 +#: src/screens/SignupQueued.tsx:86 +#: src/screens/SignupQueued.tsx:90 msgid "Check my status" msgstr "" -#: src/screens/Login/LoginForm.tsx:463 +#: src/screens/Login/LoginForm.tsx:464 msgid "Check your email for a sign in code and enter it here." msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:161 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:163 msgid "Child safety" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:165 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:167 msgid "Child Sexual Abuse Material (CSAM)" msgstr "" @@ -2834,7 +2834,7 @@ msgstr "Choose at least one. We'll use this to customize your experience. You ca msgid "Choose domain verification method" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:205 +#: src/screens/StarterPack/Wizard/index.tsx:201 msgid "Choose Feeds" msgstr "" @@ -2846,7 +2846,7 @@ msgstr "" msgid "Choose languages" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:201 +#: src/screens/StarterPack/Wizard/index.tsx:197 msgid "Choose People" msgstr "" @@ -2859,7 +2859,7 @@ msgstr "" msgid "Choose this color as your avatar" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:243 +#: src/screens/Messages/components/InviteLinkDialog.tsx:240 msgid "Choose who can join this group chat and how." msgstr "Choose who can join this group chat and how." @@ -2880,7 +2880,7 @@ msgstr "" msgid "Choose your password" msgstr "" -#: src/screens/Signup/index.tsx:196 +#: src/screens/Signup/index.tsx:193 msgid "Choose your username" msgstr "" @@ -2890,11 +2890,11 @@ msgstr "" msgid "Clear" msgstr "Clear" -#: src/screens/Settings/Settings.tsx:517 +#: src/screens/Settings/Settings.tsx:519 msgid "Clear all storage data" msgstr "" -#: src/screens/Settings/Settings.tsx:519 +#: src/screens/Settings/Settings.tsx:521 msgid "Clear all storage data (restart after this)" msgstr "" @@ -2940,8 +2940,8 @@ msgstr "Click here to delete your account" msgid "Click here to log out" msgstr "" -#: src/screens/Settings/components/DeleteAccountDialog.tsx:263 -#: src/screens/Settings/components/DeleteAccountDialog.tsx:272 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:269 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:278 msgid "Click here to resend the email" msgstr "Click here to resend the email" @@ -2967,7 +2967,7 @@ msgstr "Click here to view the invite link for this group chat" msgid "Click to open tag menu for {0}" msgstr "" -#: src/components/dms/MessageItem.tsx:643 +#: src/components/dms/MessageItem.tsx:658 msgid "Click to retry failed message" msgstr "" @@ -2993,7 +2993,7 @@ msgstr "" #: src/components/dialogs/nuxs/LiveNowBetaDialog.tsx:192 #: src/components/dialogs/nuxs/LiveNowBetaDialog.tsx:200 #: src/components/dialogs/SearchablePeopleList.tsx:340 -#: src/components/dialogs/StarterPackDialog.tsx:187 +#: src/components/dialogs/StarterPackDialog.tsx:184 #: src/components/dms/AddMembersFlow.tsx:384 #: src/components/dms/AfterReportConversationDialog.tsx:91 #: src/components/dms/AfterReportConversationDialog.tsx:96 @@ -3005,24 +3005,24 @@ msgstr "" #: src/components/dms/AfterReportDialog.tsx:217 #: src/components/dms/EmojiPopup.android.tsx:59 #: src/components/dms/InitiateChatFlow.tsx:670 -#: src/components/intents/GroupChatJoinDialog.tsx:246 -#: src/components/intents/GroupChatJoinDialog.tsx:281 -#: src/components/NewskieDialog.tsx:169 -#: src/components/NewskieDialog.tsx:175 +#: src/components/intents/GroupChatJoinDialog.tsx:251 +#: src/components/intents/GroupChatJoinDialog.tsx:286 +#: src/components/NewskieDialog.tsx:170 +#: src/components/NewskieDialog.tsx:176 #: src/components/ProgressGuide/FollowDialog.tsx:462 -#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:122 -#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:128 +#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:119 +#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:125 #: src/components/verification/VerificationsDialog.tsx:146 #: src/components/verification/VerifierDialog.tsx:147 +#: src/components/WhoCanReply.tsx:229 #: src/components/WhoCanReply.tsx:236 -#: src/components/WhoCanReply.tsx:243 #: src/features/gifPicker/components/GifPickerErrorBoundary.tsx:45 -#: src/features/liveNow/components/EditLiveDialog.tsx:210 -#: src/features/liveNow/components/EditLiveDialog.tsx:216 -#: src/screens/Messages/components/InviteLinkDialog.tsx:524 -#: src/screens/Messages/components/InviteLinkDialog.tsx:529 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:288 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:293 +#: src/features/liveNow/components/EditLiveDialog.tsx:212 +#: src/features/liveNow/components/EditLiveDialog.tsx:218 +#: src/screens/Messages/components/InviteLinkDialog.tsx:521 +#: src/screens/Messages/components/InviteLinkDialog.tsx:526 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:292 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:297 #: src/view/com/feeds/MissingFeed.tsx:211 #: src/view/com/feeds/MissingFeed.tsx:218 msgid "Close" @@ -3042,13 +3042,13 @@ msgid "Close banner" msgstr "Close banner" #. Accessibility label for the button that dismisses the GIF picker error dialog. -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:223 -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:229 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:232 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:238 #: src/components/dialogs/LanguageSelectDialog.tsx:221 #: src/components/dialogs/LanguageSelectDialog.tsx:322 #: src/components/dialogs/LanguageSelectDialog.tsx:354 #: src/components/dialogs/NotificationSettingsDialog.tsx:102 -#: src/components/moderation/BlockDialog.tsx:201 +#: src/components/moderation/BlockDialog.tsx:198 #: src/components/verification/VerificationsDialog.tsx:138 #: src/components/verification/VerifierDialog.tsx:140 #: src/features/gifPicker/components/GifPickerErrorBoundary.tsx:36 @@ -3083,10 +3083,10 @@ msgstr "Close scanner" msgid "Close the incoming requests banner" msgstr "Close the incoming requests banner" -#: src/components/intents/GroupChatJoinDialog.tsx:239 -#: src/components/intents/GroupChatJoinDialog.tsx:240 -#: src/components/intents/GroupChatJoinDialog.tsx:276 -#: src/components/intents/GroupChatJoinDialog.tsx:277 +#: src/components/intents/GroupChatJoinDialog.tsx:244 +#: src/components/intents/GroupChatJoinDialog.tsx:245 +#: src/components/intents/GroupChatJoinDialog.tsx:281 +#: src/components/intents/GroupChatJoinDialog.tsx:282 #: src/components/Menu/index.tsx:367 msgid "Close this dialog" msgstr "" @@ -3099,15 +3099,15 @@ msgstr "" msgid "Closes password update alert" msgstr "" -#: src/view/com/composer/Composer.tsx:1857 +#: src/view/com/composer/Composer.tsx:1881 msgid "Closes post composer and discards post draft" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:601 +#: src/view/com/notifications/NotificationFeedItem.tsx:593 msgid "Collapse list of users" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:983 +#: src/view/com/notifications/NotificationFeedItem.tsx:974 msgid "Collapses list of users for a given notification" msgstr "" @@ -3115,12 +3115,12 @@ msgstr "" msgid "Color" msgstr "Color" -#: src/components/dialogs/Embed.tsx:150 +#: src/components/dialogs/Embed.tsx:151 #: src/screens/Settings/AppearanceSettings.tsx:79 msgid "Color mode" msgstr "" -#: src/components/dialogs/Embed.tsx:147 +#: src/components/dialogs/Embed.tsx:148 msgid "Color theme" msgstr "" @@ -3142,7 +3142,7 @@ msgstr "" msgid "Complete onboarding and start using your account" msgstr "" -#: src/screens/Signup/index.tsx:198 +#: src/screens/Signup/index.tsx:195 msgid "Complete the challenge" msgstr "" @@ -3153,7 +3153,7 @@ msgid "Compose new post" msgstr "" #. placeholder {0}: MAX_GRAPHEME_LENGTH || 0 -#: src/view/com/composer/Composer.tsx:1733 +#: src/view/com/composer/Composer.tsx:1757 msgid "Compose posts up to {0, plural, other {# characters}} in length" msgstr "" @@ -3161,11 +3161,11 @@ msgstr "" msgid "Compose reply" msgstr "" -#: src/view/com/composer/Composer.tsx:2687 +#: src/view/com/composer/Composer.tsx:2711 msgid "Compressing GIF..." msgstr "" -#: src/view/com/composer/Composer.tsx:2689 +#: src/view/com/composer/Composer.tsx:2713 msgid "Compressing video..." msgstr "" @@ -3179,31 +3179,31 @@ msgstr "" #: src/components/Prompt.tsx:211 #: src/components/Prompt.tsx:214 -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:186 -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:189 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:194 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:197 msgid "Confirm" msgstr "" #: src/components/dialogs/EmailDialog/components/TokenField.tsx:37 -#: src/screens/Login/LoginForm.tsx:442 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:187 +#: src/screens/Login/LoginForm.tsx:443 #: src/screens/Settings/components/ChangePasswordDialog.tsx:191 -#: src/screens/Settings/components/DeleteAccountDialog.tsx:244 -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:145 -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:151 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:195 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:250 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:153 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:159 msgid "Confirmation code" msgstr "" -#: src/screens/Login/ForgotPasswordForm.tsx:132 +#: src/screens/Login/ForgotPasswordForm.tsx:136 msgid "Connecting to service..." msgstr "" -#: src/screens/Login/LoginForm.tsx:542 +#: src/screens/Login/LoginForm.tsx:543 msgid "Connecting to service…" msgstr "Connecting to service…" -#: src/screens/Login/ForgotPasswordForm.tsx:138 -#: src/screens/Login/LoginForm.tsx:548 +#: src/screens/Login/ForgotPasswordForm.tsx:142 +#: src/screens/Login/LoginForm.tsx:549 msgid "Connecting…" msgstr "Connecting…" @@ -3222,26 +3222,26 @@ msgstr "" msgid "Contact our support team" msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:156 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:160 +#: src/screens/Signup/index.tsx:240 #: src/screens/Signup/index.tsx:243 -#: src/screens/Signup/index.tsx:246 msgid "Contact support" msgstr "" #: src/screens/Onboarding/StepFindContactsIntro/index.tsx:65 -#: src/screens/Settings/FindContactsSettings.tsx:164 +#: src/screens/Settings/FindContactsSettings.tsx:162 msgid "Contact sync is not available on this device, as the app is unable to access your contacts." msgstr "" -#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:98 +#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:100 msgid "Contact us" msgstr "" -#: src/screens/Settings/FindContactsSettings.tsx:526 +#: src/screens/Settings/FindContactsSettings.tsx:530 msgid "Contacts imported" msgstr "" -#: src/screens/Settings/FindContactsSettings.tsx:499 +#: src/screens/Settings/FindContactsSettings.tsx:503 msgid "Contacts removed" msgstr "" @@ -3249,8 +3249,8 @@ msgstr "" msgid "Content & Media" msgstr "" -#: src/screens/Settings/Settings.tsx:207 -#: src/screens/Settings/Settings.tsx:210 +#: src/screens/Settings/Settings.tsx:209 +#: src/screens/Settings/Settings.tsx:212 msgid "Content and media" msgstr "" @@ -3271,18 +3271,18 @@ msgid "Content languages" msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:102 -#: src/lib/moderation/useModerationCauseDescription.ts:83 +#: src/lib/moderation/useModerationCauseDescription.ts:80 msgid "Content Not Available" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:192 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:194 msgid "Content promoting or depicting self-harm" msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:70 #: src/components/moderation/ScreenHider.tsx:98 #: src/lib/moderation/useGlobalLabelStrings.ts:22 -#: src/lib/moderation/useModerationCauseDescription.ts:46 +#: src/lib/moderation/useModerationCauseDescription.ts:43 msgid "Content Warning" msgstr "" @@ -3300,7 +3300,7 @@ msgstr "" #: src/screens/Login/components/ConfirmHostingProviderDialog.tsx:152 #: src/screens/Onboarding/StepInterests/index.tsx:87 #: src/screens/Onboarding/StepProfile/index.tsx:301 -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:305 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:309 #: src/screens/Onboarding/StepSuggestedStarterpacks/index.tsx:117 msgid "Continue" msgstr "" @@ -3329,17 +3329,17 @@ msgstr "Continue to group name" #: src/screens/Onboarding/StepInterests/index.tsx:80 #: src/screens/Onboarding/StepProfile/index.tsx:298 -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:302 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:306 #: src/screens/Onboarding/StepSuggestedStarterpacks/index.tsx:114 #: src/screens/Signup/BackNextButtons.tsx:61 msgid "Continue to next step" msgstr "" -#: src/screens/Messages/Conversation.tsx:63 +#: src/screens/Messages/Conversation.tsx:65 msgid "Conversation" msgstr "" -#: src/screens/Messages/components/ChatListItem.tsx:321 +#: src/screens/Messages/components/ChatListItem.tsx:326 msgid "Conversation deleted" msgstr "" @@ -3355,9 +3355,9 @@ msgctxt "toast" msgid "Conversation left" msgstr "Conversation left" -#: src/components/dms/LeaveConvoPrompt.tsx:40 -#: src/screens/Messages/JoinRequests.tsx:196 -#: src/screens/Messages/JoinRequests.tsx:229 +#: src/components/dms/LeaveConvoPrompt.tsx:43 +#: src/screens/Messages/JoinRequests.tsx:193 +#: src/screens/Messages/JoinRequests.tsx:225 msgid "Conversation not found." msgstr "Conversation not found." @@ -3369,17 +3369,17 @@ msgstr "" msgid "Copied link to clipboard" msgstr "Copied link to clipboard" -#: src/components/dms/ChatInvite/Root.tsx:99 -#: src/components/dms/MessageContextMenu.tsx:97 +#: src/components/dms/ChatInvite/Root.tsx:100 +#: src/components/dms/MessageContextMenu.tsx:95 #: src/components/PostControls/DiscoverDebug.tsx:36 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:273 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:269 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:75 #: src/lib/sharing.ts:24 #: src/lib/sharing.ts:42 msgid "Copied to clipboard" msgstr "" -#: src/components/dialogs/Embed.tsx:197 +#: src/components/dialogs/Embed.tsx:198 #: src/screens/Messages/components/CopyTextButton.tsx:71 #: src/screens/Settings/components/CopyButton.tsx:66 msgid "Copied!" @@ -3393,7 +3393,7 @@ msgstr "" msgid "Copies the canonical profile URL to the clipboard" msgstr "Copies the canonical profile URL to the clipboard" -#: src/components/StarterPack/QrCodeDialog.tsx:198 +#: src/components/StarterPack/QrCodeDialog.tsx:193 #: src/features/inviteFriends/InviteFriendsDialogInner.tsx:266 msgid "Copy" msgstr "" @@ -3412,8 +3412,8 @@ msgstr "" msgid "Copy author DID" msgstr "" -#: src/components/dialogs/Embed.tsx:186 -#: src/components/dialogs/Embed.tsx:202 +#: src/components/dialogs/Embed.tsx:187 +#: src/components/dialogs/Embed.tsx:203 msgid "Copy code" msgstr "" @@ -3431,9 +3431,9 @@ msgstr "" msgid "Copy invite link" msgstr "Copy invite link" -#: src/components/dms/ChatInvite/Root.tsx:91 +#: src/components/dms/ChatInvite/Root.tsx:92 #: src/components/StarterPack/ShareDialog.tsx:115 -#: src/screens/StarterPack/StarterPackScreen.tsx:644 +#: src/screens/StarterPack/StarterPackScreen.tsx:637 msgid "Copy link" msgstr "" @@ -3441,8 +3441,8 @@ msgstr "" msgid "Copy Link" msgstr "" -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:160 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:164 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:161 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:165 msgid "Copy link to list" msgstr "" @@ -3458,12 +3458,12 @@ msgstr "" msgid "Copy link to profile" msgstr "" -#: src/screens/StarterPack/StarterPackScreen.tsx:637 +#: src/screens/StarterPack/StarterPackScreen.tsx:630 msgid "Copy link to starter pack" msgstr "" -#: src/components/dms/MessageContextMenu.tsx:198 -#: src/components/dms/MessageContextMenu.tsx:202 +#: src/components/dms/MessageContextMenu.tsx:196 +#: src/components/dms/MessageContextMenu.tsx:200 msgid "Copy message text" msgstr "" @@ -3472,12 +3472,12 @@ msgstr "" msgid "Copy post at:// URI" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:578 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:580 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:574 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:576 msgid "Copy post text" msgstr "" -#: src/components/StarterPack/QrCodeDialog.tsx:184 +#: src/components/StarterPack/QrCodeDialog.tsx:179 msgid "Copy QR code" msgstr "" @@ -3512,7 +3512,7 @@ msgstr "Could not copy – please try again" msgid "Could not download – please try again" msgstr "Could not download – please try again" -#: src/screens/Messages/components/MessageInputEmbed.tsx:200 +#: src/screens/Messages/components/MessageInputEmbed.tsx:191 msgid "Could not fetch post" msgstr "Could not fetch post" @@ -3521,23 +3521,23 @@ msgid "Could not find profile" msgstr "" #: src/screens/Settings/FindContactsSettings.tsx:233 -#: src/screens/Settings/FindContactsSettings.tsx:424 +#: src/screens/Settings/FindContactsSettings.tsx:428 msgid "Could not follow all matches - please check your network connection." msgstr "" #. placeholder {0}: cleanError(err) #: src/screens/Settings/FindContactsSettings.tsx:239 -#: src/screens/Settings/FindContactsSettings.tsx:430 +#: src/screens/Settings/FindContactsSettings.tsx:434 msgid "Could not follow all matches. {0}" msgstr "" #: src/components/dms/AfterReportConversationDialog.tsx:158 #: src/components/dms/AfterReportDialog.tsx:139 -#: src/components/dms/LeaveConvoPrompt.tsx:36 +#: src/components/dms/LeaveConvoPrompt.tsx:37 msgid "Could not leave chat" msgstr "" -#: src/components/moderation/BlockDialog.tsx:282 +#: src/components/moderation/BlockDialog.tsx:279 msgid "Could not leave chat." msgstr "Could not leave chat." @@ -3546,8 +3546,8 @@ msgid "Could not load feed" msgstr "" #: src/screens/ProfileList/components/ErrorScreen.tsx:27 -#: src/screens/ProfileList/index.tsx:80 -#: src/screens/ProfileList/index.tsx:102 +#: src/screens/ProfileList/index.tsx:77 +#: src/screens/ProfileList/index.tsx:99 msgid "Could not load list" msgstr "" @@ -3564,7 +3564,7 @@ msgid "Could not remove member." msgstr "Could not remove member." #. placeholder {0}: cleanError(error) -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:295 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:292 msgid "Could not save changes: {0}" msgstr "" @@ -3572,16 +3572,16 @@ msgstr "" msgid "Could not share – please try again" msgstr "Could not share – please try again" -#: src/state/queries/notifications/settings.ts:124 +#: src/state/queries/notifications/settings.ts:123 msgid "Could not update notification settings" msgstr "" #. placeholder {0}: cleanError(err) -#: src/components/contacts/screens/GetContacts.tsx:171 +#: src/components/contacts/screens/GetContacts.tsx:173 msgid "Could not upload contacts. {0}" msgstr "" -#: src/components/contacts/screens/GetContacts.tsx:165 +#: src/components/contacts/screens/GetContacts.tsx:166 msgid "Could not upload contacts. You need to re-verify your phone number to proceed" msgstr "" @@ -3601,8 +3601,8 @@ msgstr "cows pigs" #. Text on button to create a new starter pack #. Text on button to create a new starter pack -#: src/components/dialogs/StarterPackDialog.tsx:113 -#: src/components/dialogs/StarterPackDialog.tsx:210 +#: src/components/dialogs/StarterPackDialog.tsx:110 +#: src/components/dialogs/StarterPackDialog.tsx:207 #: src/components/dms/InitiateChatFlow.tsx:607 #: src/components/StarterPack/ProfileStarterPacks.tsx:332 msgid "Create" @@ -3613,11 +3613,11 @@ msgstr "" msgid "Create a list" msgstr "" -#: src/screens/StarterPack/StarterPackScreen.tsx:620 +#: src/screens/StarterPack/StarterPackScreen.tsx:613 msgid "Create a list from this starter pack" msgstr "" -#: src/components/StarterPack/QrCodeDialog.tsx:169 +#: src/components/StarterPack/QrCodeDialog.tsx:164 msgid "Create a QR code for a starter pack" msgstr "" @@ -3627,8 +3627,8 @@ msgstr "" msgid "Create a starter pack" msgstr "" -#: src/view/screens/Profile.tsx:576 -#: src/view/screens/Profile.tsx:577 +#: src/view/screens/Profile.tsx:573 +#: src/view/screens/Profile.tsx:574 msgid "Create a Starter Pack" msgstr "" @@ -3638,7 +3638,7 @@ msgstr "" #: src/components/WelcomeModal.tsx:153 #: src/components/WelcomeModal.tsx:161 -#: src/screens/Messages/JoinRequest.tsx:310 +#: src/screens/Messages/JoinRequest.tsx:318 #: src/screens/Signup/index.tsx:139 #: src/view/com/auth/SplashScreen.tsx:108 #: src/view/com/auth/SplashScreen.web.tsx:118 @@ -3653,13 +3653,13 @@ msgstr "" #: src/components/dialogs/Signin.tsx:87 #: src/components/dialogs/Signin.tsx:89 -#: src/screens/Hashtag.tsx:236 +#: src/screens/Hashtag.tsx:238 #: src/screens/Search/SearchResults.tsx:418 msgid "Create an account" msgstr "" -#: src/screens/StarterPack/StarterPackLandingScreen.tsx:310 -#: src/screens/StarterPack/StarterPackLandingScreen.tsx:317 +#: src/screens/StarterPack/StarterPackLandingScreen.tsx:302 +#: src/screens/StarterPack/StarterPackLandingScreen.tsx:309 msgid "Create an account without using this starter pack" msgstr "" @@ -3675,55 +3675,55 @@ msgstr "" msgid "Create group chat" msgstr "Create group chat" -#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:180 -#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:188 +#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:179 +#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:187 msgid "Create list" msgstr "" -#: src/screens/StarterPack/StarterPackScreen.tsx:626 +#: src/screens/StarterPack/StarterPackScreen.tsx:619 msgid "Create list from members" msgstr "" -#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:148 -#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:152 +#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:147 +#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:151 msgid "Create list from starter pack" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:367 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:375 msgid "Create moderation list" msgstr "" -#: src/screens/Messages/JoinRequest.tsx:304 +#: src/screens/Messages/JoinRequest.tsx:312 #: src/view/com/auth/SplashScreen.tsx:89 #: src/view/com/auth/SplashScreen.web.tsx:110 msgid "Create new account" msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:554 +#: src/screens/Messages/ConversationSettings/index.tsx:549 msgid "Create or modify an invite link for this group chat" msgstr "Create or modify an invite link for this group chat" #. Accessibility label for button to create a moderation report for the selected option #. placeholder {0}: option.title -#: src/components/moderation/ReportDialog/index.tsx:834 -#: src/components/moderation/ReportDialog/index.tsx:879 +#: src/components/moderation/ReportDialog/index.tsx:835 +#: src/components/moderation/ReportDialog/index.tsx:880 msgid "Create report for {0}" msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:108 -#: src/components/dialogs/StarterPackDialog.tsx:205 +#: src/components/dialogs/StarterPackDialog.tsx:105 +#: src/components/dialogs/StarterPackDialog.tsx:202 msgid "Create starter pack" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:366 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:374 msgid "Create user list" msgstr "" #. placeholder {0}: i18n.date(appPassword.createdAt, { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', }) #. placeholder {0}: i18n.date(createdAt, { dateStyle: 'long', timeStyle: 'short', }) #. placeholder {0}: i18n.date(createdAt, { month: 'long', day: 'numeric', year: 'numeric', }) -#: src/screens/Messages/components/InviteLinkDialog.tsx:355 -#: src/screens/Messages/ConversationSettings/index.tsx:509 +#: src/screens/Messages/components/InviteLinkDialog.tsx:352 +#: src/screens/Messages/ConversationSettings/index.tsx:504 #: src/screens/Settings/AppPasswords.tsx:174 msgid "Created {0}" msgstr "" @@ -3740,7 +3740,7 @@ msgstr "" msgid "Current beta features" msgstr "Current beta features" -#: src/components/moderation/BlockDialog.tsx:375 +#: src/components/moderation/BlockDialog.tsx:376 msgid "Current chat" msgstr "Current chat" @@ -3749,7 +3749,7 @@ msgstr "Current chat" msgid "Custom" msgstr "" -#: src/components/dialogs/Embed.tsx:140 +#: src/components/dialogs/Embed.tsx:141 msgid "Customization options" msgstr "" @@ -3757,16 +3757,16 @@ msgstr "" msgid "Customizes your Bluesky experience" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:200 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:202 msgid "Dangerous challenges or activities" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:204 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:206 msgid "Dangerous substances or drug abuse" msgstr "" -#: src/components/dialogs/Embed.tsx:164 -#: src/components/dialogs/Embed.tsx:166 +#: src/components/dialogs/Embed.tsx:165 +#: src/components/dialogs/Embed.tsx:167 #: src/screens/Settings/AppearanceSettings.tsx:91 #: src/screens/Settings/AppearanceSettings.tsx:112 msgid "Dark" @@ -3795,11 +3795,11 @@ msgstr "Day" #: src/screens/Settings/AccountSettings.tsx:177 #: src/screens/Settings/AccountSettings.tsx:182 -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:73 +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:74 msgid "Deactivate account" msgstr "" -#: src/screens/Settings/Settings.tsx:482 +#: src/screens/Settings/Settings.tsx:484 msgid "Debug Moderation" msgstr "" @@ -3807,7 +3807,7 @@ msgstr "" msgid "Decline this language suggestion" msgstr "Decline this language suggestion" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:84 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:85 msgid "Deepfake adult content" msgstr "" @@ -3820,12 +3820,12 @@ msgid "Default icons" msgstr "" #: src/components/dms/MessageOverlays.tsx:184 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:828 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:275 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:824 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:276 #: src/screens/Settings/AppPasswords.tsx:213 -#: src/screens/StarterPack/StarterPackScreen.tsx:615 -#: src/screens/StarterPack/StarterPackScreen.tsx:715 -#: src/screens/StarterPack/StarterPackScreen.tsx:794 +#: src/screens/StarterPack/StarterPackScreen.tsx:608 +#: src/screens/StarterPack/StarterPackScreen.tsx:708 +#: src/screens/StarterPack/StarterPackScreen.tsx:787 msgid "Delete" msgstr "" @@ -3834,8 +3834,8 @@ msgstr "" msgid "Delete account" msgstr "" -#: src/screens/Settings/components/DeleteAccountDialog.tsx:183 -#: src/screens/Settings/components/DeleteAccountDialog.tsx:230 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:189 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:236 msgid "Delete account “{currentHandle}”" msgstr "" @@ -3852,12 +3852,12 @@ msgstr "" msgid "Delete chat" msgstr "" -#: src/screens/Settings/Settings.tsx:487 #: src/screens/Settings/Settings.tsx:489 +#: src/screens/Settings/Settings.tsx:491 msgid "Delete chat declaration record" msgstr "" -#: src/screens/Settings/FindContactsSettings.tsx:567 +#: src/screens/Settings/FindContactsSettings.tsx:571 msgid "Delete contacts" msgstr "" @@ -3869,12 +3869,12 @@ msgstr "" msgid "Delete conversation" msgstr "" -#: src/components/dms/MessageContextMenu.tsx:212 +#: src/components/dms/MessageContextMenu.tsx:210 msgid "Delete for me" msgstr "" -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:199 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:202 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:200 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:203 msgid "Delete list" msgstr "" @@ -3882,38 +3882,38 @@ msgstr "" msgid "Delete message" msgstr "" -#: src/components/dms/MessageContextMenu.tsx:209 +#: src/components/dms/MessageContextMenu.tsx:207 msgid "Delete message for me" msgstr "" -#: src/screens/Settings/components/DeleteAccountDialog.tsx:309 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:315 msgid "Delete my account" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:812 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:814 -#: src/view/com/composer/Composer.tsx:1745 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:808 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:810 +#: src/view/com/composer/Composer.tsx:1769 msgid "Delete post" msgstr "" -#: src/screens/StarterPack/StarterPackScreen.tsx:609 -#: src/screens/StarterPack/StarterPackScreen.tsx:785 +#: src/screens/StarterPack/StarterPackScreen.tsx:602 +#: src/screens/StarterPack/StarterPackScreen.tsx:778 msgid "Delete starter pack" msgstr "" -#: src/screens/StarterPack/StarterPackScreen.tsx:681 +#: src/screens/StarterPack/StarterPackScreen.tsx:674 msgid "Delete starter pack?" msgstr "" -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:270 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:271 msgid "Delete this list?" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:825 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:821 msgid "Delete this post?" msgstr "" -#: src/components/Post/Embed/index.tsx:206 +#: src/components/Post/Embed/index.tsx:207 msgid "Deleted" msgstr "" @@ -3934,7 +3934,7 @@ msgstr "" msgid "Deleted list" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:453 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:461 #: src/components/SendErrorReportDialog.tsx:78 #: src/screens/Profile/Header/EditProfileDialog.tsx:360 #: src/screens/Profile/Header/EditProfileDialog.tsx:367 @@ -3950,12 +3950,12 @@ msgstr "Description (1000 characters max)" msgid "Descriptive alt text" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:716 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:726 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:712 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:722 msgid "Detach quote" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:861 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:857 msgid "Detach quote post?" msgstr "" @@ -3969,8 +3969,8 @@ msgctxt "toast" msgid "Developer mode enabled" msgstr "" -#: src/screens/Settings/Settings.tsx:292 -#: src/screens/Settings/Settings.tsx:295 +#: src/screens/Settings/Settings.tsx:294 +#: src/screens/Settings/Settings.tsx:297 msgid "Developer options" msgstr "" @@ -3978,7 +3978,7 @@ msgstr "" msgid "Device failed to translate :(" msgstr "Device failed to translate :(" -#: src/components/WhoCanReply.tsx:222 +#: src/components/WhoCanReply.tsx:215 msgid "Dialog: adjust who can interact with this post" msgstr "" @@ -3990,8 +3990,8 @@ msgstr "Dialog: Set search filters" msgid "Dim" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:385 -#: src/screens/Messages/components/InviteLinkDialog.tsx:390 +#: src/screens/Messages/components/InviteLinkDialog.tsx:382 +#: src/screens/Messages/components/InviteLinkDialog.tsx:387 msgid "Disable" msgstr "Disable" @@ -4008,7 +4008,7 @@ msgstr "Disable captions" msgid "Disable email 2FA" msgstr "" -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:90 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:98 msgid "Disable Email 2FA" msgstr "" @@ -4017,20 +4017,20 @@ msgstr "" msgid "Disable haptic feedback" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:488 -#: src/screens/Messages/components/InviteLinkDialog.tsx:494 +#: src/screens/Messages/components/InviteLinkDialog.tsx:485 +#: src/screens/Messages/components/InviteLinkDialog.tsx:491 msgid "Disable link" msgstr "Disable link" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:628 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:625 msgid "Disable quote posts of this post" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:465 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:462 msgid "Disable replies entirely" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:475 +#: src/screens/Messages/components/InviteLinkDialog.tsx:472 msgid "Disable this invite link?" msgstr "Disable this invite link?" @@ -4041,43 +4041,43 @@ msgstr "Disable this invite link?" msgid "Disabled" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:101 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:102 #: src/screens/Profile/Header/EditProfileDialog.tsx:79 -#: src/view/com/composer/Composer.tsx:1525 -#: src/view/com/composer/Composer.tsx:1569 -#: src/view/com/composer/Composer.tsx:1778 +#: src/view/com/composer/Composer.tsx:1549 +#: src/view/com/composer/Composer.tsx:1593 +#: src/view/com/composer/Composer.tsx:1802 #: src/view/com/composer/drafts/DraftItem.tsx:242 #: src/view/com/composer/drafts/DraftsButton.tsx:131 msgid "Discard" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:98 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:99 #: src/screens/Profile/Header/EditProfileDialog.tsx:76 msgid "Discard changes?" msgstr "" -#: src/view/com/composer/Composer.tsx:1523 +#: src/view/com/composer/Composer.tsx:1547 #: src/view/com/composer/drafts/DraftItem.tsx:239 #: src/view/com/composer/drafts/DraftsButton.tsx:98 msgid "Discard draft?" msgstr "" -#: src/view/com/composer/Composer.tsx:1540 -#: src/view/com/composer/Composer.tsx:1770 +#: src/view/com/composer/Composer.tsx:1564 +#: src/view/com/composer/Composer.tsx:1794 msgid "Discard post?" msgstr "" -#: src/screens/Profile/components/GermButton.tsx:262 -#: src/screens/Profile/components/GermButton.tsx:269 +#: src/screens/Profile/components/GermButton.tsx:263 +#: src/screens/Profile/components/GermButton.tsx:270 msgid "Disconnect Germ DM" msgstr "" -#: src/screens/Settings/components/PwiOptOut.tsx:88 -#: src/screens/Settings/components/PwiOptOut.tsx:92 +#: src/screens/Settings/components/PwiOptOut.tsx:89 +#: src/screens/Settings/components/PwiOptOut.tsx:93 msgid "Discourage apps from showing my account to logged-out users" msgstr "" -#: src/screens/Search/Explore.tsx:448 +#: src/screens/Search/Explore.tsx:444 msgid "Discover feeds" msgstr "Discover feeds" @@ -4101,7 +4101,7 @@ msgstr "" msgid "Dismiss banner" msgstr "" -#: src/view/com/composer/Composer.tsx:2629 +#: src/view/com/composer/Composer.tsx:2653 msgid "Dismiss error" msgstr "" @@ -4118,7 +4118,7 @@ msgstr "" msgid "Dismiss live event banner" msgstr "" -#: src/components/interstitials/TrendingVideos.tsx:89 +#: src/components/interstitials/TrendingVideos.tsx:91 msgid "Dismiss this section" msgstr "" @@ -4149,7 +4149,7 @@ msgstr "" msgid "DNS Panel" msgstr "" -#: src/components/dialogs/MutedWords.tsx:311 +#: src/components/dialogs/MutedWords.tsx:313 msgid "Do not apply this mute word to users you follow" msgstr "" @@ -4165,7 +4165,7 @@ msgstr "" msgid "Don't have a code or need a new one? <0>Click here." msgstr "" -#: src/screens/Settings/components/DeleteAccountDialog.tsx:269 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:275 msgid "Don’t see a code? <0>Click here to resend." msgstr "" @@ -4184,8 +4184,8 @@ msgstr "" #: src/components/contacts/components/InviteInfo.tsx:73 #: src/components/contacts/components/InviteInfo.tsx:79 -#: src/components/contacts/screens/ViewMatches.tsx:395 -#: src/components/contacts/screens/ViewMatches.tsx:412 +#: src/components/contacts/screens/ViewMatches.tsx:403 +#: src/components/contacts/screens/ViewMatches.tsx:420 #: src/components/dialogs/BirthDateSettings.tsx:193 #: src/components/dialogs/BirthDateSettings.tsx:200 #: src/components/dialogs/EmailDialog/screens/Verify.tsx:195 @@ -4214,7 +4214,7 @@ msgstr "" msgid "Done" msgstr "" -#: src/components/dms/MessageItem.tsx:541 +#: src/components/dms/MessageItem.tsx:556 msgid "Double tap or long press the message to add a reaction" msgstr "" @@ -4222,7 +4222,7 @@ msgstr "" msgid "Double tap to close the dialog" msgstr "" -#: src/screens/VideoFeed/index.tsx:1200 +#: src/screens/VideoFeed/index.tsx:1196 msgid "Double tap to like" msgstr "" @@ -4230,15 +4230,15 @@ msgstr "" msgid "Download" msgstr "Download" -#: src/screens/StarterPack/StarterPackLandingScreen.tsx:329 +#: src/screens/StarterPack/StarterPackLandingScreen.tsx:321 msgid "Download Bluesky" msgstr "" -#: src/screens/Settings/components/ExportCarDialog.tsx:149 +#: src/screens/Settings/components/ExportCarDialog.tsx:155 msgid "Download chat data" msgstr "" -#: src/screens/Settings/components/ExportCarDialog.tsx:154 +#: src/screens/Settings/components/ExportCarDialog.tsx:160 msgctxt "button" msgid "Download chat data" msgstr "Download chat data" @@ -4252,16 +4252,16 @@ msgstr "Download image" msgid "Download invite QR code" msgstr "Download invite QR code" -#: src/screens/Settings/components/ExportCarDialog.tsx:118 +#: src/screens/Settings/components/ExportCarDialog.tsx:124 msgid "Download profile data" msgstr "Download profile data" -#: src/screens/Settings/components/ExportCarDialog.tsx:123 +#: src/screens/Settings/components/ExportCarDialog.tsx:129 msgctxt "button" msgid "Download profile data" msgstr "Download profile data" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:115 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:116 msgid "Doxxing" msgstr "" @@ -4272,7 +4272,7 @@ msgstr "" msgid "Drafts" msgstr "" -#: src/view/com/composer/text-input/TextInput.web.tsx:369 +#: src/view/com/composer/text-input/TextInput.web.tsx:371 msgid "Drop to add images" msgstr "" @@ -4280,7 +4280,7 @@ msgstr "" msgid "Due to laws in your region, certain features on Bluesky are currently restricted until you're able to verify you're an adult." msgstr "" -#: src/components/dialogs/MutedWords.tsx:162 +#: src/components/dialogs/MutedWords.tsx:164 msgid "Duration:" msgstr "" @@ -4304,32 +4304,32 @@ msgstr "" msgid "E.g. artistic nudes." msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:370 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:378 msgid "e.g. Great Posters" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:371 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:379 msgid "e.g. Spammers" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:374 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:382 msgid "e.g. The posters who never miss." msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:375 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:383 msgid "e.g. Users that repeatedly reply with ads." msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:196 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:198 msgid "Eating disorders" msgstr "" #: src/screens/Messages/components/EditTextButton.tsx:52 #: src/screens/Settings/AccountSettings.tsx:148 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:250 -#: src/screens/StarterPack/StarterPackScreen.tsx:604 -#: src/screens/StarterPack/Wizard/index.tsx:331 -#: src/screens/StarterPack/Wizard/index.tsx:336 +#: src/screens/StarterPack/StarterPackScreen.tsx:597 +#: src/screens/StarterPack/Wizard/index.tsx:327 +#: src/screens/StarterPack/Wizard/index.tsx:332 msgid "Edit" msgstr "" @@ -4344,7 +4344,7 @@ msgstr "" msgid "Edit avatar" msgstr "" -#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:116 +#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:113 msgid "Edit Feeds" msgstr "" @@ -4359,8 +4359,8 @@ msgstr "Edit group name" msgid "Edit image" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:793 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:806 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:789 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:802 msgid "Edit interaction settings" msgstr "" @@ -4369,21 +4369,21 @@ msgstr "" msgid "Edit interests" msgstr "" -#: src/screens/Messages/JoinRequests.tsx:299 +#: src/screens/Messages/JoinRequests.tsx:296 msgid "Edit invite link" msgstr "Edit invite link" -#: src/screens/Messages/JoinRequests.tsx:305 +#: src/screens/Messages/JoinRequests.tsx:302 msgctxt "button" msgid "Edit invite link" msgstr "Edit invite link" -#: src/screens/Messages/components/InviteLinkDialog.tsx:369 +#: src/screens/Messages/components/InviteLinkDialog.tsx:366 msgid "Edit link settings" msgstr "Edit link settings" -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:191 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:194 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:192 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:195 msgid "Edit list details" msgstr "" @@ -4392,7 +4392,7 @@ msgstr "" msgid "Edit live status" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:364 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:372 msgid "Edit moderation list" msgstr "" @@ -4401,7 +4401,7 @@ msgstr "" msgid "Edit My Feeds" msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:544 +#: src/screens/Messages/ConversationSettings/index.tsx:539 msgid "Edit name" msgstr "Edit name" @@ -4410,41 +4410,41 @@ msgstr "Edit name" msgid "Edit notifications from {0}" msgstr "Edit notifications from {0}" -#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:114 +#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:111 msgid "Edit People" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:116 -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:266 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:113 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:263 msgid "Edit post interaction settings" msgstr "" #: src/screens/Profile/Header/EditProfileDialog.tsx:265 #: src/screens/Profile/Header/EditProfileDialog.tsx:271 -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:305 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:301 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:330 msgid "Edit profile" msgstr "" -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:308 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:304 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:332 msgid "Edit Profile" msgstr "" -#: src/screens/StarterPack/StarterPackScreen.tsx:596 +#: src/screens/StarterPack/StarterPackScreen.tsx:589 msgid "Edit starter pack" msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:493 -#: src/screens/Messages/ConversationSettings/index.tsx:543 +#: src/screens/Messages/ConversationSettings/index.tsx:488 +#: src/screens/Messages/ConversationSettings/index.tsx:538 msgid "Edit this group chat’s name" msgstr "Edit this group chat’s name" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:363 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:371 msgid "Edit user list" msgstr "" -#: src/components/WhoCanReply.tsx:116 +#: src/components/WhoCanReply.tsx:109 msgid "Edit who can reply" msgstr "" @@ -4465,7 +4465,7 @@ msgstr "" msgid "Email" msgstr "" -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:65 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:68 msgctxt "toast" msgid "Email 2FA disabled" msgstr "" @@ -4474,11 +4474,11 @@ msgstr "" msgid "Email 2FA enabled" msgstr "" -#: src/screens/Login/ForgotPasswordForm.tsx:90 +#: src/screens/Login/ForgotPasswordForm.tsx:94 msgid "Email address" msgstr "" -#: src/components/intents/VerifyEmailIntentDialog.tsx:99 +#: src/components/intents/VerifyEmailIntentDialog.tsx:100 msgid "Email Resent" msgstr "" @@ -4486,7 +4486,7 @@ msgstr "" msgid "Email sent!" msgstr "" -#: src/screens/Settings/components/DeleteAccountDialog.tsx:260 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:266 msgid "Email sent! <0>Click here to resend." msgstr "" @@ -4494,22 +4494,22 @@ msgstr "" msgid "Email verification complete!" msgstr "" -#: src/components/intents/VerifyEmailIntentDialog.tsx:74 +#: src/components/intents/VerifyEmailIntentDialog.tsx:75 msgid "Email Verified" msgstr "" -#: src/components/dialogs/Embed.tsx:178 +#: src/components/dialogs/Embed.tsx:179 msgid "Embed HTML code" msgstr "" -#: src/components/dialogs/Embed.tsx:105 -#: src/components/dialogs/Embed.tsx:109 +#: src/components/dialogs/Embed.tsx:106 +#: src/components/dialogs/Embed.tsx:110 #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:118 #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:123 msgid "Embed post" msgstr "" -#: src/components/dialogs/Embed.tsx:113 +#: src/components/dialogs/Embed.tsx:114 msgid "Embed this post in your website. Simply copy the following snippet and paste it into the HTML code of your website." msgstr "" @@ -4569,7 +4569,7 @@ msgstr "" msgid "Enable push notifications" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:629 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:626 msgid "Enable quote posts of this post" msgstr "" @@ -4599,22 +4599,22 @@ msgstr "" msgid "Ensure you have selected a language for each caption file." msgstr "Ensure you have selected a language for each caption file." -#: src/components/contacts/screens/VerifyNumber.tsx:231 +#: src/components/contacts/screens/VerifyNumber.tsx:234 msgid "Enter 6-digit code that was sent to your phone number" msgstr "" -#: src/screens/Login/SetNewPasswordForm.tsx:136 +#: src/screens/Login/SetNewPasswordForm.tsx:141 msgid "Enter a password" msgstr "" -#: src/components/dialogs/MutedWords.tsx:136 -#: src/components/dialogs/MutedWords.tsx:137 +#: src/components/dialogs/MutedWords.tsx:138 +#: src/components/dialogs/MutedWords.tsx:139 msgid "Enter a word or tag" msgstr "" #: src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx:199 #: src/components/dialogs/EmailDialog/screens/Verify.tsx:328 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:64 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:66 msgid "Enter code" msgstr "" @@ -4622,7 +4622,7 @@ msgstr "" msgid "Enter fullscreen" msgstr "" -#: src/components/contacts/screens/VerifyNumber.tsx:226 +#: src/components/contacts/screens/VerifyNumber.tsx:229 msgid "Enter the 6-digit code sent to {prettyNumber}" msgstr "" @@ -4630,11 +4630,11 @@ msgstr "" msgid "Enter the domain you want to use" msgstr "" -#: src/screens/Login/ForgotPasswordForm.tsx:109 +#: src/screens/Login/ForgotPasswordForm.tsx:113 msgid "Enter the email you used to create your account. We'll send you a \"reset code\" so you can set a new password." msgstr "" -#: src/screens/Login/LoginForm.tsx:337 +#: src/screens/Login/LoginForm.tsx:338 msgid "Enter the username or email address you used when you created your account" msgstr "" @@ -4642,13 +4642,13 @@ msgstr "" msgid "Enter your birthdate" msgstr "" -#: src/screens/Login/ForgotPasswordForm.tsx:96 +#: src/screens/Login/ForgotPasswordForm.tsx:100 #: src/screens/Signup/StepInfo/index.tsx:243 msgid "Enter your email address" msgstr "" -#: src/screens/Login/LoginForm.tsx:389 -#: src/screens/Settings/components/DeleteAccountDialog.tsx:291 +#: src/screens/Login/LoginForm.tsx:390 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:297 msgid "Enter your password" msgstr "" @@ -4660,16 +4660,16 @@ msgstr "" msgid "Enters full screen" msgstr "" -#: src/screens/Search/modules/ExploreTrendingTopics.tsx:246 +#: src/screens/Search/modules/ExploreTrendingTopics.tsx:244 msgid "Entertainment" msgstr "" -#: src/view/com/composer/Composer.tsx:2707 +#: src/view/com/composer/Composer.tsx:2731 #: src/view/com/util/error/ErrorScreen.tsx:40 msgid "Error" msgstr "" -#: src/screens/Settings/FindContactsSettings.tsx:95 +#: src/screens/Settings/FindContactsSettings.tsx:93 msgid "Error getting the latest data." msgstr "" @@ -4685,8 +4685,8 @@ msgstr "" msgid "Error message" msgstr "" -#: src/screens/Settings/components/ExportCarDialog.tsx:47 -#: src/screens/Settings/components/ExportCarDialog.tsx:80 +#: src/screens/Settings/components/ExportCarDialog.tsx:55 +#: src/screens/Settings/components/ExportCarDialog.tsx:86 msgid "Error occurred while saving file" msgstr "" @@ -4698,11 +4698,11 @@ msgstr "" msgid "Error: {error}" msgstr "" -#: src/components/WhoCanReply.tsx:85 +#: src/components/WhoCanReply.tsx:78 msgid "Everybody can reply" msgstr "" -#: src/components/WhoCanReply.tsx:279 +#: src/components/WhoCanReply.tsx:272 msgid "Everybody can reply to this post." msgstr "" @@ -4740,11 +4740,11 @@ msgstr "Everything look right?" msgid "Exclude" msgstr "Exclude" -#: src/components/dialogs/MutedWords.tsx:320 +#: src/components/dialogs/MutedWords.tsx:322 msgid "Exclude users you follow" msgstr "" -#: src/components/dialogs/MutedWords.tsx:605 +#: src/components/dialogs/MutedWords.tsx:607 msgid "Excludes users you follow" msgstr "" @@ -4757,11 +4757,11 @@ msgstr "" msgid "Expand alt text" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:602 +#: src/view/com/notifications/NotificationFeedItem.tsx:594 msgid "Expand list of users" msgstr "" -#: src/view/com/composer/ComposerReplyTo.tsx:103 +#: src/view/com/composer/ComposerReplyTo.tsx:98 msgid "Expand or collapse the full post you are replying to" msgstr "" @@ -4769,11 +4769,11 @@ msgstr "" msgid "Expand post text" msgstr "" -#: src/screens/VideoFeed/index.tsx:1076 +#: src/screens/VideoFeed/index.tsx:1072 msgid "Expands or collapses post text" msgstr "" -#: src/lib/api/index.ts:490 +#: src/lib/api/index.ts:500 msgid "Expected uri to resolve to a record" msgstr "" @@ -4781,12 +4781,12 @@ msgstr "" msgid "Experimental" msgstr "" -#: src/components/dialogs/MutedWords.tsx:520 +#: src/components/dialogs/MutedWords.tsx:522 msgid "Expired" msgstr "" #. placeholder {0}: formatDistance(expiryDate, new Date(), { addSuffix: true, }) -#: src/components/dialogs/MutedWords.tsx:589 +#: src/components/dialogs/MutedWords.tsx:591 msgid "Expires {0}" msgstr "" @@ -4799,7 +4799,7 @@ msgstr "" #. placeholder {0}: displayDuration(i18n, minutesUntilExpiry) #. placeholder {1}: i18n.date(expiryDateTime, { hour: 'numeric', minute: '2-digit', hour12: true, }) -#: src/features/liveNow/components/EditLiveDialog.tsx:125 +#: src/features/liveNow/components/EditLiveDialog.tsx:127 msgid "Expires in {0} at {1}" msgstr "" @@ -4826,7 +4826,7 @@ msgstr "" #: src/screens/Messages/Settings.tsx:329 #: src/screens/Messages/Settings.tsx:338 -#: src/screens/Settings/components/ExportCarDialog.tsx:130 +#: src/screens/Settings/components/ExportCarDialog.tsx:136 msgid "Export my chat data" msgstr "Export my chat data" @@ -4835,7 +4835,7 @@ msgstr "Export my chat data" msgid "Export my data" msgstr "" -#: src/screens/Settings/components/ExportCarDialog.tsx:97 +#: src/screens/Settings/components/ExportCarDialog.tsx:103 msgid "Export my profile data" msgstr "Export my profile data" @@ -4859,7 +4859,7 @@ msgstr "" msgid "External Media Preferences" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:146 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:147 msgid "Extremist content" msgstr "" @@ -4868,12 +4868,12 @@ msgctxt "toast" msgid "Failed to accept chat" msgstr "" -#: src/screens/Messages/JoinRequests.tsx:190 +#: src/screens/Messages/JoinRequests.tsx:187 msgid "Failed to accept join request" msgstr "Failed to accept join request" -#: src/components/dms/ActionsWrapper.web.tsx:92 -#: src/components/dms/MessageContextMenu.tsx:140 +#: src/components/dms/ActionsWrapper.web.tsx:93 +#: src/components/dms/MessageContextMenu.tsx:138 msgid "Failed to add emoji reaction" msgstr "" @@ -4886,7 +4886,7 @@ msgstr "Failed to add members" msgid "Failed to add to list" msgstr "Failed to add to list" -#: src/components/dialogs/StarterPackDialog.tsx:280 +#: src/components/dialogs/StarterPackDialog.tsx:277 msgid "Failed to add to starter pack" msgstr "" @@ -4909,12 +4909,12 @@ msgstr "" msgid "Failed to create conversation" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:106 +#: src/screens/Messages/components/InviteLinkDialog.tsx:103 msgid "Failed to create invite link" msgstr "Failed to create invite link" -#: src/screens/StarterPack/Wizard/index.tsx:250 -#: src/screens/StarterPack/Wizard/index.tsx:260 +#: src/screens/StarterPack/Wizard/index.tsx:246 +#: src/screens/StarterPack/Wizard/index.tsx:256 msgid "Failed to create starter pack" msgstr "" @@ -4928,77 +4928,77 @@ msgstr "" msgid "Failed to delete message" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:220 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:216 msgid "Failed to delete post, please try again" msgstr "" -#: src/screens/StarterPack/StarterPackScreen.tsx:754 +#: src/screens/StarterPack/StarterPackScreen.tsx:747 msgid "Failed to delete starter pack" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:132 +#: src/screens/Messages/components/InviteLinkDialog.tsx:129 msgid "Failed to disable invite link" msgstr "Failed to disable invite link" #. placeholder {0}: error?.message -#: src/screens/Profile/components/GermButton.tsx:196 +#: src/screens/Profile/components/GermButton.tsx:197 msgid "Failed to disconnect Germ DM. Error: {0}" msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:381 +#: src/screens/Messages/ConversationSettings/index.tsx:375 msgid "Failed to edit group chat name" msgstr "Failed to edit group chat name" -#: src/screens/Messages/components/InviteLinkDialog.tsx:119 +#: src/screens/Messages/components/InviteLinkDialog.tsx:116 msgid "Failed to edit invite link" msgstr "Failed to edit invite link" -#: src/screens/Messages/components/InviteLinkDialog.tsx:142 +#: src/screens/Messages/components/InviteLinkDialog.tsx:139 msgid "Failed to enable invite link" msgstr "Failed to enable invite link" -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:143 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:147 msgid "Failed to follow all suggested accounts, please try again" msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:148 +#: src/components/contacts/screens/ViewMatches.tsx:154 msgid "Failed to follow all your friends, please try again" msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:236 +#: src/components/contacts/screens/ViewMatches.tsx:244 msgid "Failed to hide suggestion, please check your internet connection" msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:147 +#: src/components/intents/GroupChatJoinDialog.tsx:145 msgid "Failed to join the group chat. Please try again." msgstr "Failed to join the group chat. Please try again." -#: src/components/contacts/screens/ViewMatches.tsx:582 +#: src/components/contacts/screens/ViewMatches.tsx:590 msgid "Failed to launch SMS app" msgstr "" #: src/screens/Messages/components/ChatEnded.tsx:41 -#: src/screens/Messages/components/ChatLocked.tsx:61 -#: src/screens/Messages/ConversationSettings/index.tsx:408 +#: src/screens/Messages/components/ChatLocked.tsx:65 +#: src/screens/Messages/ConversationSettings/index.tsx:402 msgctxt "toast" msgid "Failed to leave group chat" msgstr "Failed to leave group chat" #: src/screens/Messages/ChatList.tsx:420 -#: src/screens/Messages/Inbox.tsx:202 +#: src/screens/Messages/Inbox.tsx:201 msgid "Failed to load conversations" msgstr "" -#: src/screens/Search/Explore.tsx:526 -#: src/screens/Search/Explore.tsx:571 -#: src/screens/Search/Explore.tsx:617 +#: src/screens/Search/Explore.tsx:522 +#: src/screens/Search/Explore.tsx:567 +#: src/screens/Search/Explore.tsx:613 msgid "Failed to load feeds" msgstr "" -#: src/screens/Search/Explore.tsx:485 -#: src/screens/Search/Explore.tsx:540 -#: src/screens/Search/Explore.tsx:585 -#: src/screens/Search/Explore.tsx:631 +#: src/screens/Search/Explore.tsx:481 +#: src/screens/Search/Explore.tsx:536 +#: src/screens/Search/Explore.tsx:581 +#: src/screens/Search/Explore.tsx:627 msgid "Failed to load feeds preferences" msgstr "" @@ -5023,18 +5023,18 @@ msgstr "" msgid "Failed to load profiles" msgstr "Failed to load profiles" -#: src/screens/Search/Explore.tsx:478 -#: src/screens/Search/Explore.tsx:533 -#: src/screens/Search/Explore.tsx:578 -#: src/screens/Search/Explore.tsx:624 +#: src/screens/Search/Explore.tsx:474 +#: src/screens/Search/Explore.tsx:529 +#: src/screens/Search/Explore.tsx:574 +#: src/screens/Search/Explore.tsx:620 msgid "Failed to load suggested feeds" msgstr "" -#: src/screens/Search/Explore.tsx:386 +#: src/screens/Search/Explore.tsx:382 msgid "Failed to load suggested follows" msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:433 +#: src/screens/Messages/ConversationSettings/index.tsx:427 msgid "Failed to lock group chat" msgstr "Failed to lock group chat" @@ -5044,39 +5044,39 @@ msgid "Failed to mark all chats as read" msgstr "Failed to mark all chats as read" #: src/screens/Messages/ChatList.tsx:645 -#: src/screens/Messages/Inbox.tsx:304 +#: src/screens/Messages/Inbox.tsx:303 #: src/view/shell/bottom-bar/BottomBar.tsx:456 msgid "Failed to mark all requests as read" msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:397 +#: src/screens/Messages/ConversationSettings/index.tsx:391 msgid "Failed to mute group chat" msgstr "Failed to mute group chat" -#: src/state/queries/pinned-post.ts:75 +#: src/state/queries/pinned-post.ts:80 msgid "Failed to pin post" msgstr "" #. placeholder {0}: e?.message -#: src/screens/Profile/components/GermButton.tsx:164 +#: src/screens/Profile/components/GermButton.tsx:165 msgid "Failed to reconnect Germ DM. Error: {0}" msgstr "" -#: src/screens/Messages/JoinRequests.tsx:223 +#: src/screens/Messages/JoinRequests.tsx:219 msgid "Failed to reject join request" msgstr "Failed to reject join request" -#: src/screens/Settings/FindContactsSettings.tsx:509 +#: src/screens/Settings/FindContactsSettings.tsx:513 msgid "Failed to remove data due to a network error, please check your internet connection." msgstr "" #. placeholder {0}: cleanError(err) -#: src/screens/Settings/FindContactsSettings.tsx:515 +#: src/screens/Settings/FindContactsSettings.tsx:519 msgid "Failed to remove data. {0}" msgstr "" -#: src/components/dms/ActionsWrapper.web.tsx:77 -#: src/components/dms/MessageContextMenu.tsx:125 +#: src/components/dms/ActionsWrapper.web.tsx:78 +#: src/components/dms/MessageContextMenu.tsx:123 #: src/components/dms/ReactionsDialog.tsx:179 msgid "Failed to remove emoji reaction" msgstr "" @@ -5085,7 +5085,7 @@ msgstr "" msgid "Failed to remove from list" msgstr "Failed to remove from list" -#: src/components/dialogs/StarterPackDialog.tsx:293 +#: src/components/dialogs/StarterPackDialog.tsx:290 msgid "Failed to remove from starter pack" msgstr "" @@ -5098,8 +5098,8 @@ msgstr "Failed to remove group chat member" msgid "Failed to remove verification" msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:193 -#: src/screens/Messages/components/OutgoingRequestListItem.tsx:34 +#: src/components/intents/GroupChatJoinDialog.tsx:198 +#: src/screens/Messages/components/OutgoingRequestListItem.tsx:32 msgid "Failed to rescind your request. Please try again." msgstr "Failed to rescind your request. Please try again." @@ -5107,7 +5107,7 @@ msgstr "Failed to rescind your request. Please try again." msgid "Failed to resolve location. Please try again." msgstr "" -#: src/view/com/composer/Composer.tsx:759 +#: src/view/com/composer/Composer.tsx:775 msgid "Failed to save draft" msgstr "" @@ -5121,11 +5121,11 @@ msgstr "Failed to save image" msgid "Failed to save image: {0}" msgstr "" -#: src/screens/ModerationInteractionSettings/index.tsx:109 +#: src/screens/ModerationInteractionSettings/index.tsx:110 msgid "Failed to save settings. Please try again." msgstr "" -#: src/screens/Settings/InterestsSettings.tsx:147 +#: src/screens/Settings/InterestsSettings.tsx:148 msgctxt "toast" msgid "Failed to save your interests." msgstr "" @@ -5139,22 +5139,22 @@ msgstr "" msgid "Failed to send report" msgstr "Failed to send report" -#: src/components/moderation/AppealForm.tsx:77 -#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:77 -#: src/screens/Messages/components/ChatDisabled.tsx:119 +#: src/components/moderation/AppealForm.tsx:88 +#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:76 +#: src/screens/Messages/components/ChatDisabled.tsx:117 msgid "Failed to submit appeal, please try again." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:252 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:248 msgid "Failed to toggle thread mute, please try again" msgstr "" -#: src/screens/Messages/components/ChatLocked.tsx:51 -#: src/screens/Messages/ConversationSettings/index.tsx:442 +#: src/screens/Messages/components/ChatLocked.tsx:55 +#: src/screens/Messages/ConversationSettings/index.tsx:437 msgid "Failed to unlock group chat" msgstr "Failed to unlock group chat" -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:107 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:108 msgid "Failed to unpin list" msgstr "" @@ -5167,11 +5167,11 @@ msgstr "" msgid "Failed to update email, please try again." msgstr "" -#: src/components/FeedCard.tsx:316 +#: src/components/FeedCard.tsx:313 msgid "Failed to update feeds" msgstr "" -#: src/state/queries/activity-subscriptions.ts:101 +#: src/state/queries/activity-subscriptions.ts:95 msgid "Failed to update notification declaration" msgstr "" @@ -5179,10 +5179,10 @@ msgstr "" msgid "Failed to update settings" msgstr "" -#: src/lib/media/video/upload.ts:98 -#: src/lib/media/video/upload.web.ts:98 -#: src/lib/media/video/upload.web.ts:102 -#: src/lib/media/video/upload.web.ts:112 +#: src/lib/media/video/upload.ts:104 +#: src/lib/media/video/upload.web.ts:104 +#: src/lib/media/video/upload.web.ts:108 +#: src/lib/media/video/upload.web.ts:118 msgid "Failed to upload video" msgstr "" @@ -5194,11 +5194,11 @@ msgstr "" msgid "Failed to verify handle. Please try again." msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:47 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:48 msgid "Fake account or bot" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:55 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:56 msgid "False information about elections" msgstr "" @@ -5209,9 +5209,9 @@ msgstr "" #. placeholder {0}: sanitizeHandle(creator.handle, '@') #. placeholder {0}: sanitizeHandle(feed.creatorHandle, '@') #. placeholder {0}: sanitizeHandle(view.creator.handle, '@') -#: src/components/FeedCard.tsx:170 -#: src/state/queries/feed.ts:127 -#: src/view/com/feeds/FeedSourceCard.tsx:151 +#: src/components/FeedCard.tsx:167 +#: src/state/queries/feed.ts:122 +#: src/view/com/feeds/FeedSourceCard.tsx:149 msgid "Feed by {0}" msgstr "" @@ -5227,7 +5227,7 @@ msgstr "" msgid "Feed menu" msgstr "" -#: src/components/StarterPack/Wizard/WizardListCard.tsx:59 +#: src/components/StarterPack/Wizard/WizardListCard.tsx:58 msgid "Feed toggle" msgstr "" @@ -5241,8 +5241,8 @@ msgstr "" msgid "Feedback" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:308 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:331 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:304 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:327 msgctxt "toast" msgid "Feedback sent to feed operator" msgstr "" @@ -5251,9 +5251,9 @@ msgstr "" #: src/screens/SavedFeeds.tsx:112 #: src/screens/SavedFeeds.tsx:303 #: src/screens/Search/SearchResults.tsx:113 -#: src/screens/StarterPack/StarterPackScreen.tsx:196 +#: src/screens/StarterPack/StarterPackScreen.tsx:193 #: src/view/screens/Feeds.tsx:504 -#: src/view/screens/Profile.tsx:241 +#: src/view/screens/Profile.tsx:238 #: src/view/shell/desktop/LeftNav.tsx:712 #: src/view/shell/Drawer.tsx:592 msgid "Feeds" @@ -5264,7 +5264,7 @@ msgstr "" msgid "Feeds are custom algorithms that users build with a little coding expertise. <0>See this guide for more information." msgstr "" -#: src/components/FeedCard.tsx:313 +#: src/components/FeedCard.tsx:310 #: src/screens/SavedFeeds.tsx:92 #: src/screens/SavedFeeds.tsx:271 msgctxt "toast" @@ -5275,8 +5275,8 @@ msgstr "" msgid "Fetch update" msgstr "" -#: src/screens/Settings/components/ExportCarDialog.tsx:43 -#: src/screens/Settings/components/ExportCarDialog.tsx:76 +#: src/screens/Settings/components/ExportCarDialog.tsx:51 +#: src/screens/Settings/components/ExportCarDialog.tsx:82 msgid "File saved successfully!" msgstr "" @@ -5341,9 +5341,9 @@ msgid "Find accounts to follow" msgstr "" #: src/features/inviteFriends/components/FollowersPromoBanner.tsx:49 -#: src/screens/Settings/FindContactsSettings.tsx:81 -#: src/screens/Settings/Settings.tsx:218 -#: src/screens/Settings/Settings.tsx:221 +#: src/screens/Settings/FindContactsSettings.tsx:79 +#: src/screens/Settings/Settings.tsx:220 +#: src/screens/Settings/Settings.tsx:223 msgid "Find and invite friends" msgstr "Find and invite friends" @@ -5352,8 +5352,8 @@ msgstr "Find and invite friends" msgid "Find Contacts" msgstr "" -#: src/components/contacts/screens/GetContacts.tsx:273 -#: src/components/contacts/screens/GetContacts.tsx:287 +#: src/components/contacts/screens/GetContacts.tsx:275 +#: src/components/contacts/screens/GetContacts.tsx:289 msgid "Find my friends" msgstr "" @@ -5365,7 +5365,7 @@ msgstr "" msgid "Find people to follow" msgstr "" -#: src/screens/Settings/FindContactsSettings.tsx:130 +#: src/screens/Settings/FindContactsSettings.tsx:128 msgid "Find people you know" msgstr "Find people you know" @@ -5378,7 +5378,7 @@ msgid "Find your friends" msgstr "" #: src/screens/Onboarding/StepFindContactsIntro/index.tsx:46 -#: src/screens/Settings/FindContactsSettings.tsx:133 +#: src/screens/Settings/FindContactsSettings.tsx:131 msgid "Find your friends on Bluesky by verifying your phone number and matching with your contacts. We protect your information and you control what happens next. <0>Learn more" msgstr "" @@ -5386,11 +5386,11 @@ msgstr "" msgid "Find your people" msgstr "" -#: src/components/contacts/screens/GetContacts.tsx:281 +#: src/components/contacts/screens/GetContacts.tsx:283 msgid "Finding friends..." msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:206 +#: src/screens/StarterPack/Wizard/index.tsx:202 msgid "Finish" msgstr "" @@ -5418,15 +5418,15 @@ msgid "Focus the search field" msgstr "Focus the search field" #. User is not following this account, click to follow -#: src/components/ProfileCard.tsx:548 -#: src/components/ProfileHoverCard/index.web.tsx:495 -#: src/components/ProfileHoverCard/index.web.tsx:506 +#: src/components/ProfileCard.tsx:545 +#: src/components/ProfileHoverCard/index.web.tsx:492 +#: src/components/ProfileHoverCard/index.web.tsx:503 #: src/screens/Messages/ConversationSettings/Member.tsx:170 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:157 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:409 -#: src/screens/VideoFeed/index.tsx:959 +#: src/screens/VideoFeed/index.tsx:955 +#: src/view/com/notifications/NotificationFeedItem.tsx:890 #: src/view/com/notifications/NotificationFeedItem.tsx:897 -#: src/view/com/notifications/NotificationFeedItem.tsx:904 msgid "Follow" msgstr "" @@ -5440,7 +5440,7 @@ msgstr "" msgid "Follow {displayName}" msgstr "Follow {displayName}" -#: src/screens/VideoFeed/index.tsx:938 +#: src/screens/VideoFeed/index.tsx:934 msgid "Follow {handle}" msgstr "" @@ -5461,64 +5461,64 @@ msgstr "" msgid "Follow account" msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:276 -#: src/components/contacts/screens/ViewMatches.tsx:291 -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:294 -#: src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx:162 -#: src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx:169 -#: src/screens/Settings/FindContactsSettings.tsx:465 -#: src/screens/Settings/FindContactsSettings.tsx:475 -#: src/screens/StarterPack/StarterPackScreen.tsx:452 -#: src/screens/StarterPack/StarterPackScreen.tsx:460 +#: src/components/contacts/screens/ViewMatches.tsx:284 +#: src/components/contacts/screens/ViewMatches.tsx:299 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:298 +#: src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx:158 +#: src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx:165 +#: src/screens/Settings/FindContactsSettings.tsx:469 +#: src/screens/Settings/FindContactsSettings.tsx:479 +#: src/screens/StarterPack/StarterPackScreen.tsx:445 +#: src/screens/StarterPack/StarterPackScreen.tsx:453 msgid "Follow all" msgstr "" -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:291 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:295 msgid "Follow all accounts" msgstr "" #. User is not following this account, click to follow back -#: src/components/ProfileCard.tsx:544 +#: src/components/ProfileCard.tsx:541 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:155 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:407 +#: src/view/com/notifications/NotificationFeedItem.tsx:890 #: src/view/com/notifications/NotificationFeedItem.tsx:897 -#: src/view/com/notifications/NotificationFeedItem.tsx:904 msgid "Follow back" msgstr "" -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:132 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:136 msgid "Followed all accounts!" msgstr "" -#: src/screens/Settings/FindContactsSettings.tsx:418 +#: src/screens/Settings/FindContactsSettings.tsx:422 msgid "Followed all matches" msgstr "" #. placeholder {0}: slice[0].profile.displayName -#: src/components/KnownFollowers.tsx:233 +#: src/components/KnownFollowers.tsx:232 msgid "Followed by <0>{0}" msgstr "" #. placeholder {0}: slice[0].profile.displayName #. placeholder {1}: serverCount - 1 -#: src/components/KnownFollowers.tsx:219 +#: src/components/KnownFollowers.tsx:218 msgid "Followed by <0>{0} and {1, plural, one {# other} other {# others}}" msgstr "" #. placeholder {0}: slice[0].profile.displayName #. placeholder {1}: slice[1].profile.displayName -#: src/components/KnownFollowers.tsx:206 +#: src/components/KnownFollowers.tsx:205 msgid "Followed by <0>{0} and <1>{1}" msgstr "" #. placeholder {0}: slice[0].profile.displayName #. placeholder {1}: slice[1].profile.displayName #. placeholder {2}: serverCount - 2 -#: src/components/KnownFollowers.tsx:189 +#: src/components/KnownFollowers.tsx:188 msgid "Followed by <0>{0}, <1>{1}, and {2, plural, one {# other} other {# others}}" msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:355 +#: src/components/intents/GroupChatJoinDialog.tsx:360 msgid "Followers can join" msgstr "Followers can join" @@ -5534,14 +5534,14 @@ msgstr "" #. User is following this account, click to unfollow #. User is following this account, click to unfollow -#: src/components/ProfileCard.tsx:539 -#: src/components/ProfileHoverCard/index.web.tsx:494 -#: src/components/ProfileHoverCard/index.web.tsx:505 +#: src/components/ProfileCard.tsx:536 +#: src/components/ProfileHoverCard/index.web.tsx:491 +#: src/components/ProfileHoverCard/index.web.tsx:502 #: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:160 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:405 -#: src/screens/VideoFeed/index.tsx:957 -#: src/view/com/notifications/NotificationFeedItem.tsx:876 -#: src/view/com/notifications/NotificationFeedItem.tsx:892 +#: src/screens/VideoFeed/index.tsx:953 +#: src/view/com/notifications/NotificationFeedItem.tsx:869 +#: src/view/com/notifications/NotificationFeedItem.tsx:885 msgid "Following" msgstr "" @@ -5554,9 +5554,9 @@ msgstr "" #. placeholder {0}: sanitizeDisplayName( profile.displayName || profile.handle, moderation.ui('displayName'), ) #. placeholder {0}: sanitizeDisplayName( profile.displayName || profile.handle, moderation.ui('displayName'), ) #. placeholder {0}: sanitizeDisplayName( profile.displayName || profile.handle, ) -#: src/components/ProfileCard.tsx:500 +#: src/components/ProfileCard.tsx:497 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:247 -#: src/view/com/notifications/NotificationFeedItem.tsx:827 +#: src/view/com/notifications/NotificationFeedItem.tsx:820 msgid "Following {0}" msgstr "" @@ -5564,7 +5564,7 @@ msgstr "" msgid "Following {displayName}" msgstr "Following {displayName}" -#: src/screens/VideoFeed/index.tsx:937 +#: src/screens/VideoFeed/index.tsx:933 msgid "Following {handle}" msgstr "" @@ -5578,7 +5578,7 @@ msgstr "" msgid "Following Feed Preferences" msgstr "" -#: src/components/Pills.tsx:214 +#: src/components/Pills.tsx:215 #: src/screens/Profile/Header/Handle.tsx:33 msgid "Follows you" msgstr "" @@ -5599,7 +5599,7 @@ msgstr "" msgid "For organizational accounts, use the birthdate of the person who is responsible for the account." msgstr "" -#: src/screens/Settings/components/DeleteAccountDialog.tsx:186 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:192 msgid "For security reasons, we’ll need to send a confirmation code to your email address <0>{currentEmail}." msgstr "" @@ -5612,14 +5612,14 @@ msgid "For the best experience, we recommend using the theme font." msgstr "" #: src/components/ProgressGuide/FollowDialog.tsx:131 -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:349 -#: src/screens/Search/modules/ExploreSuggestedAccounts.tsx:86 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:353 +#: src/screens/Search/modules/ExploreSuggestedAccounts.tsx:87 msgid "For You" msgstr "" -#: src/components/dialogs/MutedWords.tsx:187 -#: src/components/dialogs/MutedWords.tsx:572 -#: src/components/dialogs/MutedWords.tsx:575 +#: src/components/dialogs/MutedWords.tsx:189 +#: src/components/dialogs/MutedWords.tsx:574 +#: src/components/dialogs/MutedWords.tsx:577 msgid "Forever" msgstr "" @@ -5632,8 +5632,8 @@ msgstr "" msgid "Forgot Password" msgstr "" -#: src/screens/Login/LoginForm.tsx:421 -#: src/screens/Login/LoginForm.tsx:428 +#: src/screens/Login/LoginForm.tsx:422 +#: src/screens/Login/LoginForm.tsx:429 msgid "Forgot password?" msgstr "" @@ -5651,15 +5651,15 @@ msgstr "" msgid "From" msgstr "" -#: src/screens/Hashtag.tsx:123 +#: src/screens/Hashtag.tsx:125 msgid "From {sanitizedAuthor}" msgstr "From {sanitizedAuthor}" -#: src/screens/Hashtag.tsx:124 +#: src/screens/Hashtag.tsx:126 msgid "From @{sanitizedAuthor}" msgstr "" -#: src/view/com/posts/PostFeedReason.tsx:48 +#: src/view/com/posts/PostFeedReason.tsx:50 msgctxt "from-feed" msgid "From <0/>" msgstr "" @@ -5673,35 +5673,35 @@ msgstr "From these people" msgid "Generate a starter pack" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:239 -#: src/screens/Messages/components/InviteLinkDialog.tsx:277 -#: src/screens/Messages/components/InviteLinkDialog.tsx:305 +#: src/screens/Messages/components/InviteLinkDialog.tsx:236 +#: src/screens/Messages/components/InviteLinkDialog.tsx:274 +#: src/screens/Messages/components/InviteLinkDialog.tsx:302 msgid "Generate invite link" msgstr "Generate invite link" -#: src/screens/Messages/components/InviteLinkDialog.tsx:446 +#: src/screens/Messages/components/InviteLinkDialog.tsx:443 msgid "Generate new invite link" msgstr "Generate new invite link" -#: src/screens/Messages/components/InviteLinkDialog.tsx:451 +#: src/screens/Messages/components/InviteLinkDialog.tsx:448 msgid "Generate new link" msgstr "Generate new link" -#: src/screens/Profile/components/GermButton.tsx:86 -#: src/screens/Profile/components/GermButton.tsx:225 +#: src/screens/Profile/components/GermButton.tsx:84 +#: src/screens/Profile/components/GermButton.tsx:226 msgid "Germ DM" msgstr "" -#: src/screens/Profile/components/GermButton.tsx:183 +#: src/screens/Profile/components/GermButton.tsx:184 msgid "Germ DM disconnected" msgstr "" -#: src/screens/Profile/components/GermButton.tsx:234 -#: src/screens/Profile/components/GermButton.tsx:239 +#: src/screens/Profile/components/GermButton.tsx:235 +#: src/screens/Profile/components/GermButton.tsx:240 msgid "Germ DM Link" msgstr "" -#: src/screens/Profile/components/GermButton.tsx:160 +#: src/screens/Profile/components/GermButton.tsx:161 msgid "Germ DM reconnected" msgstr "" @@ -5766,11 +5766,11 @@ msgstr "" msgid "Get notified about posts and replies from accounts you choose." msgstr "Get notified about posts and replies from accounts you choose." -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:226 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:223 msgid "Get notified of new posts from {name}" msgstr "" -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:233 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:230 msgid "Get notified of this account’s activity" msgstr "" @@ -5784,17 +5784,17 @@ msgstr "" #: src/components/dialogs/EmailDialog/screens/VerificationReminder.tsx:71 #: src/components/dialogs/EmailDialog/screens/VerificationReminder.tsx:81 -#: src/screens/Messages/components/InviteLinkDialog.tsx:219 -#: src/screens/Messages/components/InviteLinkDialog.tsx:226 +#: src/screens/Messages/components/InviteLinkDialog.tsx:216 +#: src/screens/Messages/components/InviteLinkDialog.tsx:223 msgid "Get started" msgstr "" -#: src/components/MediaPreview.tsx:182 +#: src/components/MediaPreview.tsx:188 #: src/components/Post/Embed/VideoEmbed/GifPresentationControls.tsx:93 msgid "GIF" msgstr "" -#: src/view/com/composer/Composer.tsx:2712 +#: src/view/com/composer/Composer.tsx:2736 msgid "GIF uploaded" msgstr "" @@ -5802,7 +5802,7 @@ msgstr "" msgid "Give your profile a face" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:142 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:143 msgid "Glorification of violence" msgstr "" @@ -5817,8 +5817,8 @@ msgstr "" #: src/screens/ProfileList/components/ErrorScreen.tsx:35 #: src/screens/ProfileList/components/ErrorScreen.tsx:41 #: src/screens/VideoFeed/components/Header.tsx:163 +#: src/screens/VideoFeed/index.tsx:1257 #: src/screens/VideoFeed/index.tsx:1261 -#: src/screens/VideoFeed/index.tsx:1265 #: src/view/com/auth/LoggedOut.tsx:127 #: src/view/com/profile/ProfileFollowers.tsx:225 #: src/view/com/profile/ProfileFollowers.tsx:226 @@ -5832,7 +5832,7 @@ msgstr "" #: src/screens/List/ListHiddenScreen.tsx:228 #: src/screens/Profile/ErrorState.tsx:63 #: src/screens/Profile/ErrorState.tsx:67 -#: src/screens/StarterPack/StarterPackScreen.tsx:807 +#: src/screens/StarterPack/StarterPackScreen.tsx:800 msgid "Go Back" msgstr "" @@ -5842,7 +5842,7 @@ msgstr "" msgid "Go back to previous step" msgstr "" -#: src/screens/Bookmarks.tsx:296 +#: src/screens/Bookmarks.tsx:294 #: src/view/screens/NotFound.tsx:51 #: src/view/screens/NotFound.tsx:52 #: src/view/screens/NotFound.tsx:57 @@ -5871,11 +5871,11 @@ msgid "Go live for" msgstr "" #. placeholder {0}: name.displayName -#: src/screens/PostThread/components/LikesStat.tsx:146 +#: src/screens/PostThread/components/LikesStat.tsx:148 msgid "Go to {0}'s profile" msgstr "Go to {0}'s profile" -#: src/view/com/notifications/NotificationFeedItem.tsx:258 +#: src/view/com/notifications/NotificationFeedItem.tsx:250 msgid "Go to {firstAuthorName}'s profile" msgstr "" @@ -5891,11 +5891,11 @@ msgstr "" msgid "Go to conversation with {0}" msgstr "" -#: src/view/com/posts/PostFeedReason.tsx:39 +#: src/view/com/posts/PostFeedReason.tsx:41 msgid "Go to feed" msgstr "" -#: src/screens/Login/ForgotPasswordForm.tsx:168 +#: src/screens/Login/ForgotPasswordForm.tsx:172 msgid "Go to next" msgstr "" @@ -5919,14 +5919,14 @@ msgstr "" msgid "Go to user’s profile" msgstr "Go to user’s profile" -#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:105 +#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:104 msgid "Going live is currently disabled for your account" msgstr "" #: src/components/dialogs/nuxs/GroupChatsAnnouncement.tsx:121 #: src/components/dialogs/nuxs/GroupChatsAnnouncement.tsx:126 -#: src/screens/Profile/components/GermButton.tsx:253 -#: src/screens/Profile/components/GermButton.tsx:258 +#: src/screens/Profile/components/GermButton.tsx:254 +#: src/screens/Profile/components/GermButton.tsx:259 msgid "Got it" msgstr "" @@ -5942,51 +5942,51 @@ msgstr "Grant access" msgid "Graphic Media" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:138 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:139 msgid "Graphic violent content" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:169 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:171 msgid "Grooming or predatory behavior" msgstr "" -#: src/components/dms/ChatInvite/Card.tsx:51 -#: src/components/intents/GroupChatJoinDialog.tsx:333 -#: src/screens/Messages/JoinRequest.tsx:125 +#: src/components/dms/ChatInvite/Card.tsx:53 +#: src/components/intents/GroupChatJoinDialog.tsx:338 +#: src/screens/Messages/JoinRequest.tsx:133 msgid "Group chat" msgstr "Group chat" -#: src/screens/Messages/components/InviteLinkDialog.tsx:556 +#: src/screens/Messages/components/InviteLinkDialog.tsx:553 msgid "Group chat invite link dialog" msgstr "Group chat invite link dialog" -#: src/screens/Messages/ConversationSettings/index.tsx:424 +#: src/screens/Messages/ConversationSettings/index.tsx:418 msgctxt "toast" msgid "Group chat locked" msgstr "Group chat locked" -#: src/screens/Messages/ConversationSettings/index.tsx:389 +#: src/screens/Messages/ConversationSettings/index.tsx:383 msgctxt "toast" msgid "Group chat muted" msgstr "Group chat muted" -#: src/screens/Messages/ConversationSettings/index.tsx:376 +#: src/screens/Messages/ConversationSettings/index.tsx:370 msgctxt "toast" msgid "Group chat name updated" msgstr "Group chat name updated" #: src/Navigation.tsx:514 -#: src/screens/Messages/ConversationSettings/index.tsx:113 +#: src/screens/Messages/ConversationSettings/index.tsx:110 msgid "Group chat settings" msgstr "Group chat settings" -#: src/screens/Messages/components/ChatLocked.tsx:41 -#: src/screens/Messages/ConversationSettings/index.tsx:427 +#: src/screens/Messages/components/ChatLocked.tsx:42 +#: src/screens/Messages/ConversationSettings/index.tsx:421 msgctxt "toast" msgid "Group chat unlocked" msgstr "Group chat unlocked" -#: src/screens/Messages/ConversationSettings/index.tsx:392 +#: src/screens/Messages/ConversationSettings/index.tsx:386 msgctxt "toast" msgid "Group chat unmuted" msgstr "Group chat unmuted" @@ -6000,7 +6000,7 @@ msgid "Group chats are only available to users 18 and over." msgstr "Group chats are only available to users 18 and over." #. placeholder {0}: convo.details.memberLimit -#: src/screens/Messages/components/InviteLinkDialog.tsx:205 +#: src/screens/Messages/components/InviteLinkDialog.tsx:202 msgid "Group chats can only have a maximum of {0, plural, other {# people}}." msgstr "Group chats can only have a maximum of {0, plural, other {# people}}." @@ -6020,11 +6020,11 @@ msgstr "Group name" msgid "Group name is too long. {MAX_GROUP_NAME_GRAPHEME_LENGTH, plural, other {The maximum number of characters is #.}}" msgstr "Group name is too long. {MAX_GROUP_NAME_GRAPHEME_LENGTH, plural, other {The maximum number of characters is #.}}" -#: src/screens/Settings/Settings.tsx:467 +#: src/screens/Settings/Settings.tsx:469 msgid "GrowthBook" msgstr "GrowthBook" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:219 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:221 msgid "Hacking or system attacks" msgstr "" @@ -6051,7 +6051,7 @@ msgstr "" msgid "Handle too long. Please try a shorter one." msgstr "" -#: src/components/FeedCard.tsx:156 +#: src/components/FeedCard.tsx:153 #: src/features/liveEvents/components/LiveEventFeedCardWide.tsx:123 msgid "Happening now" msgstr "" @@ -6065,15 +6065,15 @@ msgstr "Happy GIFs" msgid "Haptics" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:99 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:100 msgid "Harassment or hate" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:189 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:191 msgid "Harmful or high-risk activities" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:162 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:164 msgid "Harming or endangering minors" msgstr "" @@ -6085,7 +6085,7 @@ msgstr "" msgid "Hashtag {tag}" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:111 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:112 msgid "Hate speech" msgstr "" @@ -6098,7 +6098,7 @@ msgstr "" msgid "Have we got your location wrong? <0>Tap here to update your location with GPS." msgstr "Have we got your location wrong? <0>Tap here to update your location with GPS." -#: src/screens/Signup/index.tsx:241 +#: src/screens/Signup/index.tsx:238 msgid "Having trouble?" msgstr "" @@ -6106,8 +6106,8 @@ msgstr "" msgid "Held by Bluesky for 7 days to prevent abuse, then deleted" msgstr "" -#: src/screens/Settings/Settings.tsx:257 -#: src/screens/Settings/Settings.tsx:261 +#: src/screens/Settings/Settings.tsx:259 +#: src/screens/Settings/Settings.tsx:263 #: src/view/shell/desktop/RightNav.tsx:130 #: src/view/shell/desktop/RightNav.tsx:133 #: src/view/shell/Drawer.tsx:443 @@ -6135,41 +6135,41 @@ msgstr "" msgid "Hi there!" msgstr "" -#: src/components/VideoPostCard.tsx:176 -#: src/components/VideoPostCard.tsx:459 +#: src/components/VideoPostCard.tsx:168 +#: src/components/VideoPostCard.tsx:451 msgid "Hidden" msgstr "" -#: src/screens/VideoFeed/index.tsx:736 +#: src/screens/VideoFeed/index.tsx:735 msgid "Hidden by your moderation settings." msgstr "" -#: src/components/ListCard.tsx:133 +#: src/components/ListCard.tsx:130 msgid "Hidden list" msgstr "" -#: src/components/interstitials/FeedTrendingTopics.tsx:168 +#: src/components/interstitials/FeedTrendingTopics.tsx:169 #: src/components/interstitials/Trending.tsx:149 -#: src/components/interstitials/TrendingVideos.tsx:138 +#: src/components/interstitials/TrendingVideos.tsx:140 #: src/components/moderation/ContentHider.tsx:217 #: src/components/moderation/LabelPreference.tsx:141 -#: src/components/moderation/PostHider.tsx:140 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:836 +#: src/components/moderation/PostHider.tsx:137 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:832 #: src/lib/moderation/useLabelBehaviorDescription.ts:18 #: src/lib/moderation/useLabelBehaviorDescription.ts:23 #: src/lib/moderation/useLabelBehaviorDescription.ts:28 #: src/lib/moderation/useLabelBehaviorDescription.ts:33 -#: src/screens/Search/modules/ExploreTrendingTopics.tsx:106 +#: src/screens/Search/modules/ExploreTrendingTopics.tsx:104 #: src/view/shell/desktop/SidebarTrendingTopics.tsx:173 msgid "Hide" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:989 +#: src/view/com/notifications/NotificationFeedItem.tsx:980 msgctxt "action" msgid "Hide" msgstr "" -#: src/components/dialogs/Embed.tsx:124 +#: src/components/dialogs/Embed.tsx:125 msgid "Hide customization options" msgstr "" @@ -6177,26 +6177,26 @@ msgstr "" msgid "Hide group chat updates" msgstr "Hide group chat updates" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:533 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:530 msgid "Hide lists" msgstr "" -#: src/screens/Login/LoginForm.tsx:613 +#: src/screens/Login/LoginForm.tsx:614 msgid "Hide password" msgstr "Hide password" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:676 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:679 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:672 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:675 msgid "Hide post for me" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:690 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:700 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:686 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:696 msgid "Hide reply for everyone" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:676 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:679 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:672 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:675 msgid "Hide reply for me" msgstr "" @@ -6209,19 +6209,19 @@ msgstr "Hide reposts in feeds" msgid "Hide this card" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:833 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:829 msgid "Hide this post?" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:833 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:868 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:829 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:864 msgid "Hide this reply?" msgstr "" -#: src/components/Post/Translated/index.tsx:216 -#: src/components/Post/Translated/index.tsx:350 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:553 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:555 +#: src/components/Post/Translated/index.tsx:213 +#: src/components/Post/Translated/index.tsx:347 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:549 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:551 msgid "Hide translation" msgstr "Hide translation" @@ -6229,18 +6229,18 @@ msgstr "Hide translation" msgid "Hide trending topics" msgstr "" -#: src/components/interstitials/FeedTrendingTopics.tsx:166 +#: src/components/interstitials/FeedTrendingTopics.tsx:167 #: src/components/interstitials/Trending.tsx:147 -#: src/screens/Search/modules/ExploreTrendingTopics.tsx:104 +#: src/screens/Search/modules/ExploreTrendingTopics.tsx:102 #: src/view/shell/desktop/SidebarTrendingTopics.tsx:171 msgid "Hide trending topics?" msgstr "" -#: src/components/interstitials/TrendingVideos.tsx:136 +#: src/components/interstitials/TrendingVideos.tsx:138 msgid "Hide trending videos?" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:982 +#: src/view/com/notifications/NotificationFeedItem.tsx:973 msgid "Hide user list" msgstr "" @@ -6250,7 +6250,7 @@ msgid "Hide verification badges" msgstr "" #: src/components/moderation/ContentHider.tsx:168 -#: src/components/moderation/PostHider.tsx:94 +#: src/components/moderation/PostHider.tsx:91 msgid "Hides the content" msgstr "" @@ -6270,23 +6270,23 @@ msgstr "Hmm, it seems we weren't able to compute your level of access. Please tr msgid "Hmm, it seems your device was unable to share age information with us." msgstr "Hmm, it seems your device was unable to share age information with us." -#: src/view/com/posts/PostFeedErrorMessage.tsx:125 +#: src/view/com/posts/PostFeedErrorMessage.tsx:123 msgid "Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue." msgstr "" -#: src/view/com/posts/PostFeedErrorMessage.tsx:113 +#: src/view/com/posts/PostFeedErrorMessage.tsx:111 msgid "Hmm, the feed server appears to be misconfigured. Please let the feed owner know about this issue." msgstr "" -#: src/view/com/posts/PostFeedErrorMessage.tsx:119 +#: src/view/com/posts/PostFeedErrorMessage.tsx:117 msgid "Hmm, the feed server appears to be offline. Please let the feed owner know about this issue." msgstr "" -#: src/view/com/posts/PostFeedErrorMessage.tsx:116 +#: src/view/com/posts/PostFeedErrorMessage.tsx:114 msgid "Hmm, the feed server gave a bad response. Please let the feed owner know about this issue." msgstr "" -#: src/view/com/posts/PostFeedErrorMessage.tsx:110 +#: src/view/com/posts/PostFeedErrorMessage.tsx:108 msgid "Hmm, we're having trouble finding this feed. It may have been deleted." msgstr "" @@ -6298,7 +6298,7 @@ msgstr "" msgid "Hmmmm, we couldn't load that moderation service." msgstr "" -#: src/view/com/composer/state/video.ts:522 +#: src/view/com/composer/state/video.ts:527 msgid "Hold up! We’re gradually giving access to video, and you’re still waiting in line. Check back soon!" msgstr "" @@ -6316,17 +6316,17 @@ msgstr "" #: src/components/dialogs/ServerInput.tsx:138 #: src/screens/Login/components/HostingProviderDialog.tsx:159 -#: src/screens/Login/ForgotPasswordForm.tsx:80 -#: src/screens/Login/LoginForm.tsx:659 +#: src/screens/Login/ForgotPasswordForm.tsx:84 +#: src/screens/Login/LoginForm.tsx:660 msgid "Hosting provider" msgstr "" #. placeholder {0}: toNiceHostingUrl(state.pdsUrl) -#: src/screens/Login/LoginForm.tsx:655 +#: src/screens/Login/LoginForm.tsx:656 msgid "Hosting provider: {0}" msgstr "Hosting provider: {0}" -#: src/screens/Login/LoginForm.tsx:657 +#: src/screens/Login/LoginForm.tsx:658 msgid "Hosting provider: Bluesky" msgstr "Hosting provider: Bluesky" @@ -6343,16 +6343,16 @@ msgstr "" msgid "How we use your number:" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:150 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:152 msgid "Human trafficking" msgstr "" -#: src/components/contacts/screens/GetContacts.tsx:267 +#: src/components/contacts/screens/GetContacts.tsx:269 msgid "I consent to Bluesky using my contacts for mutual friend discovery and to retain hashed data for matching until I opt out." msgstr "" -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:134 -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:137 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:142 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:145 msgid "I have a code" msgstr "" @@ -6366,7 +6366,7 @@ msgid "I understand" msgstr "" #. placeholder {0}: currentAccount.handle -#: src/components/contacts/screens/ViewMatches.tsx:578 +#: src/components/contacts/screens/ViewMatches.tsx:586 msgid "I'm on Bluesky as {0} - come find me! https://bsky.app/download" msgstr "" @@ -6390,7 +6390,7 @@ msgstr "" msgid "If you believe your birthdate is incorrect, you can update it by <0>clicking here." msgstr "" -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:272 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:273 msgid "If you delete this list, you won't be able to recover it." msgstr "" @@ -6402,7 +6402,7 @@ msgstr "" msgid "If you need to update your email, <0>click here." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:826 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:822 msgid "If you remove this post, you won't be able to recover it." msgstr "" @@ -6410,7 +6410,7 @@ msgstr "" msgid "If you update your email address, email 2FA will be disabled." msgstr "" -#: src/screens/Settings/components/ChangePasswordDialog.tsx:60 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:62 msgid "If you want to change your password, we will send you a code to verify that this is your account." msgstr "" @@ -6423,7 +6423,7 @@ msgstr "" msgid "If you're a developer, you can host your own server." msgstr "" -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:92 +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:93 msgid "If you're trying to change your handle or email, do so before you deactivate." msgstr "" @@ -6463,12 +6463,12 @@ msgid "Image gallery, {0} images" msgstr "Image gallery, {0} images" #. Image has been moderated and user has the option of showing it temporarily -#: src/features/liveNow/components/LiveStatusDialog.tsx:300 +#: src/features/liveNow/components/LiveStatusDialog.tsx:297 msgid "Image is hidden due to your moderation settings." msgstr "Image is hidden due to your moderation settings." #. Image has been moderated and is not visible to the user -#: src/features/liveNow/components/LiveStatusDialog.tsx:310 +#: src/features/liveNow/components/LiveStatusDialog.tsx:307 msgid "Image is unavailable." msgstr "Image is unavailable." @@ -6492,14 +6492,14 @@ msgstr "" msgid "Images cannot be saved unless permission is granted to access your photo library." msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:51 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:52 msgid "Impersonation" msgstr "" #: src/screens/Onboarding/StepFindContactsIntro/index.tsx:76 #: src/screens/Onboarding/StepFindContactsIntro/index.tsx:81 -#: src/screens/Settings/FindContactsSettings.tsx:153 -#: src/screens/Settings/FindContactsSettings.tsx:158 +#: src/screens/Settings/FindContactsSettings.tsx:151 +#: src/screens/Settings/FindContactsSettings.tsx:156 msgid "Import contacts" msgstr "" @@ -6518,7 +6518,7 @@ msgid "Import contacts to find your friends" msgstr "" #. placeholder {0}: i18n.date(new Date(syncedAt), { dateStyle: 'long', }) -#: src/screens/Settings/FindContactsSettings.tsx:535 +#: src/screens/Settings/FindContactsSettings.tsx:539 msgid "Imported on {0}" msgstr "" @@ -6559,7 +6559,7 @@ msgid "Inbox empty" msgstr "Inbox empty" #. Title message shown in chat requests inbox when it's empty -#: src/screens/Messages/Inbox.tsx:219 +#: src/screens/Messages/Inbox.tsx:218 msgid "Inbox zero!" msgstr "" @@ -6593,27 +6593,27 @@ msgid "Include posts made until this date" msgstr "Include posts made until this date" #. Checkbox shown when reporting a video. The value is the current playback position, formatted as minutes:seconds. -#: src/components/moderation/ReportDialog/index.tsx:701 +#: src/components/moderation/ReportDialog/index.tsx:702 msgid "Include video timestamp ({videoTimestamp})" msgstr "Include video timestamp ({videoTimestamp})" -#: src/screens/Login/LoginForm.tsx:165 +#: src/screens/Login/LoginForm.tsx:164 msgid "Incorrect username or password" msgstr "" -#: src/screens/Login/SetNewPasswordForm.tsx:124 +#: src/screens/Login/SetNewPasswordForm.tsx:129 msgid "Input code sent to your email for password reset" msgstr "" -#: src/screens/Login/SetNewPasswordForm.tsx:148 +#: src/screens/Login/SetNewPasswordForm.tsx:153 msgid "Input new password" msgstr "" -#: src/screens/Login/LoginForm.tsx:456 +#: src/screens/Login/LoginForm.tsx:457 msgid "Input the code which has been emailed to you" msgstr "" -#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:136 +#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:137 msgid "Interaction limited" msgstr "" @@ -6641,8 +6641,8 @@ msgstr "Introducing group chats" msgid "Introducing saved posts AKA bookmarks" msgstr "" -#: src/screens/Login/LoginForm.tsx:156 -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:71 +#: src/screens/Login/LoginForm.tsx:155 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:79 msgid "Invalid 2FA confirmation code." msgstr "" @@ -6654,7 +6654,7 @@ msgstr "Invalid group chat code." msgid "Invalid handle. Please try a different one." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:401 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:397 msgctxt "toast" msgid "Invalid interaction settings." msgstr "" @@ -6664,24 +6664,24 @@ msgstr "" msgid "Invalid phone number" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:139 +#: src/components/moderation/ReportDialog/index.tsx:140 msgid "Invalid report subject" msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:200 -#: src/screens/Messages/components/OutgoingRequestListItem.tsx:41 +#: src/components/intents/GroupChatJoinDialog.tsx:205 +#: src/screens/Messages/components/OutgoingRequestListItem.tsx:39 msgid "Invalid rescind request." msgstr "Invalid rescind request." -#: src/components/intents/VerifyEmailIntentDialog.tsx:86 +#: src/components/intents/VerifyEmailIntentDialog.tsx:87 msgid "Invalid Verification Code" msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:587 +#: src/components/contacts/screens/ViewMatches.tsx:595 msgid "Invite" msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:567 +#: src/components/contacts/screens/ViewMatches.tsx:575 msgid "Invite {name} to join Bluesky" msgstr "" @@ -6703,16 +6703,16 @@ msgstr "Invite friends" msgid "Invite Friends" msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:301 +#: src/components/contacts/screens/ViewMatches.tsx:309 msgid "Invite friends <0/>" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:193 -#: src/screens/Messages/components/InviteLinkDialog.tsx:329 -#: src/screens/Messages/components/InviteLinkDialog.tsx:335 -#: src/screens/Messages/components/InviteLinkDialog.tsx:514 +#: src/screens/Messages/components/InviteLinkDialog.tsx:190 +#: src/screens/Messages/components/InviteLinkDialog.tsx:326 +#: src/screens/Messages/components/InviteLinkDialog.tsx:332 +#: src/screens/Messages/components/InviteLinkDialog.tsx:511 #: src/screens/Messages/components/MessagesListGroupInfoPanel.tsx:149 -#: src/screens/Messages/ConversationSettings/index.tsx:557 +#: src/screens/Messages/ConversationSettings/index.tsx:552 msgid "Invite link" msgstr "Invite link" @@ -6726,20 +6726,20 @@ msgstr "Invite link" msgid "Invite link copied" msgstr "Invite link copied" -#: src/components/dms/getSystemMessageInfo.ts:120 +#: src/components/dms/getSystemMessageInfo.ts:140 msgid "Invite link created" msgstr "Invite link created" -#: src/components/dms/getSystemMessageInfo.ts:138 -#: src/screens/Messages/components/InviteLinkDialog.tsx:329 +#: src/components/dms/getSystemMessageInfo.ts:164 +#: src/screens/Messages/components/InviteLinkDialog.tsx:326 msgid "Invite link disabled" msgstr "Invite link disabled" -#: src/components/dms/getSystemMessageInfo.ts:126 +#: src/components/dms/getSystemMessageInfo.ts:148 msgid "Invite link edited" msgstr "Invite link edited" -#: src/components/dms/getSystemMessageInfo.ts:132 +#: src/components/dms/getSystemMessageInfo.ts:156 msgid "Invite link enabled" msgstr "Invite link enabled" @@ -6773,16 +6773,16 @@ msgid "It's correct" msgstr "" #. placeholder {0}: getName(items[0]) -#: src/screens/StarterPack/Wizard/index.tsx:483 +#: src/screens/StarterPack/Wizard/index.tsx:479 msgid "It's just <0>{0} right now! Add more people to your starter pack by searching above." msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:478 +#: src/screens/StarterPack/Wizard/index.tsx:474 msgid "It's just you right now! Add more people to your starter pack by searching above." msgstr "" #. placeholder {0}: videoState.jobId -#: src/view/com/composer/Composer.tsx:2648 +#: src/view/com/composer/Composer.tsx:2672 msgid "Job ID: {0}" msgstr "" @@ -6791,29 +6791,29 @@ msgstr "" msgid "Jobs" msgstr "" -#: src/components/dms/ChatInvite/Root.tsx:117 -#: src/components/intents/GroupChatJoinDialog.tsx:296 +#: src/components/dms/ChatInvite/Root.tsx:118 +#: src/components/intents/GroupChatJoinDialog.tsx:301 msgid "Join" msgstr "Join" -#: src/screens/StarterPack/StarterPackLandingScreen.tsx:210 -#: src/screens/StarterPack/StarterPackLandingScreen.tsx:215 -#: src/screens/StarterPack/StarterPackScreen.tsx:478 -#: src/screens/StarterPack/StarterPackScreen.tsx:488 +#: src/screens/StarterPack/StarterPackLandingScreen.tsx:202 +#: src/screens/StarterPack/StarterPackLandingScreen.tsx:207 +#: src/screens/StarterPack/StarterPackScreen.tsx:471 +#: src/screens/StarterPack/StarterPackScreen.tsx:481 msgid "Join Bluesky" msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:72 -#: src/components/intents/GroupChatJoinDialog.tsx:464 +#: src/components/intents/GroupChatJoinDialog.tsx:70 +#: src/components/intents/GroupChatJoinDialog.tsx:469 msgid "Join group chat" msgstr "Join group chat" -#: src/components/intents/GroupChatJoinDialog.tsx:189 -#: src/screens/Messages/components/OutgoingRequestListItem.tsx:31 +#: src/components/intents/GroupChatJoinDialog.tsx:194 +#: src/screens/Messages/components/OutgoingRequestListItem.tsx:29 msgid "Join request rescinded." msgstr "Join request rescinded." -#: src/components/StarterPack/QrCode.tsx:72 +#: src/components/StarterPack/QrCode.tsx:67 #: src/view/shell/NavSignupCard.tsx:41 msgid "Join the conversation" msgstr "" @@ -6822,22 +6822,22 @@ msgstr "" msgid "Journalism" msgstr "" -#: src/view/com/composer/Composer.tsx:1573 -#: src/view/com/composer/Composer.tsx:1583 +#: src/view/com/composer/Composer.tsx:1597 +#: src/view/com/composer/Composer.tsx:1607 #: src/view/com/composer/drafts/DraftsButton.tsx:135 msgid "Keep editing" msgstr "" -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:230 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:227 msgid "Keep me posted" msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:200 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:209 msgid "KWS website" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:631 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:633 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:627 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:629 msgid "Label reply for algo" msgstr "Label reply for algo" @@ -6851,7 +6851,7 @@ msgid "Labeled by the author." msgstr "" #: src/view/com/composer/labels/LabelsBtn.tsx:70 -#: src/view/screens/Profile.tsx:234 +#: src/view/screens/Profile.tsx:231 msgid "Labels" msgstr "" @@ -6880,8 +6880,8 @@ msgid "Language Settings" msgstr "" #: src/screens/Settings/LanguageSettings.tsx:98 -#: src/screens/Settings/Settings.tsx:241 -#: src/screens/Settings/Settings.tsx:244 +#: src/screens/Settings/Settings.tsx:243 +#: src/screens/Settings/Settings.tsx:246 msgid "Languages" msgstr "" @@ -6899,9 +6899,9 @@ msgstr "" msgid "Last initiated just now" msgstr "" -#: src/screens/Hashtag.tsx:97 +#: src/screens/Hashtag.tsx:99 #: src/screens/Search/SearchResults.tsx:95 -#: src/screens/Topic.tsx:64 +#: src/screens/Topic.tsx:66 msgid "Latest" msgstr "" @@ -6938,7 +6938,7 @@ msgstr "" #: src/components/contacts/screens/PhoneInput.tsx:303 #: src/screens/Onboarding/StepFindContactsIntro/index.tsx:53 -#: src/screens/Settings/FindContactsSettings.tsx:140 +#: src/screens/Settings/FindContactsSettings.tsx:138 msgctxt "english-only-resource" msgid "Learn more about importing contacts" msgstr "" @@ -6961,7 +6961,7 @@ msgstr "" msgid "Learn more about these changes and how to share your thoughts with us by reading our blog post." msgstr "" -#: src/components/moderation/PostHider.tsx:116 +#: src/components/moderation/PostHider.tsx:113 #: src/components/moderation/ScreenHider.tsx:132 msgid "Learn more about this warning" msgstr "" @@ -6977,7 +6977,7 @@ msgstr "" msgid "Learn more about what is public on Bluesky." msgstr "" -#: src/screens/Profile/components/GermButton.tsx:212 +#: src/screens/Profile/components/GermButton.tsx:213 msgid "Learn more about your Germ DM link" msgstr "" @@ -6991,8 +6991,8 @@ msgstr "" msgid "Learn more." msgstr "" -#: src/components/dms/LeaveConvoPrompt.tsx:59 -#: src/screens/Messages/ConversationSettings/index.tsx:591 +#: src/components/dms/LeaveConvoPrompt.tsx:63 +#: src/screens/Messages/ConversationSettings/index.tsx:586 msgid "Leave" msgstr "" @@ -7004,10 +7004,10 @@ msgstr "Leave" #: src/components/dms/MessagesListBlockedFooter.tsx:109 #: src/components/dms/MessagesListBlockedFooter.tsx:116 -#: src/components/moderation/BlockDialog.tsx:381 -#: src/components/moderation/BlockDialog.tsx:388 +#: src/components/moderation/BlockDialog.tsx:382 +#: src/components/moderation/BlockDialog.tsx:389 #: src/screens/Messages/components/ChatEnded.tsx:66 -#: src/screens/Messages/components/ChatLocked.tsx:112 +#: src/screens/Messages/components/ChatLocked.tsx:116 msgid "Leave chat" msgstr "" @@ -7017,7 +7017,7 @@ msgstr "" #: src/components/dms/ConvoMenu.tsx:240 #: src/components/dms/ConvoMenu.tsx:308 #: src/components/dms/ConvoMenu.tsx:312 -#: src/components/dms/LeaveConvoPrompt.tsx:53 +#: src/components/dms/LeaveConvoPrompt.tsx:57 msgid "Leave conversation" msgstr "" @@ -7032,7 +7032,7 @@ msgstr "Leave conversation" msgid "Leave group chat" msgstr "Leave group chat" -#: src/screens/Messages/ConversationSettings/index.tsx:590 +#: src/screens/Messages/ConversationSettings/index.tsx:585 msgid "Leave this group chat" msgstr "Leave this group chat" @@ -7045,11 +7045,11 @@ msgstr "" msgid "Leaving this chat will lock it permanently and you won’t be able to rejoin." msgstr "Leaving this chat will lock it permanently and you won’t be able to rejoin." -#: src/components/moderation/BlockDialog.tsx:274 +#: src/components/moderation/BlockDialog.tsx:271 msgid "Left group chat." msgstr "Left group chat." -#: src/screens/SignupQueued.tsx:158 +#: src/screens/SignupQueued.tsx:165 msgid "left to go." msgstr "" @@ -7066,8 +7066,8 @@ msgstr "" msgid "Let's go!" msgstr "" -#: src/components/dialogs/Embed.tsx:159 -#: src/components/dialogs/Embed.tsx:161 +#: src/components/dialogs/Embed.tsx:160 +#: src/components/dialogs/Embed.tsx:162 #: src/screens/Settings/AppearanceSettings.tsx:87 msgid "Light" msgstr "" @@ -7083,7 +7083,7 @@ msgstr "" #. Accessibility label for the like button when the post has not been liked, verb form followed by number of likes and noun form #. placeholder {0}: post.likeCount || 0 -#: src/components/PostControls/index.tsx:284 +#: src/components/PostControls/index.tsx:280 msgid "Like ({0, plural, one {# like} other {# likes}})" msgstr "" @@ -7100,7 +7100,7 @@ msgstr "" msgid "Like this feed" msgstr "" -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:149 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:145 msgid "Like this labeler" msgstr "" @@ -7117,16 +7117,16 @@ msgstr "" #. placeholder {0}: count || 0 #. placeholder {0}: feed.likeCount || 0 -#: src/components/FeedCard.tsx:249 -#: src/view/com/feeds/FeedSourceCard.tsx:173 +#: src/components/FeedCard.tsx:246 +#: src/view/com/feeds/FeedSourceCard.tsx:171 msgid "Liked by {0, plural, one {# user} other {# users}}" msgstr "" #. Social proof below the post stats; the bolded name is a person the viewer follows who liked the post #. placeholder {0}: nameLink(names[0]) #. placeholder {0}: names[0].displayName -#: src/screens/PostThread/components/LikesStat.tsx:132 -#: src/screens/PostThread/components/LikesStat.tsx:173 +#: src/screens/PostThread/components/LikesStat.tsx:134 +#: src/screens/PostThread/components/LikesStat.tsx:175 msgid "Liked by {0}" msgstr "Liked by {0}" @@ -7135,22 +7135,22 @@ msgstr "Liked by {0}" #. placeholder {0}: names[0].displayName #. placeholder {1}: nameLink(names[1]) #. placeholder {1}: names[1].displayName -#: src/screens/PostThread/components/LikesStat.tsx:131 -#: src/screens/PostThread/components/LikesStat.tsx:169 +#: src/screens/PostThread/components/LikesStat.tsx:133 +#: src/screens/PostThread/components/LikesStat.tsx:171 msgid "Liked by {0} and {1}" msgstr "Liked by {0} and {1}" #: src/components/LabelingServiceCard/index.tsx:96 #: src/screens/CustomFeed/components/CustomFeedHeader.tsx:540 -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:169 -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:183 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:165 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:179 msgid "Liked by {likeCount, plural, one {# user} other {# users}}" msgstr "" #: src/lib/hooks/useNotificationHandler.ts:158 #: src/screens/Settings/NotificationSettings/index.tsx:159 #: src/screens/Settings/NotificationSettings/index.tsx:314 -#: src/view/screens/Profile.tsx:240 +#: src/view/screens/Profile.tsx:237 msgid "Likes" msgstr "" @@ -7160,7 +7160,7 @@ msgstr "" msgid "Likes of your reposts" msgstr "" -#: src/screens/PostThread/components/LikesStat.tsx:39 +#: src/screens/PostThread/components/LikesStat.tsx:41 msgid "Likes on this post" msgstr "" @@ -7177,7 +7177,7 @@ msgstr "Link copied to clipboard" msgid "List" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:397 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:405 msgid "List avatar" msgstr "" @@ -7188,8 +7188,8 @@ msgstr "" #. placeholder {0}: sanitizeHandle(creator.handle, '@') #. placeholder {0}: sanitizeHandle(feed.creatorHandle, '@') -#: src/components/ListCard.tsx:153 -#: src/view/com/feeds/FeedSourceCard.tsx:153 +#: src/components/ListCard.tsx:150 +#: src/view/com/feeds/FeedSourceCard.tsx:151 msgid "List by {0}" msgstr "" @@ -7201,7 +7201,7 @@ msgstr "" msgid "List by you" msgstr "" -#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:127 +#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:126 msgid "List created, but failed to add some members" msgstr "" @@ -7209,16 +7209,16 @@ msgstr "" msgid "List creator" msgstr "" -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:93 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:94 msgctxt "toast" msgid "List deleted" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:446 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:454 msgid "List description" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:466 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:474 msgid "List description is too long. {DESCRIPTION_MAX_GRAPHEMES, plural, other {The maximum number of characters is #.}}" msgstr "" @@ -7226,11 +7226,11 @@ msgstr "" msgid "List has been hidden" msgstr "" -#: src/screens/ProfileList/index.tsx:175 +#: src/screens/ProfileList/index.tsx:172 msgid "List Hidden" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:438 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:446 msgid "List must have a name." msgstr "" @@ -7239,36 +7239,36 @@ msgctxt "toast" msgid "List muted" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:410 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:418 msgid "List name" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:430 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:438 msgid "List name is too long. {DISPLAY_NAME_MAX_GRAPHEMES, plural, other {The maximum number of characters is #.}}" msgstr "" -#: src/screens/ProfileList/components/Header.tsx:115 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:130 +#: src/screens/ProfileList/components/Header.tsx:116 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:131 msgctxt "toast" msgid "List unblocked" msgstr "" -#: src/screens/ProfileList/components/Header.tsx:101 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:116 +#: src/screens/ProfileList/components/Header.tsx:102 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:117 msgctxt "toast" msgid "List unmuted" msgstr "" #: src/Navigation.tsx:174 #: src/view/screens/Lists.tsx:60 -#: src/view/screens/Profile.tsx:235 -#: src/view/screens/Profile.tsx:243 +#: src/view/screens/Profile.tsx:232 +#: src/view/screens/Profile.tsx:240 #: src/view/shell/desktop/LeftNav.tsx:722 #: src/view/shell/Drawer.tsx:607 msgid "Lists" msgstr "" -#: src/view/com/lists/MyLists.tsx:72 +#: src/view/com/lists/MyLists.tsx:75 msgid "Lists allow you to see content from your favorite people." msgstr "" @@ -7297,23 +7297,23 @@ msgstr "" msgid "Live event unhidden" msgstr "" -#: src/features/liveNow/components/LiveStatusDialog.tsx:245 +#: src/features/liveNow/components/LiveStatusDialog.tsx:242 msgid "Live feature is in beta" msgstr "" -#: src/features/liveNow/components/EditLiveDialog.tsx:142 -#: src/features/liveNow/components/EditLiveDialog.tsx:146 +#: src/features/liveNow/components/EditLiveDialog.tsx:144 +#: src/features/liveNow/components/EditLiveDialog.tsx:148 #: src/features/liveNow/components/GoLiveDialog.tsx:137 #: src/features/liveNow/components/GoLiveDialog.tsx:141 msgid "Live link" msgstr "" -#: src/screens/Search/Explore.tsx:87 +#: src/screens/Search/Explore.tsx:83 msgid "Load more" msgstr "" -#: src/screens/Search/Explore.tsx:515 -#: src/screens/Search/Explore.tsx:606 +#: src/screens/Search/Explore.tsx:511 +#: src/screens/Search/Explore.tsx:602 msgid "Load more suggested feeds" msgstr "" @@ -7328,16 +7328,16 @@ msgstr "" msgid "Load new posts" msgstr "" -#: src/screens/Messages/components/MessageComposer.tsx:254 +#: src/screens/Messages/components/MessageComposer.tsx:255 msgctxt "placeholder" msgid "Loading chat…" msgstr "Loading chat…" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:575 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:572 msgid "Loading lists..." msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:279 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:276 msgid "Loading post interaction settings..." msgstr "" @@ -7345,7 +7345,7 @@ msgstr "" msgid "Loading..." msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:571 +#: src/screens/Messages/ConversationSettings/index.tsx:566 msgid "Lock" msgstr "Lock" @@ -7357,11 +7357,11 @@ msgstr "Lock group chat" msgid "Lock group chat?" msgstr "Lock group chat?" -#: src/screens/Messages/ConversationSettings/index.tsx:569 +#: src/screens/Messages/ConversationSettings/index.tsx:564 msgid "Lock this group chat" msgstr "Lock this group chat" -#: src/screens/Messages/ConversationSettings/index.tsx:571 +#: src/screens/Messages/ConversationSettings/index.tsx:566 msgid "Locked" msgstr "Locked" @@ -7396,7 +7396,7 @@ msgstr "" msgid "Longer videos" msgstr "Longer videos" -#: src/screens/Login/SetNewPasswordForm.tsx:114 +#: src/screens/Login/SetNewPasswordForm.tsx:119 msgid "Looks like XXXXX-XXXXX" msgstr "" @@ -7438,7 +7438,7 @@ msgstr "" msgid "Manage verification settings" msgstr "" -#: src/components/dialogs/MutedWords.tsx:111 +#: src/components/dialogs/MutedWords.tsx:113 msgid "Manage your muted words and tags" msgstr "" @@ -7447,8 +7447,8 @@ msgstr "" msgid "Manual" msgstr "Manual" -#: src/screens/Messages/Inbox.tsx:312 -#: src/screens/Messages/Inbox.tsx:318 +#: src/screens/Messages/Inbox.tsx:311 +#: src/screens/Messages/Inbox.tsx:317 msgid "Mark all as read" msgstr "" @@ -7471,7 +7471,7 @@ msgstr "Mark all requests as read" msgid "Mark as read" msgstr "" -#: src/screens/Messages/Inbox.tsx:299 +#: src/screens/Messages/Inbox.tsx:298 msgid "Marked all as read" msgstr "" @@ -7495,7 +7495,7 @@ msgid "Me" msgstr "Me" #: src/screens/Search/components/AdvancedSearchDialog/index.tsx:375 -#: src/view/screens/Profile.tsx:238 +#: src/view/screens/Profile.tsx:235 msgid "Media" msgstr "" @@ -7526,7 +7526,7 @@ msgstr "Members" msgid "Members can still read chat history but can’t send new messages." msgstr "Members can still read chat history but can’t send new messages." -#: src/components/WhoCanReply.tsx:320 +#: src/components/WhoCanReply.tsx:313 msgid "mentioned users" msgstr "" @@ -7551,7 +7551,7 @@ msgctxt "action" msgid "Message" msgstr "Message" -#: src/screens/Messages/components/MessageComposer.tsx:255 +#: src/screens/Messages/components/MessageComposer.tsx:256 msgctxt "description" msgid "Message" msgstr "Message" @@ -7565,7 +7565,7 @@ msgstr "" msgid "Message {displayName}" msgstr "Message {displayName}" -#: src/screens/Messages/components/ChatListItem.tsx:322 +#: src/screens/Messages/components/ChatListItem.tsx:327 msgid "Message deleted" msgstr "" @@ -7574,30 +7574,30 @@ msgctxt "toast" msgid "Message deleted" msgstr "" -#: src/components/dms/MessageItem.tsx:637 +#: src/components/dms/MessageItem.tsx:652 msgid "Message failed to send." msgstr "Message failed to send." #. placeholder {0}: sender?.handle ?? 'unknown' #. placeholder {1}: message.text -#: src/components/dms/MessageContextMenu.tsx:166 +#: src/components/dms/MessageContextMenu.tsx:164 msgid "Message from @{0}: {1}" msgstr "" #. placeholder {0}: rawError.message -#: src/view/com/posts/PostFeedErrorMessage.tsx:209 +#: src/view/com/posts/PostFeedErrorMessage.tsx:207 msgid "Message from server: {0}" msgstr "" -#: src/screens/Messages/components/MessageComposer.tsx:251 +#: src/screens/Messages/components/MessageComposer.tsx:252 msgid "Message input field" msgstr "" -#: src/screens/Messages/components/MessageComposer.tsx:116 +#: src/screens/Messages/components/MessageComposer.tsx:117 msgid "Message is too long ({graphemeCount}/{MAX_DM_GRAPHEME_LENGTH})" msgstr "Message is too long ({graphemeCount}/{MAX_DM_GRAPHEME_LENGTH})" -#: src/components/dms/MessageContextMenu.tsx:165 +#: src/components/dms/MessageContextMenu.tsx:163 msgid "Message options" msgstr "" @@ -7605,11 +7605,11 @@ msgstr "" msgid "Messages" msgstr "" -#: src/components/dms/MessageItem.tsx:736 +#: src/components/dms/MessageItem.tsx:751 msgid "Messages from this person are hidden while they are blocking you." msgstr "Messages from this person are hidden while they are blocking you." -#: src/components/dms/MessageItem.tsx:731 +#: src/components/dms/MessageItem.tsx:746 msgid "Messages from this person are hidden while you are blocking them." msgstr "Messages from this person are hidden while you are blocking them." @@ -7618,11 +7618,11 @@ msgctxt "Name of app icon variant" msgid "Midnight" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:177 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:179 msgid "Minor harassment or bullying" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:35 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:36 msgid "Misleading" msgstr "" @@ -7635,8 +7635,8 @@ msgstr "" msgid "Moderation" msgstr "" -#: src/screens/Settings/Settings.tsx:191 -#: src/screens/Settings/Settings.tsx:194 +#: src/screens/Settings/Settings.tsx:193 +#: src/screens/Settings/Settings.tsx:196 msgid "Moderation and content filters" msgstr "Moderation and content filters" @@ -7647,7 +7647,7 @@ msgstr "" #. placeholder {0}: sanitizeHandle(creator.handle, '@') #. placeholder {0}: sanitizeHandle(list.creator.handle, '@') #: src/components/dialogs/lists/UserAddRemoveListsDialog.tsx:311 -#: src/components/ListCard.tsx:152 +#: src/components/ListCard.tsx:149 msgid "Moderation list by {0}" msgstr "" @@ -7660,12 +7660,12 @@ msgstr "" msgid "Moderation list by you" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:256 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:264 msgctxt "toast" msgid "Moderation list created" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:242 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:250 msgctxt "toast" msgid "Moderation list updated" msgstr "" @@ -7692,7 +7692,7 @@ msgid "Moderation tools" msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:72 -#: src/lib/moderation/useModerationCauseDescription.ts:48 +#: src/lib/moderation/useModerationCauseDescription.ts:45 msgid "Moderator has chosen to set a general warning on the content." msgstr "" @@ -7706,7 +7706,7 @@ msgstr "" msgid "More languages..." msgstr "" -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:144 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:145 #: src/view/com/composer/drafts/DraftItem.tsx:190 #: src/view/com/profile/ProfileMenu.tsx:292 #: src/view/com/profile/ProfileMenu.tsx:299 @@ -7729,7 +7729,7 @@ msgstr "" msgid "Music" msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:536 +#: src/screens/Messages/ConversationSettings/index.tsx:531 msgid "Mute" msgstr "Mute" @@ -7745,8 +7745,8 @@ msgstr "" msgid "Mute {0}" msgstr "Mute {0}" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:755 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:761 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:751 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:757 #: src/view/com/profile/ProfileMenu.tsx:512 #: src/view/com/profile/ProfileMenu.tsx:519 msgid "Mute account" @@ -7762,7 +7762,7 @@ msgstr "" msgid "Mute conversation" msgstr "" -#: src/components/dialogs/MutedWords.tsx:262 +#: src/components/dialogs/MutedWords.tsx:264 msgid "Mute in:" msgstr "" @@ -7774,41 +7774,41 @@ msgstr "" msgid "Mute these accounts?" msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:534 +#: src/screens/Messages/ConversationSettings/index.tsx:529 msgid "Mute this group chat" msgstr "Mute this group chat" -#: src/components/dialogs/MutedWords.tsx:194 +#: src/components/dialogs/MutedWords.tsx:196 msgid "Mute this word for 24 hours" msgstr "" -#: src/components/dialogs/MutedWords.tsx:233 +#: src/components/dialogs/MutedWords.tsx:235 msgid "Mute this word for 30 days" msgstr "" -#: src/components/dialogs/MutedWords.tsx:218 +#: src/components/dialogs/MutedWords.tsx:220 msgid "Mute this word for 7 days" msgstr "" -#: src/components/dialogs/MutedWords.tsx:267 +#: src/components/dialogs/MutedWords.tsx:269 msgid "Mute this word in post text and tags" msgstr "" -#: src/components/dialogs/MutedWords.tsx:283 +#: src/components/dialogs/MutedWords.tsx:285 msgid "Mute this word in tags only" msgstr "" -#: src/components/dialogs/MutedWords.tsx:179 +#: src/components/dialogs/MutedWords.tsx:181 msgid "Mute this word until you unmute it" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:646 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:649 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:642 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:645 msgid "Mute thread" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:659 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:661 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:655 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:657 msgid "Mute words & tags" msgstr "" @@ -7816,7 +7816,7 @@ msgstr "" msgid "Mute, leave, or remove people anytime. It’s your chat." msgstr "Mute, leave, or remove people anytime. It’s your chat." -#: src/screens/Messages/ConversationSettings/index.tsx:536 +#: src/screens/Messages/ConversationSettings/index.tsx:531 msgid "Muted" msgstr "Muted" @@ -7834,7 +7834,7 @@ msgid "Muted accounts have their posts removed from your feed and from your noti msgstr "" #. placeholder {0}: cause.source.list.name -#: src/lib/moderation/useModerationCauseDescription.ts:93 +#: src/lib/moderation/useModerationCauseDescription.ts:90 msgid "Muted by \"{0}\"" msgstr "" @@ -7846,7 +7846,7 @@ msgstr "" msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." msgstr "" -#: src/components/moderation/BlockDialog.tsx:174 +#: src/components/moderation/BlockDialog.tsx:171 msgid "Mutual group chats" msgstr "Mutual group chats" @@ -7867,7 +7867,7 @@ msgstr "" msgid "My starter pack" msgstr "My starter pack" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:416 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:424 msgid "Name" msgstr "" @@ -7877,18 +7877,18 @@ msgstr "" #. placeholder {0}: record.name #. placeholder {0}: view.record.name -#: src/components/StarterPack/StarterPackCard.tsx:134 -#: src/components/StarterPack/StarterPackCard.tsx:169 +#: src/components/StarterPack/StarterPackCard.tsx:127 +#: src/components/StarterPack/StarterPackCard.tsx:157 msgid "Navigate to {0}" msgstr "" -#: src/components/StarterPack/StarterPackCard.tsx:135 +#: src/components/StarterPack/StarterPackCard.tsx:128 msgid "Navigate to starter pack" msgstr "" -#: src/screens/Login/ForgotPasswordForm.tsx:144 -#: src/screens/Login/ForgotPasswordForm.tsx:169 -#: src/screens/Login/LoginForm.tsx:555 +#: src/screens/Login/ForgotPasswordForm.tsx:148 +#: src/screens/Login/ForgotPasswordForm.tsx:173 +#: src/screens/Login/LoginForm.tsx:556 msgid "Navigates to the next screen" msgstr "" @@ -7896,8 +7896,8 @@ msgstr "" msgid "Navigates to your profile" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:414 -#: src/components/moderation/ReportDialog/index.tsx:430 +#: src/components/moderation/ReportDialog/index.tsx:415 +#: src/components/moderation/ReportDialog/index.tsx:431 msgid "Need to report a copyright violation, legal request, or regulatory compliance issue?" msgstr "" @@ -7920,16 +7920,16 @@ msgctxt "nux-description" msgid "New" msgstr "New" -#: src/view/com/notifications/NotificationFeedItem.tsx:559 +#: src/view/com/notifications/NotificationFeedItem.tsx:551 msgid "New {postsCount, plural, one {post} other {posts}} from {firstAuthorLink}" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:543 +#: src/view/com/notifications/NotificationFeedItem.tsx:535 msgid "New {postsCount, plural, one {post} other {posts}} from {firstAuthorName}" msgstr "" -#: src/components/dms/dialogs/NewChatDialog.tsx:169 -#: src/components/dms/dialogs/NewChatDialog.tsx:184 +#: src/components/dms/dialogs/NewChatDialog.tsx:162 +#: src/components/dms/dialogs/NewChatDialog.tsx:177 #: src/screens/Messages/ChatList.tsx:231 #: src/screens/Messages/ChatList.tsx:232 #: src/screens/Messages/ChatList.tsx:455 @@ -8013,18 +8013,18 @@ msgstr "New message requests" msgid "New messages" msgstr "New messages" -#: src/screens/Login/SetNewPasswordForm.tsx:130 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:204 +#: src/screens/Login/SetNewPasswordForm.tsx:135 #: src/screens/Settings/components/ChangePasswordDialog.tsx:208 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:212 msgid "New password" msgstr "" #: src/screens/CustomFeed/index.tsx:218 -#: src/screens/ProfileList/index.tsx:237 -#: src/screens/ProfileList/index.tsx:280 +#: src/screens/ProfileList/index.tsx:234 +#: src/screens/ProfileList/index.tsx:277 #: src/view/screens/Feeds.tsx:545 #: src/view/screens/Notifications.tsx:165 -#: src/view/screens/Profile.tsx:607 +#: src/view/screens/Profile.tsx:604 msgid "New post" msgstr "" @@ -8034,23 +8034,23 @@ msgctxt "action" msgid "New post" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:548 +#: src/view/com/notifications/NotificationFeedItem.tsx:540 msgid "New posts from {firstAuthorLink} and <0>{additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} " msgstr "New posts from {firstAuthorLink} and <0>{additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}} " -#: src/view/com/notifications/NotificationFeedItem.tsx:536 +#: src/view/com/notifications/NotificationFeedItem.tsx:528 msgid "New posts from {firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}}" msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:202 +#: src/components/dialogs/StarterPackDialog.tsx:199 msgid "New starter pack" msgstr "" -#: src/screens/Login/LoginForm.tsx:569 +#: src/screens/Login/LoginForm.tsx:570 msgid "New to Bluesky? <0>Sign up" msgstr "New to Bluesky? <0>Sign up" -#: src/components/NewskieDialog.tsx:122 +#: src/components/NewskieDialog.tsx:123 msgid "New user info dialog" msgstr "" @@ -8062,27 +8062,27 @@ msgid "Newest replies first" msgstr "" #: src/lib/interests.ts:67 -#: src/screens/Search/modules/ExploreTrendingTopics.tsx:248 +#: src/screens/Search/modules/ExploreTrendingTopics.tsx:246 msgid "News" msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:395 -#: src/components/contacts/screens/ViewMatches.tsx:410 +#: src/components/contacts/screens/ViewMatches.tsx:403 +#: src/components/contacts/screens/ViewMatches.tsx:418 #: src/components/dms/AddMembersFlow.tsx:325 #: src/components/dms/InitiateChatFlow.tsx:599 -#: src/screens/Login/ForgotPasswordForm.tsx:143 -#: src/screens/Login/ForgotPasswordForm.tsx:150 -#: src/screens/Login/SetNewPasswordForm.tsx:171 -#: src/screens/Login/SetNewPasswordForm.tsx:177 +#: src/screens/Login/ForgotPasswordForm.tsx:147 +#: src/screens/Login/ForgotPasswordForm.tsx:154 +#: src/screens/Login/SetNewPasswordForm.tsx:176 +#: src/screens/Login/SetNewPasswordForm.tsx:182 #: src/screens/Onboarding/StepFinished/index.tsx:330 #: src/screens/Onboarding/StepFinished/index.tsx:339 #: src/screens/Settings/components/AddAppPasswordDialog.tsx:157 #: src/screens/Settings/components/AddAppPasswordDialog.tsx:165 #: src/screens/Signup/BackNextButtons.tsx:68 +#: src/screens/StarterPack/Wizard/index.tsx:194 #: src/screens/StarterPack/Wizard/index.tsx:198 -#: src/screens/StarterPack/Wizard/index.tsx:202 -#: src/screens/StarterPack/Wizard/index.tsx:384 -#: src/screens/StarterPack/Wizard/index.tsx:391 +#: src/screens/StarterPack/Wizard/index.tsx:380 +#: src/screens/StarterPack/Wizard/index.tsx:387 msgid "Next" msgstr "" @@ -8094,8 +8094,8 @@ msgstr "" msgid "Night" msgstr "Night" -#: src/components/moderation/ReportDialog/index.tsx:1010 -#: src/components/moderation/ReportDialog/index.tsx:1019 +#: src/components/moderation/ReportDialog/index.tsx:1011 +#: src/components/moderation/ReportDialog/index.tsx:1020 msgctxt "Answer to a yes/no question" msgid "No" msgstr "No" @@ -8117,11 +8117,11 @@ msgstr "No author filter" msgid "No beta features at the moment." msgstr "No beta features at the moment." -#: src/components/contacts/screens/ViewMatches.tsx:681 +#: src/components/contacts/screens/ViewMatches.tsx:689 msgid "No contacts found" msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:659 +#: src/components/contacts/screens/ViewMatches.tsx:667 msgid "No contacts with the name “{query}” found" msgstr "" @@ -8138,11 +8138,11 @@ msgstr "" msgid "No drafts yet" msgstr "" -#: src/features/liveNow/components/EditLiveDialog.tsx:134 +#: src/features/liveNow/components/EditLiveDialog.tsx:136 msgid "No expiry set" msgstr "" -#: src/screens/StarterPack/Wizard/StepFeeds.tsx:122 +#: src/screens/StarterPack/Wizard/StepFeeds.tsx:123 msgid "No feeds found. Try searching for something else." msgstr "" @@ -8170,9 +8170,9 @@ msgstr "" msgid "No language filter" msgstr "No language filter" -#: src/components/LikedByList.tsx:84 -#: src/view/com/post-thread/PostLikedBy.tsx:84 -#: src/view/screens/Profile.tsx:539 +#: src/components/LikedByList.tsx:90 +#: src/view/com/post-thread/PostLikedBy.tsx:90 +#: src/view/screens/Profile.tsx:536 msgid "No likes yet" msgstr "" @@ -8183,9 +8183,9 @@ msgstr "" #. placeholder {0}: sanitizeDisplayName( profile.displayName || profile.handle, moderation.ui('displayName'), ) #. placeholder {0}: sanitizeDisplayName( profile.displayName || profile.handle, moderation.ui('displayName'), ) #. placeholder {0}: sanitizeDisplayName( profile.displayName || profile.handle, ) -#: src/components/ProfileCard.tsx:523 +#: src/components/ProfileCard.tsx:520 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:273 -#: src/view/com/notifications/NotificationFeedItem.tsx:848 +#: src/view/com/notifications/NotificationFeedItem.tsx:841 msgid "No longer following {0}" msgstr "" @@ -8194,7 +8194,7 @@ msgstr "" msgid "No media filter" msgstr "No media filter" -#: src/view/screens/Profile.tsx:485 +#: src/view/screens/Profile.tsx:482 msgid "No media yet" msgstr "" @@ -8206,7 +8206,7 @@ msgstr "" msgid "No more doomscrolling junk-filled algorithms. Find feeds that work for you, not against you." msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:563 +#: src/components/contacts/screens/ViewMatches.tsx:571 msgid "No name" msgstr "" @@ -8231,11 +8231,11 @@ msgctxt "enable for" msgid "No one" msgstr "" -#: src/components/WhoCanReply.tsx:303 +#: src/components/WhoCanReply.tsx:296 msgid "No one but the author can quote this post." msgstr "" -#: src/screens/Messages/components/ChatLocked.tsx:73 +#: src/screens/Messages/components/ChatLocked.tsx:77 msgid "No one can send messages" msgstr "No one can send messages" @@ -8249,11 +8249,11 @@ msgid "No posts here" msgstr "" #: src/screens/Profile/Sections/Feed.tsx:84 -#: src/view/screens/Profile.tsx:443 +#: src/view/screens/Profile.tsx:440 msgid "No posts yet" msgstr "" -#: src/view/com/post-thread/PostQuotes.tsx:114 +#: src/view/com/post-thread/PostQuotes.tsx:107 msgid "No quotes yet" msgstr "" @@ -8262,7 +8262,7 @@ msgstr "" msgid "No recent GIFs yet. Pick one to see it here." msgstr "No recent GIFs yet. Pick one to see it here." -#: src/view/screens/Profile.tsx:470 +#: src/view/screens/Profile.tsx:467 msgid "No replies yet" msgstr "" @@ -8271,7 +8271,7 @@ msgid "No reposts yet" msgstr "" #: src/view/com/composer/text-input/mobile/Autocomplete.tsx:60 -#: src/view/com/composer/text-input/web/Autocomplete.tsx:192 +#: src/view/com/composer/text-input/web/Autocomplete.tsx:193 msgid "No result" msgstr "" @@ -8283,7 +8283,7 @@ msgid "No results" msgstr "" #. placeholder {0}: interestsDisplayNames[selectedInterest] -#: src/screens/Search/Explore.tsx:817 +#: src/screens/Search/Explore.tsx:813 msgid "No results for \"{0}\"." msgstr "" @@ -8308,11 +8308,11 @@ msgstr "No results found for “<0>{query}”." msgid "No results found for your query with advanced search filters applied." msgstr "No results found for your query with advanced search filters applied." -#: src/screens/Search/Explore.tsx:821 +#: src/screens/Search/Explore.tsx:817 msgid "No results." msgstr "" -#: src/view/screens/Profile.tsx:571 +#: src/view/screens/Profile.tsx:568 msgid "No Starter Packs yet" msgstr "" @@ -8325,20 +8325,20 @@ msgstr "" msgid "No translation received from your device." msgstr "No translation received from your device." -#: src/view/screens/Profile.tsx:512 +#: src/view/screens/Profile.tsx:509 msgid "No video posts yet" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:471 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:468 msgid "Nobody" msgstr "" -#: src/components/LikedByList.tsx:86 -#: src/view/com/post-thread/PostLikedBy.tsx:86 +#: src/components/LikedByList.tsx:92 +#: src/view/com/post-thread/PostLikedBy.tsx:92 msgid "Nobody has liked this yet. Maybe you should be the first!" msgstr "" -#: src/view/com/post-thread/PostQuotes.tsx:116 +#: src/view/com/post-thread/PostQuotes.tsx:109 msgid "Nobody has quoted this yet. Maybe you should be the first!" msgstr "" @@ -8350,7 +8350,7 @@ msgstr "" msgid "Nobody was found. Try searching for someone else." msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:80 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:81 msgid "Non-consensual intimate imagery" msgstr "" @@ -8376,16 +8376,16 @@ msgid "Not available on this platform" msgstr "Not available on this platform" #: src/screens/FindContactsFlowScreen.tsx:62 -#: src/screens/Settings/FindContactsSettings.tsx:106 +#: src/screens/Settings/FindContactsSettings.tsx:104 msgid "Not available on this platform." msgstr "" -#: src/components/KnownFollowers.tsx:254 +#: src/components/KnownFollowers.tsx:253 msgid "Not followed by anyone you’re following" msgstr "Not followed by anyone you’re following" #: src/Navigation.tsx:169 -#: src/view/screens/Profile.tsx:133 +#: src/view/screens/Profile.tsx:130 msgid "Not Found" msgstr "" @@ -8405,7 +8405,7 @@ msgstr "" msgid "Note: This post is only visible to logged-in users." msgstr "" -#: src/screens/Bookmarks.tsx:292 +#: src/screens/Bookmarks.tsx:290 msgid "Nothing saved yet" msgstr "" @@ -8427,8 +8427,8 @@ msgstr "" #: src/screens/Notifications/ActivityList.tsx:31 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:93 #: src/screens/Settings/NotificationSettings/index.tsx:125 -#: src/screens/Settings/Settings.tsx:199 -#: src/screens/Settings/Settings.tsx:202 +#: src/screens/Settings/Settings.tsx:201 +#: src/screens/Settings/Settings.tsx:204 #: src/view/screens/Notifications.tsx:128 #: src/view/shell/bottom-bar/BottomBar.tsx:279 #: src/view/shell/desktop/LeftNav.tsx:687 @@ -8463,8 +8463,8 @@ msgid "Oh no!" msgstr "" #. Confirm button text. -#: src/components/contacts/screens/GetContacts.tsx:317 -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:227 +#: src/components/contacts/screens/GetContacts.tsx:319 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:223 #: src/screens/Search/modules/ExploreInterestsCard.tsx:44 #: src/screens/Settings/AppIconSettings/index.tsx:46 #: src/screens/Settings/AppIconSettings/index.tsx:232 @@ -8474,9 +8474,9 @@ msgstr "" #: src/components/BotAccountAlert.tsx:52 #: src/components/BotAccountAlert.tsx:57 #: src/components/dms/InitiateChatFlow.tsx:838 -#: src/components/dms/MessageItem.tsx:743 +#: src/components/dms/MessageItem.tsx:758 #: src/screens/Login/PasswordUpdatedForm.tsx:35 -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:658 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:655 msgid "Okay" msgstr "" @@ -8491,29 +8491,29 @@ msgstr "" msgid "On" msgstr "On" -#: src/components/StarterPack/QrCode.tsx:86 +#: src/components/StarterPack/QrCode.tsx:81 msgid "on<0><1/><2><3/>" msgstr "" -#: src/screens/Settings/Settings.tsx:407 +#: src/screens/Settings/Settings.tsx:409 msgid "Onboarding reset" msgstr "" -#: src/components/dms/dialogs/NewChatDialog.tsx:117 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:133 +#: src/components/dms/dialogs/NewChatDialog.tsx:108 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:124 msgid "One of the selected recipients does not allow group chats." msgstr "One of the selected recipients does not allow group chats." -#: src/components/dms/dialogs/NewChatDialog.tsx:100 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:116 +#: src/components/dms/dialogs/NewChatDialog.tsx:96 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:112 msgid "One of the selected recipients has blocked you and cannot be messaged." msgstr "One of the selected recipients has blocked you and cannot be messaged." -#: src/view/com/composer/Composer.tsx:967 +#: src/view/com/composer/Composer.tsx:983 msgid "One or more GIFs is missing alt text." msgstr "" -#: src/view/com/composer/Composer.tsx:964 +#: src/view/com/composer/Composer.tsx:980 msgid "One or more images is missing alt text." msgstr "" @@ -8525,16 +8525,16 @@ msgstr "" msgid "One or more of your selected files are too large. Maximum size is {VIDEO_MAX_SIZE_MB} MB." msgstr "One or more of your selected files are too large. Maximum size is {VIDEO_MAX_SIZE_MB} MB." -#: src/view/com/composer/Composer.tsx:770 +#: src/view/com/composer/Composer.tsx:786 msgid "One or more posts are too long to save as a draft. {MAX_DRAFT_GRAPHEME_LENGTH, plural, one {The maximum number of characters is # character.} other {The maximum number of characters is # characters.}}" msgstr "" -#: src/view/com/composer/Composer.tsx:974 +#: src/view/com/composer/Composer.tsx:990 msgid "One or more videos is missing alt text." msgstr "" #. placeholder {0}: settings.map((rule, i) => ( )) -#: src/components/WhoCanReply.tsx:283 +#: src/components/WhoCanReply.tsx:276 msgid "Only {0} can reply." msgstr "" @@ -8542,19 +8542,19 @@ msgstr "" #. placeholder {0}: result.accepted.length #. placeholder {1}: next.length #. placeholder {2}: next.length -#: src/view/com/composer/Composer.tsx:235 +#: src/view/com/composer/Composer.tsx:237 msgid "Only {0} of {1} {2, plural, one {image} other {images}} added; limit is {MAX_GALLERY_IMAGES}" msgstr "Only {0} of {1} {2, plural, one {image} other {images}} added; limit is {MAX_GALLERY_IMAGES}" -#: src/screens/Messages/JoinRequests.tsx:200 +#: src/screens/Messages/JoinRequests.tsx:196 msgid "Only admins can accept join requests." msgstr "Only admins can accept join requests." -#: src/screens/Messages/JoinRequests.tsx:233 +#: src/screens/Messages/JoinRequests.tsx:228 msgid "Only admins can reject join requests." msgstr "Only admins can reject join requests." -#: src/components/intents/GroupChatJoinDialog.tsx:155 +#: src/components/intents/GroupChatJoinDialog.tsx:154 msgid "Only followers can join this group chat." msgstr "Only followers can join this group chat." @@ -8569,12 +8569,12 @@ msgid "Only image files are supported" msgstr "" #. placeholder {0}: createSanitizedDisplayName( joinLinkPreview.owner, true, moderateProfile(joinLinkPreview.owner, moderationOpts).ui( 'displayName', ), ) -#: src/screens/Messages/JoinRequest.tsx:218 +#: src/screens/Messages/JoinRequest.tsx:226 msgid "Only people {0} follows can join." msgstr "Only people {0} follows can join." -#: src/components/dms/ChatInvite/Root.tsx:127 -#: src/components/intents/GroupChatJoinDialog.tsx:306 +#: src/components/dms/ChatInvite/Root.tsx:128 +#: src/components/intents/GroupChatJoinDialog.tsx:311 msgid "Only people the chat owner follows can join" msgstr "Only people the chat owner follows can join" @@ -8611,7 +8611,7 @@ msgstr "Oops, this profile doesn't exist. Try scanning another one." #: src/components/StarterPack/ProfileStarterPacks.tsx:375 #: src/screens/Settings/AppPasswords.tsx:59 #: src/screens/Settings/components/ChangeHandleDialog.tsx:108 -#: src/view/screens/Profile.tsx:133 +#: src/view/screens/Profile.tsx:130 msgid "Oops!" msgstr "" @@ -8627,8 +8627,8 @@ msgstr "" msgid "Open camera" msgstr "" -#: src/components/dms/ChatInvite/Root.tsx:104 -#: src/components/intents/GroupChatJoinDialog.tsx:453 +#: src/components/dms/ChatInvite/Root.tsx:105 +#: src/components/intents/GroupChatJoinDialog.tsx:458 msgid "Open chat" msgstr "Open chat" @@ -8636,8 +8636,8 @@ msgstr "Open chat" msgid "Open chat member options for {displayName}" msgstr "Open chat member options for {displayName}" -#: src/screens/Messages/components/ChatListItem.tsx:490 -#: src/screens/Messages/components/ChatListItem.tsx:494 +#: src/screens/Messages/components/ChatListItem.tsx:505 +#: src/screens/Messages/components/ChatListItem.tsx:509 msgid "Open conversation options" msgstr "" @@ -8649,8 +8649,8 @@ msgstr "" msgid "Open drawer menu" msgstr "" -#: src/screens/Messages/components/MessageComposer.tsx:213 -#: src/view/com/composer/Composer.tsx:2281 +#: src/screens/Messages/components/MessageComposer.tsx:214 +#: src/view/com/composer/Composer.tsx:2305 msgid "Open emoji picker" msgstr "" @@ -8668,11 +8668,11 @@ msgstr "" msgid "Open full emoji list" msgstr "" -#: src/screens/Profile/components/GermButton.tsx:75 +#: src/screens/Profile/components/GermButton.tsx:73 msgid "Open Germ DM" msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:446 +#: src/components/intents/GroupChatJoinDialog.tsx:451 msgid "Open group chat" msgstr "Open group chat" @@ -8681,21 +8681,21 @@ msgstr "Open group chat" msgid "Open group chat settings" msgstr "Open group chat settings" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:563 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:569 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:559 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:565 msgid "Open in Google Translate" msgstr "Open in Google Translate" #: src/components/Post/Embed/ExternalEmbed/index.tsx:88 -#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:147 +#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:146 msgid "Open link to {niceUrl}" msgstr "" -#: src/components/dms/ActionsWrapper.tsx:40 +#: src/components/dms/ActionsWrapper.tsx:41 msgid "Open message options" msgstr "" -#: src/screens/Settings/Settings.tsx:480 +#: src/screens/Settings/Settings.tsx:482 msgid "Open moderation debug page" msgstr "" @@ -8703,16 +8703,16 @@ msgstr "" msgid "Open muted words and tags settings" msgstr "" -#: src/screens/Search/components/StarterPackCard.tsx:128 +#: src/screens/Search/components/StarterPackCard.tsx:120 msgid "Open pack" msgstr "" -#: src/components/PostControls/PostMenu/index.tsx:67 +#: src/components/PostControls/PostMenu/index.tsx:63 msgid "Open post options menu" msgstr "" -#: src/features/liveNow/components/LiveStatusDialog.tsx:219 -#: src/features/liveNow/components/LiveStatusDialog.tsx:229 +#: src/features/liveNow/components/LiveStatusDialog.tsx:216 +#: src/features/liveNow/components/LiveStatusDialog.tsx:226 msgid "Open profile" msgstr "" @@ -8725,23 +8725,23 @@ msgstr "Open QR code scanner" msgid "Open settings" msgstr "Open settings" -#: src/components/PostControls/ShareMenu/index.tsx:98 +#: src/components/PostControls/ShareMenu/index.tsx:94 msgid "Open share menu" msgstr "" -#: src/screens/StarterPack/StarterPackScreen.tsx:582 +#: src/screens/StarterPack/StarterPackScreen.tsx:575 msgid "Open starter pack menu" msgstr "" -#: src/screens/Settings/Settings.tsx:473 +#: src/screens/Settings/Settings.tsx:475 msgid "Open storybook page" msgstr "" -#: src/screens/Settings/Settings.tsx:458 +#: src/screens/Settings/Settings.tsx:460 msgid "Open system log" msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:447 +#: src/components/intents/GroupChatJoinDialog.tsx:452 msgid "Open this group chat" msgstr "Open this group chat" @@ -8754,11 +8754,11 @@ msgstr "" msgid "Opens a dialog to add a content warning to your post" msgstr "" -#: src/screens/Login/LoginForm.tsx:645 +#: src/screens/Login/LoginForm.tsx:646 msgid "Opens a dialog to change the hosting provider you sign in to" msgstr "Opens a dialog to change the hosting provider you sign in to" -#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:151 +#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:152 msgid "Opens a dialog to choose who can interact with this post" msgstr "" @@ -8799,13 +8799,13 @@ msgstr "" msgid "Opens device gallery to select up to {MAX_GALLERY_IMAGES, plural, other {# images}}, or a single video or GIF." msgstr "Opens device gallery to select up to {MAX_GALLERY_IMAGES, plural, other {# images}}, or a single video or GIF." -#: src/screens/Messages/JoinRequest.tsx:305 +#: src/screens/Messages/JoinRequest.tsx:313 #: src/view/com/auth/SplashScreen.tsx:91 #: src/view/com/auth/SplashScreen.web.tsx:112 msgid "Opens flow to create a new Bluesky account" msgstr "" -#: src/screens/Messages/JoinRequest.tsx:291 +#: src/screens/Messages/JoinRequest.tsx:299 #: src/view/com/auth/SplashScreen.tsx:120 #: src/view/com/auth/SplashScreen.web.tsx:126 msgid "Opens flow to sign in to your existing Bluesky account" @@ -8815,7 +8815,7 @@ msgstr "" msgid "Opens full image" msgstr "Opens full image" -#: src/screens/Settings/Settings.tsx:258 +#: src/screens/Settings/Settings.tsx:260 msgid "Opens helpdesk in browser" msgstr "" @@ -8865,15 +8865,15 @@ msgstr "" msgid "Opens this profile" msgstr "" -#: src/components/dialogs/MutedWords.tsx:308 +#: src/components/dialogs/MutedWords.tsx:310 msgid "Options:" msgstr "" -#: src/screens/Deactivated.tsx:192 +#: src/screens/Deactivated.tsx:197 msgid "Or, continue with another account." msgstr "" -#: src/screens/Deactivated.tsx:179 +#: src/screens/Deactivated.tsx:184 msgid "Or, sign in to one of your other accounts." msgstr "" @@ -8893,8 +8893,8 @@ msgstr "" msgid "OTA status: None available" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:238 -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:242 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:240 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:244 #: src/view/com/composer/labels/LabelsBtn.tsx:181 msgid "Other" msgstr "" @@ -8903,31 +8903,31 @@ msgstr "" msgid "Other account" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:181 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:183 msgid "Other child safety issue" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:208 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:210 msgid "Other dangerous content" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:119 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:120 msgid "Other harassing or hateful content" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:59 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:60 msgid "Other misleading content" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:231 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:233 msgid "Other network rule-breaking" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:92 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:93 msgid "Other sexual violence content" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:154 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:156 msgid "Other violent content" msgstr "" @@ -8946,11 +8946,11 @@ msgstr "" msgid "Our moderators have reviewed reports and decided to disable your access to chats on Bluesky." msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:384 +#: src/components/dialogs/StarterPackDialog.tsx:376 msgid "Owner" msgstr "Owner" -#: src/components/dms/LeaveConvoPrompt.tsx:44 +#: src/components/dms/LeaveConvoPrompt.tsx:46 msgid "Owner must lock the group before leaving." msgstr "Owner must lock the group before leaving." @@ -8966,20 +8966,20 @@ msgstr "" msgid "Party GIFs" msgstr "Party GIFs" -#: src/screens/Login/LoginForm.tsx:365 -#: src/screens/Login/LoginForm.tsx:372 +#: src/screens/Login/LoginForm.tsx:366 +#: src/screens/Login/LoginForm.tsx:373 #: src/screens/Settings/AccountSettings.tsx:124 #: src/screens/Settings/AccountSettings.tsx:128 -#: src/screens/Settings/components/DeleteAccountDialog.tsx:284 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:290 #: src/screens/Signup/StepInfo/index.tsx:258 msgid "Password" msgstr "" -#: src/screens/Settings/components/ChangePasswordDialog.tsx:70 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:72 msgid "Password changed" msgstr "" -#: src/screens/Settings/components/ChangePasswordDialog.tsx:125 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:127 msgid "Password must be at least 8 characters long." msgstr "" @@ -9004,17 +9004,17 @@ msgstr "" msgid "Pause video" msgstr "" -#: src/screens/ProfileList/index.tsx:167 +#: src/screens/ProfileList/index.tsx:164 #: src/screens/Search/SearchResults.tsx:107 -#: src/screens/StarterPack/StarterPackScreen.tsx:195 +#: src/screens/StarterPack/StarterPackScreen.tsx:192 msgid "People" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:164 +#: src/screens/Messages/components/InviteLinkDialog.tsx:161 msgid "People {ownerName} follows can join instantly" msgstr "People {ownerName} follows can join instantly" -#: src/screens/Messages/components/InviteLinkDialog.tsx:169 +#: src/screens/Messages/components/InviteLinkDialog.tsx:166 msgid "People {ownerName} follows can request to join" msgstr "People {ownerName} follows can request to join" @@ -9048,19 +9048,19 @@ msgctxt "display posts made by" msgid "People I follow" msgstr "People I follow" -#: src/screens/Messages/components/InviteLinkDialog.tsx:163 +#: src/screens/Messages/components/InviteLinkDialog.tsx:160 msgid "People I follow can join instantly" msgstr "People I follow can join instantly" -#: src/screens/Messages/components/InviteLinkDialog.tsx:168 +#: src/screens/Messages/components/InviteLinkDialog.tsx:165 msgid "People I follow can request to join" msgstr "People I follow can request to join" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:510 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:507 msgid "People you follow" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:524 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:521 msgid "People you mention" msgstr "" @@ -9068,7 +9068,7 @@ msgstr "" msgid "Permission to access your photo library was denied. Please enable it in your system settings." msgstr "" -#: src/components/StarterPack/Wizard/WizardListCard.tsx:59 +#: src/components/StarterPack/Wizard/WizardListCard.tsx:58 msgid "Person toggle" msgstr "" @@ -9085,7 +9085,7 @@ msgstr "" msgid "Phone number" msgstr "" -#: src/components/contacts/screens/VerifyNumber.tsx:304 +#: src/components/contacts/screens/VerifyNumber.tsx:307 msgid "Phone number verified" msgstr "" @@ -9101,15 +9101,15 @@ msgstr "Pick a color theme" msgid "Pictures meant for adults." msgstr "" -#: src/components/FeedCard.tsx:364 +#: src/components/FeedCard.tsx:361 #: src/screens/CustomFeed/components/CustomFeedHeader.tsx:569 #: src/screens/CustomFeed/components/CustomFeedHeader.tsx:578 #: src/screens/SavedFeeds.tsx:559 msgid "Pin feed" msgstr "" -#: src/screens/ProfileList/components/Header.tsx:151 -#: src/screens/ProfileList/components/Header.tsx:158 +#: src/screens/ProfileList/components/Header.tsx:152 +#: src/screens/ProfileList/components/Header.tsx:159 msgid "Pin to home" msgstr "" @@ -9117,12 +9117,12 @@ msgstr "" msgid "Pin to Home" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:522 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:527 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:518 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:523 msgid "Pin to your profile" msgstr "" -#: src/view/com/posts/PostFeedReason.tsx:116 +#: src/view/com/posts/PostFeedReason.tsx:118 msgid "Pinned" msgstr "" @@ -9137,7 +9137,7 @@ msgstr "" msgid "Pinned Feeds" msgstr "" -#: src/screens/ProfileList/components/Header.tsx:77 +#: src/screens/ProfileList/components/Header.tsx:78 msgid "Pinned to your feeds" msgstr "" @@ -9184,15 +9184,15 @@ msgstr "" msgid "Please add any content warning labels that are applicable for the media you are posting." msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:189 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:198 msgid "Please check your email inbox for further instructions. It may take a minute or two to arrive." msgstr "" -#: src/screens/Signup/state.ts:313 +#: src/screens/Signup/state.ts:311 msgid "Please choose your handle." msgstr "" -#: src/screens/Signup/state.ts:305 +#: src/screens/Signup/state.ts:303 #: src/screens/Signup/StepInfo/index.tsx:149 msgid "Please choose your password." msgstr "" @@ -9201,24 +9201,24 @@ msgstr "" msgid "Please click on the link in the email we just sent you to verify your new email address. This is an important step to allow you to continue enjoying all the features of Bluesky." msgstr "" -#: src/screens/Signup/state.ts:325 +#: src/screens/Signup/state.ts:323 msgid "Please complete the verification captcha." msgstr "" -#: src/view/com/composer/state/video.ts:546 +#: src/view/com/composer/state/video.ts:551 msgid "Please confirm your email address to upload videos." msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:102 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:104 #: src/screens/Signup/StepInfo/index.tsx:139 msgid "Please double-check that you have entered your email address correctly." msgstr "" -#: src/screens/Login/SetNewPasswordForm.tsx:56 +#: src/screens/Login/SetNewPasswordForm.tsx:57 msgid "Please enter a password." msgstr "" -#: src/screens/Settings/components/ChangePasswordDialog.tsx:120 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:122 msgid "Please enter a password. It must be at least 8 characters long." msgstr "" @@ -9232,16 +9232,16 @@ msgstr "" msgid "Please enter a valid code." msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:111 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:113 #: src/components/dialogs/EmailDialog/screens/Update.tsx:147 msgid "Please enter a valid email address." msgstr "" -#: src/components/dialogs/MutedWords.tsx:89 +#: src/components/dialogs/MutedWords.tsx:91 msgid "Please enter a valid word, tag, or phrase to mute" msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:145 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:148 msgid "Please enter a valid, non-temporary email address. You may need to access this email in the future." msgstr "" @@ -9250,7 +9250,7 @@ msgstr "" msgid "Please enter the code we sent to <0>{0} below." msgstr "" -#: src/screens/Settings/components/ChangePasswordDialog.tsx:66 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:68 msgid "Please enter the code you received and the new password you would like to use." msgstr "" @@ -9258,7 +9258,7 @@ msgstr "" msgid "Please enter the security code we sent to your previous email address." msgstr "" -#: src/screens/Signup/state.ts:289 +#: src/screens/Signup/state.ts:287 #: src/screens/Signup/StepInfo/index.tsx:122 msgid "Please enter your email." msgstr "" @@ -9271,24 +9271,24 @@ msgstr "" msgid "Please enter your new email address." msgstr "" -#: src/screens/Login/LoginForm.tsx:196 +#: src/screens/Login/LoginForm.tsx:197 msgid "Please enter your password" msgstr "" -#: src/screens/Login/LoginForm.tsx:190 +#: src/screens/Login/LoginForm.tsx:191 msgid "Please enter your username" msgstr "" #. placeholder {0}: labeler ? sanitizeHandle(labeler.creator.handle, '@') : label.src -#: src/components/moderation/AppealForm.tsx:120 +#: src/components/moderation/AppealForm.tsx:131 msgid "Please explain why you think this label was incorrectly applied by {0}" msgstr "" -#: src/screens/Messages/components/ChatDisabled.tsx:143 +#: src/screens/Messages/components/ChatDisabled.tsx:141 msgid "Please explain why you think your chats were incorrectly disabled" msgstr "" -#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:124 +#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:123 msgid "Please explain why you think your Go Live access was incorrectly disabled." msgstr "" @@ -9297,19 +9297,19 @@ msgstr "" msgid "Please go back, or activate this element to return to the start of the active content." msgstr "" -#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:102 +#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:104 msgid "Please provide any additional details you feel moderators may need in order to properly assess your Age Assurance status." msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:304 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:313 msgid "Please select a language" msgstr "" -#: src/components/moderation/ReportDialog/action.ts:39 +#: src/components/moderation/ReportDialog/action.ts:38 msgid "Please select a moderation service" msgstr "" -#: src/components/moderation/ReportDialog/action.ts:36 +#: src/components/moderation/ReportDialog/action.ts:35 msgid "Please select a reason for this report" msgstr "" @@ -9319,7 +9319,7 @@ msgstr "" msgid "Please sign in as @{0}" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:961 +#: src/components/moderation/ReportDialog/index.tsx:962 msgctxt "english-only-resource" msgid "Please submit your report through the Report non-consensual intimate imagery (NCII) form." msgstr "Please submit your report through the Report non-consensual intimate imagery (NCII) form." @@ -9328,7 +9328,7 @@ msgstr "Please submit your report through the Report non-consensual intimate ima msgid "Please use the Bluesky mobile app to scan a QR code." msgstr "Please use the Bluesky mobile app to scan a QR code." -#: src/screens/Settings/FindContactsSettings.tsx:107 +#: src/screens/Settings/FindContactsSettings.tsx:105 msgid "Please use the native app to import your contacts." msgstr "" @@ -9345,7 +9345,7 @@ msgid "Please write your message below:" msgstr "" #: src/lib/interests.ts:70 -#: src/screens/Search/modules/ExploreTrendingTopics.tsx:242 +#: src/screens/Search/modules/ExploreTrendingTopics.tsx:240 msgid "Politics" msgstr "" @@ -9353,7 +9353,7 @@ msgstr "" msgid "Porn" msgstr "" -#: src/view/com/composer/Composer.tsx:1923 +#: src/view/com/composer/Composer.tsx:1947 msgctxt "action" msgid "Post" msgstr "" @@ -9369,22 +9369,22 @@ msgctxt "post-number-in-thread" msgid "Post {index} of {count}" msgstr "Post {index} of {count}" -#: src/view/screens/Profile.tsx:489 -#: src/view/screens/Profile.tsx:490 +#: src/view/screens/Profile.tsx:486 +#: src/view/screens/Profile.tsx:487 msgid "Post a photo" msgstr "" -#: src/view/screens/Profile.tsx:516 -#: src/view/screens/Profile.tsx:517 +#: src/view/screens/Profile.tsx:513 +#: src/view/screens/Profile.tsx:514 msgid "Post a video" msgstr "" -#: src/view/com/composer/Composer.tsx:1921 +#: src/view/com/composer/Composer.tsx:1945 msgctxt "action" msgid "Post All" msgstr "" -#: src/view/com/composer/Composer.tsx:1582 +#: src/view/com/composer/Composer.tsx:1606 msgid "Post anyway" msgstr "Post anyway" @@ -9400,38 +9400,38 @@ msgstr "" msgid "Post by @{0}" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:200 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:196 msgctxt "toast" msgid "Post deleted" msgstr "" -#: src/lib/api/index.ts:189 +#: src/lib/api/index.ts:194 msgid "Post failed to upload. Please check your Internet connection and try again." msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:138 -#: src/screens/PostThread/components/ThreadItemPost.tsx:124 -#: src/screens/PostThread/components/ThreadItemTreePost.tsx:115 -#: src/screens/VideoFeed/index.tsx:552 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:134 +#: src/screens/PostThread/components/ThreadItemPost.tsx:121 +#: src/screens/PostThread/components/ThreadItemTreePost.tsx:112 +#: src/screens/VideoFeed/index.tsx:551 msgid "Post has been deleted" msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:127 -#: src/lib/moderation/useModerationCauseDescription.ts:107 +#: src/lib/moderation/useModerationCauseDescription.ts:104 msgid "Post Hidden by Muted Word" msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:130 -#: src/lib/moderation/useModerationCauseDescription.ts:116 +#: src/lib/moderation/useModerationCauseDescription.ts:113 msgid "Post Hidden by You" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:685 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:682 msgid "Post interaction settings" msgstr "" #: src/Navigation.tsx:200 -#: src/screens/ModerationInteractionSettings/index.tsx:35 +#: src/screens/ModerationInteractionSettings/index.tsx:36 msgid "Post Interaction Settings" msgstr "" @@ -9444,8 +9444,8 @@ msgstr "Post it to Bluesky or share anywhere." msgid "Post language selection" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:395 -#: src/screens/Messages/components/InviteLinkDialog.tsx:411 +#: src/screens/Messages/components/InviteLinkDialog.tsx:392 +#: src/screens/Messages/components/InviteLinkDialog.tsx:408 msgid "Post link" msgstr "Post link" @@ -9454,7 +9454,7 @@ msgstr "Post link" msgid "Post not found" msgstr "" -#: src/state/queries/pinned-post.ts:59 +#: src/state/queries/pinned-post.ts:64 msgctxt "toast" msgid "Post pinned" msgstr "" @@ -9464,25 +9464,25 @@ msgstr "" msgid "Post type" msgstr "Post type" -#: src/state/queries/pinned-post.ts:61 +#: src/state/queries/pinned-post.ts:66 msgctxt "toast" msgid "Post unpinned" msgstr "" -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:257 -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:269 -#: src/screens/ProfileList/index.tsx:167 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:254 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:266 +#: src/screens/ProfileList/index.tsx:164 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:216 -#: src/screens/StarterPack/StarterPackScreen.tsx:197 -#: src/view/screens/Profile.tsx:236 +#: src/screens/StarterPack/StarterPackScreen.tsx:194 +#: src/view/screens/Profile.tsx:233 msgid "Posts" msgstr "" -#: src/components/dialogs/MutedWords.tsx:123 +#: src/components/dialogs/MutedWords.tsx:125 msgid "Posts can be muted based on their text, their tags, or both. We recommend avoiding common words that appear in many posts, since it can result in no posts being shown." msgstr "" -#: src/view/com/posts/PostFeedErrorMessage.tsx:76 +#: src/view/com/posts/PostFeedErrorMessage.tsx:74 msgid "Posts hidden" msgstr "" @@ -9498,7 +9498,7 @@ msgstr "" msgid "Potentially misleading link warning" msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:292 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:301 msgid "Preferred language" msgstr "" @@ -9509,12 +9509,12 @@ msgstr "" #: src/components/Error.tsx:56 #: src/components/Lists.tsx:104 #: src/screens/Messages/components/MessageListError.tsx:23 -#: src/screens/Messages/JoinRequests.tsx:355 +#: src/screens/Messages/JoinRequests.tsx:352 #: src/screens/Signup/BackNextButtons.tsx:48 msgid "Press to retry" msgstr "" -#: src/components/KnownFollowers.tsx:125 +#: src/components/KnownFollowers.tsx:124 msgid "Press to view followers of this account that you also follow" msgstr "" @@ -9536,8 +9536,8 @@ msgstr "" msgid "Privacy" msgstr "" -#: src/screens/Settings/Settings.tsx:183 -#: src/screens/Settings/Settings.tsx:186 +#: src/screens/Settings/Settings.tsx:185 +#: src/screens/Settings/Settings.tsx:188 msgid "Privacy and security" msgstr "" @@ -9564,23 +9564,23 @@ msgstr "" msgid "Privacy Policy" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:173 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:175 msgid "Privacy violation of a minor" msgstr "" -#: src/view/com/composer/Composer.tsx:2701 +#: src/view/com/composer/Composer.tsx:2725 msgid "Processing GIF..." msgstr "" -#: src/view/com/composer/Composer.tsx:2703 +#: src/view/com/composer/Composer.tsx:2727 msgid "Processing video..." msgstr "" -#: src/lib/api/index.ts:62 +#: src/lib/api/index.ts:65 msgid "Processing..." msgstr "" -#: src/view/screens/DebugMod.tsx:956 +#: src/view/screens/DebugMod.tsx:1160 msgid "profile" msgstr "" @@ -9591,12 +9591,12 @@ msgstr "" msgid "Profile" msgstr "" -#: src/screens/Profile/Header/Shell.tsx:164 +#: src/screens/Profile/Header/Shell.tsx:165 msgid "Profile banner placeholder" msgstr "Profile banner placeholder" #: src/features/inviteFriends/InviteScannerScreen.tsx:210 -#: src/lib/strings/errors.ts:39 +#: src/lib/strings/errors.ts:74 msgid "Profile not found" msgstr "" @@ -9605,36 +9605,36 @@ msgctxt "toast" msgid "Profile updated" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:223 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:225 msgid "Promoting or selling prohibited items or services" msgstr "" -#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:160 #: src/view/com/composer/threadgate/ThreadgateBtn.tsx:161 +#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:162 msgid "Psst! You can edit who can interact with this post." msgstr "" -#: src/view/com/lists/MyLists.tsx:77 +#: src/view/com/lists/MyLists.tsx:80 msgid "Public, sharable lists of users to mute or block in bulk." msgstr "" #. Accessibility label for button to publish a single post -#: src/view/com/composer/Composer.tsx:1907 +#: src/view/com/composer/Composer.tsx:1931 msgid "Publish post" msgstr "" #. Accessibility label for button to publish multiple posts in a thread -#: src/view/com/composer/Composer.tsx:1902 +#: src/view/com/composer/Composer.tsx:1926 msgid "Publish posts" msgstr "" #. Accessibility label for button to publish multiple replies in a thread -#: src/view/com/composer/Composer.tsx:1891 +#: src/view/com/composer/Composer.tsx:1915 msgid "Publish replies" msgstr "" #. Accessibility label for button to publish a single reply -#: src/view/com/composer/Composer.tsx:1896 +#: src/view/com/composer/Composer.tsx:1920 msgid "Publish reply" msgstr "" @@ -9654,15 +9654,15 @@ msgstr "Push, everyone" msgid "Push, people you follow" msgstr "Push, people you follow" -#: src/components/StarterPack/QrCodeDialog.tsx:143 +#: src/components/StarterPack/QrCodeDialog.tsx:138 msgid "QR code copied to your clipboard!" msgstr "" -#: src/components/StarterPack/QrCodeDialog.tsx:121 +#: src/components/StarterPack/QrCodeDialog.tsx:116 msgid "QR code has been downloaded!" msgstr "" -#: src/components/StarterPack/QrCodeDialog.tsx:122 +#: src/components/StarterPack/QrCodeDialog.tsx:117 #: src/features/inviteFriends/InviteFriendsDialogInner.tsx:101 msgid "QR code saved to your camera roll!" msgstr "" @@ -9675,11 +9675,11 @@ msgstr "" msgid "Quote post" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:351 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:347 msgid "Quote post was re-attached" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:350 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:346 msgid "Quote post was successfully detached" msgstr "" @@ -9705,20 +9705,20 @@ msgstr "" msgid "Rate limit exceeded – you've tried to change your handle too many times in a short period. Please wait a minute before trying again." msgstr "" -#: src/components/contacts/screens/PhoneInput.tsx:109 +#: src/components/contacts/screens/PhoneInput.tsx:110 msgid "Rate limit exceeded. Please try again later." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:715 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:725 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:711 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:721 msgid "Re-attach quote" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:433 +#: src/screens/Messages/components/InviteLinkDialog.tsx:430 msgid "Re-enable invite link" msgstr "Re-enable invite link" -#: src/screens/Messages/components/InviteLinkDialog.tsx:440 +#: src/screens/Messages/components/InviteLinkDialog.tsx:437 msgid "Re-enable link" msgstr "Re-enable link" @@ -9731,7 +9731,7 @@ msgstr "" msgid "Reactions" msgstr "Reactions" -#: src/screens/Deactivated.tsx:133 +#: src/screens/Deactivated.tsx:138 msgid "Reactivate your account" msgstr "" @@ -9750,11 +9750,11 @@ msgstr "Read about how to use advanced search filters" msgid "Read blog post" msgstr "" -#: src/screens/VideoFeed/index.tsx:1077 +#: src/screens/VideoFeed/index.tsx:1073 msgid "Read less" msgstr "" -#: src/screens/VideoFeed/index.tsx:1077 +#: src/screens/VideoFeed/index.tsx:1073 msgid "Read more" msgstr "" @@ -9788,8 +9788,8 @@ msgstr "" msgid "Real people." msgstr "" -#: src/screens/Takendown.tsx:158 -#: src/screens/Takendown.tsx:166 +#: src/screens/Takendown.tsx:156 +#: src/screens/Takendown.tsx:164 msgid "Reason for appeal" msgstr "" @@ -9824,7 +9824,7 @@ msgid "Reject" msgstr "" #. Reject a request to join a chat -#: src/screens/Messages/JoinRequests.tsx:489 +#: src/screens/Messages/JoinRequests.tsx:486 msgctxt "button" msgid "Reject" msgstr "Reject" @@ -9833,30 +9833,30 @@ msgstr "Reject" msgid "Reject chat request" msgstr "" -#: src/screens/Messages/JoinRequests.tsx:483 +#: src/screens/Messages/JoinRequests.tsx:480 msgid "Reject join request" msgstr "Reject join request" #: src/screens/Messages/ChatList.tsx:424 -#: src/screens/Messages/Inbox.tsx:206 +#: src/screens/Messages/Inbox.tsx:205 msgid "Reload conversations" msgstr "" -#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:193 +#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:194 #: src/components/dialogs/lists/UserAddRemoveListsDialog.tsx:319 #: src/components/dialogs/lists/UserAddRemoveListsDialog.tsx:326 -#: src/components/dialogs/MutedWords.tsx:457 -#: src/components/dialogs/StarterPackDialog.tsx:376 -#: src/components/dialogs/StarterPackDialog.tsx:386 -#: src/components/FeedCard.tsx:376 -#: src/components/StarterPack/Wizard/WizardListCard.tsx:106 -#: src/components/StarterPack/Wizard/WizardListCard.tsx:113 -#: src/screens/Bookmarks.tsx:258 +#: src/components/dialogs/MutedWords.tsx:459 +#: src/components/dialogs/StarterPackDialog.tsx:368 +#: src/components/dialogs/StarterPackDialog.tsx:378 +#: src/components/FeedCard.tsx:373 +#: src/components/StarterPack/Wizard/WizardListCard.tsx:105 +#: src/components/StarterPack/Wizard/WizardListCard.tsx:112 +#: src/screens/Bookmarks.tsx:256 #: src/screens/Messages/ConversationSettings/Member.tsx:162 #: src/screens/Messages/ConversationSettings/prompts.tsx:178 #: src/screens/Moderation/index.tsx:477 -#: src/screens/Settings/Settings.tsx:693 -#: src/view/com/posts/PostFeedErrorMessage.tsx:221 +#: src/screens/Settings/Settings.tsx:695 +#: src/view/com/posts/PostFeedErrorMessage.tsx:219 msgid "Remove" msgstr "" @@ -9864,7 +9864,7 @@ msgstr "" msgid "Remove {displayName} from group chat" msgstr "Remove {displayName} from group chat" -#: src/components/StarterPack/Wizard/WizardListCard.tsx:62 +#: src/components/StarterPack/Wizard/WizardListCard.tsx:61 msgid "Remove {displayName} from starter pack" msgstr "" @@ -9881,13 +9881,13 @@ msgstr "Remove {displayName}?" msgid "Remove {q}" msgstr "Remove {q}" -#: src/screens/Settings/Settings.tsx:674 -#: src/screens/Settings/Settings.tsx:677 +#: src/screens/Settings/Settings.tsx:676 +#: src/screens/Settings/Settings.tsx:679 msgid "Remove account" msgstr "" -#: src/screens/Settings/FindContactsSettings.tsx:576 -#: src/screens/Settings/FindContactsSettings.tsx:584 +#: src/screens/Settings/FindContactsSettings.tsx:580 +#: src/screens/Settings/FindContactsSettings.tsx:588 msgid "Remove all contacts" msgstr "" @@ -9909,19 +9909,19 @@ msgstr "" msgid "Remove caption file" msgstr "Remove caption file" -#: src/screens/Messages/components/MessageInputEmbed.tsx:234 -#: src/screens/Messages/components/MessageInputEmbed.tsx:289 -#: src/screens/Messages/components/MessageInputEmbed.tsx:344 +#: src/screens/Messages/components/MessageInputEmbed.tsx:225 +#: src/screens/Messages/components/MessageInputEmbed.tsx:280 +#: src/screens/Messages/components/MessageInputEmbed.tsx:335 msgid "Remove embed" msgstr "" #: src/view/com/posts/FeedShutdownMsg.tsx:121 #: src/view/com/posts/FeedShutdownMsg.tsx:125 -#: src/view/com/posts/PostFeedErrorMessage.tsx:177 +#: src/view/com/posts/PostFeedErrorMessage.tsx:175 msgid "Remove feed" msgstr "" -#: src/view/com/posts/PostFeedErrorMessage.tsx:218 +#: src/view/com/posts/PostFeedErrorMessage.tsx:216 msgid "Remove feed?" msgstr "" @@ -9936,13 +9936,13 @@ msgstr "Remove from chat" #: src/screens/CustomFeed/components/CustomFeedHeader.tsx:368 #: src/screens/CustomFeed/components/CustomFeedHeader.tsx:373 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:176 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:179 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:177 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:180 #: src/screens/SavedFeeds.tsx:549 msgid "Remove from my feeds" msgstr "" -#: src/screens/Settings/Settings.tsx:687 +#: src/screens/Settings/Settings.tsx:689 msgid "Remove from quick access?" msgstr "" @@ -9952,11 +9952,11 @@ msgid "Remove from saved feeds" msgstr "" #: src/components/PostControls/BookmarkButton.tsx:120 -#: src/screens/Bookmarks.tsx:252 +#: src/screens/Bookmarks.tsx:250 msgid "Remove from saved posts" msgstr "" -#: src/components/FeedCard.tsx:373 +#: src/components/FeedCard.tsx:370 msgid "Remove from your feeds?" msgstr "" @@ -9964,17 +9964,17 @@ msgstr "" msgid "Remove image" msgstr "" -#: src/features/liveNow/components/EditLiveDialog.tsx:221 -#: src/features/liveNow/components/EditLiveDialog.tsx:228 +#: src/features/liveNow/components/EditLiveDialog.tsx:223 +#: src/features/liveNow/components/EditLiveDialog.tsx:230 msgid "Remove live status" msgstr "" -#: src/components/moderation/BlockDialog.tsx:362 -#: src/components/moderation/BlockDialog.tsx:369 +#: src/components/moderation/BlockDialog.tsx:363 +#: src/components/moderation/BlockDialog.tsx:370 msgid "Remove member" msgstr "Remove member" -#: src/components/dialogs/MutedWords.tsx:613 +#: src/components/dialogs/MutedWords.tsx:615 msgid "Remove mute word from your list" msgstr "" @@ -9987,12 +9987,12 @@ msgstr "" msgid "Remove repost" msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:500 +#: src/components/contacts/screens/ViewMatches.tsx:508 #: src/screens/Settings/FindContactsSettings.tsx:349 msgid "Remove suggestion" msgstr "" -#: src/view/com/posts/PostFeedErrorMessage.tsx:219 +#: src/view/com/posts/PostFeedErrorMessage.tsx:217 msgid "Remove this feed from your saved feeds" msgstr "" @@ -10000,7 +10000,7 @@ msgstr "" msgid "Remove unavailable moderation services" msgstr "" -#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:179 +#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:180 msgid "Remove user from list" msgstr "" @@ -10015,15 +10015,15 @@ msgstr "" msgid "Remove your verification for this account?" msgstr "" -#: src/components/Post/Embed/index.tsx:241 +#: src/components/Post/Embed/index.tsx:242 msgid "Removed by author" msgstr "" -#: src/components/Post/Embed/index.tsx:239 +#: src/components/Post/Embed/index.tsx:240 msgid "Removed by you" msgstr "" -#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:136 +#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:137 #: src/components/dialogs/lists/UserAddRemoveListsDialog.tsx:246 msgid "Removed from list" msgstr "" @@ -10033,16 +10033,16 @@ msgid "Removed from saved feeds" msgstr "" #: src/components/PostControls/BookmarkButton.tsx:88 -#: src/screens/Bookmarks.tsx:211 +#: src/screens/Bookmarks.tsx:209 msgid "Removed from saved posts" msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:287 +#: src/components/dialogs/StarterPackDialog.tsx:284 msgid "Removed from starter pack" msgstr "" #: src/screens/CustomFeed/components/CustomFeedHeader.tsx:128 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:77 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:78 #: src/view/com/posts/FeedShutdownMsg.tsx:45 msgid "Removed from your feeds" msgstr "" @@ -10055,15 +10055,15 @@ msgstr "" msgid "Removed verification" msgstr "" -#: src/components/dialogs/MutedWords.tsx:541 +#: src/components/dialogs/MutedWords.tsx:543 msgid "Renew" msgstr "" -#: src/components/dialogs/MutedWords.tsx:547 +#: src/components/dialogs/MutedWords.tsx:549 msgid "Renew duration" msgstr "" -#: src/components/dialogs/MutedWords.tsx:531 +#: src/components/dialogs/MutedWords.tsx:533 msgid "Renew mute word" msgstr "" @@ -10092,63 +10092,63 @@ msgctxt "description" msgid "Replied to you" msgstr "" -#: src/components/dms/MessageItem.tsx:926 +#: src/components/dms/MessageItem.tsx:943 msgid "Replied-to message from {senderName}, tap to scroll to it" msgstr "Replied-to message from {senderName}, tap to scroll to it" -#: src/components/dms/MessageItem.tsx:924 +#: src/components/dms/MessageItem.tsx:941 msgid "Replied-to message was sent before you joined" msgstr "Replied-to message was sent before you joined" -#: src/components/dms/MessageItem.tsx:927 +#: src/components/dms/MessageItem.tsx:944 msgid "Replied-to message, tap to scroll to it" msgstr "Replied-to message, tap to scroll to it" -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:274 -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:286 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:271 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:283 #: src/lib/hooks/useNotificationHandler.ts:172 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:218 #: src/screens/Settings/NotificationSettings/index.tsx:181 #: src/screens/Settings/NotificationSettings/index.tsx:330 -#: src/view/screens/Profile.tsx:237 +#: src/view/screens/Profile.tsx:234 msgid "Replies" msgstr "" -#: src/components/WhoCanReply.tsx:87 +#: src/components/WhoCanReply.tsx:80 msgid "Replies disabled" msgstr "" -#: src/components/WhoCanReply.tsx:281 +#: src/components/WhoCanReply.tsx:274 msgid "Replies to this post are disabled." msgstr "" -#: src/components/dms/MessageContextMenu.tsx:182 -#: src/components/dms/MessageContextMenu.tsx:185 +#: src/components/dms/MessageContextMenu.tsx:180 +#: src/components/dms/MessageContextMenu.tsx:183 msgid "Reply" msgstr "Reply" -#: src/view/com/composer/Composer.tsx:1919 +#: src/view/com/composer/Composer.tsx:1943 msgctxt "action" msgid "Reply" msgstr "" #. Accessibility label for the reply button, verb form followed by number of replies and noun form #. placeholder {0}: post.replyCount || 0 -#: src/components/PostControls/index.tsx:240 +#: src/components/PostControls/index.tsx:236 msgid "Reply ({0, plural, one {# reply} other {# replies}})" msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:136 -#: src/lib/moderation/useModerationCauseDescription.ts:126 +#: src/lib/moderation/useModerationCauseDescription.ts:123 msgid "Reply Hidden by Thread Author" msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:135 -#: src/lib/moderation/useModerationCauseDescription.ts:125 +#: src/lib/moderation/useModerationCauseDescription.ts:122 msgid "Reply Hidden by You" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:418 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:415 msgid "Reply settings are chosen by the author of the thread" msgstr "" @@ -10156,18 +10156,18 @@ msgstr "" msgid "Reply sorting" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:389 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:385 msgctxt "toast" msgid "Reply visibility updated" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:388 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:384 msgid "Reply was successfully hidden" msgstr "" -#: src/components/dms/MessageContextMenu.tsx:220 -#: src/features/liveNow/components/LiveStatusDialog.tsx:267 -#: src/screens/Messages/ConversationSettings/index.tsx:583 +#: src/components/dms/MessageContextMenu.tsx:218 +#: src/features/liveNow/components/LiveStatusDialog.tsx:264 +#: src/screens/Messages/ConversationSettings/index.tsx:578 msgid "Report" msgstr "" @@ -10183,8 +10183,8 @@ msgstr "" msgid "Report conversation" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:137 -#: src/components/moderation/ReportDialog/index.tsx:335 +#: src/components/moderation/ReportDialog/index.tsx:138 +#: src/components/moderation/ReportDialog/index.tsx:336 msgid "Report dialog" msgstr "" @@ -10193,17 +10193,17 @@ msgstr "" msgid "Report feed" msgstr "" -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:210 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:213 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:211 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:214 msgid "Report list" msgstr "" -#: src/components/dms/MessageContextMenu.tsx:217 +#: src/components/dms/MessageContextMenu.tsx:215 msgid "Report message" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:781 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:783 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:777 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:779 msgid "Report post" msgstr "" @@ -10211,8 +10211,8 @@ msgstr "" msgid "Report sent" msgstr "Report sent" -#: src/screens/StarterPack/StarterPackScreen.tsx:657 -#: src/screens/StarterPack/StarterPackScreen.tsx:660 +#: src/screens/StarterPack/StarterPackScreen.tsx:650 +#: src/screens/StarterPack/StarterPackScreen.tsx:653 msgid "Report starter pack" msgstr "" @@ -10232,7 +10232,7 @@ msgstr "" msgid "Report this feed" msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:582 +#: src/screens/Messages/ConversationSettings/index.tsx:577 msgid "Report this group chat" msgstr "Report this group chat" @@ -10241,7 +10241,7 @@ msgid "Report this list" msgstr "" #: src/components/moderation/ReportDialog/copy.ts:19 -#: src/features/liveNow/components/LiveStatusDialog.tsx:250 +#: src/features/liveNow/components/LiveStatusDialog.tsx:247 msgid "Report this livestream" msgstr "" @@ -10278,7 +10278,7 @@ msgstr "" #: src/components/PostControls/RepostButton.tsx:146 #: src/components/PostControls/RepostButton.web.tsx:43 #: src/components/PostControls/RepostButton.web.tsx:103 -#: src/screens/StarterPack/StarterPackScreen.tsx:577 +#: src/screens/StarterPack/StarterPackScreen.tsx:570 msgid "Repost or quote post" msgstr "" @@ -10286,13 +10286,13 @@ msgstr "" msgid "Reposted By" msgstr "" -#: src/view/com/posts/PostFeedReason.tsx:78 -#: src/view/com/posts/PostFeedReason.tsx:97 +#: src/view/com/posts/PostFeedReason.tsx:80 +#: src/view/com/posts/PostFeedReason.tsx:99 msgid "Reposted by {reposter}" msgstr "" -#: src/view/com/posts/PostFeedReason.tsx:78 -#: src/view/com/posts/PostFeedReason.tsx:95 +#: src/view/com/posts/PostFeedReason.tsx:80 +#: src/view/com/posts/PostFeedReason.tsx:97 msgid "Reposted by you" msgstr "" @@ -10302,7 +10302,7 @@ msgstr "" msgid "Reposts" msgstr "" -#: src/components/Pills.tsx:229 +#: src/components/Pills.tsx:230 msgid "Reposts Hidden" msgstr "Reposts Hidden" @@ -10326,29 +10326,29 @@ msgctxt "toast" msgid "Reposts will be shown in feeds" msgstr "Reposts will be shown in feeds" -#: src/components/intents/GroupChatJoinDialog.tsx:463 +#: src/components/intents/GroupChatJoinDialog.tsx:468 msgid "Request access to group chat" msgstr "Request access to group chat" -#: src/screens/Messages/JoinRequests.tsx:182 +#: src/screens/Messages/JoinRequests.tsx:179 msgid "Request approved." msgstr "Request approved." -#: src/screens/Settings/components/ChangePasswordDialog.tsx:226 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:232 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:230 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:236 msgid "Request code" msgstr "" -#: src/screens/Messages/JoinRequests.tsx:215 +#: src/screens/Messages/JoinRequests.tsx:211 msgid "Request rejected." msgstr "Request rejected." -#: src/components/dms/ChatInvite/Root.tsx:117 -#: src/components/intents/GroupChatJoinDialog.tsx:295 +#: src/components/dms/ChatInvite/Root.tsx:118 +#: src/components/intents/GroupChatJoinDialog.tsx:300 msgid "Request to join" msgstr "Request to join" -#: src/components/dms/ChatInvite/Root.tsx:131 +#: src/components/dms/ChatInvite/Root.tsx:132 msgid "Requested" msgstr "Requested" @@ -10358,8 +10358,8 @@ msgid "Requests" msgstr "Requests" #: src/Navigation.tsx:519 -#: src/screens/Messages/JoinRequests.tsx:59 -#: src/screens/Messages/JoinRequests.tsx:425 +#: src/screens/Messages/JoinRequests.tsx:56 +#: src/screens/Messages/JoinRequests.tsx:422 msgid "Requests to join" msgstr "Requests to join" @@ -10380,13 +10380,13 @@ msgstr "" msgid "Required in your region" msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:310 +#: src/components/intents/GroupChatJoinDialog.tsx:315 +#: src/screens/Messages/components/OutgoingRequestListItem.tsx:113 #: src/screens/Messages/components/OutgoingRequestListItem.tsx:115 -#: src/screens/Messages/components/OutgoingRequestListItem.tsx:117 msgid "Rescind request" msgstr "Rescind request" -#: src/screens/Messages/components/OutgoingRequestListItem.tsx:50 +#: src/screens/Messages/components/OutgoingRequestListItem.tsx:48 msgid "Rescind request to join group chat" msgstr "Rescind request to join group chat" @@ -10394,48 +10394,48 @@ msgstr "Rescind request to join group chat" msgid "Resend" msgstr "" -#: src/components/contacts/screens/VerifyNumber.tsx:365 -#: src/components/contacts/screens/VerifyNumber.tsx:382 +#: src/components/contacts/screens/VerifyNumber.tsx:368 +#: src/components/contacts/screens/VerifyNumber.tsx:385 msgid "Resend code" msgstr "" #. placeholder {0}: String( clamp(Math.round(timeUntilCanResend / 1000), 0, 30), ).padStart(2, '0') -#: src/components/contacts/screens/VerifyNumber.tsx:372 +#: src/components/contacts/screens/VerifyNumber.tsx:375 msgid "Resend code in <0>00:{0}" msgstr "" -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:174 -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:177 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:182 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:185 msgid "Resend email" msgstr "" -#: src/components/intents/VerifyEmailIntentDialog.tsx:124 +#: src/components/intents/VerifyEmailIntentDialog.tsx:125 msgid "Resend Email" msgstr "" -#: src/components/intents/VerifyEmailIntentDialog.tsx:117 +#: src/components/intents/VerifyEmailIntentDialog.tsx:118 msgid "Resend Verification Email" msgstr "" -#: src/screens/Settings/Settings.tsx:509 #: src/screens/Settings/Settings.tsx:511 +#: src/screens/Settings/Settings.tsx:513 msgid "Reset activity subscription nudge" msgstr "" -#: src/screens/Login/SetNewPasswordForm.tsx:108 +#: src/screens/Login/SetNewPasswordForm.tsx:113 msgid "Reset code" msgstr "" -#: src/screens/Settings/Settings.tsx:494 #: src/screens/Settings/Settings.tsx:496 +#: src/screens/Settings/Settings.tsx:498 msgid "Reset onboarding state" msgstr "" -#: src/screens/Login/ForgotPasswordForm.tsx:77 +#: src/screens/Login/ForgotPasswordForm.tsx:81 msgid "Reset password" msgstr "" -#: src/screens/Login/LoginForm.tsx:422 +#: src/screens/Login/LoginForm.tsx:423 msgid "Reset your password by sending a code to your email" msgstr "Reset your password by sending a code to your email" @@ -10447,12 +10447,12 @@ msgstr "Restore default" msgid "Restore the {defaultChannel} deployment" msgstr "Restore the {defaultChannel} deployment" -#: src/screens/Settings/FindContactsSettings.tsx:544 -#: src/screens/Settings/FindContactsSettings.tsx:560 +#: src/screens/Settings/FindContactsSettings.tsx:548 +#: src/screens/Settings/FindContactsSettings.tsx:564 msgid "Resync contacts" msgstr "" -#: src/screens/Login/LoginForm.tsx:532 +#: src/screens/Login/LoginForm.tsx:533 msgid "Retries signing in" msgstr "" @@ -10463,22 +10463,22 @@ msgstr "" #: src/components/ageAssurance/AgeAssuranceErrors.tsx:28 #: src/components/ageAssurance/AgeAssuranceErrors.tsx:31 -#: src/components/contacts/screens/VerifyNumber.tsx:350 -#: src/components/contacts/screens/VerifyNumber.tsx:355 +#: src/components/contacts/screens/VerifyNumber.tsx:353 +#: src/components/contacts/screens/VerifyNumber.tsx:358 #: src/components/Error.tsx:60 #: src/components/Lists.tsx:115 -#: src/components/moderation/ReportDialog/index.tsx:369 +#: src/components/moderation/ReportDialog/index.tsx:370 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:56 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:59 #: src/components/StarterPack/ProfileStarterPacks.tsx:380 -#: src/screens/Login/LoginForm.tsx:531 -#: src/screens/Login/LoginForm.tsx:537 +#: src/screens/Login/LoginForm.tsx:532 +#: src/screens/Login/LoginForm.tsx:538 #: src/screens/Messages/ChatList.tsx:429 #: src/screens/Messages/components/MessageListError.tsx:24 -#: src/screens/Messages/Inbox.tsx:211 -#: src/screens/Messages/JoinRequests.tsx:361 -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:268 -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:271 +#: src/screens/Messages/Inbox.tsx:210 +#: src/screens/Messages/JoinRequests.tsx:358 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:272 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:275 #: src/screens/Onboarding/StepSuggestedStarterpacks/index.tsx:92 #: src/screens/Onboarding/StepSuggestedStarterpacks/index.tsx:95 #: src/screens/PostThread/components/ThreadError.tsx:81 @@ -10490,14 +10490,14 @@ msgstr "" msgid "Retry" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:366 +#: src/components/moderation/ReportDialog/index.tsx:367 #: src/view/screens/Storybook/Admonitions.tsx:61 msgid "Retry loading report options" msgstr "" #: src/components/Error.tsx:68 #: src/screens/List/ListHiddenScreen.tsx:223 -#: src/screens/StarterPack/StarterPackScreen.tsx:801 +#: src/screens/StarterPack/StarterPackScreen.tsx:794 msgid "Return to previous page" msgstr "" @@ -10507,16 +10507,16 @@ msgstr "" #: src/screens/ProfileList/components/ErrorScreen.tsx:36 #: src/screens/Settings/components/ChangeHandleDialog.tsx:577 -#: src/screens/VideoFeed/index.tsx:1262 +#: src/screens/VideoFeed/index.tsx:1258 #: src/view/screens/NotFound.tsx:54 msgid "Returns to previous page" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:316 +#: src/screens/StarterPack/Wizard/index.tsx:312 msgid "Returns to the previous step" msgstr "" -#: src/screens/Login/LoginForm.tsx:613 +#: src/screens/Login/LoginForm.tsx:614 msgid "Reveal password" msgstr "Reveal password" @@ -10526,13 +10526,13 @@ msgid "Sad GIFs" msgstr "Sad GIFs" #: src/components/dialogs/BirthDateSettings.tsx:200 -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:309 -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:324 -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:668 -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:673 -#: src/components/StarterPack/QrCodeDialog.tsx:210 -#: src/features/liveNow/components/EditLiveDialog.tsx:197 -#: src/features/liveNow/components/EditLiveDialog.tsx:204 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:317 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:332 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:665 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:670 +#: src/components/StarterPack/QrCodeDialog.tsx:205 +#: src/features/liveNow/components/EditLiveDialog.tsx:199 +#: src/features/liveNow/components/EditLiveDialog.tsx:206 #: src/screens/Messages/ConversationSettings/prompts.tsx:82 #: src/screens/Profile/Header/EditProfileDialog.tsx:233 #: src/screens/Profile/Header/EditProfileDialog.tsx:247 @@ -10552,34 +10552,34 @@ msgstr "" msgid "Save birthdate" msgstr "" -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:198 -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:207 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:195 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:204 #: src/screens/SavedFeeds.tsx:120 #: src/screens/SavedFeeds.tsx:124 #: src/screens/SavedFeeds.tsx:311 #: src/screens/SavedFeeds.tsx:315 -#: src/view/com/composer/Composer.tsx:1563 +#: src/view/com/composer/Composer.tsx:1587 #: src/view/com/composer/drafts/DraftsButton.tsx:125 msgid "Save changes" msgstr "" -#: src/view/com/composer/Composer.tsx:1535 +#: src/view/com/composer/Composer.tsx:1559 #: src/view/com/composer/drafts/DraftsButton.tsx:93 msgid "Save changes?" msgstr "" -#: src/view/com/composer/Composer.tsx:1563 +#: src/view/com/composer/Composer.tsx:1587 #: src/view/com/composer/drafts/DraftsButton.tsx:125 msgid "Save draft" msgstr "" -#: src/view/com/composer/Composer.tsx:1537 +#: src/view/com/composer/Composer.tsx:1561 #: src/view/com/composer/drafts/DraftsButton.tsx:95 msgid "Save draft?" msgstr "" #: src/components/Lightbox/chrome/ImageMenu.tsx:99 -#: src/components/MediaPreview.tsx:231 +#: src/components/MediaPreview.tsx:237 #: src/components/Post/Embed/ImageContextMenu.tsx:70 #: src/components/StarterPack/ShareDialog.tsx:144 #: src/components/StarterPack/ShareDialog.tsx:150 @@ -10590,12 +10590,12 @@ msgstr "" msgid "Save new handle" msgstr "" -#: src/components/StarterPack/QrCodeDialog.tsx:202 +#: src/components/StarterPack/QrCodeDialog.tsx:197 msgid "Save QR code" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:649 -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:655 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:646 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:652 msgid "Save these options for next time" msgstr "" @@ -10617,18 +10617,18 @@ msgstr "" #: src/components/dialogs/nuxs/BookmarksAnnouncement.tsx:145 #: src/Navigation.tsx:579 -#: src/screens/Bookmarks.tsx:59 +#: src/screens/Bookmarks.tsx:57 msgid "Saved Posts" msgstr "" #: src/screens/CustomFeed/components/CustomFeedHeader.tsx:138 -#: src/screens/ProfileList/components/Header.tsx:88 +#: src/screens/ProfileList/components/Header.tsx:89 msgid "Saved to your feeds" msgstr "" -#: src/components/NewskieDialog.tsx:141 -#: src/view/com/notifications/NotificationFeedItem.tsx:947 -#: src/view/com/notifications/NotificationFeedItem.tsx:955 +#: src/components/NewskieDialog.tsx:142 +#: src/view/com/notifications/NotificationFeedItem.tsx:938 +#: src/view/com/notifications/NotificationFeedItem.tsx:946 msgid "Say hello!" msgstr "" @@ -10637,7 +10637,7 @@ msgstr "" msgid "Say hi to someone" msgstr "Say hi to someone" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:43 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:44 msgid "Scam" msgstr "" @@ -10663,7 +10663,7 @@ msgstr "" msgid "Scroll right" msgstr "" -#: src/components/dms/MessageItem.tsx:820 +#: src/components/dms/MessageItem.tsx:835 msgid "Scroll to the message this is replying to" msgstr "Scroll to the message this is replying to" @@ -10694,7 +10694,7 @@ msgstr "" msgid "Search by name or interest" msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:356 +#: src/components/contacts/screens/ViewMatches.tsx:364 msgid "Search contacts" msgstr "" @@ -10720,15 +10720,15 @@ msgstr "Search for “{searchText}”" msgid "Search for {q}" msgstr "Search for {q}" -#: src/screens/StarterPack/Wizard/index.tsx:541 +#: src/screens/StarterPack/Wizard/index.tsx:537 msgid "Search for feeds that you want to suggest to others." msgstr "" -#: src/screens/Search/Explore.tsx:373 +#: src/screens/Search/Explore.tsx:369 msgid "Search for more accounts" msgstr "" -#: src/screens/Search/Explore.tsx:452 +#: src/screens/Search/Explore.tsx:448 msgid "Search for more feeds" msgstr "" @@ -10741,7 +10741,7 @@ msgstr "Search for people" msgid "Search GIFs" msgstr "" -#: src/screens/Hashtag.tsx:228 +#: src/screens/Hashtag.tsx:230 #: src/screens/Search/SearchResults.tsx:410 msgid "Search is currently unavailable when logged out" msgstr "" @@ -10821,7 +10821,7 @@ msgstr "" #: src/components/FeedInterstitials.tsx:495 #: src/components/FeedInterstitials.tsx:554 -#: src/components/interstitials/FeedTrendingTopics.tsx:112 +#: src/components/interstitials/FeedTrendingTopics.tsx:113 #: src/view/shell/desktop/SidebarTrendingTopics.tsx:80 msgid "See more" msgstr "" @@ -10830,7 +10830,7 @@ msgstr "" msgid "See more suggested profiles" msgstr "" -#: src/components/interstitials/FeedTrendingTopics.tsx:98 +#: src/components/interstitials/FeedTrendingTopics.tsx:99 #: src/view/shell/desktop/SidebarTrendingTopics.tsx:66 msgid "See more trending topics" msgstr "See more trending topics" @@ -10876,7 +10876,7 @@ msgstr "" msgid "Select a color" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:452 +#: src/components/moderation/ReportDialog/index.tsx:453 msgid "Select a reason" msgstr "" @@ -10932,11 +10932,11 @@ msgstr "Select filter type (currently: {0})" msgid "Select from an existing account" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:553 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:550 msgid "Select from your lists" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:555 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:552 msgid "Select from your lists <0>{numberOfListsSelected, plural, other {(# selected)}}" msgstr "" @@ -10955,7 +10955,7 @@ msgstr "" msgid "Select group chat members" msgstr "Select group chat members" -#: src/components/dialogs/MutedWords.tsx:151 +#: src/components/dialogs/MutedWords.tsx:153 msgid "Select how long to mute this word for." msgstr "" @@ -10973,7 +10973,7 @@ msgstr "" msgid "Select languages" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:507 +#: src/components/moderation/ReportDialog/index.tsx:508 msgid "Select moderation service" msgstr "" @@ -10993,7 +10993,7 @@ msgstr "" msgid "Select the {emojiName} emoji as your avatar" msgstr "" -#: src/components/Post/Translated/index.tsx:446 +#: src/components/Post/Translated/index.tsx:443 msgid "Select the source language" msgstr "Select the source language" @@ -11002,7 +11002,7 @@ msgstr "Select the source language" msgid "Select up to 3 languages used in this post" msgstr "" -#: src/components/dialogs/MutedWords.tsx:251 +#: src/components/dialogs/MutedWords.tsx:253 msgid "Select what content this mute word should apply to." msgstr "" @@ -11019,7 +11019,7 @@ msgid "Select your date of birth" msgstr "" #: src/screens/Onboarding/StepInterests/index.tsx:115 -#: src/screens/Settings/InterestsSettings.tsx:178 +#: src/screens/Settings/InterestsSettings.tsx:179 msgid "Select your interests from the options below" msgstr "" @@ -11035,7 +11035,7 @@ msgstr "" msgid "Selecting multiple media types is not supported." msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:188 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:190 msgid "Self-harm or dangerous behaviors" msgstr "" @@ -11047,7 +11047,7 @@ msgstr "" #: src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx:172 #: src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx:179 #: src/components/dialogs/EmailDialog/screens/Verify.tsx:311 -#: src/screens/Settings/components/DeleteAccountDialog.tsx:199 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:205 msgid "Send email" msgstr "" @@ -11062,20 +11062,20 @@ msgstr "Send error report" msgid "Send feedback" msgstr "" -#: src/screens/Messages/components/MessageComposer.tsx:324 +#: src/screens/Messages/components/MessageComposer.tsx:325 msgid "Send message" msgstr "" -#: src/components/PostControls/ShareMenu/RecentChats.tsx:146 +#: src/components/PostControls/ShareMenu/RecentChats.tsx:143 msgid "Send post to {name}" msgstr "" -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:167 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:176 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:160 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:169 msgid "Send post to..." msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:1063 +#: src/components/moderation/ReportDialog/index.tsx:1064 msgid "Send report to {title}" msgstr "" @@ -11084,8 +11084,8 @@ msgid "Send the message from your phone" msgstr "" #: src/components/dialogs/EmailDialog/screens/Verify.tsx:304 -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:121 -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:124 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:129 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:132 msgid "Send verification email" msgstr "" @@ -11097,7 +11097,7 @@ msgid "Send via chat" msgstr "Send via chat" #. placeholder {0}: i18n.date(new Date(message.sentAt), { timeStyle: 'short', }) -#: src/components/dms/MessageContextMenu.tsx:175 +#: src/components/dms/MessageContextMenu.tsx:173 msgid "Sent at {0}" msgstr "Sent at {0}" @@ -11115,11 +11115,11 @@ msgstr "" msgid "Set app icon to {0}" msgstr "" -#: src/screens/Login/SetNewPasswordForm.tsx:99 +#: src/screens/Login/SetNewPasswordForm.tsx:104 msgid "Set new password" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:481 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:478 msgid "Set precisely which groups of people can reply to your post" msgstr "" @@ -11127,7 +11127,7 @@ msgstr "" msgid "Set up your account" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:431 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:428 msgid "Set who can reply to your post" msgstr "" @@ -11135,20 +11135,20 @@ msgstr "" msgid "Set your birthdate below and we'll get you back to posting and exploring in no time!" msgstr "" -#: src/screens/Login/LoginForm.tsx:352 +#: src/screens/Login/LoginForm.tsx:353 msgid "set your hosting provider manually" msgstr "set your hosting provider manually" -#: src/screens/Login/LoginForm.tsx:486 +#: src/screens/Login/LoginForm.tsx:487 msgid "Set your hosting provider manually" msgstr "Set your hosting provider manually" -#: src/screens/Login/ForgotPasswordForm.tsx:104 +#: src/screens/Login/ForgotPasswordForm.tsx:108 msgid "Sets email for password reset" msgstr "" #: src/Navigation.tsx:215 -#: src/screens/Settings/Settings.tsx:100 +#: src/screens/Settings/Settings.tsx:102 #: src/view/shell/desktop/LeftNav.tsx:755 #: src/view/shell/Drawer.tsx:671 msgid "Settings" @@ -11208,7 +11208,7 @@ msgstr "" msgid "Settings for repost notifications" msgstr "" -#: src/screens/ModerationInteractionSettings/index.tsx:103 +#: src/screens/ModerationInteractionSettings/index.tsx:104 msgctxt "toast" msgid "Settings saved" msgstr "" @@ -11222,14 +11222,14 @@ msgid "Sexually Suggestive" msgstr "" #: src/components/Link.tsx:428 -#: src/components/MediaPreview.tsx:237 +#: src/components/MediaPreview.tsx:243 #: src/components/Post/Embed/ImageContextMenu.tsx:74 -#: src/components/StarterPack/QrCodeDialog.tsx:198 -#: src/screens/Hashtag.tsx:130 -#: src/screens/Messages/components/InviteLinkDialog.tsx:415 -#: src/screens/Messages/components/InviteLinkDialog.tsx:426 -#: src/screens/StarterPack/StarterPackScreen.tsx:447 -#: src/screens/Topic.tsx:89 +#: src/components/StarterPack/QrCodeDialog.tsx:193 +#: src/screens/Hashtag.tsx:132 +#: src/screens/Messages/components/InviteLinkDialog.tsx:412 +#: src/screens/Messages/components/InviteLinkDialog.tsx:423 +#: src/screens/StarterPack/StarterPackScreen.tsx:440 +#: src/screens/Topic.tsx:91 msgid "Share" msgstr "" @@ -11275,8 +11275,8 @@ msgstr "" msgid "Share link dialog" msgstr "" -#: src/screens/Settings/FindContactsSettings.tsx:172 -#: src/screens/Settings/FindContactsSettings.tsx:181 +#: src/screens/Settings/FindContactsSettings.tsx:170 +#: src/screens/Settings/FindContactsSettings.tsx:179 msgid "Share my profile" msgstr "Share my profile" @@ -11304,7 +11304,7 @@ msgstr "" msgid "Share this search" msgstr "Share this search" -#: src/screens/StarterPack/StarterPackScreen.tsx:440 +#: src/screens/StarterPack/StarterPackScreen.tsx:433 msgid "Share this starter pack" msgstr "" @@ -11314,16 +11314,16 @@ msgstr "" #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:130 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:133 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:160 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:166 -#: src/screens/StarterPack/StarterPackScreen.tsx:638 -#: src/screens/StarterPack/StarterPackScreen.tsx:646 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:161 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:167 +#: src/screens/StarterPack/StarterPackScreen.tsx:631 +#: src/screens/StarterPack/StarterPackScreen.tsx:639 #: src/view/com/profile/ProfileMenu.tsx:321 #: src/view/com/profile/ProfileMenu.tsx:333 msgid "Share via..." msgstr "" -#: src/components/contacts/screens/GetContacts.tsx:232 +#: src/components/contacts/screens/GetContacts.tsx:234 msgid "Share your contacts to find friends" msgstr "" @@ -11347,7 +11347,7 @@ msgstr "Sharing your age range reveals only the range associated with your Googl #: src/components/moderation/ContentHider.tsx:217 #: src/components/moderation/LabelPreference.tsx:143 -#: src/components/moderation/PostHider.tsx:140 +#: src/components/moderation/PostHider.tsx:137 msgid "Show" msgstr "" @@ -11357,16 +11357,16 @@ msgstr "" #: src/components/moderation/ScreenHider.tsx:175 #: src/components/moderation/ScreenHider.tsx:178 -#: src/features/liveNow/components/LiveStatusDialog.tsx:318 -#: src/features/liveNow/components/LiveStatusDialog.tsx:322 +#: src/features/liveNow/components/LiveStatusDialog.tsx:315 +#: src/features/liveNow/components/LiveStatusDialog.tsx:319 #: src/screens/List/ListHiddenScreen.tsx:194 -#: src/screens/VideoFeed/index.tsx:739 -#: src/screens/VideoFeed/index.tsx:745 +#: src/screens/VideoFeed/index.tsx:738 +#: src/screens/VideoFeed/index.tsx:744 msgid "Show anyway" msgstr "" -#: src/screens/Settings/AutomationLabelSettings.tsx:185 -#: src/screens/Settings/AutomationLabelSettings.tsx:198 +#: src/screens/Settings/AutomationLabelSettings.tsx:184 +#: src/screens/Settings/AutomationLabelSettings.tsx:197 msgid "Show automation label" msgstr "Show automation label" @@ -11379,7 +11379,7 @@ msgstr "" msgid "Show badge and filter from feeds" msgstr "" -#: src/components/dialogs/Embed.tsx:125 +#: src/components/dialogs/Embed.tsx:126 msgid "Show customization options" msgstr "" @@ -11387,8 +11387,8 @@ msgstr "" msgid "Show group chat updates" msgstr "Show group chat updates" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:609 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:611 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:605 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:607 msgid "Show less like this" msgstr "" @@ -11396,7 +11396,7 @@ msgstr "" msgid "Show list anyway" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:534 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:531 msgid "Show lists of users to select from" msgstr "" @@ -11404,8 +11404,8 @@ msgstr "" msgid "Show more" msgstr "Show more" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:601 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:603 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:597 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:599 msgid "Show more like this" msgstr "" @@ -11431,8 +11431,8 @@ msgstr "" msgid "Show replies as" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:689 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:699 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:685 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:695 msgid "Show reply for everyone" msgstr "" @@ -11464,16 +11464,16 @@ msgstr "" msgid "Show when you’re live" msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:599 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:596 msgid "Shows information about when this post was created" msgstr "" -#: src/screens/Settings/Settings.tsx:125 +#: src/screens/Settings/Settings.tsx:127 msgid "Shows other accounts you can switch to" msgstr "" #: src/components/moderation/ContentHider.tsx:169 -#: src/components/moderation/PostHider.tsx:94 +#: src/components/moderation/PostHider.tsx:91 msgid "Shows the content" msgstr "" @@ -11481,14 +11481,14 @@ msgstr "" #: src/components/dialogs/Signin.tsx:100 #: src/components/WelcomeModal.tsx:193 #: src/components/WelcomeModal.tsx:204 -#: src/screens/Hashtag.tsx:231 +#: src/screens/Hashtag.tsx:233 #: src/screens/Login/index.tsx:143 #: src/screens/Login/index.tsx:165 -#: src/screens/Login/LoginForm.tsx:274 -#: src/screens/Login/LoginForm.tsx:554 -#: src/screens/Login/LoginForm.tsx:560 -#: src/screens/Messages/JoinRequest.tsx:290 -#: src/screens/Messages/JoinRequest.tsx:296 +#: src/screens/Login/LoginForm.tsx:275 +#: src/screens/Login/LoginForm.tsx:555 +#: src/screens/Login/LoginForm.tsx:561 +#: src/screens/Messages/JoinRequest.tsx:298 +#: src/screens/Messages/JoinRequest.tsx:304 #: src/screens/Search/SearchResults.tsx:413 #: src/view/com/auth/SplashScreen.tsx:118 #: src/view/com/auth/SplashScreen.tsx:125 @@ -11512,8 +11512,8 @@ msgstr "" msgid "Sign in as…" msgstr "Sign in as…" -#: src/screens/Deactivated.tsx:195 -#: src/screens/Deactivated.tsx:201 +#: src/screens/Deactivated.tsx:200 +#: src/screens/Deactivated.tsx:206 msgid "Sign in or create an account" msgstr "" @@ -11521,7 +11521,7 @@ msgstr "" msgid "Sign in or create your account to join the conversation!" msgstr "" -#: src/screens/Messages/JoinRequest.tsx:216 +#: src/screens/Messages/JoinRequest.tsx:224 msgid "Sign in to accept invite." msgstr "Sign in to accept invite." @@ -11533,37 +11533,37 @@ msgstr "" msgid "Sign in to Bluesky or create a new account" msgstr "" -#: src/screens/Messages/JoinRequest.tsx:215 +#: src/screens/Messages/JoinRequest.tsx:223 msgid "Sign in to request access to this group chat." msgstr "Sign in to request access to this group chat." -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:587 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:589 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:583 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:585 msgid "Sign in to view post" msgstr "" -#: src/screens/Settings/Settings.tsx:275 #: src/screens/Settings/Settings.tsx:277 -#: src/screens/Settings/Settings.tsx:309 -#: src/screens/SignupQueued.tsx:94 -#: src/screens/SignupQueued.tsx:97 -#: src/screens/Takendown.tsx:88 +#: src/screens/Settings/Settings.tsx:279 +#: src/screens/Settings/Settings.tsx:311 +#: src/screens/SignupQueued.tsx:101 +#: src/screens/SignupQueued.tsx:104 +#: src/screens/Takendown.tsx:86 #: src/view/shell/desktop/LeftNav.tsx:227 #: src/view/shell/desktop/LeftNav.tsx:282 #: src/view/shell/desktop/LeftNav.tsx:285 msgid "Sign out" msgstr "" -#: src/screens/Takendown.tsx:91 +#: src/screens/Takendown.tsx:89 msgid "Sign Out" msgstr "" -#: src/screens/Settings/Settings.tsx:306 +#: src/screens/Settings/Settings.tsx:308 #: src/view/shell/desktop/LeftNav.tsx:224 msgid "Sign out?" msgstr "" -#: src/screens/Login/LoginForm.tsx:572 +#: src/screens/Login/LoginForm.tsx:573 msgid "Sign up" msgstr "Sign up" @@ -11584,23 +11584,23 @@ msgid "Since" msgstr "Since" #: src/components/contacts/screens/PhoneInput.tsx:163 -#: src/components/contacts/screens/VerifyNumber.tsx:205 +#: src/components/contacts/screens/VerifyNumber.tsx:208 #: src/screens/Onboarding/StepFindContactsIntro/index.tsx:86 #: src/screens/Onboarding/StepFindContactsIntro/index.tsx:90 #: src/screens/Onboarding/StepFinished/index.tsx:295 #: src/screens/Onboarding/StepFinished/index.tsx:317 -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:281 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:285 #: src/screens/Onboarding/StepSuggestedStarterpacks/index.tsx:105 -#: src/screens/StarterPack/Wizard/index.tsx:206 +#: src/screens/StarterPack/Wizard/index.tsx:202 msgid "Skip" msgstr "" #: src/components/contacts/screens/PhoneInput.tsx:160 -#: src/components/contacts/screens/VerifyNumber.tsx:202 +#: src/components/contacts/screens/VerifyNumber.tsx:205 msgid "Skip contact sharing and continue to the app" msgstr "" -#: src/view/com/composer/Composer.tsx:1580 +#: src/view/com/composer/Composer.tsx:1604 msgid "Skip empty posts?" msgstr "Skip empty posts?" @@ -11609,7 +11609,7 @@ msgstr "Skip empty posts?" msgid "Skip introduction and start using your account" msgstr "" -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:278 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:282 #: src/screens/Onboarding/StepSuggestedStarterpacks/index.tsx:102 msgid "Skip to next step" msgstr "" @@ -11650,70 +11650,70 @@ msgstr "" msgid "Some other feeds you might like" msgstr "" -#: src/components/WhoCanReply.tsx:88 +#: src/components/WhoCanReply.tsx:81 msgid "Some people can reply" msgstr "" -#: src/components/dms/getSystemMessageInfo.ts:86 +#: src/components/dms/getSystemMessageInfo.ts:91 msgid "Someone joined" msgstr "Someone joined" -#: src/components/dms/getSystemMessageInfo.ts:87 +#: src/components/dms/getSystemMessageInfo.ts:92 msgid "Someone joined the group" msgstr "Someone joined the group" -#: src/components/dms/getSystemMessageInfo.ts:99 +#: src/components/dms/getSystemMessageInfo.ts:106 msgid "Someone left" msgstr "Someone left" -#: src/components/dms/getSystemMessageInfo.ts:100 +#: src/components/dms/getSystemMessageInfo.ts:107 msgid "Someone left the group" msgstr "Someone left the group" #. placeholder {0}: reaction.value -#: src/components/dms/MessageItem.tsx:368 +#: src/components/dms/MessageItem.tsx:380 msgid "Someone reacted {0}" msgstr "" #. placeholder {0}: reaction.value -#: src/components/dms/getReactionInfo.ts:67 +#: src/components/dms/getReactionInfo.ts:72 msgid "Someone reacted {0} to {target}" msgstr "Someone reacted {0} to {target}" -#: src/components/dms/MessageItem.tsx:813 +#: src/components/dms/MessageItem.tsx:828 msgid "Someone replied" msgstr "Someone replied" -#: src/components/dms/getSystemMessageInfo.ts:60 +#: src/components/dms/getSystemMessageInfo.ts:61 msgid "Someone was added" msgstr "Someone was added" -#: src/components/dms/getSystemMessageInfo.ts:61 +#: src/components/dms/getSystemMessageInfo.ts:62 msgid "Someone was added to the group" msgstr "Someone was added to the group" -#: src/components/dms/getSystemMessageInfo.ts:73 +#: src/components/dms/getSystemMessageInfo.ts:76 msgid "Someone was removed" msgstr "Someone was removed" -#: src/components/dms/getSystemMessageInfo.ts:74 +#: src/components/dms/getSystemMessageInfo.ts:77 msgid "Someone was removed from the group" msgstr "Someone was removed from the group" -#: src/components/moderation/ReportDialog/index.tsx:142 +#: src/components/moderation/ReportDialog/index.tsx:143 msgid "Something wasn't quite right with the data you're trying to report. Please contact support." msgstr "" -#: src/screens/Messages/Conversation.tsx:133 -#: src/screens/Messages/ConversationSettings/index.tsx:137 -#: src/screens/Messages/JoinRequests.tsx:88 +#: src/screens/Messages/Conversation.tsx:135 +#: src/screens/Messages/ConversationSettings/index.tsx:134 +#: src/screens/Messages/JoinRequests.tsx:85 msgid "Something went wrong" msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:139 -#: src/components/moderation/ReportDialog/index.tsx:361 -#: src/screens/Deactivated.tsx:86 -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:59 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:141 +#: src/components/moderation/ReportDialog/index.tsx:362 +#: src/screens/Deactivated.tsx:92 +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:60 #: src/view/screens/Storybook/Admonitions.tsx:56 msgid "Something went wrong, please try again" msgstr "" @@ -11733,7 +11733,7 @@ msgstr "" msgid "Something went wrong. Please try again in a moment." msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:308 +#: src/components/moderation/ReportDialog/index.tsx:309 msgid "Something went wrong. Please try again." msgstr "" @@ -11741,7 +11741,7 @@ msgstr "" msgid "Something wrong? Let us know." msgstr "" -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:231 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:235 msgid "Sorry, we're unable to load account suggestions at this time." msgstr "" @@ -11767,20 +11767,20 @@ msgstr "" msgid "Source: <0>{sourceName}" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:39 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:40 msgid "Spam" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:36 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:37 msgid "Spam or other inauthentic behavior or deception" msgstr "" #: src/lib/interests.ts:72 -#: src/screens/Search/modules/ExploreTrendingTopics.tsx:240 +#: src/screens/Search/modules/ExploreTrendingTopics.tsx:238 msgid "Sports" msgstr "" -#: src/components/PostControls/ShareMenu/RecentChats.tsx:234 +#: src/components/PostControls/ShareMenu/RecentChats.tsx:231 msgid "Start a conversation, and it will appear here." msgstr "" @@ -11789,7 +11789,7 @@ msgstr "" msgid "Start a group chat" msgstr "Start a group chat" -#: src/components/dms/dialogs/NewChatDialog.tsx:192 +#: src/components/dms/dialogs/NewChatDialog.tsx:185 msgid "Start a new chat" msgstr "" @@ -11814,12 +11814,12 @@ msgstr "" #: src/Navigation.tsx:550 #: src/Navigation.tsx:555 -#: src/screens/StarterPack/Wizard/index.tsx:197 +#: src/screens/StarterPack/Wizard/index.tsx:193 msgid "Starter Pack" msgstr "" #. placeholder {0}: sanitizeHandle(creator.handle, '@') -#: src/components/StarterPack/StarterPackCard.tsx:91 +#: src/components/StarterPack/StarterPackCard.tsx:87 msgid "Starter pack by {0}" msgstr "" @@ -11827,12 +11827,12 @@ msgstr "" msgid "Starter pack by <0/>" msgstr "" -#: src/components/StarterPack/StarterPackCard.tsx:90 +#: src/components/StarterPack/StarterPackCard.tsx:86 #: src/view/com/profile/ProfileSubpageHeader.tsx:169 msgid "Starter pack by you" msgstr "" -#: src/screens/StarterPack/StarterPackScreen.tsx:765 +#: src/screens/StarterPack/StarterPackScreen.tsx:758 msgid "Starter pack is invalid" msgstr "" @@ -11840,8 +11840,8 @@ msgstr "" msgid "Starter packs" msgstr "Starter packs" -#: src/screens/Search/Explore.tsx:661 -#: src/view/screens/Profile.tsx:242 +#: src/screens/Search/Explore.tsx:657 +#: src/view/screens/Profile.tsx:239 msgid "Starter Packs" msgstr "" @@ -11853,7 +11853,7 @@ msgstr "" msgid "Starter packs let you share your favorite feeds and people with your friends." msgstr "" -#: src/view/screens/Profile.tsx:569 +#: src/view/screens/Profile.tsx:566 msgid "Starter Packs let you share your favorite feeds and people with your friends." msgstr "" @@ -11867,11 +11867,11 @@ msgstr "" #. placeholder {1}: state.serviceDescription && !state.serviceDescription.phoneVerificationRequired ? '2' : '3' #. placeholder {1}: state.totalSteps #: src/screens/Onboarding/Layout.tsx:226 -#: src/screens/Signup/index.tsx:184 +#: src/screens/Signup/index.tsx:181 msgid "Step {0} of {1}" msgstr "" -#: src/screens/Settings/Settings.tsx:412 +#: src/screens/Settings/Settings.tsx:414 msgid "Storage cleared, you need to restart the app now." msgstr "" @@ -11880,7 +11880,7 @@ msgid "Stored as part of a secure code for matching with others" msgstr "" #: src/Navigation.tsx:308 -#: src/screens/Settings/Settings.tsx:475 +#: src/screens/Settings/Settings.tsx:477 msgid "Storybook" msgstr "" @@ -11888,25 +11888,25 @@ msgstr "" msgid "Streaming on Twitch? Set your live status on Bluesky to add a badge to your avatar. Tapping it takes people straight to your stream." msgstr "" -#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:122 -#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:128 -#: src/components/moderation/AppealForm.tsx:154 -#: src/components/moderation/AppealForm.tsx:155 +#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:124 +#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:130 +#: src/components/moderation/AppealForm.tsx:165 +#: src/components/moderation/AppealForm.tsx:166 #: src/components/SendErrorReportDialog.tsx:90 #: src/components/SendErrorReportDialog.tsx:95 +#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:138 #: src/features/liveNow/components/GoLiveDisabledDialog.tsx:139 -#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:140 +#: src/screens/Messages/components/ChatDisabled.tsx:173 #: src/screens/Messages/components/ChatDisabled.tsx:175 -#: src/screens/Messages/components/ChatDisabled.tsx:177 #: src/screens/Settings/components/BetaFeaturesFeedbackDialog.tsx:133 msgid "Submit" msgstr "" -#: src/screens/Takendown.tsx:76 +#: src/screens/Takendown.tsx:74 msgid "Submit appeal" msgstr "" -#: src/screens/Takendown.tsx:80 +#: src/screens/Takendown.tsx:78 msgid "Submit Appeal" msgstr "" @@ -11914,13 +11914,13 @@ msgstr "" msgid "Submit feedback" msgstr "Submit feedback" -#: src/components/moderation/ReportDialog/index.tsx:585 -#: src/components/moderation/ReportDialog/index.tsx:658 -#: src/components/moderation/ReportDialog/index.tsx:665 +#: src/components/moderation/ReportDialog/index.tsx:586 +#: src/components/moderation/ReportDialog/index.tsx:659 +#: src/components/moderation/ReportDialog/index.tsx:666 msgid "Submit report" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:941 +#: src/components/moderation/ReportDialog/index.tsx:942 msgctxt "english-only-resource" msgid "Submit your report through the Report non-consensual intimate imagery (NCII) form" msgstr "Submit your report through the Report non-consensual intimate imagery (NCII) form" @@ -11930,13 +11930,13 @@ msgid "Subscribe" msgstr "" #. placeholder {0}: highlightedPublisher.name -#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:447 -#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:476 +#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:446 +#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:475 msgid "Subscribe on {0}" msgstr "Subscribe on {0}" #. placeholder {0}: highlightedPublisher.name -#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:475 +#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:474 msgid "Subscribe to {publicationTitle} on {0}" msgstr "Subscribe to {publicationTitle} on {0}" @@ -11945,15 +11945,15 @@ msgstr "Subscribe to {publicationTitle} on {0}" msgid "Subscribe to @{0} to use these labels:" msgstr "" -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:252 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:249 msgid "Subscribe to account activity" msgstr "" -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:356 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:352 msgid "Subscribe to Labeler" msgstr "" -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:322 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:318 msgid "Subscribe to this labeler" msgstr "" @@ -11966,12 +11966,12 @@ msgstr "" msgid "Success" msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:183 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:192 #: src/components/dialogs/EmailDialog/screens/Update.tsx:282 msgid "Success!" msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:131 +#: src/components/intents/GroupChatJoinDialog.tsx:129 msgid "Successfully joined the group chat!" msgstr "Successfully joined the group chat!" @@ -11984,13 +11984,13 @@ msgstr "" msgid "Suggested" msgstr "Suggested" -#: src/screens/Search/Explore.tsx:369 +#: src/screens/Search/Explore.tsx:365 msgid "Suggested accounts" msgstr "" #. Accounts suggested to the user for them to follow #: src/components/FeedInterstitials.tsx:472 -#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:182 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:186 msgid "Suggested for you" msgstr "" @@ -12023,21 +12023,21 @@ msgstr "" msgid "Support for this feature in your country has not been enabled yet! Please check back later." msgstr "" -#: src/components/dms/dialogs/NewChatDialog.tsx:98 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:114 +#: src/components/dms/dialogs/NewChatDialog.tsx:93 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:109 msgid "Suspended accounts cannot participate in a group chat." msgstr "Suspended accounts cannot participate in a group chat." -#: src/components/dms/dialogs/NewChatDialog.tsx:60 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:75 +#: src/components/dms/dialogs/NewChatDialog.tsx:58 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:73 msgid "Suspended accounts cannot participate in chat." msgstr "Suspended accounts cannot participate in chat." #: src/components/dialogs/SwitchAccount.tsx:47 #: src/components/dialogs/SwitchAccount.tsx:50 -#: src/screens/Settings/Settings.tsx:124 -#: src/screens/Settings/Settings.tsx:136 -#: src/screens/Settings/Settings.tsx:634 +#: src/screens/Settings/Settings.tsx:126 +#: src/screens/Settings/Settings.tsx:138 +#: src/screens/Settings/Settings.tsx:636 #: src/view/shell/desktop/LeftNav.tsx:262 msgid "Switch account" msgstr "" @@ -12051,8 +12051,8 @@ msgstr "" msgid "Switch to {0}" msgstr "" -#: src/components/dialogs/Embed.tsx:154 -#: src/components/dialogs/Embed.tsx:156 +#: src/components/dialogs/Embed.tsx:155 +#: src/components/dialogs/Embed.tsx:157 #: src/screens/Settings/AppearanceSettings.tsx:83 #: src/screens/Settings/AppearanceSettings.tsx:133 msgid "System" @@ -12061,11 +12061,11 @@ msgstr "" #: src/screens/Log.tsx:49 #: src/screens/Settings/AboutSettings.tsx:117 #: src/screens/Settings/AboutSettings.tsx:120 -#: src/screens/Settings/Settings.tsx:460 +#: src/screens/Settings/Settings.tsx:462 msgid "System log" msgstr "" -#: src/components/dialogs/MutedWords.tsx:291 +#: src/components/dialogs/MutedWords.tsx:293 msgid "Tags only" msgstr "" @@ -12077,7 +12077,7 @@ msgstr "Take the conversation private." msgid "Tap below to allow Bluesky to access your GPS location. We will then use that data to more accurately determine the content and features available in your region." msgstr "" -#: src/components/dms/MessageItem.tsx:682 +#: src/components/dms/MessageItem.tsx:697 msgid "Tap for details" msgstr "Tap for details" @@ -12104,7 +12104,7 @@ msgstr "" msgid "Tap to close context menu" msgstr "" -#: src/components/dms/ChatInvite/Root.tsx:92 +#: src/components/dms/ChatInvite/Root.tsx:93 msgid "Tap to copy this invite link" msgstr "Tap to copy this invite link" @@ -12112,12 +12112,12 @@ msgstr "Tap to copy this invite link" msgid "Tap to dismiss" msgstr "" -#: src/components/dms/ChatInvite/Root.tsx:140 -#: src/components/intents/GroupChatJoinDialog.tsx:469 +#: src/components/dms/ChatInvite/Root.tsx:141 +#: src/components/intents/GroupChatJoinDialog.tsx:474 msgid "Tap to join this group chat immediately" msgstr "Tap to join this group chat immediately" -#: src/components/dms/ChatInvite/Root.tsx:105 +#: src/components/dms/ChatInvite/Root.tsx:106 msgid "Tap to open this group chat" msgstr "Tap to open this group chat" @@ -12130,12 +12130,12 @@ msgstr "Tap to remove" msgid "Tap to remove your {0} reaction" msgstr "Tap to remove your {0} reaction" -#: src/components/dms/ChatInvite/Root.tsx:139 -#: src/components/intents/GroupChatJoinDialog.tsx:468 +#: src/components/dms/ChatInvite/Root.tsx:140 +#: src/components/intents/GroupChatJoinDialog.tsx:473 msgid "Tap to request access to join this group chat" msgstr "Tap to request access to join this group chat" -#: src/components/dms/MessageItem.tsx:647 +#: src/components/dms/MessageItem.tsx:662 msgid "Tap to retry" msgstr "Tap to retry" @@ -12148,11 +12148,11 @@ msgstr "Tap to show {0} reactions" msgid "Tap to show all reactions" msgstr "Tap to show all reactions" -#: src/components/dms/MessageItem.tsx:394 +#: src/components/dms/MessageItem.tsx:406 msgid "Tap to view reactions" msgstr "Tap to view reactions" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:107 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:108 msgid "Targeted harassment" msgstr "" @@ -12180,7 +12180,7 @@ msgstr "" msgid "Tell us a little more" msgstr "" -#: src/screens/Settings/components/DeleteAccountDialog.tsx:214 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:220 msgid "Temporarily deactivate your account" msgstr "Temporarily deactivate your account" @@ -12201,13 +12201,13 @@ msgstr "" msgid "Terms of Service" msgstr "" -#: src/components/dialogs/MutedWords.tsx:275 +#: src/components/dialogs/MutedWords.tsx:277 msgid "Text & tags" msgstr "" -#: src/components/moderation/AppealForm.tsx:118 -#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:122 -#: src/screens/Messages/components/ChatDisabled.tsx:142 +#: src/components/moderation/AppealForm.tsx:129 +#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:121 +#: src/screens/Messages/components/ChatDisabled.tsx:140 msgid "Text input field" msgstr "" @@ -12219,7 +12219,7 @@ msgstr "" msgid "Thanks for your feedback!" msgstr "Thanks for your feedback!" -#: src/components/intents/VerifyEmailIntentDialog.tsx:77 +#: src/components/intents/VerifyEmailIntentDialog.tsx:78 msgid "Thanks, you have successfully verified your email address. You can close this dialog." msgstr "" @@ -12232,12 +12232,12 @@ msgstr "" msgid "That contains the following:" msgstr "" -#: src/screens/StarterPack/StarterPackScreen.tsx:115 -#: src/screens/StarterPack/StarterPackScreen.tsx:116 -#: src/screens/StarterPack/StarterPackScreen.tsx:160 -#: src/screens/StarterPack/StarterPackScreen.tsx:161 -#: src/screens/StarterPack/Wizard/index.tsx:116 -#: src/screens/StarterPack/Wizard/index.tsx:126 +#: src/screens/StarterPack/StarterPackScreen.tsx:112 +#: src/screens/StarterPack/StarterPackScreen.tsx:113 +#: src/screens/StarterPack/StarterPackScreen.tsx:157 +#: src/screens/StarterPack/StarterPackScreen.tsx:158 +#: src/screens/StarterPack/Wizard/index.tsx:112 +#: src/screens/StarterPack/Wizard/index.tsx:122 msgid "That starter pack could not be found." msgstr "" @@ -12245,15 +12245,15 @@ msgstr "" msgid "That username is already taken" msgstr "" -#: src/view/com/post-thread/PostQuotes.tsx:142 +#: src/view/com/post-thread/PostQuotes.tsx:135 msgid "That's all, folks!" msgstr "" -#: src/screens/VideoFeed/index.tsx:1234 +#: src/screens/VideoFeed/index.tsx:1230 msgid "That's everything!" msgstr "" -#: src/components/moderation/BlockDialog.tsx:153 +#: src/components/moderation/BlockDialog.tsx:150 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:184 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:422 msgid "The account will be able to interact with you after unblocking." @@ -12265,7 +12265,7 @@ msgid "The app will be restarted" msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:139 -#: src/lib/moderation/useModerationCauseDescription.ts:129 +#: src/lib/moderation/useModerationCauseDescription.ts:126 msgid "The author of this thread has hidden this reply." msgstr "" @@ -12293,7 +12293,7 @@ msgstr "" msgid "The Discover feed now knows what you like" msgstr "" -#: src/screens/StarterPack/StarterPackLandingScreen.tsx:332 +#: src/screens/StarterPack/StarterPackLandingScreen.tsx:324 msgid "The experience is better in the app. Download Bluesky now and we'll pick back up where you left off." msgstr "" @@ -12313,7 +12313,7 @@ msgstr "" msgid "The following services are enabled for your account: {allowedServices}" msgstr "" -#: src/screens/ModerationInteractionSettings/index.tsx:43 +#: src/screens/ModerationInteractionSettings/index.tsx:44 msgid "The following settings will be used as your defaults when creating new posts. You can edit these for a specific post from the composer." msgstr "" @@ -12321,12 +12321,12 @@ msgstr "" msgid "The laws in your region require you to verify you're an adult to access certain features. Tap to learn more." msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:165 -#: src/screens/Messages/JoinRequests.tsx:205 +#: src/components/intents/GroupChatJoinDialog.tsx:163 +#: src/screens/Messages/JoinRequests.tsx:199 msgid "The member limit has been reached." msgstr "The member limit has been reached." -#: src/components/moderation/ReportDialog/index.tsx:314 +#: src/components/moderation/ReportDialog/index.tsx:315 msgid "The moderation service is temporarily unavailable. Please try again later." msgstr "The moderation service is temporarily unavailable. Please try again later." @@ -12338,41 +12338,41 @@ msgstr "The post you’re replying to was marked as being written in {suggestedL msgid "The Privacy Policy has been moved to <0/>" msgstr "" -#: src/view/com/composer/state/video.ts:493 +#: src/view/com/composer/state/video.ts:498 msgid "The processed video is too large. Please try again with a smaller file." msgstr "The processed video is too large. Please try again with a smaller file." -#: src/view/com/composer/state/video.ts:468 +#: src/view/com/composer/state/video.ts:473 msgid "The selected video could not be encoded." msgstr "The selected video could not be encoded." -#: src/view/com/composer/state/video.ts:458 +#: src/view/com/composer/state/video.ts:463 msgid "The selected video could not be processed." msgstr "The selected video could not be processed." -#: src/view/com/composer/state/video.ts:488 +#: src/view/com/composer/state/video.ts:493 msgid "The selected video has an unsupported aspect ratio." msgstr "The selected video has an unsupported aspect ratio." -#: src/view/com/composer/state/video.ts:504 -#: src/view/com/composer/state/video.ts:543 +#: src/view/com/composer/state/video.ts:509 +#: src/view/com/composer/state/video.ts:548 msgid "The selected video is larger than {VIDEO_MAX_SIZE_MB} MB. Please try again with a smaller file." msgstr "The selected video is larger than {VIDEO_MAX_SIZE_MB} MB. Please try again with a smaller file." -#: src/view/com/composer/state/video.ts:486 +#: src/view/com/composer/state/video.ts:491 msgid "The selected video is too long." msgstr "The selected video is too long." -#: src/view/com/composer/state/video.ts:490 +#: src/view/com/composer/state/video.ts:495 msgid "The selected video uses an unsupported format." msgstr "The selected video uses an unsupported format." #: src/lib/hooks/useCleanError.ts:41 -#: src/lib/strings/errors.ts:18 +#: src/lib/strings/errors.ts:53 msgid "The server appears to be experiencing issues. Please try again in a few moments." msgstr "" -#: src/screens/StarterPack/StarterPackScreen.tsx:775 +#: src/screens/StarterPack/StarterPackScreen.tsx:768 msgid "The starter pack that you are trying to view is invalid. You may delete this starter pack instead." msgstr "" @@ -12388,17 +12388,17 @@ msgstr "" msgid "The Terms of Service have been moved to" msgstr "" -#: src/components/intents/VerifyEmailIntentDialog.tsx:89 +#: src/components/intents/VerifyEmailIntentDialog.tsx:90 msgid "The verification code you have provided is invalid. Please make sure that you have used the correct verification link or request a new one." msgstr "" #: src/components/contacts/screens/PhoneInput.tsx:115 -#: src/components/contacts/screens/VerifyNumber.tsx:113 -#: src/components/contacts/screens/VerifyNumber.tsx:165 +#: src/components/contacts/screens/VerifyNumber.tsx:118 +#: src/components/contacts/screens/VerifyNumber.tsx:170 msgid "The verification provider was unable to send a code to your phone number. Please check your phone number and try again." msgstr "" -#: src/view/com/composer/state/video.ts:471 +#: src/view/com/composer/state/video.ts:476 msgid "The video could not be uploaded to your hosting provider. Please try again." msgstr "The video could not be uploaded to your hosting provider. Please try again." @@ -12410,11 +12410,11 @@ msgstr "" msgid "There is a limit to how often you can change your birthdate. You may need to wait a day or two before updating it again." msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:519 +#: src/screens/Messages/components/InviteLinkDialog.tsx:516 msgid "There is no invite link for this group chat." msgstr "There is no invite link for this group chat." -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:86 +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:87 msgid "There is no time limit for account deactivation, come back any time." msgstr "" @@ -12423,23 +12423,23 @@ msgstr "" msgid "There was a problem loading GIFs. Check your connection and try again." msgstr "There was a problem loading GIFs. Check your connection and try again." -#: src/components/contacts/screens/GetContacts.tsx:147 -#: src/components/intents/GroupChatJoinDialog.tsx:149 -#: src/components/intents/GroupChatJoinDialog.tsx:195 -#: src/screens/Messages/components/OutgoingRequestListItem.tsx:36 +#: src/components/contacts/screens/GetContacts.tsx:148 +#: src/components/intents/GroupChatJoinDialog.tsx:147 +#: src/components/intents/GroupChatJoinDialog.tsx:200 +#: src/screens/Messages/components/OutgoingRequestListItem.tsx:34 msgid "There was a problem with your internet connection, please try again" msgstr "" #: src/screens/CustomFeed/components/CustomFeedHeader.tsx:184 -#: src/screens/ProfileList/components/Header.tsx:91 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:79 +#: src/screens/ProfileList/components/Header.tsx:92 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:80 #: src/screens/SavedFeeds.tsx:99 #: src/screens/SavedFeeds.tsx:278 msgid "There was an issue contacting the server" msgstr "" #: src/screens/CustomFeed/components/CustomFeedHeader.tsx:467 -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:101 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:97 msgid "There was an issue contacting the server, please check your internet connection and try again." msgstr "" @@ -12447,8 +12447,8 @@ msgstr "" msgid "There was an issue fetching notifications. Tap here to try again." msgstr "" -#: src/screens/Search/Explore.tsx:1015 -#: src/view/com/posts/PostFeed.tsx:834 +#: src/screens/Search/Explore.tsx:1011 +#: src/view/com/posts/PostFeed.tsx:828 msgid "There was an issue fetching posts. Tap here to try again." msgstr "" @@ -12469,7 +12469,7 @@ msgstr "" msgid "There was an issue fetching your service info" msgstr "" -#: src/view/com/posts/PostFeedErrorMessage.tsx:153 +#: src/view/com/posts/PostFeedErrorMessage.tsx:151 msgid "There was an issue removing this feed. Please check your internet connection and try again." msgstr "" @@ -12480,9 +12480,9 @@ msgid "There was an issue updating your feeds, please check your internet connec msgstr "" #. placeholder {0}: e.toString() -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:432 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:455 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:475 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:428 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:451 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:471 #: src/screens/Messages/ConversationSettings/Member.tsx:71 #: src/screens/Messages/ConversationSettings/MemberMenu.tsx:114 #: src/screens/Messages/ConversationSettings/MemberMenu.tsx:127 @@ -12503,14 +12503,14 @@ msgstr "" msgid "There was an issue! {0}" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:241 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:238 #: src/screens/List/ListHiddenScreen.tsx:67 #: src/screens/List/ListHiddenScreen.tsx:81 #: src/screens/List/ListHiddenScreen.tsx:103 -#: src/screens/ProfileList/components/Header.tsx:106 -#: src/screens/ProfileList/components/Header.tsx:120 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:121 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:135 +#: src/screens/ProfileList/components/Header.tsx:107 +#: src/screens/ProfileList/components/Header.tsx:121 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:122 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:136 #: src/screens/ProfileList/components/SubscribeMenu.tsx:38 #: src/screens/ProfileList/components/SubscribeMenu.tsx:53 msgid "There was an issue. Please check your internet connection and try again." @@ -12523,11 +12523,11 @@ msgstr "" msgid "There was an unexpected issue in the application. Please let us know if this happened to you!" msgstr "" -#: src/screens/SignupQueued.tsx:133 +#: src/screens/SignupQueued.tsx:140 msgid "There's been a rush of new users to Bluesky! We'll activate your account as soon as we can." msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:660 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:657 msgid "These are your default settings" msgstr "" @@ -12550,7 +12550,7 @@ msgstr "" msgid "These URLs" msgstr "These URLs" -#: src/components/moderation/BlockDialog.tsx:353 +#: src/components/moderation/BlockDialog.tsx:354 msgid "They own this chat" msgstr "They own this chat" @@ -12590,12 +12590,12 @@ msgstr "" msgid "This app is running a deployment of <0>{currentChannel}. Restore the {defaultChannel} deployment to get back to a standard build." msgstr "This app is running a deployment of <0>{currentChannel}. Restore the {defaultChannel} deployment to get back to a standard build." -#: src/components/moderation/AppealForm.tsx:96 +#: src/components/moderation/AppealForm.tsx:107 msgid "This appeal will be sent to <0>{sourceName}." msgstr "" -#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:114 -#: src/screens/Messages/components/ChatDisabled.tsx:138 +#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:113 +#: src/screens/Messages/components/ChatDisabled.tsx:136 msgid "This appeal will be sent to Bluesky's moderation service." msgstr "" @@ -12604,7 +12604,7 @@ msgstr "" msgid "This author has chosen to make their posts visible only to people who are signed in." msgstr "" -#: src/screens/Profile/components/GermButton.tsx:244 +#: src/screens/Profile/components/GermButton.tsx:245 msgid "This button lets others open the Germ DM app to send you a message. You can manage its visibility from the Germ DM app, or you can disconnect your Bluesky account from Germ DM altogether by clicking the button below." msgstr "" @@ -12612,18 +12612,18 @@ msgstr "" msgid "This chat has ended" msgstr "This chat has ended" -#: src/components/dms/ChatInvite/Root.tsx:122 -#: src/components/intents/GroupChatJoinDialog.tsx:301 +#: src/components/dms/ChatInvite/Root.tsx:123 +#: src/components/intents/GroupChatJoinDialog.tsx:306 msgid "This chat is full" msgstr "This chat is full" -#: src/screens/Messages/components/ChatListItem.tsx:376 -#: src/screens/Messages/components/ChatLocked.tsx:69 +#: src/screens/Messages/components/ChatListItem.tsx:391 +#: src/screens/Messages/components/ChatLocked.tsx:73 msgid "This chat is locked" msgstr "This chat is locked" -#: src/screens/Messages/components/ChatLocked.tsx:45 -#: src/screens/Messages/ConversationSettings/index.tsx:437 +#: src/screens/Messages/components/ChatLocked.tsx:49 +#: src/screens/Messages/ConversationSettings/index.tsx:432 msgid "This chat is locked by a moderation action" msgstr "This chat is locked by a moderation action" @@ -12631,11 +12631,11 @@ msgstr "This chat is locked by a moderation action" msgid "This chat was disconnected" msgstr "" -#: src/components/contacts/screens/VerifyNumber.tsx:106 +#: src/components/contacts/screens/VerifyNumber.tsx:110 msgid "This code is invalid. Resend to get a new code." msgstr "" -#: src/screens/Settings/components/ChangePasswordDialog.tsx:145 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:149 msgid "This confirmation code is not valid. Please try again." msgstr "" @@ -12653,11 +12653,11 @@ msgid "This content is hosted by {0}. Do you want to enable external media?" msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:104 -#: src/lib/moderation/useModerationCauseDescription.ts:85 +#: src/lib/moderation/useModerationCauseDescription.ts:82 msgid "This content is not available because one of the users involved has blocked the other." msgstr "" -#: src/view/com/posts/PostFeedErrorMessage.tsx:122 +#: src/view/com/posts/PostFeedErrorMessage.tsx:120 msgid "This content is not viewable without a Bluesky account." msgstr "" @@ -12686,7 +12686,7 @@ msgstr "This exact phrase" msgid "This feature allows users to receive notifications for your new posts and replies. Who do you want to enable this for?" msgstr "" -#: src/screens/Settings/components/ExportCarDialog.tsx:166 +#: src/screens/Settings/components/ExportCarDialog.tsx:172 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost." msgstr "" @@ -12694,11 +12694,11 @@ msgstr "" msgid "This feature is not available while using an app password. Please sign in with your main password." msgstr "" -#: src/lib/strings/errors.ts:30 +#: src/lib/strings/errors.ts:65 msgid "This feature is not available while using an App Password. Please sign in with your main password." msgstr "" -#: src/view/com/posts/PostFeedErrorMessage.tsx:128 +#: src/view/com/posts/PostFeedErrorMessage.tsx:126 msgid "This feed is currently receiving high traffic and is temporarily unavailable. Please try again later." msgstr "" @@ -12716,7 +12716,7 @@ msgstr "" msgid "This feed is no longer online. We are showing <0>Discover instead." msgstr "" -#: src/screens/Messages/components/ChatLocked.tsx:72 +#: src/screens/Messages/components/ChatLocked.tsx:76 msgid "This group is locked by a moderation action on the owner" msgstr "This group is locked by a moderation action on the owner" @@ -12732,11 +12732,11 @@ msgstr "This image could not be used. Try a different format like .jpg or .png." msgid "This information is private and not shared with other users." msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:161 +#: src/components/intents/GroupChatJoinDialog.tsx:160 msgid "This invite link has been disabled." msgstr "This invite link has been disabled." -#: src/components/intents/GroupChatJoinDialog.tsx:236 +#: src/components/intents/GroupChatJoinDialog.tsx:241 msgid "This invite link is invalid" msgstr "This invite link is invalid" @@ -12744,12 +12744,12 @@ msgstr "This invite link is invalid" msgid "This is just a one-time security check, since we haven't seen this account on this device before." msgstr "This is just a one-time security check, since we haven't seen this account on this device before." -#: src/features/liveNow/components/EditLiveDialog.tsx:171 +#: src/features/liveNow/components/EditLiveDialog.tsx:173 #: src/features/liveNow/components/GoLiveDialog.tsx:161 msgid "This is not a valid link" msgstr "" -#: src/screens/Settings/AutomationLabelSettings.tsx:173 +#: src/screens/Settings/AutomationLabelSettings.tsx:172 msgid "This label lets the world know that this account is automated. If turned on, this label appears next to the account's name on their profile and posts. It can be turned on or off at any time." msgstr "This label lets the world know that this account is automated. If turned on, this label appears next to the account's name on their profile and posts. It can be turned on or off at any time." @@ -12787,21 +12787,21 @@ msgstr "" msgid "This list is empty." msgstr "" -#: src/screens/Messages/components/ChatListItem.tsx:335 +#: src/screens/Messages/components/ChatListItem.tsx:340 msgid "This message is hidden" msgstr "This message is hidden" -#: src/components/dms/MessageItem.tsx:680 -#: src/components/dms/MessageItem.tsx:709 +#: src/components/dms/MessageItem.tsx:695 +#: src/components/dms/MessageItem.tsx:724 msgid "This message is hidden because this user is blocking you." msgstr "This message is hidden because this user is blocking you." -#: src/components/dms/MessageItem.tsx:679 -#: src/components/dms/MessageItem.tsx:705 +#: src/components/dms/MessageItem.tsx:694 +#: src/components/dms/MessageItem.tsx:720 msgid "This message is hidden because you are blocking this user." msgstr "This message is hidden because you are blocking this user." -#: src/components/moderation/ReportDialog/index.tsx:312 +#: src/components/moderation/ReportDialog/index.tsx:313 msgid "This moderation service does not support that report reason. Please choose a different reason or moderation service." msgstr "This moderation service does not support that report reason. Please choose a different reason or moderation service." @@ -12815,11 +12815,11 @@ msgstr "This person is blocking you" #. placeholder {0}: niceDate(i18n, createdAt) #. placeholder {1}: niceDate(i18n, indexedAt) -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:639 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:636 msgid "This post claims to have been created on <0>{0}, but was first seen by Bluesky on <1>{1}." msgstr "" -#: src/components/WhoCanReply.tsx:274 +#: src/components/WhoCanReply.tsx:267 msgid "This post has an unknown type of threadgate on it. Your app may be out of date." msgstr "" @@ -12827,15 +12827,15 @@ msgstr "" msgid "This post is only visible to logged-in users." msgstr "" -#: src/screens/Bookmarks.tsx:248 +#: src/screens/Bookmarks.tsx:246 msgid "This post was deleted by its author" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:834 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:830 msgid "This post will be hidden from feeds and threads. This cannot be undone." msgstr "" -#: src/view/com/composer/Composer.tsx:1155 +#: src/view/com/composer/Composer.tsx:1177 msgid "This post's author has disabled quote posts." msgstr "" @@ -12843,12 +12843,12 @@ msgstr "" msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't signed in." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:869 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:865 msgid "This reply will be sorted into a hidden section at the bottom of your thread and will mute notifications for subsequent replies - both for yourself and others." msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:156 -#: src/screens/Messages/JoinRequests.tsx:111 +#: src/screens/Messages/ConversationSettings/index.tsx:153 +#: src/screens/Messages/JoinRequests.tsx:108 msgid "This screen is only available for group conversations." msgstr "This screen is only available for group conversations." @@ -12856,7 +12856,7 @@ msgstr "This screen is only available for group conversations." msgid "This service has not provided terms of service or a privacy policy." msgstr "" -#: src/features/liveNow/index.tsx:209 +#: src/features/liveNow/index.tsx:204 msgid "This service is not supported while the Live feature is in beta. Allowed services: {formatted}." msgstr "" @@ -12864,7 +12864,7 @@ msgstr "" msgid "This should create a domain record at:" msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:214 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:223 msgid "This should only take a few minutes." msgstr "" @@ -12876,18 +12876,18 @@ msgstr "" msgid "This user doesn't have any followers." msgstr "" -#: src/components/dms/dialogs/NewChatDialog.tsx:64 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:79 +#: src/components/dms/dialogs/NewChatDialog.tsx:61 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:76 msgid "This user has blocked you and cannot be messaged." msgstr "This user has blocked you and cannot be messaged." #: src/components/moderation/ModerationDetailsDialog.tsx:99 -#: src/lib/moderation/useModerationCauseDescription.ts:76 +#: src/lib/moderation/useModerationCauseDescription.ts:73 msgid "This user has blocked you. You cannot view their content." msgstr "" -#: src/components/dms/dialogs/NewChatDialog.tsx:68 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:83 +#: src/components/dms/dialogs/NewChatDialog.tsx:64 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:79 msgid "This user has disabled chat and cannot be messaged." msgstr "This user has disabled chat and cannot be messaged." @@ -12905,7 +12905,7 @@ msgstr "" msgid "This user is included in the <0>{0} list which you have muted." msgstr "" -#: src/components/NewskieDialog.tsx:49 +#: src/components/NewskieDialog.tsx:50 msgid "This user is new here. Press for more info about when they joined." msgstr "" @@ -12921,25 +12921,25 @@ msgstr "This video can’t be played on your device. Your browser or system may msgid "This video can’t be previewed in your browser. It will still be uploaded." msgstr "This video can’t be previewed in your browser. It will still be uploaded." -#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:157 +#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:156 msgid "This will create a new list with the same name, description, and members as this starter pack." msgstr "" #. placeholder {0}: word.value -#: src/components/dialogs/MutedWords.tsx:454 +#: src/components/dialogs/MutedWords.tsx:456 msgid "This will delete \"{0}\" from your muted words. You can always add it back later." msgstr "" -#: src/screens/Settings/components/DeleteAccountDialog.tsx:330 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:336 msgid "This will irreversibly delete your Bluesky account <0>{currentHandle} and all associated data. Note that this will affect any other <1>AT Protocol services you use with this account." msgstr "" #. placeholder {0}: account.handle -#: src/screens/Settings/Settings.tsx:688 +#: src/screens/Settings/Settings.tsx:690 msgid "This will remove @{0} from the quick access list." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:862 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:858 msgid "This will remove your post from this quote post for all users, and replace it with a placeholder." msgstr "" @@ -12971,7 +12971,7 @@ msgstr "" msgid "Threads Preferences" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:134 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:135 msgid "Threats or incitement" msgstr "" @@ -12988,7 +12988,7 @@ msgstr "Title" msgid "Title (100 characters max)" msgstr "Title (100 characters max)" -#: src/screens/Settings/components/DisableEmail2FADialog.tsx:100 +#: src/screens/Settings/components/DisableEmail2FADialog.tsx:108 msgid "To disable the email 2FA method, please verify your access to the email address." msgstr "" @@ -13014,21 +13014,21 @@ msgstr "" msgid "Toggles the sound" msgstr "" -#: src/components/contacts/screens/VerifyNumber.tsx:123 +#: src/components/contacts/screens/VerifyNumber.tsx:127 msgid "Too many attempts. Please wait a few minutes and try again." msgstr "" -#: src/components/contacts/screens/VerifyNumber.tsx:176 +#: src/components/contacts/screens/VerifyNumber.tsx:179 msgid "Too many codes sent. Please wait a few minutes and try again." msgstr "" -#: src/components/contacts/screens/GetContacts.tsx:156 +#: src/components/contacts/screens/GetContacts.tsx:158 msgid "Too many contacts - you've exceeded the number of contacts you can import to find your friends" msgstr "" -#: src/screens/Hashtag.tsx:86 +#: src/screens/Hashtag.tsx:88 #: src/screens/Search/SearchResults.tsx:83 -#: src/screens/Topic.tsx:58 +#: src/screens/Topic.tsx:60 msgid "Top" msgstr "" @@ -13043,25 +13043,25 @@ msgstr "" msgid "Topic" msgstr "" -#: src/components/dms/MessageContextMenu.tsx:191 -#: src/components/dms/MessageContextMenu.tsx:194 -#: src/components/Post/Translated/index.tsx:150 -#: src/components/Post/Translated/index.tsx:157 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:564 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:570 +#: src/components/dms/MessageContextMenu.tsx:189 +#: src/components/dms/MessageContextMenu.tsx:192 +#: src/components/Post/Translated/index.tsx:147 +#: src/components/Post/Translated/index.tsx:154 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:560 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:566 msgid "Translate" msgstr "" -#: src/components/Post/Translated/index.tsx:326 +#: src/components/Post/Translated/index.tsx:323 msgid "Translated" msgstr "Translated" -#: src/components/Post/Translated/index.tsx:113 +#: src/components/Post/Translated/index.tsx:110 msgid "Translating" msgstr "Translating" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:545 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:547 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:541 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:543 msgid "Translating…" msgstr "Translating…" @@ -13074,8 +13074,8 @@ msgstr "Translation to the same language is unavailable on your device." msgid "Tree view" msgstr "" -#: src/components/interstitials/FeedTrendingTopics.tsx:94 -#: src/screens/Search/modules/ExploreTrendingTopics.tsx:71 +#: src/components/interstitials/FeedTrendingTopics.tsx:95 +#: src/screens/Search/modules/ExploreTrendingTopics.tsx:69 #: src/view/shell/desktop/SidebarTrendingTopics.tsx:63 msgid "Trending" msgstr "" @@ -13085,17 +13085,17 @@ msgstr "" msgid "Trending GIFs" msgstr "Trending GIFs" -#: src/components/interstitials/FeedTrendingTopics.tsx:121 -#: src/screens/Search/modules/ExploreTrendingTopics.tsx:74 +#: src/components/interstitials/FeedTrendingTopics.tsx:122 +#: src/screens/Search/modules/ExploreTrendingTopics.tsx:72 #: src/view/shell/desktop/SidebarTrendingTopics.tsx:90 msgid "Trending options" msgstr "" -#: src/components/interstitials/TrendingVideos.tsx:86 +#: src/components/interstitials/TrendingVideos.tsx:88 msgid "Trending videos" msgstr "Trending videos" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:103 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:104 msgid "Trolling" msgstr "" @@ -13125,8 +13125,8 @@ msgstr "" msgid "Try again in a moment." msgstr "Try again in a moment." -#: src/components/Post/Translated/index.tsx:229 -#: src/components/Post/Translated/index.tsx:242 +#: src/components/Post/Translated/index.tsx:226 +#: src/components/Post/Translated/index.tsx:239 msgid "Try Google Translate" msgstr "Try Google Translate" @@ -13156,50 +13156,50 @@ msgid "Unable to access location. You'll need to visit your system settings to e msgstr "" #: src/lib/hooks/useCleanError.ts:30 -#: src/lib/strings/errors.ts:11 +#: src/lib/strings/errors.ts:40 msgid "Unable to connect. Please check your internet connection and try again." msgstr "" -#: src/screens/Settings/components/ChangePasswordDialog.tsx:96 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:141 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:98 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:143 msgid "Unable to contact your service. Please check your internet connection and try again." msgstr "" -#: src/screens/Login/ForgotPasswordForm.tsx:66 +#: src/screens/Login/ForgotPasswordForm.tsx:70 #: src/screens/Login/index.tsx:100 -#: src/screens/Login/LoginForm.tsx:169 -#: src/screens/Login/SetNewPasswordForm.tsx:77 +#: src/screens/Login/LoginForm.tsx:168 +#: src/screens/Login/SetNewPasswordForm.tsx:82 #: src/screens/Signup/index.tsx:89 msgid "Unable to contact your service. Please check your Internet connection." msgstr "" -#: src/screens/StarterPack/StarterPackScreen.tsx:700 +#: src/screens/StarterPack/StarterPackScreen.tsx:693 msgid "Unable to delete" msgstr "" -#: src/screens/Messages/JoinRequests.tsx:351 +#: src/screens/Messages/JoinRequests.tsx:348 msgid "Unable to fetch join requests." msgstr "Unable to fetch join requests." -#: src/components/dms/dialogs/NewChatDialog.tsx:113 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:129 +#: src/components/dms/dialogs/NewChatDialog.tsx:105 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:121 msgid "Unable to find a selected recipient." msgstr "Unable to find a selected recipient." -#: src/components/dms/dialogs/NewChatDialog.tsx:77 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:92 +#: src/components/dms/dialogs/NewChatDialog.tsx:70 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:85 msgid "Unable to find the selected recipient." msgstr "Unable to find the selected recipient." -#: src/lib/strings/errors.ts:42 +#: src/lib/strings/errors.ts:77 msgid "Unable to resolve handle" msgstr "" -#: src/screens/Settings/Settings.tsx:534 +#: src/screens/Settings/Settings.tsx:536 msgid "Unapply Pull Request" msgstr "" -#: src/screens/Settings/Settings.tsx:536 +#: src/screens/Settings/Settings.tsx:538 msgid "Unapply Pull Request {currentChannel}" msgstr "" @@ -13211,18 +13211,18 @@ msgstr "" msgid "Unavailable feed information" msgstr "" -#: src/components/dms/MessageItem.tsx:747 +#: src/components/dms/MessageItem.tsx:762 #: src/components/dms/MessagesListBlockedFooter.tsx:97 #: src/components/dms/MessagesListBlockedFooter.tsx:104 -#: src/components/moderation/BlockDialog.tsx:187 -#: src/components/moderation/BlockDialog.tsx:191 -#: src/components/moderation/BlockDialog.tsx:213 +#: src/components/moderation/BlockDialog.tsx:184 +#: src/components/moderation/BlockDialog.tsx:188 +#: src/components/moderation/BlockDialog.tsx:210 #: src/screens/Messages/ConversationSettings/MemberMenu.tsx:227 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:190 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:365 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:427 -#: src/screens/ProfileList/components/Header.tsx:166 -#: src/screens/ProfileList/components/Header.tsx:173 +#: src/screens/ProfileList/components/Header.tsx:167 +#: src/screens/ProfileList/components/Header.tsx:174 msgid "Unblock" msgstr "" @@ -13242,7 +13242,7 @@ msgstr "Unblock {displayName}" msgid "Unblock account" msgstr "" -#: src/components/moderation/BlockDialog.tsx:146 +#: src/components/moderation/BlockDialog.tsx:143 msgid "Unblock account?" msgstr "Unblock account?" @@ -13251,8 +13251,8 @@ msgstr "Unblock account?" msgid "Unblock Account?" msgstr "" -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:242 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:245 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:243 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:246 msgid "Unblock list" msgstr "" @@ -13260,8 +13260,8 @@ msgstr "" #: src/features/liveEvents/components/DiscoverFeedLiveEventFeedsAndTrendingBanner.tsx:56 #: src/features/liveEvents/components/SidebarLiveEventFeedsBanner.tsx:40 #: src/features/liveEvents/components/SidebarLiveEventFeedsBanner.tsx:46 -#: src/screens/Profile/components/GermButton.tsx:186 #: src/screens/Profile/components/GermButton.tsx:187 +#: src/screens/Profile/components/GermButton.tsx:188 msgid "Undo" msgstr "" @@ -13291,11 +13291,11 @@ msgstr "" msgid "Unfollow account" msgstr "" -#: src/screens/VideoFeed/index.tsx:941 +#: src/screens/VideoFeed/index.tsx:937 msgid "Unfollows the user" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:569 +#: src/components/moderation/ReportDialog/index.tsx:570 msgid "Unfortunately, none of your subscribed labelers supports this report type." msgstr "" @@ -13311,11 +13311,11 @@ msgstr "" msgid "Unknown verifier" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:72 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:73 msgid "Unlabeled adult content" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:68 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:69 msgid "Unlabeled, abusive, or non-consensual adult content" msgstr "" @@ -13325,20 +13325,20 @@ msgstr "" #. Accessibility label for the like button when the post has been liked, verb followed by number of likes and noun #. placeholder {0}: post.likeCount || 0 -#: src/components/PostControls/index.tsx:276 +#: src/components/PostControls/index.tsx:272 msgid "Unlike ({0, plural, one {# like} other {# likes}})" msgstr "" -#: src/screens/Messages/components/ChatLocked.tsx:91 +#: src/screens/Messages/components/ChatLocked.tsx:95 msgid "Unlock chat" msgstr "Unlock chat" -#: src/screens/Messages/ConversationSettings/index.tsx:568 +#: src/screens/Messages/ConversationSettings/index.tsx:563 msgid "Unlock this group chat" msgstr "Unlock this group chat" -#: src/screens/ProfileList/components/Header.tsx:180 -#: src/screens/ProfileList/components/Header.tsx:187 +#: src/screens/ProfileList/components/Header.tsx:181 +#: src/screens/ProfileList/components/Header.tsx:188 msgid "Unmute" msgstr "" @@ -13354,8 +13354,8 @@ msgstr "" msgid "Unmute {0}" msgstr "Unmute {0}" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:754 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:760 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:750 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:756 #: src/view/com/profile/ProfileMenu.tsx:511 #: src/view/com/profile/ProfileMenu.tsx:517 msgid "Unmute account" @@ -13365,17 +13365,17 @@ msgstr "" msgid "Unmute conversation" msgstr "" -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:252 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:255 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:253 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:256 msgid "Unmute list" msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:533 +#: src/screens/Messages/ConversationSettings/index.tsx:528 msgid "Unmute this group chat" msgstr "Unmute this group chat" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:646 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:649 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:642 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:645 msgid "Unmute thread" msgstr "" @@ -13383,12 +13383,12 @@ msgstr "" msgid "Unmute video" msgstr "" -#: src/screens/ProfileList/components/Header.tsx:151 -#: src/screens/ProfileList/components/Header.tsx:158 +#: src/screens/ProfileList/components/Header.tsx:152 +#: src/screens/ProfileList/components/Header.tsx:159 msgid "Unpin" msgstr "" -#: src/components/FeedCard.tsx:355 +#: src/components/FeedCard.tsx:352 #: src/screens/CustomFeed/components/CustomFeedHeader.tsx:569 #: src/screens/CustomFeed/components/CustomFeedHeader.tsx:576 #: src/screens/SavedFeeds.tsx:467 @@ -13400,13 +13400,13 @@ msgstr "" msgid "Unpin from home" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:522 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:527 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:518 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:523 msgid "Unpin from profile" msgstr "" -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:225 -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:228 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:226 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:229 msgid "Unpin moderation list" msgstr "" @@ -13415,20 +13415,20 @@ msgstr "" msgid "Unpinned {0} from Home" msgstr "" -#: src/screens/ProfileList/components/Header.tsx:78 +#: src/screens/ProfileList/components/Header.tsx:79 msgid "Unpinned from your feeds" msgstr "" -#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:105 +#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:106 msgid "Unpinned list" msgstr "" -#: src/screens/Settings/Settings.tsx:501 #: src/screens/Settings/Settings.tsx:503 +#: src/screens/Settings/Settings.tsx:505 msgid "Unsnooze email reminder" msgstr "" -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:354 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:350 msgid "Unsubscribe" msgstr "" @@ -13437,7 +13437,7 @@ msgstr "" msgid "Unsubscribe from list" msgstr "" -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:321 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:317 msgid "Unsubscribe from this labeler" msgstr "" @@ -13445,11 +13445,11 @@ msgstr "" msgid "Unsubscribed from list" msgstr "" -#: src/view/com/composer/text-input/TextInput.tsx:127 +#: src/view/com/composer/text-input/TextInput.tsx:129 msgid "Unsupported clipboard content" msgstr "Unsupported clipboard content" -#: src/view/com/composer/Composer.tsx:1672 +#: src/view/com/composer/Composer.tsx:1696 msgid "Unsupported video type: {mimeType}" msgstr "" @@ -13483,9 +13483,9 @@ msgstr "" msgid "Update in Lists" msgstr "Update in Lists" -#: src/screens/Messages/components/InviteLinkDialog.tsx:239 -#: src/screens/Messages/components/InviteLinkDialog.tsx:275 -#: src/screens/Messages/components/InviteLinkDialog.tsx:303 +#: src/screens/Messages/components/InviteLinkDialog.tsx:236 +#: src/screens/Messages/components/InviteLinkDialog.tsx:272 +#: src/screens/Messages/components/InviteLinkDialog.tsx:300 msgid "Update invite link" msgstr "Update invite link" @@ -13505,12 +13505,12 @@ msgstr "" msgid "Update your location" msgstr "Update your location" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:356 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:352 msgctxt "toast" msgid "Updating quote attachment failed" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:405 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:401 msgctxt "toast" msgid "Updating reply visibility failed" msgstr "" @@ -13542,21 +13542,21 @@ msgstr "" msgid "Upload from Library" msgstr "" -#: src/view/com/composer/Composer.tsx:2694 +#: src/view/com/composer/Composer.tsx:2718 msgid "Uploading GIF..." msgstr "" -#: src/lib/api/index.ts:327 -#: src/lib/api/index.ts:354 +#: src/lib/api/index.ts:336 +#: src/lib/api/index.ts:363 msgid "Uploading images..." msgstr "" -#: src/lib/api/index.ts:426 -#: src/lib/api/index.ts:450 +#: src/lib/api/index.ts:436 +#: src/lib/api/index.ts:460 msgid "Uploading link thumbnail..." msgstr "" -#: src/view/com/composer/Composer.tsx:2696 +#: src/view/com/composer/Composer.tsx:2720 msgid "Uploading video..." msgstr "" @@ -13591,11 +13591,11 @@ msgstr "" msgid "Use this to sign in to the other app along with your handle." msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:278 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:287 msgid "Use your account email address, or another real email address you control, in case KWS or Bluesky needs to contact you." msgstr "" -#: src/view/screens/Profile.tsx:396 +#: src/view/screens/Profile.tsx:393 msgid "user" msgstr "user" @@ -13606,12 +13606,12 @@ msgid "User blocked" msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:91 -#: src/lib/moderation/useModerationCauseDescription.ts:64 +#: src/lib/moderation/useModerationCauseDescription.ts:61 msgid "User Blocked" msgstr "" #. placeholder {0}: cause.source.list.name -#: src/lib/moderation/useModerationCauseDescription.ts:56 +#: src/lib/moderation/useModerationCauseDescription.ts:53 msgid "User Blocked by \"{0}\"" msgstr "" @@ -13623,7 +13623,7 @@ msgstr "" msgid "User Blocked by List" msgstr "" -#: src/lib/moderation/useModerationCauseDescription.ts:74 +#: src/lib/moderation/useModerationCauseDescription.ts:71 msgid "User Blocking You" msgstr "" @@ -13637,7 +13637,7 @@ msgid "User list by {0}" msgstr "" #. placeholder {0}: sanitizeHandle(view.creator.handle, '@') -#: src/state/queries/feed.ts:171 +#: src/state/queries/feed.ts:168 msgid "User List by {0}" msgstr "" @@ -13645,12 +13645,12 @@ msgstr "" msgid "User list by you" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:255 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:263 msgctxt "toast" msgid "User list created" msgstr "" -#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:241 +#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:249 msgctxt "toast" msgid "User list updated" msgstr "" @@ -13667,21 +13667,21 @@ msgstr "" msgid "Username must only contain letters (a-z), numbers, and hyphens" msgstr "" -#: src/screens/Login/LoginForm.tsx:305 +#: src/screens/Login/LoginForm.tsx:306 msgid "Username or email" msgstr "Username or email" -#: src/screens/Login/LoginForm.tsx:315 +#: src/screens/Login/LoginForm.tsx:316 msgid "Username or email address" msgstr "" #. placeholder {0}: post.author.handle -#: src/components/WhoCanReply.tsx:337 +#: src/components/WhoCanReply.tsx:330 msgid "users followed by <0>@{0}" msgstr "" #. placeholder {0}: post.author.handle -#: src/components/WhoCanReply.tsx:324 +#: src/components/WhoCanReply.tsx:317 msgid "users following <0>@{0}" msgstr "" @@ -13739,7 +13739,7 @@ msgstr "" msgid "Verify email code" msgstr "" -#: src/components/intents/VerifyEmailIntentDialog.tsx:62 +#: src/components/intents/VerifyEmailIntentDialog.tsx:63 msgid "Verify email dialog" msgstr "" @@ -13753,7 +13753,7 @@ msgid "Verify now using KWS" msgstr "Verify now using KWS" #: src/components/contacts/screens/PhoneInput.tsx:175 -#: src/components/contacts/screens/VerifyNumber.tsx:217 +#: src/components/contacts/screens/VerifyNumber.tsx:220 msgid "Verify phone number" msgstr "" @@ -13766,7 +13766,7 @@ msgstr "" msgid "Verify this account?" msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:183 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:192 msgid "Verify your age" msgstr "" @@ -13786,7 +13786,7 @@ msgstr "" msgid "Verifying your age assurance status" msgstr "" -#: src/components/contacts/screens/VerifyNumber.tsx:307 +#: src/components/contacts/screens/VerifyNumber.tsx:310 msgid "Verifying..." msgstr "" @@ -13797,7 +13797,7 @@ msgid "Version {0}" msgstr "" #. When the source of a follow is a starter pack, i.e., 'via starter pack {starterPackName}'. -#: src/view/com/notifications/NotificationFeedItem.tsx:746 +#: src/view/com/notifications/NotificationFeedItem.tsx:738 msgid "via starter pack <0/><1>{starterPackName}" msgstr "via starter pack <0/><1>{starterPackName}" @@ -13806,7 +13806,7 @@ msgstr "via starter pack <0/><1>{starterPackName}" msgid "Video" msgstr "" -#: src/view/com/composer/state/video.ts:479 +#: src/view/com/composer/state/video.ts:484 msgid "Video failed to process" msgstr "" @@ -13815,25 +13815,25 @@ msgid "Video Feed" msgstr "" #. placeholder {0}: author.handle -#: src/components/VideoPostCard.tsx:121 +#: src/components/VideoPostCard.tsx:113 msgid "Video from {0}: {text}" msgstr "" #. placeholder {0}: sanitizeHandle( post.author.handle, '@', ) -#: src/screens/VideoFeed/index.tsx:1196 +#: src/screens/VideoFeed/index.tsx:1192 msgid "Video from {0}. Tap to play or pause the video" msgstr "" #: src/lib/interests.ts:62 -#: src/screens/Search/modules/ExploreTrendingTopics.tsx:244 +#: src/screens/Search/modules/ExploreTrendingTopics.tsx:242 msgid "Video Games" msgstr "" -#: src/screens/VideoFeed/index.tsx:1195 +#: src/screens/VideoFeed/index.tsx:1191 msgid "Video is paused" msgstr "" -#: src/screens/VideoFeed/index.tsx:1195 +#: src/screens/VideoFeed/index.tsx:1191 msgid "Video is playing" msgstr "" @@ -13845,7 +13845,7 @@ msgstr "" msgid "Video settings" msgstr "" -#: src/view/com/composer/Composer.tsx:2714 +#: src/view/com/composer/Composer.tsx:2738 msgid "Video uploaded" msgstr "" @@ -13854,35 +13854,35 @@ msgstr "" msgid "Video: {0}" msgstr "" -#: src/view/screens/Profile.tsx:239 +#: src/view/screens/Profile.tsx:236 msgid "Videos" msgstr "" -#: src/view/com/composer/Composer.tsx:453 -#: src/view/com/composer/Composer.tsx:596 +#: src/view/com/composer/Composer.tsx:463 +#: src/view/com/composer/Composer.tsx:608 #: src/view/com/composer/SelectMediaButton.tsx:452 msgid "Videos must be 10 minutes or less." msgstr "Videos must be 10 minutes or less." -#: src/view/com/composer/Composer.tsx:454 -#: src/view/com/composer/Composer.tsx:597 +#: src/view/com/composer/Composer.tsx:464 +#: src/view/com/composer/Composer.tsx:609 #: src/view/com/composer/SelectMediaButton.tsx:453 msgid "Videos must be less than 3 minutes long." msgstr "" -#: src/view/com/composer/Composer.tsx:1262 +#: src/view/com/composer/Composer.tsx:1284 msgctxt "Action to view the post the user just created" msgid "View" msgstr "" #. placeholder {0}: view.source.title -#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:340 -#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:670 +#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:339 +#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:669 msgid "View {0}" msgstr "View {0}" #. placeholder {0}: profile.handle -#: src/screens/Profile/Header/Shell.tsx:257 +#: src/screens/Profile/Header/Shell.tsx:258 msgid "View {0}'s avatar" msgstr "" @@ -13891,13 +13891,13 @@ msgstr "" #. placeholder {0}: profile.handle #: src/screens/CustomFeed/components/CustomFeedHeader.tsx:505 #: src/screens/Search/components/SearchProfileCard.tsx:37 -#: src/screens/VideoFeed/index.tsx:902 -#: src/view/com/notifications/NotificationFeedItem.tsx:608 +#: src/screens/VideoFeed/index.tsx:898 +#: src/view/com/notifications/NotificationFeedItem.tsx:600 msgid "View {0}'s profile" msgstr "" #. placeholder {0}: profile.displayName || sanitizeHandle(profile.handle) -#: src/components/ProfileCard.tsx:150 +#: src/components/ProfileCard.tsx:147 msgid "View {0}’s profile" msgstr "View {0}’s profile" @@ -13906,15 +13906,15 @@ msgstr "View {0}’s profile" msgid "View {displayName}’s profile" msgstr "View {displayName}’s profile" -#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:478 +#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:477 msgid "View {publicationTitle}" msgstr "View {publicationTitle}" -#: src/components/ProfileHoverCard/index.web.tsx:479 +#: src/components/ProfileHoverCard/index.web.tsx:476 msgid "View blocked user's profile" msgstr "" -#: src/screens/Settings/components/ExportCarDialog.tsx:170 +#: src/screens/Settings/components/ExportCarDialog.tsx:176 msgid "View blogpost for more details" msgstr "" @@ -13922,8 +13922,8 @@ msgstr "" msgid "View debug entry" msgstr "" -#: src/screens/VideoFeed/index.tsx:767 -#: src/screens/VideoFeed/index.tsx:785 +#: src/screens/VideoFeed/index.tsx:766 +#: src/screens/VideoFeed/index.tsx:784 msgid "View details" msgstr "" @@ -13932,7 +13932,7 @@ msgstr "" msgid "View full thread" msgstr "" -#: src/screens/Settings/Settings.tsx:465 +#: src/screens/Settings/Settings.tsx:467 msgid "View GrowthBook information" msgstr "View GrowthBook information" @@ -13952,38 +13952,38 @@ msgstr "View incoming requests to join this group chat" msgid "View information about these labels" msgstr "" -#: src/components/interstitials/TrendingVideos.tsx:199 -#: src/components/interstitials/TrendingVideos.tsx:221 -#: src/screens/Search/modules/ExploreTrendingVideos.tsx:191 -#: src/screens/Search/modules/ExploreTrendingVideos.tsx:210 +#: src/components/interstitials/TrendingVideos.tsx:201 +#: src/components/interstitials/TrendingVideos.tsx:223 +#: src/screens/Search/modules/ExploreTrendingVideos.tsx:193 +#: src/screens/Search/modules/ExploreTrendingVideos.tsx:212 msgid "View more" msgstr "" -#: src/components/interstitials/TrendingVideos.tsx:227 +#: src/components/interstitials/TrendingVideos.tsx:229 msgid "View more trending videos" msgstr "" -#: src/view/com/composer/Composer.tsx:1257 +#: src/view/com/composer/Composer.tsx:1279 msgid "View post" msgstr "" -#: src/components/ProfileHoverCard/index.web.tsx:465 -#: src/components/ProfileHoverCard/index.web.tsx:485 -#: src/components/ProfileHoverCard/index.web.tsx:512 -#: src/view/com/posts/PostFeedErrorMessage.tsx:183 -#: src/view/com/util/PostMeta.tsx:92 -#: src/view/com/util/PostMeta.tsx:120 +#: src/components/ProfileHoverCard/index.web.tsx:462 +#: src/components/ProfileHoverCard/index.web.tsx:482 +#: src/components/ProfileHoverCard/index.web.tsx:509 +#: src/view/com/posts/PostFeedErrorMessage.tsx:181 +#: src/view/com/util/PostMeta.tsx:93 +#: src/view/com/util/PostMeta.tsx:121 msgid "View profile" msgstr "" -#: src/screens/Profile/Header/Shell.tsx:163 +#: src/screens/Profile/Header/Shell.tsx:164 msgid "View profile banner" msgstr "View profile banner" -#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:340 -#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:448 -#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:479 -#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:670 +#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:339 +#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:447 +#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:478 +#: src/components/Post/Embed/StandardSiteEmbed/index.tsx:669 msgid "View publication" msgstr "View publication" @@ -13991,7 +13991,7 @@ msgstr "View publication" msgid "View the avatar" msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:555 +#: src/screens/Messages/ConversationSettings/index.tsx:550 msgid "View the invite link for this group chat" msgstr "View the invite link for this group chat" @@ -14008,7 +14008,7 @@ msgstr "" msgid "View users who like this feed" msgstr "" -#: src/components/VideoPostCard.tsx:398 +#: src/components/VideoPostCard.tsx:390 msgid "View video" msgstr "" @@ -14042,15 +14042,15 @@ msgstr "" msgid "Views full image" msgstr "" -#: src/components/VideoPostCard.tsx:120 +#: src/components/VideoPostCard.tsx:112 msgid "Views video in immersive mode" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:126 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:127 msgid "Violence" msgstr "" -#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:127 +#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:128 msgid "Violent or threatening content" msgstr "" @@ -14081,12 +14081,12 @@ msgstr "" msgid "Warn content and filter from feeds" msgstr "" -#: src/features/liveNow/components/LiveStatusDialog.tsx:190 -#: src/features/liveNow/components/LiveStatusDialog.tsx:199 +#: src/features/liveNow/components/LiveStatusDialog.tsx:187 +#: src/features/liveNow/components/LiveStatusDialog.tsx:196 msgid "Watch now" msgstr "" -#: src/components/contacts/screens/GetContacts.tsx:253 +#: src/components/contacts/screens/GetContacts.tsx:255 msgid "We apply the highest privacy standards, and never share or sell your contact information." msgstr "" @@ -14098,35 +14098,35 @@ msgstr "We could not connect to the service that provides this custom feed. It m msgid "We could not find this list. It was probably deleted." msgstr "" -#: src/screens/Login/LoginForm.tsx:348 +#: src/screens/Login/LoginForm.tsx:349 msgid "We couldn't find an account with that username. Please check that you've typed it correctly, or <0>set your hosting provider manually." msgstr "We couldn't find an account with that username. Please check that you've typed it correctly, or <0>set your hosting provider manually." -#: src/screens/Hashtag.tsx:260 +#: src/screens/Hashtag.tsx:262 msgid "We couldn't find any results for that tag." msgstr "" -#: src/screens/Topic.tsx:166 +#: src/screens/Topic.tsx:168 msgid "We couldn't find any results for that topic." msgstr "" -#: src/screens/Messages/Conversation.tsx:134 +#: src/screens/Messages/Conversation.tsx:136 msgid "We couldn't load this conversation" msgstr "" -#: src/screens/Messages/JoinRequests.tsx:89 +#: src/screens/Messages/JoinRequests.tsx:86 msgid "We couldn’t load this conversation’s join requests" msgstr "We couldn’t load this conversation’s join requests" -#: src/screens/Messages/ConversationSettings/index.tsx:138 +#: src/screens/Messages/ConversationSettings/index.tsx:135 msgid "We couldn’t load this conversation’s settings" msgstr "We couldn’t load this conversation’s settings" -#: src/screens/Login/LoginForm.tsx:482 +#: src/screens/Login/LoginForm.tsx:483 msgid "We couldn’t verify your hosting provider. Check your internet connection, or <0>set your hosting provider manually." msgstr "We couldn’t verify your hosting provider. Check your internet connection, or <0>set your hosting provider manually." -#: src/components/contacts/screens/GetContacts.tsx:244 +#: src/components/contacts/screens/GetContacts.tsx:246 msgid "We delete hashes after matches are made" msgstr "" @@ -14134,16 +14134,16 @@ msgstr "" msgid "We don't store your friends' phone numbers or send any messages" msgstr "" -#: src/screens/SignupQueued.tsx:163 +#: src/screens/SignupQueued.tsx:170 msgid "We estimate {estimatedTime} until your account is ready." msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:197 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:206 msgid "We have partnered with <0>KWS to handle age verification. When you click \"Begin\" below, KWS will email you instructions to complete the verification process. If your email address has already been used to verify your age for another game or service that uses KWS, you won’t need to do it again. When you’re done, you'll be brought back to continue using Bluesky." msgstr "" #. placeholder {0}: currentAccount?.email -#: src/components/intents/VerifyEmailIntentDialog.tsx:102 +#: src/components/intents/VerifyEmailIntentDialog.tsx:103 msgid "We have sent another verification email to <0>{0}." msgstr "" @@ -14151,11 +14151,11 @@ msgstr "" msgid "We need to verify your number before we can look for your friends. A verification code will be sent to this number." msgstr "" -#: src/components/contacts/screens/GetContacts.tsx:241 +#: src/components/contacts/screens/GetContacts.tsx:243 msgid "We never keep plain phone numbers" msgstr "" -#: src/components/contacts/screens/GetContacts.tsx:247 +#: src/components/contacts/screens/GetContacts.tsx:249 msgid "We only suggest follows if both people consent" msgstr "" @@ -14163,7 +14163,7 @@ msgstr "" msgid "We ran out of posts from your follows. Here's the latest from <0/>." msgstr "" -#: src/screens/Settings/InterestsSettings.tsx:171 +#: src/screens/Settings/InterestsSettings.tsx:172 msgid "We recommend selecting at least two interests." msgstr "" @@ -14172,7 +14172,7 @@ msgstr "" msgid "We sent an email to <0>{0} containing a link. Please click on it to complete the email verification process." msgstr "" -#: src/view/com/composer/state/video.ts:526 +#: src/view/com/composer/state/video.ts:531 msgid "We were unable to determine if you are allowed to upload videos. Please try again." msgstr "" @@ -14193,11 +14193,11 @@ msgstr "" msgid "We were unable to receive the verification due to a connection issue. It may arrive later. If it does, your account will update automatically." msgstr "" -#: src/screens/SignupQueued.tsx:167 +#: src/screens/SignupQueued.tsx:174 msgid "We will let you know when your account is ready." msgstr "" -#: src/screens/Settings/FindContactsSettings.tsx:531 +#: src/screens/Settings/FindContactsSettings.tsx:535 msgid "We will notify you when we find your friends." msgstr "" @@ -14227,7 +14227,7 @@ msgstr "" msgid "We're confirming your age assurance status with our servers. This should only take a few seconds." msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:151 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:155 msgid "We're having issues initializing the age assurance process for your account. Please <0>contact support for assistance." msgstr "" @@ -14258,11 +14258,11 @@ msgstr "We're sorry, but based on the data shared by your device, you are not ol msgid "We're sorry, but based on your device's location, you are currently located in a region that requires age assurance." msgstr "" -#: src/screens/ProfileList/index.tsx:88 +#: src/screens/ProfileList/index.tsx:85 msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}." msgstr "" -#: src/components/dialogs/MutedWords.tsx:387 +#: src/components/dialogs/MutedWords.tsx:389 msgid "We're sorry, but we weren't able to load your muted words at this time. Please try again." msgstr "" @@ -14282,7 +14282,7 @@ msgstr "We’re sorry, but your search could not be completed. Please try again msgid "We're sorry, you cannot access this screen at this time." msgstr "" -#: src/view/com/composer/Composer.tsx:1153 +#: src/view/com/composer/Composer.tsx:1175 msgid "We're sorry! The post you are replying to has been deleted." msgstr "" @@ -14291,7 +14291,7 @@ msgstr "" msgid "We're sorry! We can't find the page you were looking for." msgstr "" -#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:220 +#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:216 msgid "We're sorry! You can only subscribe to twenty labelers, and you've reached your limit of twenty." msgstr "" @@ -14316,11 +14316,11 @@ msgstr "" msgid "We've confirmed your age assurance status. You can now close this dialog." msgstr "" -#: src/screens/Deactivated.tsx:117 +#: src/screens/Deactivated.tsx:122 msgid "Welcome back!" msgstr "" -#: src/components/NewskieDialog.tsx:141 +#: src/components/NewskieDialog.tsx:142 msgid "Welcome, friend!" msgstr "" @@ -14338,7 +14338,7 @@ msgid "what’s up" msgstr "what’s up" #: src/view/com/auth/SplashScreen.web.tsx:100 -#: src/view/com/composer/Composer.tsx:1636 +#: src/view/com/composer/Composer.tsx:1660 #: src/view/com/feeds/ComposerPrompt.tsx:195 msgid "What's up?" msgstr "" @@ -14347,16 +14347,16 @@ msgstr "" msgid "When you tap on a check, you’ll see which organizations have granted verification." msgstr "" -#: src/components/WhoCanReply.tsx:226 +#: src/components/WhoCanReply.tsx:219 msgid "Who can interact with this post?" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:247 +#: src/screens/Messages/components/InviteLinkDialog.tsx:244 msgid "Who can join this group chat and how" msgstr "Who can join this group chat and how" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:427 -#: src/components/WhoCanReply.tsx:116 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:424 +#: src/components/WhoCanReply.tsx:109 msgid "Who can reply" msgstr "" @@ -14366,16 +14366,16 @@ msgstr "" #: src/screens/Home/NoFeedsPinned.tsx:80 #: src/screens/Messages/ChatList.tsx:409 -#: src/screens/Messages/Inbox.tsx:191 +#: src/screens/Messages/Inbox.tsx:190 msgid "Whoops!" msgstr "" -#: src/components/interstitials/TrendingVideos.tsx:126 -#: src/screens/Search/modules/ExploreTrendingVideos.tsx:108 +#: src/components/interstitials/TrendingVideos.tsx:128 +#: src/screens/Search/modules/ExploreTrendingVideos.tsx:110 msgid "Whoops! Trending videos failed to load." msgstr "" -#: src/screens/Takendown.tsx:169 +#: src/screens/Takendown.tsx:167 msgid "Why are you appealing?" msgstr "" @@ -14424,21 +14424,21 @@ msgstr "Would you like to block this user and/or leave this conversation?" msgid "Would you like to save this as a draft before viewing your drafts?" msgstr "" -#: src/view/com/composer/Composer.tsx:1551 +#: src/view/com/composer/Composer.tsx:1575 msgid "Would you like to save this as a draft to edit later?" msgstr "" -#: src/view/screens/Profile.tsx:447 -#: src/view/screens/Profile.tsx:448 +#: src/view/screens/Profile.tsx:444 +#: src/view/screens/Profile.tsx:445 msgid "Write a post" msgstr "" -#: src/view/com/composer/Composer.tsx:1732 +#: src/view/com/composer/Composer.tsx:1756 msgid "Write post" msgstr "" #: src/screens/PostThread/components/ThreadComposePrompt.tsx:91 -#: src/view/com/composer/Composer.tsx:1634 +#: src/view/com/composer/Composer.tsx:1658 msgid "Write your reply" msgstr "" @@ -14451,18 +14451,18 @@ msgstr "" msgid "Wrong DID returned from server. Received: {0}" msgstr "" -#: src/screens/Messages/ConversationSettings/index.tsx:155 -#: src/screens/Messages/JoinRequests.tsx:110 +#: src/screens/Messages/ConversationSettings/index.tsx:152 +#: src/screens/Messages/JoinRequests.tsx:107 msgid "Wrong kind of conversation" msgstr "Wrong kind of conversation" -#: src/features/liveNow/components/EditLiveDialog.tsx:147 +#: src/features/liveNow/components/EditLiveDialog.tsx:149 #: src/features/liveNow/components/GoLiveDialog.tsx:142 msgid "www.mylivestream.tv" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:994 -#: src/components/moderation/ReportDialog/index.tsx:1003 +#: src/components/moderation/ReportDialog/index.tsx:995 +#: src/components/moderation/ReportDialog/index.tsx:1004 msgctxt "Answer to a yes/no question" msgid "Yes" msgstr "Yes" @@ -14472,28 +14472,28 @@ msgstr "Yes" msgid "Yes GIFs" msgstr "Yes GIFs" -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:105 -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:107 +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:106 +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:108 msgid "Yes, deactivate" msgstr "" -#: src/screens/Settings/components/DeleteAccountDialog.tsx:348 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:354 msgid "Yes, delete my account" msgstr "" -#: src/screens/StarterPack/StarterPackScreen.tsx:712 +#: src/screens/StarterPack/StarterPackScreen.tsx:705 msgid "Yes, delete this starter pack" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:864 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:860 msgid "Yes, detach" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:871 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:867 msgid "Yes, hide" msgstr "" -#: src/screens/Deactivated.tsx:139 +#: src/screens/Deactivated.tsx:144 msgid "Yes, reactivate my account" msgstr "" @@ -14510,7 +14510,7 @@ msgid "You are accessing Bluesky from a region that legally requires us to verif msgstr "" #. placeholder {0}: sanitizeHandle(profile.handle, '@') -#: src/components/dms/MessageItem.tsx:720 +#: src/components/dms/MessageItem.tsx:735 msgid "You are blocking {0}" msgstr "You are blocking {0}" @@ -14526,7 +14526,7 @@ msgstr "You are blocking this person" msgid "You are creating an account on" msgstr "" -#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:108 +#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:107 msgid "You are currently blocked from using the Go Live feature. To appeal this moderation decision, please submit the form below." msgstr "" @@ -14535,20 +14535,20 @@ msgstr "" msgid "You are currently unable to access Bluesky's Age Assurance flow. Please <0>contact our moderation team if you believe this is an error." msgstr "" -#: src/screens/SignupQueued.tsx:160 +#: src/screens/SignupQueued.tsx:167 msgid "You are in line." msgstr "" -#: src/features/liveNow/components/EditLiveDialog.tsx:113 -#: src/features/liveNow/components/EditLiveDialog.tsx:118 +#: src/features/liveNow/components/EditLiveDialog.tsx:115 +#: src/features/liveNow/components/EditLiveDialog.tsx:120 msgid "You are Live" msgstr "" -#: src/features/liveNow/index.tsx:377 +#: src/features/liveNow/index.tsx:382 msgid "You are no longer live" msgstr "" -#: src/view/com/composer/state/video.ts:519 +#: src/view/com/composer/state/video.ts:524 msgid "You are not allowed to upload videos." msgstr "" @@ -14556,7 +14556,7 @@ msgstr "" msgid "You are not following anyone yet" msgstr "" -#: src/features/liveNow/index.tsx:321 +#: src/features/liveNow/index.tsx:325 msgid "You are now live!" msgstr "" @@ -14576,7 +14576,7 @@ msgstr "" msgid "You can adjust your interests at any time from \"Content and media\" settings." msgstr "" -#: src/screens/Settings/components/DeleteAccountDialog.tsx:211 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:217 msgid "You can also <0>temporarily deactivate your account instead. Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in." msgstr "" @@ -14585,11 +14585,11 @@ msgstr "" msgid "You can also discover new Custom Feeds to follow." msgstr "" -#: src/screens/Settings/components/ExportCarDialog.tsx:139 +#: src/screens/Settings/components/ExportCarDialog.tsx:145 msgid "You can also download your chat data as a \"JSONL\" file. This file only includes chat messages that you have sent and does not include chat messages that you have received." msgstr "" -#: src/components/contacts/screens/GetContacts.tsx:250 +#: src/components/contacts/screens/GetContacts.tsx:252 msgid "You can always opt out and delete your data" msgstr "" @@ -14616,11 +14616,11 @@ msgid "You can now sign in with your new password." msgstr "" #. Toast shown when the user tries to add more images but the post gallery is already at the cap -#: src/view/com/composer/Composer.tsx:222 +#: src/view/com/composer/Composer.tsx:224 msgid "You can only add up to {MAX_GALLERY_IMAGES, plural, other {# images}} per post" msgstr "You can only add up to {MAX_GALLERY_IMAGES, plural, other {# images}} per post" -#: src/view/com/composer/Composer.tsx:1556 +#: src/view/com/composer/Composer.tsx:1580 msgid "You can only save drafts up to 1000 characters." msgstr "" @@ -14636,7 +14636,7 @@ msgstr "" msgid "You can only select one video at a time." msgstr "" -#: src/screens/Deactivated.tsx:125 +#: src/screens/Deactivated.tsx:130 msgid "You can reactivate your account to continue logging in. Your profile and posts will be visible to other users." msgstr "" @@ -14649,26 +14649,26 @@ msgstr "You can read chat history but can’t send new messages." msgid "You can select up to {MAX_GALLERY_IMAGES, plural, other {# images}} in total." msgstr "You can select up to {MAX_GALLERY_IMAGES, plural, other {# images}} in total." -#: src/components/interstitials/FeedTrendingTopics.tsx:167 +#: src/components/interstitials/FeedTrendingTopics.tsx:168 #: src/components/interstitials/Trending.tsx:148 -#: src/components/interstitials/TrendingVideos.tsx:137 -#: src/screens/Search/modules/ExploreTrendingTopics.tsx:105 +#: src/components/interstitials/TrendingVideos.tsx:139 +#: src/screens/Search/modules/ExploreTrendingTopics.tsx:103 #: src/view/shell/desktop/SidebarTrendingTopics.tsx:172 msgid "You can update this later from your settings." msgstr "" -#: src/components/dms/ActionsWrapper.web.tsx:81 -#: src/components/dms/MessageContextMenu.tsx:129 +#: src/components/dms/ActionsWrapper.web.tsx:82 +#: src/components/dms/MessageContextMenu.tsx:127 msgid "You cannot add more than {EMOJI_REACTION_LIMIT, plural, one {# emoji reaction} other {# emoji reactions}}" msgstr "You cannot add more than {EMOJI_REACTION_LIMIT, plural, one {# emoji reaction} other {# emoji reactions}}" -#: src/components/dms/dialogs/NewChatDialog.tsx:105 -#: src/components/dms/dialogs/ShareViaChatDialog.tsx:121 +#: src/components/dms/dialogs/NewChatDialog.tsx:99 +#: src/components/dms/dialogs/ShareViaChatDialog.tsx:115 msgid "You cannot create a group chat yet." msgstr "You cannot create a group chat yet." #: src/lib/hooks/useCleanError.ts:54 -#: src/lib/strings/errors.ts:27 +#: src/lib/strings/errors.ts:62 msgid "You cannot update your birthdate while using an app password. Please sign in with your main password to update your birthdate." msgstr "" @@ -14676,7 +14676,7 @@ msgstr "" msgid "You don't follow any users who follow @{name}." msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:589 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:586 msgid "You don't have any lists yet." msgstr "" @@ -14690,17 +14690,17 @@ msgstr "" msgid "You don't have any saved feeds." msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:312 +#: src/components/contacts/screens/ViewMatches.tsx:320 msgid "You got here first" msgstr "" -#: src/components/intents/GroupChatJoinDialog.tsx:176 +#: src/components/intents/GroupChatJoinDialog.tsx:179 msgid "You have been previously removed from this group and can’t join it using this link." msgstr "You have been previously removed from this group and can’t join it using this link." #: src/components/moderation/ModerationDetailsDialog.tsx:93 -#: src/lib/moderation/useModerationCauseDescription.ts:58 -#: src/lib/moderation/useModerationCauseDescription.ts:66 +#: src/lib/moderation/useModerationCauseDescription.ts:55 +#: src/lib/moderation/useModerationCauseDescription.ts:63 msgid "You have blocked this user. You cannot view their content." msgstr "" @@ -14712,13 +14712,13 @@ msgstr "" msgid "You have completely disabled reply, quote, and mention notifications, so this tab will no longer update. To adjust this, visit your <0>notification settings." msgstr "" -#: src/screens/Login/SetNewPasswordForm.tsx:48 -#: src/screens/Login/SetNewPasswordForm.tsx:89 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:113 +#: src/screens/Login/SetNewPasswordForm.tsx:49 +#: src/screens/Login/SetNewPasswordForm.tsx:94 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:115 msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." msgstr "" -#: src/lib/moderation/useModerationCauseDescription.ts:117 +#: src/lib/moderation/useModerationCauseDescription.ts:114 msgid "You have hidden this post" msgstr "" @@ -14731,20 +14731,20 @@ msgid "You have marked this account as automated. You can remove it at any time msgstr "You have marked this account as automated. You can remove it at any time from your account settings." #: src/components/moderation/ModerationDetailsDialog.tsx:124 -#: src/lib/moderation/useModerationCauseDescription.ts:100 +#: src/lib/moderation/useModerationCauseDescription.ts:97 msgid "You have muted this account." msgstr "" -#: src/lib/moderation/useModerationCauseDescription.ts:94 +#: src/lib/moderation/useModerationCauseDescription.ts:91 msgid "You have muted this user" msgstr "" #: src/components/dialogs/lists/UserAddRemoveListsDialog.tsx:71 -#: src/view/com/lists/MyLists.tsx:81 +#: src/view/com/lists/MyLists.tsx:84 msgid "You have no lists." msgstr "" -#: src/components/dialogs/StarterPackDialog.tsx:102 +#: src/components/dialogs/StarterPackDialog.tsx:99 msgid "You have no starter packs." msgstr "" @@ -14764,11 +14764,11 @@ msgstr "" msgid "You have successfully verified your email address. You can close this dialog." msgstr "" -#: src/lib/media/video/upload.shared.ts:56 +#: src/lib/media/video/upload.shared.ts:74 msgid "You have temporarily reached the limit for video uploads. Please try again later." msgstr "" -#: src/view/com/composer/Composer.tsx:1546 +#: src/view/com/composer/Composer.tsx:1570 msgid "You have unsaved changes to this draft, would you like to save them?" msgstr "" @@ -14788,25 +14788,25 @@ msgstr "" msgid "You haven't made any custom feeds yet." msgstr "" -#: src/components/dialogs/MutedWords.tsx:407 +#: src/components/dialogs/MutedWords.tsx:409 msgid "You haven't muted any words or tags yet" msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:138 -#: src/lib/moderation/useModerationCauseDescription.ts:128 +#: src/lib/moderation/useModerationCauseDescription.ts:125 msgid "You hid this reply." msgstr "" #. placeholder {0}: getTimeAgo(lastInitiatedAt, new Date(), {format: 'long'}) -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:240 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:249 msgid "You initiated this flow already, {0} ago. It may take up to 5 minutes for emails to reach your inbox. Please consider waiting a few minutes before trying again." msgstr "" -#: src/components/NewskieDialog.tsx:107 +#: src/components/NewskieDialog.tsx:108 msgid "You joined Bluesky {timeAgoString} ago" msgstr "" -#: src/components/NewskieDialog.tsx:104 +#: src/components/NewskieDialog.tsx:105 msgid "You joined Bluesky using a starter pack {timeAgoString} ago" msgstr "" @@ -14819,15 +14819,15 @@ msgstr "" msgid "You may appeal these labels if you feel they were placed in error." msgstr "" -#: src/screens/StarterPack/Wizard/State.tsx:80 +#: src/screens/StarterPack/Wizard/State.tsx:76 msgid "You may only add up to {STARTER_PACK_MAX_SIZE, plural, other {{STARTER_PACK_MAX_SIZE} profiles}}" msgstr "" -#: src/screens/StarterPack/Wizard/State.tsx:101 +#: src/screens/StarterPack/Wizard/State.tsx:97 msgid "You may only add up to 3 feeds" msgstr "" -#: src/components/moderation/BlockDialog.tsx:318 +#: src/components/moderation/BlockDialog.tsx:317 msgid "You must be a chat owner to remove a member." msgstr "You must be a chat owner to remove a member." @@ -14852,26 +14852,26 @@ msgstr "" msgid "You need to verify your email address before you can enable email 2FA." msgstr "" -#: src/components/moderation/BlockDialog.tsx:349 +#: src/components/moderation/BlockDialog.tsx:350 msgid "You own this chat" msgstr "You own this chat" #. placeholder {0}: currentAccount?.handle -#: src/screens/Deactivated.tsx:120 +#: src/screens/Deactivated.tsx:125 msgid "You previously deactivated @{0}." msgstr "" -#: src/screens/Settings/Settings.tsx:423 +#: src/screens/Settings/Settings.tsx:425 msgid "You probably want to restart the app now." msgstr "" #. placeholder {0}: reaction.value -#: src/components/dms/MessageItem.tsx:361 +#: src/components/dms/MessageItem.tsx:373 msgid "You reacted {0}" msgstr "" #. placeholder {0}: reaction.value -#: src/components/dms/getReactionInfo.ts:63 +#: src/components/dms/getReactionInfo.ts:68 msgid "You reacted {0} to {target}" msgstr "You reacted {0} to {target}" @@ -14880,42 +14880,42 @@ msgstr "You reacted {0} to {target}" msgid "You recently changed your birthdate" msgstr "" -#: src/components/dms/MessageItem.tsx:806 +#: src/components/dms/MessageItem.tsx:821 msgid "You replied" msgstr "You replied" -#: src/components/dms/MessageItem.tsx:805 +#: src/components/dms/MessageItem.tsx:820 msgid "You replied to {originalName}" msgstr "You replied to {originalName}" -#: src/components/dms/MessageItem.tsx:803 +#: src/components/dms/MessageItem.tsx:818 msgid "You replied to yourself" msgstr "You replied to yourself" #. Displayed when the user has requested to join a group chat. -#: src/screens/Messages/components/OutgoingRequestListItem.tsx:105 +#: src/screens/Messages/components/OutgoingRequestListItem.tsx:103 msgid "You requested to join" msgstr "You requested to join" -#: src/screens/Settings/Settings.tsx:307 +#: src/screens/Settings/Settings.tsx:309 #: src/view/shell/desktop/LeftNav.tsx:225 msgid "You will be signed out of all your accounts." msgstr "" #. placeholder {0}: sanitizeHandle(profile.handle, '@') -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:140 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:137 msgid "You will no longer receive notifications for {0}" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:246 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:242 msgid "You will no longer receive notifications for this thread" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:237 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:233 msgid "You will now receive notifications for this thread" msgstr "" -#: src/screens/Login/SetNewPasswordForm.tsx:101 +#: src/screens/Login/SetNewPasswordForm.tsx:106 msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password." msgstr "" @@ -14924,37 +14924,37 @@ msgid "You won’t be able to rejoin unless you’re invited." msgstr "You won’t be able to rejoin unless you’re invited." #. When the last message in a chat was made by you. -#: src/components/dms/getMessageInfo.ts:82 +#: src/components/dms/getMessageInfo.ts:77 msgid "You: {message}" msgstr "You: {message}" -#: src/screens/Signup/index.tsx:158 +#: src/screens/Signup/index.tsx:155 msgid "You'll follow the suggested users and feeds once you finish creating your account!" msgstr "" -#: src/screens/Signup/index.tsx:163 +#: src/screens/Signup/index.tsx:160 msgid "You'll follow the suggested users once you finish creating your account!" msgstr "" #. placeholder {0}: listItemsCount - 8 -#: src/screens/StarterPack/StarterPackLandingScreen.tsx:243 +#: src/screens/StarterPack/StarterPackLandingScreen.tsx:235 msgid "You'll follow these people and {0} others" msgstr "" -#: src/screens/StarterPack/StarterPackLandingScreen.tsx:241 +#: src/screens/StarterPack/StarterPackLandingScreen.tsx:233 msgid "You'll follow these people right away" msgstr "" -#: src/components/contacts/screens/GetContacts.tsx:314 +#: src/components/contacts/screens/GetContacts.tsx:316 msgid "You'll need to go to the System Settings for Bluesky and give permission if you want to use this feature." msgstr "" #. placeholder {0}: sanitizeHandle(profile.handle, '@') -#: src/components/activity-notifications/SubscribeProfileDialog.tsx:172 +#: src/components/activity-notifications/SubscribeProfileDialog.tsx:169 msgid "You'll start receiving notifications for {0}!" msgstr "" -#: src/screens/StarterPack/StarterPackLandingScreen.tsx:281 +#: src/screens/StarterPack/StarterPackLandingScreen.tsx:273 msgid "You'll stay updated with these feeds" msgstr "" @@ -14970,25 +14970,25 @@ msgstr "You're all set! However, certain features may still be restricted until msgid "You’re in control" msgstr "You’re in control" -#: src/screens/SignupQueued.tsx:130 +#: src/screens/SignupQueued.tsx:137 msgid "You're in line" msgstr "" -#: src/screens/Deactivated.tsx:81 -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:54 +#: src/screens/Deactivated.tsx:88 +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:55 msgid "You're signed in with an App Password. Please sign in with your main password to continue deactivating your account." msgstr "" -#: src/components/moderation/AppealForm.tsx:73 +#: src/components/moderation/AppealForm.tsx:84 msgid "You've already appealed this label and it's being reviewed by our moderation team." msgstr "" #: src/components/moderation/ModerationDetailsDialog.tsx:128 -#: src/lib/moderation/useModerationCauseDescription.ts:109 +#: src/lib/moderation/useModerationCauseDescription.ts:106 msgid "You've chosen to hide a word or tag within this post." msgstr "" -#: src/components/contacts/screens/GetContacts.tsx:313 +#: src/components/contacts/screens/GetContacts.tsx:315 msgid "You've denied access to your contacts" msgstr "" @@ -15004,7 +15004,7 @@ msgstr "" msgid "You've reached the end of your feed! Find some more accounts to follow." msgstr "" -#: src/view/com/composer/Composer.tsx:757 +#: src/view/com/composer/Composer.tsx:773 msgid "You've reached the maximum number of drafts" msgstr "" @@ -15020,35 +15020,35 @@ msgstr "You’ve reached the maximum of {MAX_FILTERS, plural, one {# filter} oth msgid "You've reached the start of the active content." msgstr "" -#: src/view/com/composer/state/video.ts:530 +#: src/view/com/composer/state/video.ts:535 msgid "You've reached your daily limit for video uploads (too many bytes)" msgstr "" -#: src/view/com/composer/state/video.ts:534 +#: src/view/com/composer/state/video.ts:539 msgid "You've reached your daily limit for video uploads (too many videos)" msgstr "" -#: src/screens/VideoFeed/index.tsx:1243 +#: src/screens/VideoFeed/index.tsx:1239 msgid "You've run out of videos to watch. Maybe it's a good time to take a break?" msgstr "" -#: src/screens/Signup/index.tsx:194 +#: src/screens/Signup/index.tsx:191 msgid "Your account" msgstr "" -#: src/components/moderation/ReportDialog/index.tsx:310 +#: src/components/moderation/ReportDialog/index.tsx:311 msgid "Your account cannot submit reports while it is suspended." msgstr "Your account cannot submit reports while it is suspended." -#: src/screens/Settings/components/DeleteAccountDialog.tsx:128 +#: src/screens/Settings/components/DeleteAccountDialog.tsx:133 msgid "Your account has been deleted, see ya! ✌️" msgstr "" -#: src/screens/Takendown.tsx:142 +#: src/screens/Takendown.tsx:140 msgid "Your account has been suspended" msgstr "" -#: src/view/com/composer/state/video.ts:538 +#: src/view/com/composer/state/video.ts:543 msgid "Your account is not yet old enough to upload videos. Please try again later." msgstr "" @@ -15060,15 +15060,15 @@ msgstr "Your account is too new" msgid "Your account must be at least 7 days old to create a new group chat." msgstr "Your account must be at least 7 days old to create a new group chat." -#: src/screens/Settings/components/ExportCarDialog.tsx:107 +#: src/screens/Settings/components/ExportCarDialog.tsx:113 msgid "Your account repository, containing all public data records, can be downloaded as a \"CAR\" file. This file does not include media embeds, such as images, or your private data, which must be fetched separately." msgstr "" -#: src/screens/Takendown.tsx:210 +#: src/screens/Takendown.tsx:208 msgid "Your account was found to be in violation of the <0>Bluesky Social Terms of Service. You have been sent an email outlining the specific violation and suspension period, if applicable. You can appeal this decision if you believe it was made in error." msgstr "" -#: src/screens/Takendown.tsx:150 +#: src/screens/Takendown.tsx:148 msgid "Your appeal has been submitted. If your appeal succeeds, you will receive an email." msgstr "" @@ -15084,15 +15084,15 @@ msgstr "" msgid "Your choice will be remembered for future links. You can change it at any time in settings." msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:386 +#: src/view/com/notifications/NotificationFeedItem.tsx:378 msgid "Your contact {firstAuthorLink} is on Bluesky" msgstr "" -#: src/view/com/notifications/NotificationFeedItem.tsx:384 +#: src/view/com/notifications/NotificationFeedItem.tsx:376 msgid "Your contact {firstAuthorName} is on Bluesky" msgstr "" -#: src/components/contacts/screens/ViewMatches.tsx:454 +#: src/components/contacts/screens/ViewMatches.tsx:462 msgid "Your contact {name}" msgstr "" @@ -15105,15 +15105,15 @@ msgstr "" msgid "Your declared age is under 18. Some settings below may be disabled. If this was a mistake, you may edit your birthdate in your <0>account settings." msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:252 -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:256 -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:257 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:261 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:265 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:266 msgid "Your email" msgstr "" #: src/screens/Login/ForgotPasswordForm.tsx:51 -#: src/screens/Settings/components/ChangePasswordDialog.tsx:82 -#: src/screens/Signup/state.ts:297 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:84 +#: src/screens/Signup/state.ts:295 #: src/screens/Signup/StepInfo/index.tsx:129 msgid "Your email appears to be invalid." msgstr "" @@ -15126,7 +15126,7 @@ msgstr "" msgid "Your first like!" msgstr "" -#: src/components/dialogs/PostInteractionSettingsDialog.tsx:496 +#: src/components/dialogs/PostInteractionSettingsDialog.tsx:493 msgid "Your followers" msgstr "" @@ -15143,7 +15143,7 @@ msgstr "" msgid "Your hosting provider can’t be detected from an email address, so the default Bluesky service will be used. Enter your username instead, or set your provider manually." msgstr "Your hosting provider can’t be detected from an email address, so the default Bluesky service will be used. Enter your username instead, or set your provider manually." -#: src/view/com/composer/state/video.ts:475 +#: src/view/com/composer/state/video.ts:480 msgid "Your hosting provider does not support videos this large. Please try again with a smaller file." msgstr "Your hosting provider does not support videos this large. Please try again with a smaller file." @@ -15155,11 +15155,11 @@ msgstr "Your hosting provider is detected automatically from the username you en #: src/screens/Search/modules/ExploreInterestsCard.tsx:62 #: src/screens/Settings/ContentAndMediaSettings.tsx:94 #: src/screens/Settings/ContentAndMediaSettings.tsx:97 -#: src/screens/Settings/InterestsSettings.tsx:49 +#: src/screens/Settings/InterestsSettings.tsx:50 msgid "Your interests" msgstr "" -#: src/screens/Settings/InterestsSettings.tsx:138 +#: src/screens/Settings/InterestsSettings.tsx:139 msgctxt "toast" msgid "Your interests have been updated!" msgstr "" @@ -15172,15 +15172,15 @@ msgstr "" msgid "Your location has been updated." msgstr "" -#: src/components/dialogs/MutedWords.tsx:378 +#: src/components/dialogs/MutedWords.tsx:380 msgid "Your muted words" msgstr "" -#: src/screens/Messages/components/InviteLinkDialog.tsx:211 +#: src/screens/Messages/components/InviteLinkDialog.tsx:208 msgid "Your name, avatar, the name of the group chat, and the number of members will be visible to everyone." msgstr "Your name, avatar, the name of the group chat, and the number of members will be visible to everyone." -#: src/screens/Settings/components/ChangePasswordDialog.tsx:72 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:74 msgid "Your password has been changed successfully! Please use your new password when you sign in to Bluesky from now on." msgstr "" @@ -15188,15 +15188,15 @@ msgstr "" msgid "Your password must be at least 8 characters long." msgstr "" -#: src/view/com/composer/Composer.tsx:1253 +#: src/view/com/composer/Composer.tsx:1275 msgid "Your post was sent" msgstr "" -#: src/view/com/composer/Composer.tsx:1250 +#: src/view/com/composer/Composer.tsx:1272 msgid "Your posts were sent" msgstr "" -#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:289 +#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:298 msgid "Your preferred language" msgstr "" @@ -15209,24 +15209,24 @@ msgstr "" msgid "Your profile picture surrounded by concentric circles of other users' profile pictures" msgstr "" -#: src/screens/Settings/components/DeactivateAccountDialog.tsx:75 +#: src/screens/Settings/components/DeactivateAccountDialog.tsx:76 msgid "Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in." msgstr "" -#: src/view/com/composer/Composer.tsx:1252 +#: src/view/com/composer/Composer.tsx:1274 msgid "Your reply was sent" msgstr "" #. placeholder {0}: state.selectedLabeler?.creator.displayName -#: src/components/moderation/ReportDialog/index.tsx:596 +#: src/components/moderation/ReportDialog/index.tsx:597 msgid "Your report will be sent to <0>{0}." msgstr "" -#: src/screens/Settings/InterestsSettings.tsx:63 +#: src/screens/Settings/InterestsSettings.tsx:64 msgid "Your selected interests help us serve you content you care about." msgstr "" -#: src/view/com/composer/Composer.tsx:1581 +#: src/view/com/composer/Composer.tsx:1605 msgid "Your thread has empty posts that will be skipped. The remaining posts will be published as a thread." msgstr "Your thread has empty posts that will be skipped. The remaining posts will be published as a thread." 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/Deactivated.tsx b/src/screens/Deactivated.tsx index 2782c5ef0d..3be42add78 100644 --- a/src/screens/Deactivated.tsx +++ b/src/screens/Deactivated.tsx @@ -7,10 +7,11 @@ import {Trans} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher' +import {isErrorMaybeAppPasswordPermissions} from '#/lib/strings/errors' import {logger} from '#/logger' import { type SessionAccount, - useAgent, + usePdsClient, useSession, useSessionApi, } from '#/state/session' @@ -25,6 +26,7 @@ import * as Layout from '#/components/Layout' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {IS_WEB} from '#/env' +import {com} from '#/lexicons' const COL_WIDTH = 400 @@ -36,8 +38,8 @@ export function Deactivated() { const {onPressSwitchAccount, pendingDid} = useAccountSwitcher() const {setShowLoggedOut} = useLoggedOutViewControls() const hasOtherAccounts = accounts.length > 1 - const {logoutCurrentAccount} = useSessionApi() - const agent = useAgent() + const {logoutCurrentAccount, refreshSession} = useSessionApi() + const pdsClient = usePdsClient() const [pending, setPending] = useState(false) const [error, setError] = useState() const queryClient = useQueryClient() @@ -70,21 +72,24 @@ export function Deactivated() { const handleActivate = useCallback(async () => { try { setPending(true) - await agent.com.atproto.server.activateAccount() + await pdsClient.call(com.atproto.server.activateAccount) await queryClient.resetQueries() - await agent.resumeSession(agent.session!) + await refreshSession() } catch (e: any) { - switch (e.message) { - case 'Bad token scope': - setError( - _( - msg`You're signed in with an App Password. Please sign in with your main password to continue deactivating your account.`, - ), - ) - break - default: - setError(_(msg`Something went wrong, please try again`)) - break + /* + * `activateAccount` declares no lexicon errors, so the app-password case + * arrives as an undeclared code plus a message. The shared helper matches + * both that and the plain-string form the old exact `e.message` switch + * relied on. + */ + if (isErrorMaybeAppPasswordPermissions(e)) { + setError( + _( + msg`You're signed in with an App Password. Please sign in with your main password to continue deactivating your account.`, + ), + ) + } else { + setError(_(msg`Something went wrong, please try again`)) } logger.error(e, { @@ -93,7 +98,7 @@ export function Deactivated() { } finally { setPending(false) } - }, [_, agent, setPending, setError, queryClient]) + }, [_, pdsClient, refreshSession, setPending, setError, queryClient]) return ( 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/ForgotPasswordForm.tsx b/src/screens/Login/ForgotPasswordForm.tsx index 79fdbc5d6a..1e9eda430e 100644 --- a/src/screens/Login/ForgotPasswordForm.tsx +++ b/src/screens/Login/ForgotPasswordForm.tsx @@ -1,12 +1,11 @@ import {useCallback, useState} from 'react' import {Keyboard, View} from 'react-native' -import {type ComAtprotoServerDescribeServer} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import * as EmailValidator from 'email-validator' +import {createServiceClient} from '#/lib/lexClient' import {cleanError, isNetworkError} from '#/lib/strings/errors' import {logger} from '#/logger' -import {Agent} from '#/state/session/agent' import {atoms as a, useTheme, web} from '#/alf' import {Admonition} from '#/components/Admonition' import {Button, ButtonIcon, ButtonText} from '#/components/Button' @@ -16,9 +15,10 @@ import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {IS_WEB} from '#/env' +import {com} from '#/lexicons' import {FormContainer} from './FormContainer' -type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema +type ServiceDescription = com.atproto.server.describeServer.$OutputBody export const ForgotPasswordForm = ({ error, @@ -55,8 +55,12 @@ export const ForgotPasswordForm = ({ setIsProcessing(true) try { - const agent = new Agent(null, {service: serviceUrl}) - await agent.com.atproto.server.requestPasswordReset({email}) + /* + * Pre-auth request against a user-chosen host, so it goes through a + * one-off service client rather than a session-scoped one. + */ + const client = createServiceClient(serviceUrl) + await client.call(com.atproto.server.requestPasswordReset, {email}) onEmailSent() } catch (err) { logger.warn('Failed to request password reset', {error: err}) diff --git a/src/screens/Login/LoginForm.tsx b/src/screens/Login/LoginForm.tsx index 27361219ec..4f34d3afaf 100644 --- a/src/screens/Login/LoginForm.tsx +++ b/src/screens/Login/LoginForm.tsx @@ -1,9 +1,6 @@ import {useRef, useState} from 'react' import {Keyboard, type TextInput, View} from 'react-native' -import { - ComAtprotoServerCreateSession, - type ComAtprotoServerDescribeServer, -} from '@atproto/api' +import {LexAuthFactorError} from '@atproto/lex-password-session' import {Trans, useLingui} from '@lingui/react/macro' import {DEFAULT_SERVICE, HITSLOP_10, HITSLOP_20} from '#/lib/constants' @@ -35,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, @@ -142,10 +140,11 @@ export const LoginForm = ({ } catch (err) { const errMsg = String(err) setIsProcessing(false) - if ( - err instanceof - ComAtprotoServerCreateSession.AuthFactorTokenRequiredError - ) { + /* + * `LexAuthFactorError` is what `PasswordSession.login` throws when the + * server demands an email 2FA token. + */ + if (err instanceof LexAuthFactorError) { setIsAuthFactorTokenNeeded(true) } else { onAttemptFailed() @@ -170,7 +169,9 @@ export const LoginForm = ({ ) } else { logger.warn('Failed to login', {error: errMsg}) - setError(cleanError(errMsg)) + /* the error object, not its stringification: cleanError only + * extracts the clean server message from a live LexError */ + setError(cleanError(err)) } } } diff --git a/src/screens/Login/SetNewPasswordForm.tsx b/src/screens/Login/SetNewPasswordForm.tsx index cdc8fe4e0f..f870171be8 100644 --- a/src/screens/Login/SetNewPasswordForm.tsx +++ b/src/screens/Login/SetNewPasswordForm.tsx @@ -2,10 +2,10 @@ import {useState} from 'react' import {View} from 'react-native' import {Trans, useLingui} from '@lingui/react/macro' +import {createServiceClient} from '#/lib/lexClient' import {cleanError, isNetworkError} from '#/lib/strings/errors' import {checkAndFormatResetCode} from '#/lib/strings/password' import {logger} from '#/logger' -import {Agent} from '#/state/session/agent' import {atoms as a, web} from '#/alf' import {Admonition} from '#/components/Admonition' import {Button, ButtonIcon, ButtonText} from '#/components/Button' @@ -16,6 +16,7 @@ import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {com} from '#/lexicons' import {FormContainer} from './FormContainer' export const SetNewPasswordForm = ({ @@ -61,8 +62,12 @@ export const SetNewPasswordForm = ({ setIsProcessing(true) try { - const agent = new Agent(null, {service: serviceUrl}) - await agent.com.atproto.server.resetPassword({ + /* + * Pre-auth request against a user-chosen host, so it goes through a + * one-off service client rather than a session-scoped one. + */ + const client = createServiceClient(serviceUrl) + await client.call(com.atproto.server.resetPassword, { token: formattedCode, password, }) 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 1e2f4037a8..5666e2cdb1 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, moderateProfile} from '@atproto/api' import { ScrollEdgeEffect, ScrollEdgeEffectProvider, } from '@bsky.app/expo-scroll-edge-effect' +import {moderateProfile} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import { type RouteProp, @@ -45,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' @@ -178,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 20cc0fee52..4c48a1bd50 100644 --- a/src/screens/Messages/ConversationSettings/Member.tsx +++ b/src/screens/Messages/ConversationSettings/Member.tsx @@ -1,5 +1,5 @@ import {View} from 'react-native' -import {moderateProfile} from '@atproto/api' +import {moderateProfile} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {isBlockedOrBlocking} from '#/lib/moderation/blocked-and-muted' diff --git a/src/screens/Messages/ConversationSettings/index.tsx b/src/screens/Messages/ConversationSettings/index.tsx index 5576f28674..2ddea16b4c 100644 --- a/src/screens/Messages/ConversationSettings/index.tsx +++ b/src/screens/Messages/ConversationSettings/index.tsx @@ -1,11 +1,6 @@ import {useEffect, useState} from 'react' import {Pressable, View} from 'react-native' -import { - ChatBskyActorDefs, - ChatBskyConvoDefs, - ChatBskyConvoUnlockConvo, - type ModerationOpts, -} from '@atproto/api' +import {type ModerationOpts} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -19,6 +14,7 @@ import { type NativeStackScreenProps, type NavigationProp, } from '#/lib/routes/types' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useConvoQuery} from '#/state/queries/messages/conversation' @@ -59,6 +55,7 @@ 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 * as bsky from '#/types/bsky' import {InviteLinkDialog} from '../components/InviteLinkDialog' import {AddMembersLink} from './AddMembersLink' @@ -173,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) ) } @@ -244,14 +238,12 @@ function GroupSettings({ : []), ] items.push( - ...groupMembers.map( - (profile): Item => ({ - type: 'CHAT_MEMBER', - key: profile.did, - profile, - status: primaryMember?.did === profile.did ? 'owner' : 'standard', - }), - ), + ...groupMembers.map((profile): Item => ({ + type: 'CHAT_MEMBER', + key: profile.did, + profile, + status: primaryMember?.did === profile.did ? 'owner' : 'standard', + })), ) const placeholderCount = Math.max( 0, @@ -417,7 +409,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}) @@ -432,7 +424,8 @@ function SettingsHeader({ logger.error('Failed to lock group chat', {message: e}) Toast.show(l`Failed to lock group chat`, {type: 'error'}) } else if ( - e instanceof ChatBskyConvoUnlockConvo.ConvoLockedByModerationError + matchXrpcError(e, chat.bsky.convo.unlockConvo) === + 'ConvoLockedByModeration' ) { Toast.show(l`This chat is locked by a moderation action`, { type: 'error', 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 6ce57a151b..bd7a73dade 100644 --- a/src/screens/Messages/JoinRequest.tsx +++ b/src/screens/Messages/JoinRequest.tsx @@ -1,8 +1,8 @@ import {useEffect} from 'react' import {View} from 'react-native' import {ImageBackground} from 'expo-image' -import {ChatBskyGroupDefs, moderateProfile} from '@atproto/api' import {type ThemeName} from '@bsky.app/alf' +import {moderateProfile} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' @@ -20,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') @@ -83,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 } @@ -190,19 +187,18 @@ function JoinRequestsList({ let errorMessage = l`Failed to accept join request` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if ( - error instanceof ChatBskyGroupApproveJoinRequest.InvalidConvoError - ) { - errorMessage = l`Conversation not found.` - } else if ( - error instanceof ChatBskyGroupApproveJoinRequest.InsufficientRoleError - ) { - errorMessage = l`Only admins can accept join requests.` - } else if ( - error instanceof - ChatBskyGroupApproveJoinRequest.MemberLimitReachedError - ) { - errorMessage = l`The member limit has been reached.` + } else { + switch (matchXrpcError(error, chat.bsky.group.approveJoinRequest)) { + case 'InvalidConvo': + errorMessage = l`Conversation not found.` + break + case 'InsufficientRole': + errorMessage = l`Only admins can accept join requests.` + break + case 'MemberLimitReached': + errorMessage = l`The member limit has been reached.` + break + } } Toast.show(errorMessage, {type: 'error'}) }, @@ -223,14 +219,15 @@ function JoinRequestsList({ let errorMessage = l`Failed to reject join request` if (isNetworkError(error)) { errorMessage = l`A network error occurred. Please check your internet connection.` - } else if ( - error instanceof ChatBskyGroupRejectJoinRequest.InvalidConvoError - ) { - errorMessage = l`Conversation not found.` - } else if ( - error instanceof ChatBskyGroupRejectJoinRequest.InsufficientRoleError - ) { - errorMessage = l`Only admins can reject join requests.` + } else { + switch (matchXrpcError(error, chat.bsky.group.rejectJoinRequest)) { + case 'InvalidConvo': + errorMessage = l`Conversation not found.` + break + case 'InsufficientRole': + errorMessage = l`Only admins can reject join requests.` + break + } } Toast.show(errorMessage, {type: 'error'}) }, diff --git a/src/screens/Messages/components/ChatDisabled.tsx b/src/screens/Messages/components/ChatDisabled.tsx index 8095c1e0b9..b330113d19 100644 --- a/src/screens/Messages/components/ChatDisabled.tsx +++ b/src/screens/Messages/components/ChatDisabled.tsx @@ -1,12 +1,11 @@ import {useCallback, useState} from 'react' import {type StyleProp, View, type ViewStyle} from 'react-native' -import {ToolsOzoneReportDefs} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useMutation} from '@tanstack/react-query' -import {BLUESKY_MOD_SERVICE_HEADERS} from '#/lib/constants' +import {MOD_PROXY_SERVICE} from '#/lib/constants' import {logger} from '#/logger' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, useSession} from '#/state/session' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' @@ -14,6 +13,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, tools} from '#/lexicons' export function ChatDisabled({ shape = 'pill', @@ -92,26 +92,24 @@ function DialogInner() { const control = Dialog.useDialogContext() const [details, setDetails] = useState('') const {gtMobile} = useBreakpoints() - const agent = useAgent() + const client = useAppviewClient() const {currentAccount} = useSession() const {mutate, isPending} = useMutation({ mutationFn: async () => { if (!currentAccount) throw new Error('No current account, should be unreachable') - await agent.createModerationReport( + await client.call( + com.atproto.moderation.createReport, { - reasonType: ToolsOzoneReportDefs.REASONAPPEAL, + reasonType: tools.ozone.report.defs.reasonAppeal.value, subject: { $type: 'com.atproto.admin.defs#repoRef', did: currentAccount.did, }, reason: details, }, - { - encoding: 'application/json', - headers: BLUESKY_MOD_SERVICE_HEADERS, - }, + {service: MOD_PROXY_SERVICE}, ) }, onError: err => { diff --git a/src/screens/Messages/components/ChatListItem.tsx b/src/screens/Messages/components/ChatListItem.tsx index 0acd0c28e7..f783abc7a0 100644 --- a/src/screens/Messages/components/ChatListItem.tsx +++ b/src/screens/Messages/components/ChatListItem.tsx @@ -1,11 +1,10 @@ import {useCallback, useMemo, useState} from 'react' import {type GestureResponderEvent, View} from 'react-native' import { - ChatBskyConvoDefs, moderateProfile, type ModerationDecision, type ModerationOpts, -} from '@atproto/api' +} from '@bsky/sdk/moderation' import {plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' @@ -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/ChatLocked.tsx b/src/screens/Messages/components/ChatLocked.tsx index 8718667e9c..c9dcd93c3d 100644 --- a/src/screens/Messages/components/ChatLocked.tsx +++ b/src/screens/Messages/components/ChatLocked.tsx @@ -1,10 +1,10 @@ import {Pressable} from 'react-native' -import {ChatBskyConvoUnlockConvo} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {HITSLOP_10} from '#/lib/constants' import {type NavigationProp} from '#/lib/routes/types' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' import {useLockConvo} from '#/state/queries/messages/lock-conversation' @@ -15,6 +15,7 @@ import {Lock_Stroke2_Corner0_Rounded as LockIcon} from '#/components/icons/Lock' import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {chat} from '#/lexicons' import {LeaveChatPrompt} from '../ConversationSettings/prompts' import {ChatFooter} from './ChatFooter' @@ -41,7 +42,10 @@ export function ChatLocked({ Toast.show(l({message: 'Group chat unlocked', context: 'toast'})) }, onError: e => { - if (e instanceof ChatBskyConvoUnlockConvo.ConvoLockedByModerationError) { + if ( + matchXrpcError(e, chat.bsky.convo.unlockConvo) === + 'ConvoLockedByModeration' + ) { Toast.show(l`This chat is locked by a moderation action`, { type: 'error', }) diff --git a/src/screens/Messages/components/ChatStatusInfo.tsx b/src/screens/Messages/components/ChatStatusInfo.tsx index e2e9a75faa..afb0e3e299 100644 --- a/src/screens/Messages/components/ChatStatusInfo.tsx +++ b/src/screens/Messages/components/ChatStatusInfo.tsx @@ -1,7 +1,7 @@ import {useCallback, useMemo} from 'react' import {View} from 'react-native' import {LinearGradient} from 'expo-linear-gradient' -import {moderateProfile} from '@atproto/api' +import {moderateProfile} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' 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 22acd577b0..5ac7c1e53a 100644 --- a/src/screens/Messages/components/InviteLinkDialog.tsx +++ b/src/screens/Messages/components/InviteLinkDialog.tsx @@ -1,11 +1,7 @@ import {useState} from 'react' import {View} from 'react-native' import {Image} from 'expo-image' -import { - type ChatBskyGroupDefs, - moderateProfile, - type ModerationOpts, -} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@bsky/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useOpenComposer} from '#/lib/hooks/useOpenComposer' @@ -37,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' @@ -562,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 477939cd3e..d5bcd4043c 100644 --- a/src/screens/Messages/components/MessageInputEmbed.tsx +++ b/src/screens/Messages/components/MessageInputEmbed.tsx @@ -1,12 +1,8 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {LayoutAnimation, View} from 'react-native' -import { - AppBskyFeedPost, - AppBskyRichtextFacet, - AtUri, - moderatePost, - RichText as RichTextAPI, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {moderatePost} from '@bsky/sdk/moderation' +import {RichText as RichTextAPI} from '@bsky/sdk/richtext' import {Trans, useLingui} from '@lingui/react/macro' import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native' @@ -36,6 +32,7 @@ import {ContentHider} from '#/components/moderation/ContentHider' import {PostAlerts} from '#/components/moderation/PostAlerts' import {RichText} from '#/components/RichText' import {Text} from '#/components/Typography' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' /** @@ -43,8 +40,7 @@ import * as bsky from '#/types/bsky' * embed: either a quoted post or a group chat invite link. */ export type MessageEmbedState = - | {type: 'post'; uri: string} - | {type: 'invite'; code: string} + {type: 'post'; uri: string} | {type: 'invite'; code: string} export function useMessageEmbed() { const route = @@ -106,7 +102,7 @@ export function useExtractEmbedFromFacets( for (const facet of rt.facets ?? []) { for (const feature of facet.features) { if ( - AppBskyRichtextFacet.isLink(feature) && + bsky.isType(app.bsky.richtext.facet.link, feature) && (isBskyPostUrl(feature.uri) || isBskyChatInviteUrl(feature.uri)) ) { uriFromFacet = feature.uri @@ -166,13 +162,7 @@ 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, 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 312a2277ad..056ff2f76c 100644 --- a/src/screens/Messages/components/MessagesList.tsx +++ b/src/screens/Messages/components/MessagesList.tsx @@ -22,16 +22,9 @@ 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, - AppBskyRichtextFacet, - ChatBskyConvoDefs, - type ChatBskyEmbedJoinLink, - ChatBskyGroupDefs, - RichText, -} from '@atproto/api' +import {type $Typed} from '@atproto/lex' import {useScrollEdgeEffectRef} from '@bsky.app/expo-scroll-edge-effect' +import {RichText} from '@bsky/sdk/richtext' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {mergeRefs} from '#/lib/merge-refs' @@ -52,7 +45,7 @@ import {type ConvoState, ConvoStatus} from '#/state/messages/convo/types' import {useGetJoinLinkPreview} from '#/state/queries/join-links' import {useGetPost} from '#/state/queries/post' import {createEmbedViewRecordFromPost} from '#/state/queries/postgate/util' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, useSession} from '#/state/session' import {List, type ListMethods} from '#/view/com/util/List' import {MessageComposer} from '#/screens/Messages/components/MessageComposer' import {MessageListError} from '#/screens/Messages/components/MessageListError' @@ -71,6 +64,8 @@ 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, chat} from '#/lexicons' +import * as bsky from '#/types/bsky' import {ChatStatusInfo} from './ChatStatusInfo' import {groupSystemMessages, type RenderItem} from './groupSystemMessages' import {InviteLinkDialogProvider} from './InviteLinkDialogProvider' @@ -116,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 } @@ -144,7 +139,11 @@ export function MessagesList({ }) { const ax = useAnalytics() const convoState = useConvoActive() - const agent = useAgent() + /* + * Facet/mention resolution is an appview job - it resolves handles through + * the appview, and the public fallback keeps it working when logged out. + */ + const appviewClient = useAppviewClient() const {hasSession, currentAccount} = useSession() const getPost = useGetPost() const getJoinLinkPreview = useGetJoinLinkPreview() @@ -520,7 +519,7 @@ export function MessagesList({ async ( text: string, embedState?: MessageEmbedState, - reply?: $Typed, + reply?: $Typed, ) => { let rt = new RichText({text: text.trimEnd()}, {cleanNewlines: true}) @@ -532,15 +531,12 @@ export function MessagesList({ */ rt.detectFacetsWithoutResolution() - let embed: - | $Typed - | $Typed - | undefined + 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 @@ -550,7 +546,8 @@ export function MessagesList({ const linkFacet = rt.facets?.find(facet => facet.features.find( feature => - AppBskyRichtextFacet.isLink(feature) && predicate(feature.uri), + bsky.isType(app.bsky.richtext.facet.link, feature) && + predicate(feature.uri), ), ) if (linkFacet) { @@ -617,7 +614,7 @@ export function MessagesList({ replyTo = {messageId: reply.id} } - await rt.detectFacets(agent) + await rt.detectFacets(appviewClient) rt = shortenLinks(rt) rt = stripInvalidMentions(rt) @@ -658,7 +655,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, @@ -667,7 +667,7 @@ export function MessagesList({ } }, [ - agent, + appviewClient, convoState, getPost, getJoinLinkPreview, @@ -896,7 +896,7 @@ function Composer({ onSendMessage: ( message: string, embed?: MessageEmbedState, - replyTo?: $Typed, + replyTo?: $Typed, ) => Promise messageEmbed: MessageEmbedState | undefined setEmbed: (embedUrl: string | undefined) => void @@ -906,7 +906,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 b5c39a42dc..4e7eb43a2d 100644 --- a/src/screens/Messages/components/MessagesListInfoPanel.tsx +++ b/src/screens/Messages/components/MessagesListInfoPanel.tsx @@ -1,5 +1,5 @@ import {View} from 'react-native' -import {moderateProfile} from '@atproto/api' +import {moderateProfile} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' diff --git a/src/screens/Messages/components/OutgoingRequestListItem.tsx b/src/screens/Messages/components/OutgoingRequestListItem.tsx index b5dcdf08df..9aa08fd0cf 100644 --- a/src/screens/Messages/components/OutgoingRequestListItem.tsx +++ b/src/screens/Messages/components/OutgoingRequestListItem.tsx @@ -1,11 +1,8 @@ import {View} from 'react-native' -import { - type ChatBskyGroupDefs, - ChatBskyGroupWithdrawJoinRequest, -} from '@atproto/api' import {Trans, useLingui} from '@lingui/react/macro' import {isNetworkError} from '#/lib/strings/errors' +import {matchXrpcError} from '#/lib/xrpc-error' import {useWithdrawJoinGroupChatRequest} from '#/state/queries/messages/withdraw-join-group-chat' import {TimeElapsed} from '#/view/com/util/TimeElapsed' import {atoms as a, useTheme, web} from '#/alf' @@ -14,11 +11,12 @@ import {createStaticClick, Link} from '#/components/Link' import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +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() @@ -35,8 +33,8 @@ export function OutgoingRequestListItem({ if (isNetworkError(error)) { errorMessage = l`There was a problem with your internet connection, please try again` } else if ( - error instanceof - ChatBskyGroupWithdrawJoinRequest.InvalidJoinRequestError + matchXrpcError(error, chat.bsky.group.withdrawJoinRequest) === + 'InvalidJoinRequest' ) { errorMessage = l`Invalid rescind request.` } 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/Messages/components/groupSystemMessages.ts b/src/screens/Messages/components/groupSystemMessages.ts index 5a6de8fb92..fa71f7d741 100644 --- a/src/screens/Messages/components/groupSystemMessages.ts +++ b/src/screens/Messages/components/groupSystemMessages.ts @@ -16,9 +16,7 @@ export type SystemMessageDateDividerItem = { } export type RenderItem = - | ConvoItem - | SystemMessageGroupItem - | SystemMessageDateDividerItem + ConvoItem | SystemMessageGroupItem | SystemMessageDateDividerItem function getSentAt(item: ConvoItem): string | null { if ( diff --git a/src/screens/Moderation/index.tsx b/src/screens/Moderation/index.tsx index 9aea34b89d..1f612d95a8 100644 --- a/src/screens/Moderation/index.tsx +++ b/src/screens/Moderation/index.tsx @@ -1,6 +1,6 @@ import {Fragment, useCallback} from 'react' import {Linking, View} from 'react-native' -import {LABELS} from '@atproto/api' +import {LABELS} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' diff --git a/src/screens/ModerationInteractionSettings/index.tsx b/src/screens/ModerationInteractionSettings/index.tsx index 261e6b55ae..c6ea9db622 100644 --- a/src/screens/ModerationInteractionSettings/index.tsx +++ b/src/screens/ModerationInteractionSettings/index.tsx @@ -1,5 +1,6 @@ import {useCallback, useMemo, useState} from 'react' import {View} from 'react-native' +import {type AtUriString, toDatetimeString} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -68,8 +69,8 @@ function Inner({preferences}: {preferences: UsePreferencesQueryResponse}) { const allowUI = useMemo(() => { return threadgateRecordToAllowUISetting({ $type: 'app.bsky.feed.threadgate', - post: '', - createdAt: new Date().toString(), + post: '' as AtUriString, + createdAt: toDatetimeString(new Date()), allow: preferences.postInteractionSettings.threadgateAllowRules, }) }, [preferences.postInteractionSettings.threadgateAllowRules]) diff --git a/src/screens/Onboarding/StepFinished/index.tsx b/src/screens/Onboarding/StepFinished/index.tsx index 11dfd35ceb..f90e7ddcd4 100644 --- a/src/screens/Onboarding/StepFinished/index.tsx +++ b/src/screens/Onboarding/StepFinished/index.tsx @@ -1,13 +1,9 @@ import {useCallback, useState} from 'react' import {View} from 'react-native' -import { - type AppBskyActorDefs, - type AppBskyActorProfile, - type AppBskyGraphDefs, - AppBskyGraphStarterpack, - type Un$Typed, -} from '@atproto/api' import {TID} from '@atproto/common-web' +import {type Un$Typed} from '@atproto/lex' +import {type AtUriString, toDatetimeString} from '@atproto/syntax' +import {overwriteSavedFeeds, setInterestsPref, upsertProfile} from '@bsky/sdk' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -26,7 +22,7 @@ import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-p import {getAllListMembers} from '#/state/queries/list-members' import {preferencesQueryKey} from '#/state/queries/preferences' import {RQKEY as profileRQKey} from '#/state/queries/profile' -import {useAgent} from '#/state/session' +import {useAppviewClient, usePdsClient} from '#/state/session' import {useOnboardingDispatch} from '#/state/shell' import { useActiveStarterPack, @@ -48,6 +44,7 @@ import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/ico import {Loader} from '#/components/Loader' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' import {ValuePropositionPager} from './ValuePropositionPager' @@ -57,7 +54,8 @@ export function StepFinished() { const onboardDispatch = useOnboardingDispatch() const [saving, setSaving] = useState(false) const queryClient = useQueryClient() - const agent = useAgent() + const pdsClient = usePdsClient() + const appviewClient = useAppviewClient() const requestNotificationsPermission = useRequestNotificationsPermission() const activeStarterPack = useActiveStarterPack() const setActiveStarterPack = useSetActiveStarterPack() @@ -67,22 +65,25 @@ export function StepFinished() { const finishOnboarding = useCallback(async () => { setSaving(true) - let starterPack: AppBskyGraphDefs.StarterPackView | undefined - let listItems: AppBskyGraphDefs.ListItemView[] | undefined + let starterPack: app.bsky.graph.defs.StarterPackView | undefined + let listItems: app.bsky.graph.defs.ListItemView[] | undefined if (activeStarterPack?.uri) { try { - const spRes = await agent.app.bsky.graph.getStarterPack({ - starterPack: activeStarterPack.uri, + const spRes = await appviewClient.call(app.bsky.graph.getStarterPack, { + starterPack: activeStarterPack.uri as AtUriString, }) - starterPack = spRes.data.starterPack + starterPack = spRes.starterPack } catch (e) { logger.error('Failed to fetch starter pack', {safeMessage: e}) // don't tell the user, just get them through onboarding. } try { if (starterPack?.list) { - listItems = await getAllListMembers(agent, starterPack.list.uri) + listItems = await getAllListMembers( + appviewClient, + starterPack.list.uri, + ) } } catch (e) { logger.error('Failed to fetch starter pack list items', { @@ -98,7 +99,8 @@ export function StepFinished() { await Promise.all([ bulkWriteFollows( - agent, + pdsClient, + appviewClient, [BSKY_APP_ACCOUNT_DID, ...(listItems?.map(i => i.subject.did) ?? [])], starterPack ? {uri: starterPack.uri, cid: starterPack.cid} @@ -106,10 +108,10 @@ export function StepFinished() { ), (async () => { // Interests need to get saved first, then we can write the feeds to prefs - await agent.setInterestsPref({tags: selectedInterests}) + await pdsClient.call(setInterestsPref, {tags: selectedInterests}) // Default feeds that every user should have pinned when landing in the app - const feedsToSave: AppBskyActorDefs.SavedFeed[] = [ + const feedsToSave: app.bsky.actor.defs.SavedFeed[] = [ { ...DISCOVER_SAVED_FEED, id: TID.nextStr(), @@ -128,7 +130,7 @@ export function StepFinished() { if (starterPack && starterPack.feeds?.length) { feedsToSave.push( ...starterPack.feeds.map(f => ({ - type: 'feed', + type: 'feed' as const, value: f.uri, pinned: true, id: TID.nextStr(), @@ -136,22 +138,22 @@ export function StepFinished() { ) } - await agent.overwriteSavedFeeds(feedsToSave) + await pdsClient.call(overwriteSavedFeeds, feedsToSave) })(), (async () => { const {imageUri, imageMime} = profileStepResults const blobPromise = imageUri && imageMime - ? uploadBlob(agent, imageUri, imageMime) + ? uploadBlob(pdsClient, imageUri, imageMime) : undefined - await agent.upsertProfile(async existing => { - let next: Un$Typed = existing ?? {} + await pdsClient.call(upsertProfile, async existing => { + let next: Un$Typed = existing ?? {} if (blobPromise) { const res = await blobPromise - if (res.data.blob) { - next.avatar = res.data.blob + if (res.blob) { + next.avatar = res.blob } } @@ -165,7 +167,7 @@ export function StepFinished() { next.displayName = '' if (!next.createdAt) { - next.createdAt = new Date().toISOString() + next.createdAt = toDatetimeString(new Date()) } return next }) @@ -192,7 +194,7 @@ export function StepFinished() { queryKey: preferencesQueryKey, }), queryClient.invalidateQueries({ - queryKey: profileRQKey(agent.session?.did ?? ''), + queryKey: profileRQKey(pdsClient.did ?? ''), }), ]).catch(e => { logger.error(e) @@ -209,10 +211,7 @@ export function StepFinished() { usedStarterPack: Boolean(starterPack), starterPackName: starterPack && - bsky.dangerousIsType( - starterPack.record, - AppBskyGraphStarterpack.isRecord, - ) + bsky.isType(app.bsky.graph.starterpack, starterPack.record) ? starterPack.record.name : undefined, starterPackCreator: starterPack?.creator.did, @@ -230,7 +229,8 @@ export function StepFinished() { }, [ ax, queryClient, - agent, + pdsClient, + appviewClient, dispatch, onboardDispatch, activeStarterPack, diff --git a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx index 514e892895..dc1a89c182 100644 --- a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx +++ b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx @@ -1,6 +1,6 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react' import {View} from 'react-native' -import {type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -14,7 +14,7 @@ import {logger} from '#/logger' import {updateProfileShadow} from '#/state/cache/profile-shadow' import {useLanguagePrefs} from '#/state/preferences' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' import { OnboardingControls, OnboardingPosition, @@ -42,7 +42,8 @@ export function StepSuggestedAccounts() { const t = useTheme() const {gtMobile} = useBreakpoints() const moderationOpts = useModerationOpts() - const agent = useAgent() + const appviewClient = useAppviewClient() + const pdsClient = usePdsClient() const {currentAccount} = useSession() const queryClient = useQueryClient() @@ -119,7 +120,10 @@ export function StepSuggestedAccounts() { followingUri: 'pending', }) } - const uris = await wait(1e3, bulkWriteFollows(agent, followableDids)) + const uris = await wait( + 1e3, + bulkWriteFollows(pdsClient, appviewClient, followableDids), + ) for (const did of followableDids) { const uri = uris.get(did) updateProfileShadow(queryClient, did, { diff --git a/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx b/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx index b016af65db..ecbc7a9c4d 100644 --- a/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx +++ b/src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx @@ -1,6 +1,5 @@ import {useState} from 'react' import {View} from 'react-native' -import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -11,7 +10,7 @@ import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted' import {logger} from '#/logger' import {updateProfileShadow} from '#/state/cache/profile-shadow' import {getAllListMembers} from '#/state/queries/list-members' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' import {bulkWriteFollows} from '#/screens/Onboarding/util' import {AvatarStack} from '#/screens/Search/components/StarterPackCard' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' @@ -21,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' @@ -28,14 +28,15 @@ const IGNORED_ACCOUNT = 'did:plc:pifkcjimdcfwaxkanzhwxufp' export function StarterPackCard({ view, }: { - view: AppBskyGraphDefs.StarterPackView + view: app.bsky.graph.defs.StarterPackView }) { const t = useTheme() const {_} = useLingui() const ax = useAnalytics() const {currentAccount} = useSession() const {gtPhone} = useBreakpoints() - const agent = useAgent() + const appviewClient = useAppviewClient() + const pdsClient = usePdsClient() const queryClient = useQueryClient() const record = view.record const [isProcessing, setIsProcessing] = useState(false) @@ -46,9 +47,9 @@ export function StarterPackCard({ setIsProcessing(true) - let listItems: AppBskyGraphDefs.ListItemView[] = [] + let listItems: app.bsky.graph.defs.ListItemView[] = [] try { - listItems = await getAllListMembers(agent, view.list.uri) + listItems = await getAllListMembers(appviewClient, view.list.uri) } catch (e) { setIsProcessing(false) Toast.show(_(msg`An error occurred while trying to follow all`), { @@ -73,7 +74,7 @@ export function StarterPackCard({ let followUris: Map try { - followUris = await bulkWriteFollows(agent, dids, { + followUris = await bulkWriteFollows(pdsClient, appviewClient, dids, { uri: view.uri, cid: view.cid, }) @@ -102,12 +103,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/Onboarding/util.ts b/src/screens/Onboarding/util.ts index f4ecde3c5d..07ccc2f17a 100644 --- a/src/screens/Onboarding/util.ts +++ b/src/screens/Onboarding/util.ts @@ -1,37 +1,34 @@ -import { - type $Typed, - type AppBskyGraphFollow, - type AppBskyGraphGetFollows, - type AtpAgent, - type ComAtprotoRepoApplyWrites, - type ComAtprotoRepoStrongRef, -} from '@atproto/api' import {TID} from '@atproto/common-web' +import {type $Typed, type Client} from '@atproto/lex' +import { + type AtIdentifierString, + type DidString, + toDatetimeString, +} from '@atproto/syntax' import chunk from 'lodash.chunk' import {until} from '#/lib/async/until' +import {app, com} from '#/lexicons' export async function bulkWriteFollows( - agent: AtpAgent, + pdsClient: Client, + appviewClient: Client, dids: string[], - via?: ComAtprotoRepoStrongRef.Main, + via?: com.atproto.repo.strongRef.Main, ) { - const session = agent.session + const did = pdsClient.assertDid - if (!session) { - throw new Error(`bulkWriteFollows failed: no session`) - } - - const followRecords: $Typed[] = dids.map(did => { + const followRecords: $Typed[] = dids.map(did => { return { $type: 'app.bsky.graph.follow', - subject: did, - createdAt: new Date().toISOString(), + // the helper takes the dids as plain strings + subject: did as DidString, + createdAt: toDatetimeString(new Date()), via, } }) - const followWrites: $Typed[] = + const followWrites: $Typed[] = followRecords.map(r => ({ $type: 'com.atproto.repo.applyWrites#create', collection: 'app.bsky.graph.follow', @@ -41,35 +38,35 @@ export async function bulkWriteFollows( const chunks = chunk(followWrites, 50) for (const chunk of chunks) { - await agent.com.atproto.repo.applyWrites({ - repo: session.did, + await pdsClient.call(com.atproto.repo.applyWrites, { + repo: did, writes: chunk, }) } - await whenFollowsIndexed(agent, session.did, res => !!res.data.follows.length) + await whenFollowsIndexed(appviewClient, did, res => !!res.follows.length) const followUris = new Map() for (const r of followWrites) { followUris.set( r.value.subject as string, - `at://${session.did}/app.bsky.graph.follow/${r.rkey}`, + `at://${did}/app.bsky.graph.follow/${r.rkey}`, ) } return followUris } async function whenFollowsIndexed( - agent: AtpAgent, + appviewClient: Client, actor: string, - fn: (res: AppBskyGraphGetFollows.Response) => boolean, + fn: (res: app.bsky.graph.getFollows.$OutputBody) => boolean, ) { await until( 5, // 5 tries 1e3, // 1s delay between tries fn, () => - agent.app.bsky.graph.getFollows({ - actor, + appviewClient.call(app.bsky.graph.getFollows, { + actor: actor as AtIdentifierString, limit: 1, }), ) diff --git a/src/screens/PostThread/components/LikesStat.tsx b/src/screens/PostThread/components/LikesStat.tsx index eab32dc274..fab45ab155 100644 --- a/src/screens/PostThread/components/LikesStat.tsx +++ b/src/screens/PostThread/components/LikesStat.tsx @@ -1,5 +1,6 @@ import {View} from 'react-native' -import {type AppBskyFeedDefs, AtUri, moderateProfile} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {moderateProfile} from '@bsky/sdk/moderation' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {makeProfileLink} from '#/lib/routes/links' @@ -14,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 @@ -21,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() @@ -61,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 c4d664238b..6a1ee4bb55 100644 --- a/src/screens/PostThread/components/ThreadItemAnchor.tsx +++ b/src/screens/PostThread/components/ThreadItemAnchor.tsx @@ -1,12 +1,7 @@ import {memo, useMemo} from 'react' import {Text as RNText, View} from 'react-native' -import { - AppBskyFeedDefs, - AppBskyFeedPost, - type AppBskyFeedThreadgate, - AtUri, - RichText as RichTextAPI, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {RichText as RichTextAPI} from '@bsky/sdk/richtext' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' @@ -64,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({ @@ -74,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) @@ -175,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() @@ -243,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, @@ -573,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) @@ -663,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 c7f5b3b5f7..12fff63012 100644 --- a/src/screens/PostThread/components/ThreadItemPost.tsx +++ b/src/screens/PostThread/components/ThreadItemPost.tsx @@ -1,11 +1,7 @@ import {memo, type ReactNode, useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedThreadgate, - AtUri, - RichText as RichTextAPI, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {RichText as RichTextAPI} from '@bsky/sdk/richtext' import {Trans} from '@lingui/react/macro' import {MAX_POST_LINES} from '#/lib/constants' @@ -54,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 @@ -62,7 +59,7 @@ export type ThreadItemPostProps = { topBorder?: boolean } onPostSuccess?: (data: OnPostSuccessData) => void - threadgateRecord?: AppBskyFeedThreadgate.Record + threadgateRecord?: app.bsky.feed.threadgate.Main } export function ThreadItemPost({ @@ -196,7 +193,7 @@ const ThreadItemPostInner = memo(function ThreadItemPostInner({ onPostSuccess, threadgateRecord, }: ThreadItemPostProps & { - postShadow: Shadow + postShadow: Shadow }) { const t = useTheme() const {openComposer} = useOpenComposer() 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 1560550329..6155ae1081 100644 --- a/src/screens/PostThread/components/ThreadItemTreePost.tsx +++ b/src/screens/PostThread/components/ThreadItemTreePost.tsx @@ -1,11 +1,7 @@ import {memo, useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import { - type AppBskyFeedDefs, - type AppBskyFeedThreadgate, - AtUri, - RichText as RichTextAPI, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {RichText as RichTextAPI} from '@bsky/sdk/richtext' import {Trans} from '@lingui/react/macro' import {MAX_POST_LINES} from '#/lib/constants' @@ -50,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 @@ -68,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) @@ -251,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() diff --git a/src/screens/Profile/Header/DisplayName.tsx b/src/screens/Profile/Header/DisplayName.tsx index 8bf50fee2d..a0f3e7960c 100644 --- a/src/screens/Profile/Header/DisplayName.tsx +++ b/src/screens/Profile/Header/DisplayName.tsx @@ -1,5 +1,5 @@ import {View} from 'react-native' -import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api' +import {type ModerationDecision} from '@bsky/sdk/moderation' import {sanitizeDisplayName} from '#/lib/strings/display-names' import {sanitizeHandle} from '#/lib/strings/handles' @@ -7,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 96e136da5c..c800d0ace3 100644 --- a/src/screens/Profile/Header/ProfileHeaderLabeler.tsx +++ b/src/screens/Profile/Header/ProfileHeaderLabeler.tsx @@ -1,12 +1,7 @@ import {memo, useCallback, useMemo, useState} from 'react' import {View} from 'react-native' -import { - type AppBskyActorDefs, - type AppBskyLabelerDefs, - moderateProfile, - type ModerationOpts, - type RichText as RichTextAPI, -} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@bsky/sdk/moderation' +import {type RichText as RichTextAPI} from '@bsky/sdk/richtext' import {msg, plural} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' @@ -37,6 +32,7 @@ import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_IOS} from '#/env' +import {type app} from '#/lexicons' import {ProfileHeaderDisplayName} from './DisplayName' import {EditProfileDialog} from './EditProfileDialog' import {ProfileHeaderHandle} from './Handle' @@ -44,8 +40,8 @@ import {ProfileHeaderMetrics} from './Metrics' import {ProfileHeaderShell} from './Shell' interface Props { - profile: AppBskyActorDefs.ProfileViewDetailed - labeler: AppBskyLabelerDefs.LabelerViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed + labeler: app.bsky.labeler.defs.LabelerViewDetailed descriptionRT: RichTextAPI | null moderationOpts: ModerationOpts hideBackButton?: boolean @@ -60,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() @@ -234,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 da01ded772..f66587c947 100644 --- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx +++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx @@ -1,12 +1,11 @@ import {memo, useMemo, useState} from 'react' import {View} from 'react-native' import { - type AppBskyActorDefs, moderateProfile, type ModerationDecision, type ModerationOpts, - type RichText as RichTextAPI, -} from '@atproto/api' +} from '@bsky/sdk/moderation' +import {type RichText as RichTextAPI} from '@bsky/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -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 a142e1139c..bc1efdbb91 100644 --- a/src/screens/Profile/Header/Shell.tsx +++ b/src/screens/Profile/Header/Shell.tsx @@ -5,8 +5,8 @@ import Animated, { useAnimatedRef, } from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' -import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api' import {utils} from '@bsky.app/alf' +import {type ModerationDecision} from '@bsky/sdk/moderation' import {useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' @@ -31,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 0b7ab16ec4..c8ebd3c780 100644 --- a/src/screens/Profile/Header/index.tsx +++ b/src/screens/Profile/Header/index.tsx @@ -7,13 +7,8 @@ import Animated, { } from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {scheduleOnRN} from 'react-native-worklets' -import { - type AppBskyActorDefs, - type AppBskyLabelerDefs, - moderateProfile, - type ModerationOpts, - type RichText as RichTextAPI, -} from '@atproto/api' +import {moderateProfile, type ModerationOpts} from '@bsky/sdk/moderation' +import {type RichText as RichTextAPI} from '@bsky/sdk/richtext' import {useIsFocused} from '@react-navigation/native' import {sanitizeHandle} from '#/lib/strings/handles' @@ -26,6 +21,7 @@ import {atoms as a, useTheme} from '#/alf' import {Header} from '#/components/Layout' import * as ProfileCard from '#/components/ProfileCard' import {IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' import { HeaderLabelerButtons, ProfileHeaderLabeler, @@ -60,8 +56,8 @@ ProfileHeaderLoading = memo(ProfileHeaderLoading) export {ProfileHeaderLoading} interface Props { - profile: AppBskyActorDefs.ProfileViewDetailed - labeler: AppBskyLabelerDefs.LabelerViewDetailed | undefined + profile: app.bsky.actor.defs.ProfileViewDetailed + labeler: app.bsky.labeler.defs.LabelerViewDetailed | undefined descriptionRT: RichTextAPI | null moderationOpts: ModerationOpts hideBackButton?: boolean @@ -105,8 +101,8 @@ const MinimalHeader = memo(function MinimalHeader({ hideBackButton = false, }: { onLayout: (e: LayoutChangeEvent) => void - profile: AppBskyActorDefs.ProfileViewDetailed - labeler?: AppBskyLabelerDefs.LabelerViewDetailed + profile: app.bsky.actor.defs.ProfileViewDetailed + labeler?: app.bsky.labeler.defs.LabelerViewDetailed hideBackButton?: boolean }) { const t = useTheme() 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/Sections/Labels.tsx b/src/screens/Profile/Sections/Labels.tsx index 8c70d40392..6d73429f35 100644 --- a/src/screens/Profile/Sections/Labels.tsx +++ b/src/screens/Profile/Sections/Labels.tsx @@ -1,11 +1,10 @@ import {useCallback, useEffect, useImperativeHandle, useMemo} from 'react' import {type ListRenderItemInfo, View} from 'react-native' import { - type AppBskyLabelerDefs, type InterpretedLabelValueDefinition, interpretLabelValueDefinitions, type ModerationOpts, -} from '@atproto/api' +} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -21,13 +20,14 @@ import {Loader} from '#/components/Loader' import {LabelerLabelPreference} from '#/components/moderation/LabelPreference' import {Text} from '#/components/Typography' import {IS_IOS, IS_NATIVE} from '#/env' +import {type app} from '#/lexicons' import {ErrorState} from '../ErrorState' import {type SectionRef} from './types' interface LabelsSectionProps { ref: React.Ref isLabelerLoading: boolean - labelerInfo: AppBskyLabelerDefs.LabelerViewDetailed | undefined + labelerInfo: app.bsky.labeler.defs.LabelerViewDetailed | undefined labelerError: Error | null moderationOpts: ModerationOpts scrollElRef: ListRef @@ -161,7 +161,7 @@ export function LabelerListHeader({ }: { isLabelerLoading: boolean labelerError?: Error | null - labelerInfo?: AppBskyLabelerDefs.LabelerViewDetailed + labelerInfo?: app.bsky.labeler.defs.LabelerViewDetailed hasValues: boolean isSubscribed: boolean }) { diff --git a/src/screens/Profile/components/GermButton.tsx b/src/screens/Profile/components/GermButton.tsx index 7e32d60bcc..a3b561ae20 100644 --- a/src/screens/Profile/components/GermButton.tsx +++ b/src/screens/Profile/components/GermButton.tsx @@ -1,10 +1,7 @@ import {Platform, View} from 'react-native' import {Image} from 'expo-image' -import { - type AppBskyActorDefs, - type AppBskyActorGetProfile, - type AtpAgent, -} from '@atproto/api' +import {type Client} from '@atproto/lex' +import {type DidString} from '@atproto/syntax' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -13,7 +10,7 @@ import {useMutation, useQueryClient} from '@tanstack/react-query' import {until} from '#/lib/async/until' import {isNetworkError} from '#/lib/strings/errors' import {RQKEY} from '#/state/queries/profile' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' import {atoms as a, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' @@ -24,13 +21,14 @@ import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {app, com} from '#/lexicons' import type * as bsky from '#/types/bsky' export function GermButton({ germ, profile, }: { - germ: AppBskyActorDefs.ProfileAssociatedGerm + germ: app.bsky.actor.defs.ProfileAssociatedGerm profile: bsky.profile.AnyProfileView }) { const t = useTheme() @@ -40,9 +38,9 @@ export function GermButton({ const linkWarningControl = Dialog.useDialogControl() // exclude `none` and all unknown values - if ( - !(germ.showButtonTo === 'everyone' || germ.showButtonTo === 'usersIFollow') - ) { + if (!( + germ.showButtonTo === 'everyone' || germ.showButtonTo === 'usersIFollow' + )) { return null } @@ -119,25 +117,27 @@ function GermSelfButton({did}: {did: string}) { const ax = useAnalytics() const {_} = useLingui() const selfExplanationDialogControl = Dialog.useDialogControl() - const agent = useAgent() + const appviewClient = useAppviewClient() + const pdsClient = usePdsClient() const queryClient = useQueryClient() const {mutate: deleteDeclaration, isPending} = useMutation({ mutationFn: async () => { - const previousRecord = await agent.com.germnetwork.declaration - .get({ - repo: did, + const previousRecord = await pdsClient + // the component takes the did as a plain string prop + .get(com.germnetwork.declaration, { + repo: did as DidString, rkey: 'self', }) .then(res => res.value) .catch(() => null) - await agent.com.germnetwork.declaration.delete({ - repo: did, + await pdsClient.delete(com.germnetwork.declaration, { + repo: did as DidString, rkey: 'self', }) - await whenAppViewReady(agent, did, res => !res.data.associated?.germ) + await whenAppViewReady(appviewClient, did, res => !res.associated?.germ) return previousRecord }, @@ -147,14 +147,15 @@ function GermSelfButton({did}: {did: string}) { async function undo() { if (!previousRecord) return try { - await agent.com.germnetwork.declaration.put( - { - repo: did, - rkey: 'self', - }, - previousRecord, + await pdsClient.put(com.germnetwork.declaration, previousRecord, { + repo: did as DidString, + rkey: 'self', + }) + await whenAppViewReady( + appviewClient, + did, + res => !!res.associated?.germ, ) - await whenAppViewReady(agent, did, res => !!res.data.associated?.germ) await queryClient.refetchQueries({queryKey: RQKEY(did)}) Toast.show(_(msg`Germ DM reconnected`)) @@ -277,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, ) { @@ -323,14 +324,17 @@ function platform() { } async function whenAppViewReady( - agent: AtpAgent, + appviewClient: Client, actor: string, - fn: (res: AppBskyActorGetProfile.Response) => boolean, + fn: (res: app.bsky.actor.getProfile.$OutputBody) => boolean, ) { await until( 5, // 5 tries 1e3, // 1s delay between tries fn, - () => agent.app.bsky.actor.getProfile({actor}), + () => + appviewClient.call(app.bsky.actor.getProfile, { + actor: actor as DidString, + }), ) } 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 9d6c4f3ff7..98059c37de 100644 --- a/src/screens/ProfileList/components/Header.tsx +++ b/src/screens/ProfileList/components/Header.tsx @@ -1,6 +1,6 @@ import {useMemo} from 'react' import {View} from 'react-native' -import {AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api' +import {RichText as RichTextAPI} from '@bsky/sdk/richtext' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -23,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' @@ -32,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() 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 77af470cd5..c898663c8f 100644 --- a/src/screens/ProfileList/index.tsx +++ b/src/screens/ProfileList/index.tsx @@ -1,12 +1,8 @@ import {useCallback, useMemo, useRef, useState} from 'react' import {View} from 'react-native' import {useAnimatedRef} from 'react-native-reanimated' -import { - AppBskyGraphDefs, - AtUri, - moderateUserList, - type ModerationOpts, -} from '@atproto/api' +import {AtUri} from '@atproto/syntax' +import {moderateUserList, type ModerationOpts} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -42,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' @@ -146,7 +143,7 @@ function ProfileListScreenLoaded({ preferences, }: Props & { uri: string - list: AppBskyGraphDefs.ListView + list: app.bsky.graph.defs.ListView moderationOpts: ModerationOpts preferences: UsePreferencesQueryResponse }) { @@ -158,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() @@ -38,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 c32d4eefd3..545cc0d231 100644 --- a/src/screens/Search/modules/ExploreSuggestedAccounts.tsx +++ b/src/screens/Search/modules/ExploreSuggestedAccounts.tsx @@ -1,6 +1,6 @@ import {memo, useEffect} from 'react' import {View} from 'react-native' -import {type AppBskyActorSearchActors, type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {type InfiniteData} from '@tanstack/react-query' @@ -13,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({ @@ -24,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 38c8cac0a8..6bbb6e1e54 100644 --- a/src/screens/Search/modules/ExploreTrendingTopics.tsx +++ b/src/screens/Search/modules/ExploreTrendingTopics.tsx @@ -1,11 +1,8 @@ import {useMemo} from 'react' import {Pressable, View} from 'react-native' import {Image} from 'expo-image' -import { - type AppBskyUnspeccedDefs, - moderateProfile, - RichText as RichTextApi, -} from '@atproto/api' +import {moderateProfile} from '@bsky/sdk/moderation' +import {RichText as RichTextApi} from '@bsky/sdk/richtext' import {Plural, Trans, useLingui} from '@lingui/react/macro' import {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -30,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 @@ -120,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 @@ -231,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() @@ -299,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/AppPasswords.tsx b/src/screens/Settings/AppPasswords.tsx index 65ebdf3d4f..49173b9629 100644 --- a/src/screens/Settings/AppPasswords.tsx +++ b/src/screens/Settings/AppPasswords.tsx @@ -6,7 +6,6 @@ import Animated, { LayoutAnimationConfig, LinearTransition, } from 'react-native-reanimated' -import {type ComAtprotoServerListAppPasswords} from '@atproto/api' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -33,6 +32,7 @@ import {Loader} from '#/components/Loader' import * as Prompt from '#/components/Prompt' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {type com} from '#/lexicons' import {AddAppPasswordDialog} from './components/AddAppPasswordDialog' import * as SettingsList from './components/SettingsList' @@ -135,7 +135,7 @@ export function AppPasswordsScreen({}: Props) { function AppPasswordCard({ appPassword, }: { - appPassword: ComAtprotoServerListAppPasswords.AppPassword + appPassword: com.atproto.server.listAppPasswords.AppPassword }) { const t = useTheme() const {i18n, _} = useLingui() 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 57f1edde90..bc7ef48f8e 100644 --- a/src/screens/Settings/FindContactsSettings.tsx +++ b/src/screens/Settings/FindContactsSettings.tsx @@ -1,11 +1,8 @@ import {useCallback, useEffect, useState} from 'react' import {type ListRenderItemInfo, View} from 'react-native' import * as Contacts from 'expo-contacts' -import { - type AppBskyContactDefs, - type AppBskyContactGetSyncStatus, - type ModerationOpts, -} from '@atproto/api' +import {type DidString} from '@atproto/syntax' +import {type ModerationOpts} from '@bsky/sdk/moderation' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Plural, Trans} from '@lingui/react/macro' @@ -32,7 +29,7 @@ import { useContactsMatchesQuery, useContactsSyncStatusQuery, } from '#/state/queries/find-contacts' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' import {List} from '#/view/com/util/List' import {atoms as a, tokens, useGutters, useTheme} from '#/alf' @@ -52,6 +49,7 @@ import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' import {InviteFriendsDialog} from '#/features/inviteFriends' +import {app} from '#/lexicons' import type * as bsky from '#/types/bsky' import {bulkWriteFollows} from '../Onboarding/util' @@ -190,11 +188,11 @@ function SyncStatus({ info, refetchStatus, }: { - info: AppBskyContactDefs.SyncStatus + info: app.bsky.contact.defs.SyncStatus refetchStatus: () => Promise }) { const ax = useAnalytics() - const agent = useAgent() + const client = useAppviewClient() const queryClient = useQueryClient() const {_} = useLingui() const moderationOpts = useModerationOpts() @@ -219,7 +217,9 @@ function SyncStatus({ const {mutate: dismissMatch} = useMutation({ mutationFn: async (did: string) => { - await agent.app.bsky.contact.dismissMatch({subject: did}) + await client.call(app.bsky.contact.dismissMatch, { + subject: did as DidString, + }) }, onMutate: async (did: string) => { ax.metric('contacts:settings:dismiss', {}) @@ -370,7 +370,8 @@ function StatusHeader({ }) { const {_} = useLingui() const ax = useAnalytics() - const agent = useAgent() + const pdsClient = usePdsClient() + const client = useAppviewClient() const queryClient = useQueryClient() const {currentAccount} = useSession() @@ -384,12 +385,12 @@ function StatusHeader({ let cursor: string | undefined do { - const page = await agent.app.bsky.contact.getMatches({ + const page = await client.call(app.bsky.contact.getMatches, { limit: 100, cursor, }) - cursor = page.data.cursor - for (const profile of page.data.matches) { + cursor = page.cursor + for (const profile of page.matches) { if ( profile.did !== currentAccount?.did && !isBlockedOrBlocking(profile) && @@ -405,7 +406,10 @@ function StatusHeader({ followCount: didsToFollow.length, }) - const uris = await wait(500, bulkWriteFollows(agent, didsToFollow)) + const uris = await wait( + 500, + bulkWriteFollows(pdsClient, client, didsToFollow), + ) for (const did of didsToFollow) { const uri = uris.get(did) @@ -487,17 +491,17 @@ function StatusFooter({syncedAt}: {syncedAt: string}) { const {_, i18n} = useLingui() const t = useTheme() const ax = useAnalytics() - const agent = useAgent() + const client = useAppviewClient() const queryClient = useQueryClient() const {mutate: removeData, isPending} = useMutation({ mutationFn: async () => { - await agent.app.bsky.contact.removeData({}) + await client.call(app.bsky.contact.removeData, {}) }, onMutate: () => ax.metric('contacts:settings:removeData', {}), onSuccess: () => { Toast.show(_(msg`Contacts removed`)) - queryClient.setQueryData( + queryClient.setQueryData( findContactsStatusQueryKey, {syncStatus: undefined}, ) diff --git a/src/screens/Settings/InterestsSettings.tsx b/src/screens/Settings/InterestsSettings.tsx index ced7148669..84589d7481 100644 --- a/src/screens/Settings/InterestsSettings.tsx +++ b/src/screens/Settings/InterestsSettings.tsx @@ -1,5 +1,6 @@ import {useMemo, useState} from 'react' import {type TextStyle, View, type ViewStyle} from 'react-native' +import {setInterestsPref} from '@bsky/sdk' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -23,7 +24,7 @@ import {createGetSuggestedUsersForDiscoverQueryKey} from '#/state/queries/trendi import {createGetSuggestedUsersForExploreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery' import {createGetSuggestedUsersForSeeMoreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery' import {createSuggestedStarterPacksQueryKey} from '#/state/queries/useSuggestedStarterPacksQuery' -import {useAgent} from '#/state/session' +import {usePdsClient} from '#/state/session' import {atoms as a, useGutters, useTheme} from '#/alf' import {Admonition} from '#/components/Admonition' import {Divider} from '#/components/Divider' @@ -88,7 +89,7 @@ function Inner({ setIsSaving: (isSaving: boolean) => void }) { const {_} = useLingui() - const agent = useAgent() + const pdsClient = usePdsClient() const qc = useQueryClient() const interestsDisplayNames = useInterestsDisplayNames() const preselectedInterests = useMemo( @@ -110,7 +111,7 @@ function Inner({ setIsSaving(true) try { - await agent.setInterestsPref({tags: interests}) + await pdsClient.call(setInterestsPref, {tags: interests}) qc.setQueriesData( {queryKey: preferencesQueryKey}, (old?: UsePreferencesQueryResponse) => { @@ -157,7 +158,7 @@ function Inner({ setIsSaving(false) } }, 1500) - }, [_, agent, setIsSaving, qc, preselectedInterests]) + }, [_, pdsClient, setIsSaving, qc, preselectedInterests]) const onChangeInterests = async (interests: string[]) => { setInterests(interests) diff --git a/src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx b/src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx index 3aaf5e10e5..c91bcf4acc 100644 --- a/src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx +++ b/src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx @@ -1,6 +1,6 @@ import {useCallback, useMemo} from 'react' import {type ListRenderItemInfo, Text as RNText, View} from 'react-native' -import {type ModerationOpts} from '@atproto/api' +import {type ModerationOpts} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' diff --git a/src/screens/Settings/PrivacyAndSecuritySettings.tsx b/src/screens/Settings/PrivacyAndSecuritySettings.tsx index bee4294012..b0aab2661f 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' @@ -19,6 +18,7 @@ import {ShieldCheck_Stroke2_Corner0_Rounded as ShieldIcon} from '#/components/ic import * as Layout from '#/components/Layout' import {InlineLinkText} from '#/components/Link' import {useAnalytics} from '#/analytics' +import {type app} from '#/lexicons' import {AlgoVisibilityOptOut} from './components/AlgoVisibilityOptOut' import {Email2FAToggle} from './components/Email2FAToggle' import {PwiOptOut} from './components/PwiOptOut' @@ -155,7 +155,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 37d0e9b5fa..61af150a99 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -1,7 +1,8 @@ import {useState} from 'react' import {Alert, LayoutAnimation, Linking, Pressable, View} from 'react-native' import {useReducedMotion} from 'react-native-reanimated' -import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' +import {removeNuxs} from '@bsky/sdk' +import {moderateProfile} from '@bsky/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' import {type NativeStackScreenProps} from '@react-navigation/native-stack' @@ -21,7 +22,7 @@ import {clearStorage} from '#/state/persisted' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration' import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile' -import {useAgent} from '#/state/session' +import {usePdsClient} from '#/state/session' import {type SessionAccount, useSession, useSessionApi} from '#/state/session' import {useOnboardingDispatch} from '#/state/shell' import {useLoggedOutViewControls} from '#/state/shell/logged-out' @@ -67,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' @@ -319,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() @@ -385,7 +387,7 @@ function ProfilePreview({ function DevOptions() { const {t: l} = useLingui() - const agent = useAgent() + const pdsClient = usePdsClient() const [override, setOverride] = useStorage(device, [ 'policyUpdateDebugOverride', ]) @@ -560,7 +562,7 @@ function DevOptions() {