about summary refs log tree commit diff
path: root/src/lib/api/resolve.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/api/resolve.ts')
-rw-r--r--src/lib/api/resolve.ts161
1 files changed, 161 insertions, 0 deletions
diff --git a/src/lib/api/resolve.ts b/src/lib/api/resolve.ts
new file mode 100644
index 000000000..a97a3f31c
--- /dev/null
+++ b/src/lib/api/resolve.ts
@@ -0,0 +1,161 @@
+import {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 {
+  isBskyCustomFeedUrl,
+  isBskyListUrl,
+  isBskyPostUrl,
+  isBskyStarterPackUrl,
+  isBskyStartUrl,
+  isShortLink,
+} from '#/lib/strings/url-helpers'
+import {ComposerImage} from '#/state/gallery'
+import {createComposerImage} from '#/state/gallery'
+import {Gif} from '#/state/queries/tenor'
+import {createGIFDescription} from '../gif-alt-text'
+
+type ResolvedExternalLink = {
+  type: 'external'
+  uri: string
+  title: string
+  description: string
+  thumb: ComposerImage | undefined
+}
+
+type ResolvedRecord = {
+  type: 'record'
+  record: ComAtprotoRepoStrongRef.Main
+}
+
+type ResolvedLink = ResolvedExternalLink | ResolvedRecord
+
+export async function resolveLink(
+  agent: BskyAgent,
+  uri: string,
+): Promise<ResolvedLink> {
+  if (isShortLink(uri)) {
+    uri = await resolveShortLink(uri)
+  }
+  if (isBskyPostUrl(uri)) {
+    // TODO: Remove this abstraction.
+    // TODO: Nice error messages (e.g. EmbeddingDisabledError).
+    const result = await getPostAsQuote(getPost, uri)
+    return {
+      type: 'record',
+      record: {
+        cid: result.cid,
+        uri: result.uri,
+      },
+    }
+  }
+  if (isBskyCustomFeedUrl(uri)) {
+    // TODO: Remove this abstraction.
+    const result = await getFeedAsEmbed(agent, fetchDid, uri)
+    return {
+      type: 'record',
+      record: result.embed!.record, // TODO: Fix types.
+    }
+  }
+  if (isBskyListUrl(uri)) {
+    // TODO: Remove this abstraction.
+    const result = await getListAsEmbed(agent, fetchDid, uri)
+    return {
+      type: 'record',
+      record: result.embed!.record, // TODO: Fix types.
+    }
+  }
+  if (isBskyStartUrl(uri) || isBskyStarterPackUrl(uri)) {
+    // TODO: Remove this abstraction.
+    const result = await getStarterPackAsEmbed(agent, fetchDid, uri)
+    return {
+      type: 'record',
+      record: result.embed!.record, // TODO: Fix types.
+    }
+  }
+  return resolveExternal(agent, 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,
+      })
+      urip.host = res.data.did
+    }
+    const res = await agent.getPosts({
+      uris: [urip.toString()],
+    })
+    if (res.success && res.data.posts[0]) {
+      return res.data.posts[0]
+    }
+    throw new Error('getPost: post not found')
+  }
+
+  // Forked from useFetchDid. TODO: move into RQ.
+  async function fetchDid(handleOrDid: string) {
+    let identifier = handleOrDid
+    if (!identifier.startsWith('did:')) {
+      const res = await agent.resolveHandle({handle: identifier})
+      identifier = res.data.did
+    }
+    return identifier
+  }
+}
+
+export async function resolveGif(
+  agent: BskyAgent,
+  gif: Gif,
+): Promise<ResolvedExternalLink> {
+  const uri = `${gif.media_formats.gif.url}?hh=${gif.media_formats.gif.dims[1]}&ww=${gif.media_formats.gif.dims[0]}`
+  return {
+    type: 'external',
+    uri,
+    title: gif.content_description,
+    description: createGIFDescription(gif.content_description),
+    thumb: await imageToThumb(gif.media_formats.preview.url),
+  }
+}
+
+async function resolveExternal(
+  agent: BskyAgent,
+  uri: string,
+): Promise<ResolvedExternalLink> {
+  const result = await getLinkMeta(agent, uri)
+  return {
+    type: 'external',
+    uri: result.url,
+    title: result.title ?? '',
+    description: result.description ?? '',
+    thumb: result.image ? await imageToThumb(result.image) : undefined,
+  }
+}
+
+async function imageToThumb(
+  imageUri: string,
+): Promise<ComposerImage | undefined> {
+  try {
+    const img = await downloadAndResize({
+      uri: imageUri,
+      width: POST_IMG_MAX.width,
+      height: POST_IMG_MAX.height,
+      mode: 'contain',
+      maxSize: POST_IMG_MAX.size,
+      timeout: 15e3,
+    })
+    if (img) {
+      return await createComposerImage(img)
+    }
+  } catch {}
+}