about summary refs log tree commit diff
path: root/src/lib/api
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/api')
-rw-r--r--src/lib/api/index.ts45
-rw-r--r--src/lib/api/resolve.ts93
2 files changed, 85 insertions, 53 deletions
diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts
index 6edb111e6..4b203d28b 100644
--- a/src/lib/api/index.ts
+++ b/src/lib/api/index.ts
@@ -4,7 +4,6 @@ import {
   AppBskyEmbedRecord,
   AppBskyEmbedRecordWithMedia,
   AppBskyEmbedVideo,
-  AppBskyFeedPostgate,
   AtUri,
   BlobRef,
   BskyAgent,
@@ -17,7 +16,7 @@ import {QueryClient} from '@tanstack/react-query'
 import {isNetworkError} from '#/lib/strings/errors'
 import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
 import {logger} from '#/logger'
-import {ComposerImage, compressImage} from '#/state/gallery'
+import {compressImage} from '#/state/gallery'
 import {writePostgateRecord} from '#/state/queries/postgate'
 import {
   fetchResolveGifQuery,
@@ -25,32 +24,18 @@ import {
 } from '#/state/queries/resolve-link'
 import {
   createThreadgateRecord,
-  ThreadgateAllowUISetting,
   threadgateAllowUISettingToAllowRecordValue,
   writeThreadgateRecord,
 } from '#/state/queries/threadgate'
-import {ComposerState, EmbedDraft} from '#/view/com/composer/state/composer'
+import {ComposerDraft, EmbedDraft} from '#/view/com/composer/state/composer'
 import {createGIFDescription} from '../gif-alt-text'
-import {LinkMeta} from '../link-meta/link-meta'
 import {uploadBlob} from './upload-blob'
 
 export {uploadBlob}
 
-export interface ExternalEmbedDraft {
-  uri: string
-  isLoading: boolean
-  meta?: LinkMeta
-  embed?: AppBskyEmbedRecord.Main
-  localThumb?: ComposerImage
-}
-
 interface PostOpts {
-  composerState: ComposerState // TODO: Not used yet.
-  rawText: string
+  draft: ComposerDraft
   replyTo?: string
-  labels?: string[]
-  threadgate: ThreadgateAllowUISetting[]
-  postgate: AppBskyFeedPostgate.Record
   onStateChange?: (state: string) => void
   langs?: string[]
 }
@@ -60,8 +45,12 @@ export async function post(
   queryClient: QueryClient,
   opts: PostOpts,
 ) {
+  const draft = opts.draft
   let reply
-  let rt = new RichText({text: opts.rawText.trimEnd()}, {cleanNewlines: true})
+  let rt = new RichText(
+    {text: draft.richtext.text.trimEnd()},
+    {cleanNewlines: true},
+  )
 
   opts.onStateChange?.('Processing...')
 
@@ -73,7 +62,7 @@ export async function post(
   const embed = await resolveEmbed(
     agent,
     queryClient,
-    opts.composerState,
+    draft,
     opts.onStateChange,
   )
 
@@ -98,10 +87,10 @@ export async function post(
 
   // set labels
   let labels: ComAtprotoLabelDefs.SelfLabels | undefined
-  if (opts.labels?.length) {
+  if (draft.labels.length) {
     labels = {
       $type: 'com.atproto.label.defs#selfLabels',
-      values: opts.labels.map(val => ({val})),
+      values: draft.labels.map(val => ({val})),
     }
   }
 
@@ -135,7 +124,7 @@ export async function post(
     }
   }
 
-  if (opts.threadgate.some(tg => tg.type !== 'everybody')) {
+  if (draft.threadgate.some(tg => tg.type !== 'everybody')) {
     try {
       // TODO: this needs to be batch-created with the post!
       await writeThreadgateRecord({
@@ -143,7 +132,7 @@ export async function post(
         postUri: res.uri,
         threadgate: createThreadgateRecord({
           post: res.uri,
-          allow: threadgateAllowUISettingToAllowRecordValue(opts.threadgate),
+          allow: threadgateAllowUISettingToAllowRecordValue(draft.threadgate),
         }),
       })
     } catch (e: any) {
@@ -158,8 +147,8 @@ export async function post(
   }
 
   if (
-    opts.postgate.embeddingRules?.length ||
-    opts.postgate.detachedEmbeddingUris?.length
+    draft.postgate.embeddingRules?.length ||
+    draft.postgate.detachedEmbeddingUris?.length
   ) {
     try {
       // TODO: this needs to be batch-created with the post!
@@ -167,7 +156,7 @@ export async function post(
         agent,
         postUri: res.uri,
         postgate: {
-          ...opts.postgate,
+          ...draft.postgate,
           post: res.uri,
         },
       })
@@ -188,7 +177,7 @@ export async function post(
 async function resolveEmbed(
   agent: BskyAgent,
   queryClient: QueryClient,
-  draft: ComposerState,
+  draft: ComposerDraft,
   onStateChange: ((state: string) => void) | undefined,
 ): Promise<
   | AppBskyEmbedImages.Main
diff --git a/src/lib/api/resolve.ts b/src/lib/api/resolve.ts
index 4f409e100..1115a4fb0 100644
--- a/src/lib/api/resolve.ts
+++ b/src/lib/api/resolve.ts
@@ -1,18 +1,21 @@
-import {AppBskyActorDefs, ComAtprotoRepoStrongRef} from '@atproto/api'
+import {
+  AppBskyActorDefs,
+  AppBskyFeedPost,
+  AppBskyGraphStarterpack,
+  ComAtprotoRepoStrongRef,
+} from '@atproto/api'
 import {AtUri} from '@atproto/api'
 import {BskyAgent} from '@atproto/api'
 
 import {POST_IMG_MAX} from '#/lib/constants'
-import {
-  getFeedAsEmbed,
-  getListAsEmbed,
-  getPostAsQuote,
-  getStarterPackAsEmbed,
-} from '#/lib/link-meta/bsky'
 import {getLinkMeta} from '#/lib/link-meta/link-meta'
 import {resolveShortLink} from '#/lib/link-meta/resolve-short-link'
 import {downloadAndResize} from '#/lib/media/manip'
 import {
+  createStarterPackUri,
+  parseStarterPackUri,
+} from '#/lib/strings/starter-pack'
+import {
   isBskyCustomFeedUrl,
   isBskyListUrl,
   isBskyPostUrl,
@@ -24,6 +27,7 @@ import {ComposerImage} from '#/state/gallery'
 import {createComposerImage} from '#/state/gallery'
 import {Gif} from '#/state/queries/tenor'
 import {createGIFDescription} from '../gif-alt-text'
+import {convertBskyAppUrlIfNeeded, makeRecordUri} from '../strings/url-helpers'
 
 type ResolvedExternalLink = {
   type: 'external'
@@ -60,6 +64,12 @@ export type ResolvedLink =
   | ResolvedPostRecord
   | ResolvedOtherRecord
 
+class EmbeddingDisabledError extends Error {
+  constructor() {
+    super('Embedding is disabled for this record')
+  }
+}
+
 export async function resolveLink(
   agent: BskyAgent,
   uri: string,
@@ -68,55 +78,88 @@ export async function resolveLink(
     uri = await resolveShortLink(uri)
   }
   if (isBskyPostUrl(uri)) {
-    // TODO: Remove this abstraction.
-    // TODO: Nice error messages (e.g. EmbeddingDisabledError).
-    const result = await getPostAsQuote(getPost, uri)
+    uri = convertBskyAppUrlIfNeeded(uri)
+    const [_0, user, _1, rkey] = uri.split('/').filter(Boolean)
+    const recordUri = makeRecordUri(user, 'app.bsky.feed.post', rkey)
+    const post = await getPost({uri: recordUri})
+    if (post.viewer?.embeddingDisabled) {
+      throw new EmbeddingDisabledError()
+    }
     return {
       type: 'record',
       record: {
-        cid: result.cid,
-        uri: result.uri,
+        cid: post.cid,
+        uri: post.uri,
       },
       kind: 'post',
-      meta: result,
+      meta: {
+        text: AppBskyFeedPost.isRecord(post.record) ? post.record.text : '',
+        indexedAt: post.indexedAt,
+        author: post.author,
+      },
     }
   }
   if (isBskyCustomFeedUrl(uri)) {
-    // TODO: Remove this abstraction.
-    const result = await getFeedAsEmbed(agent, fetchDid, uri)
+    uri = convertBskyAppUrlIfNeeded(uri)
+    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})
     return {
       type: 'record',
-      record: result.embed!.record,
+      record: {
+        uri: res.data.view.uri,
+        cid: res.data.view.cid,
+      },
       kind: 'other',
       meta: {
         // TODO: Include hydrated content instead.
-        title: result.meta!.title!,
+        title: res.data.view.displayName,
       },
     }
   }
   if (isBskyListUrl(uri)) {
-    // TODO: Remove this abstraction.
-    const result = await getListAsEmbed(agent, fetchDid, uri)
+    uri = convertBskyAppUrlIfNeeded(uri)
+    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})
     return {
       type: 'record',
-      record: result.embed!.record,
+      record: {
+        uri: res.data.list.uri,
+        cid: res.data.list.cid,
+      },
       kind: 'other',
       meta: {
         // TODO: Include hydrated content instead.
-        title: result.meta!.title!,
+        title: res.data.list.name,
       },
     }
   }
   if (isBskyStartUrl(uri) || isBskyStarterPackUrl(uri)) {
-    // TODO: Remove this abstraction.
-    const result = await getStarterPackAsEmbed(agent, fetchDid, uri)
+    const parsed = parseStarterPackUri(uri)
+    if (!parsed) {
+      throw new Error(
+        'Unexpectedly called getStarterPackAsEmbed with a non-starterpack url',
+      )
+    }
+    const did = await fetchDid(parsed.name)
+    const starterPack = createStarterPackUri({did, rkey: parsed.rkey})
+    const res = await agent.app.bsky.graph.getStarterPack({starterPack})
+    const record = res.data.starterPack.record
     return {
       type: 'record',
-      record: result.embed!.record,
+      record: {
+        uri: res.data.starterPack.uri,
+        cid: res.data.starterPack.cid,
+      },
       kind: 'other',
       meta: {
         // TODO: Include hydrated content instead.
-        title: result.meta!.title!,
+        title: AppBskyGraphStarterpack.isRecord(record)
+          ? record.name
+          : 'Starter Pack',
       },
     }
   }