about summary refs log tree commit diff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/api/index.ts45
-rw-r--r--src/lib/api/resolve.ts93
-rw-r--r--src/lib/link-meta/bsky.ts225
-rw-r--r--src/lib/link-meta/link-meta.ts12
4 files changed, 92 insertions, 283 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',
       },
     }
   }
diff --git a/src/lib/link-meta/bsky.ts b/src/lib/link-meta/bsky.ts
deleted file mode 100644
index 3d49f5237..000000000
--- a/src/lib/link-meta/bsky.ts
+++ /dev/null
@@ -1,225 +0,0 @@
-import {AppBskyFeedPost, AppBskyGraphStarterpack, BskyAgent} from '@atproto/api'
-
-import {useFetchDid} from '#/state/queries/handle'
-import {useGetPost} from '#/state/queries/post'
-import * as apilib from 'lib/api/index'
-import {
-  createStarterPackUri,
-  parseStarterPackUri,
-} from 'lib/strings/starter-pack'
-import {ComposerOptsQuote} from 'state/shell/composer'
-// import {match as matchRoute} from 'view/routes'
-import {convertBskyAppUrlIfNeeded, makeRecordUri} from '../strings/url-helpers'
-import {LikelyType, LinkMeta} from './link-meta'
-
-// TODO
-// import {Home} from 'view/screens/Home'
-// import {Search} from 'view/screens/Search'
-// import {Notifications} from 'view/screens/Notifications'
-// import {PostThread} from 'view/screens/PostThread'
-// import {PostUpvotedBy} from 'view/screens/PostUpvotedBy'
-// import {PostRepostedBy} from 'view/screens/PostRepostedBy'
-// import {Profile} from 'view/screens/Profile'
-// import {ProfileFollowers} from 'view/screens/ProfileFollowers'
-// import {ProfileFollows} from 'view/screens/ProfileFollows'
-
-// NOTE
-// this is a hack around the lack of hosted social metadata
-// remove once that's implemented
-// -prf
-export async function extractBskyMeta(
-  agent: BskyAgent,
-  url: string,
-): Promise<LinkMeta> {
-  url = convertBskyAppUrlIfNeeded(url)
-  // const route = matchRoute(url)
-  let meta: LinkMeta = {
-    likelyType: LikelyType.AtpData,
-    url,
-    // title: route.defaultTitle,
-  }
-
-  // if (route.Com === Home) {
-  //   meta = {
-  //     ...meta,
-  //     title: 'Bluesky',
-  //     description: 'A new kind of social network',
-  //   }
-  // } else if (route.Com === Search) {
-  //   meta = {
-  //     ...meta,
-  //     title: 'Search - Bluesky',
-  //     description: 'A new kind of social network',
-  //   }
-  // } else if (route.Com === Notifications) {
-  //   meta = {
-  //     ...meta,
-  //     title: 'Notifications - Bluesky',
-  //     description: 'A new kind of social network',
-  //   }
-  // } else if (
-  //   route.Com === PostThread ||
-  //   route.Com === PostUpvotedBy ||
-  //   route.Com === PostRepostedBy
-  // ) {
-  //   // post and post-related screens
-  //   const threadUri = makeRecordUri(
-  //     route.params.name,
-  //     'app.bsky.feed.post',
-  //     route.params.rkey,
-  //   )
-  //   const threadView = new PostThreadViewModel(store, {
-  //     uri: threadUri,
-  //     depth: 0,
-  //   })
-  //   await threadView.setup().catch(_err => undefined)
-  //   const title = [
-  //     route.Com === PostUpvotedBy
-  //       ? 'Likes on a post by'
-  //       : route.Com === PostRepostedBy
-  //       ? 'Reposts of a post by'
-  //       : 'Post by',
-  //     threadView.thread?.post.author.displayName ||
-  //       threadView.thread?.post.author.handle ||
-  //       'a bluesky user',
-  //   ].join(' ')
-  //   meta = {
-  //     ...meta,
-  //     title,
-  //     description: threadView.thread?.postRecord?.text,
-  //   }
-  // } else if (
-  //   route.Com === Profile ||
-  //   route.Com === ProfileFollowers ||
-  //   route.Com === ProfileFollows
-  // ) {
-  //   // profile and profile-related screens
-  //   const profile = await store.profiles.getProfile(route.params.name)
-  //   if (profile?.data) {
-  //     meta = {
-  //       ...meta,
-  //       title: profile.data.displayName || profile.data.handle,
-  //       description: profile.data.description,
-  //     }
-  //   }
-  // }
-
-  return meta
-}
-
-export class EmbeddingDisabledError extends Error {
-  constructor() {
-    super('Embedding is disabled for this record')
-  }
-}
-export async function getPostAsQuote(
-  getPost: ReturnType<typeof useGetPost>,
-  url: string,
-): Promise<ComposerOptsQuote> {
-  url = convertBskyAppUrlIfNeeded(url)
-  const [_0, user, _1, rkey] = url.split('/').filter(Boolean)
-  const uri = makeRecordUri(user, 'app.bsky.feed.post', rkey)
-  const post = await getPost({uri: uri})
-  if (post.viewer?.embeddingDisabled) {
-    throw new EmbeddingDisabledError()
-  }
-  return {
-    uri: post.uri,
-    cid: post.cid,
-    text: AppBskyFeedPost.isRecord(post.record) ? post.record.text : '',
-    indexedAt: post.indexedAt,
-    author: post.author,
-  }
-}
-
-export async function getFeedAsEmbed(
-  agent: BskyAgent,
-  fetchDid: ReturnType<typeof useFetchDid>,
-  url: string,
-): Promise<apilib.ExternalEmbedDraft> {
-  url = convertBskyAppUrlIfNeeded(url)
-  const [_0, handleOrDid, _1, rkey] = url.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 {
-    isLoading: false,
-    uri: feed,
-    meta: {
-      url: feed,
-      likelyType: LikelyType.AtpData,
-      title: res.data.view.displayName,
-    },
-    embed: {
-      $type: 'app.bsky.embed.record',
-      record: {
-        uri: res.data.view.uri,
-        cid: res.data.view.cid,
-      },
-    },
-  }
-}
-
-export async function getListAsEmbed(
-  agent: BskyAgent,
-  fetchDid: ReturnType<typeof useFetchDid>,
-  url: string,
-): Promise<apilib.ExternalEmbedDraft> {
-  url = convertBskyAppUrlIfNeeded(url)
-  const [_0, handleOrDid, _1, rkey] = url.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 {
-    isLoading: false,
-    uri: list,
-    meta: {
-      url: list,
-      likelyType: LikelyType.AtpData,
-      title: res.data.list.name,
-    },
-    embed: {
-      $type: 'app.bsky.embed.record',
-      record: {
-        uri: res.data.list.uri,
-        cid: res.data.list.cid,
-      },
-    },
-  }
-}
-
-export async function getStarterPackAsEmbed(
-  agent: BskyAgent,
-  fetchDid: ReturnType<typeof useFetchDid>,
-  url: string,
-): Promise<apilib.ExternalEmbedDraft> {
-  const parsed = parseStarterPackUri(url)
-  if (!parsed) {
-    throw new Error(
-      'Unexepectedly 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 {
-    isLoading: false,
-    uri: starterPack,
-    meta: {
-      url: starterPack,
-      likelyType: LikelyType.AtpData,
-      // Validation here should never fail
-      title: AppBskyGraphStarterpack.isRecord(record)
-        ? record.name
-        : 'Starter Pack',
-    },
-    embed: {
-      $type: 'app.bsky.embed.record',
-      record: {
-        uri: res.data.starterPack.uri,
-        cid: res.data.starterPack.cid,
-      },
-    },
-  }
-}
diff --git a/src/lib/link-meta/link-meta.ts b/src/lib/link-meta/link-meta.ts
index 6416df2b7..ea9190b6e 100644
--- a/src/lib/link-meta/link-meta.ts
+++ b/src/lib/link-meta/link-meta.ts
@@ -1,10 +1,9 @@
 import {BskyAgent} 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 {LINK_META_PROXY} from '#/lib/constants'
+import {getGiphyMetaUri} from '#/lib/strings/embed-player'
+import {parseStarterPackUri} from '#/lib/strings/starter-pack'
 import {isBskyAppUrl} from '../strings/url-helpers'
-import {extractBskyMeta} from './bsky'
 
 export enum LikelyType {
   HTML,
@@ -31,7 +30,10 @@ export async function getLinkMeta(
   timeout = 15e3,
 ): Promise<LinkMeta> {
   if (isBskyAppUrl(url) && !parseStarterPackUri(url)) {
-    return extractBskyMeta(agent, url)
+    return {
+      likelyType: LikelyType.AtpData,
+      url,
+    }
   }
 
   let urlp