about summary refs log tree commit diff
diff options
context:
space:
mode:
authorHailey <me@haileyok.com>2024-06-26 17:24:33 -0700
committerGitHub <noreply@github.com>2024-06-26 17:24:33 -0700
commit878b0476dd94e187504f503438ca8914a48ac630 (patch)
treea51386f8b6f3e1e11b5b5d668f7a4f3f77b91e69
parentda4dfeb9cf6506ade2a9619921de128458c4d0d2 (diff)
downloadvoidsky-878b0476dd94e187504f503438ca8914a48ac630.tar.zst
Better starterpack embed (#4659)
-rw-r--r--src/lib/link-meta/link-meta.ts8
-rw-r--r--src/lib/link-meta/resolve-short-link.ts23
-rw-r--r--src/lib/strings/starter-pack.ts4
-rw-r--r--src/lib/strings/url-helpers.ts11
-rw-r--r--src/view/com/composer/useExternalLinkFetch.ts13
-rw-r--r--src/view/com/util/post-embeds/ExternalLinkEmbed.tsx76
6 files changed, 103 insertions, 32 deletions
diff --git a/src/lib/link-meta/link-meta.ts b/src/lib/link-meta/link-meta.ts
index fa951432e..6416df2b7 100644
--- a/src/lib/link-meta/link-meta.ts
+++ b/src/lib/link-meta/link-meta.ts
@@ -1,8 +1,10 @@
 import {BskyAgent} from '@atproto/api'
-import {isBskyAppUrl} from '../strings/url-helpers'
-import {extractBskyMeta} from './bsky'
+
 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,
@@ -28,7 +30,7 @@ export async function getLinkMeta(
   url: string,
   timeout = 15e3,
 ): Promise<LinkMeta> {
-  if (isBskyAppUrl(url)) {
+  if (isBskyAppUrl(url) && !parseStarterPackUri(url)) {
     return extractBskyMeta(agent, url)
   }
 
diff --git a/src/lib/link-meta/resolve-short-link.ts b/src/lib/link-meta/resolve-short-link.ts
new file mode 100644
index 000000000..3a3e2ab46
--- /dev/null
+++ b/src/lib/link-meta/resolve-short-link.ts
@@ -0,0 +1,23 @@
+import {logger} from '#/logger'
+import {startUriToStarterPackUri} from 'lib/strings/starter-pack'
+
+export async function resolveShortLink(shortLink: string) {
+  const controller = new AbortController()
+  const to = setTimeout(() => controller.abort(), 2e3)
+
+  try {
+    const res = await fetch(shortLink, {
+      method: 'GET',
+      signal: controller.signal,
+    })
+    if (res.status !== 200) {
+      return shortLink
+    }
+    return startUriToStarterPackUri(res.url)
+  } catch (e: unknown) {
+    logger.error('Failed to resolve short link', {safeMessage: e})
+    return null
+  } finally {
+    clearTimeout(to)
+  }
+}
diff --git a/src/lib/strings/starter-pack.ts b/src/lib/strings/starter-pack.ts
index 489d0b923..01b5a6587 100644
--- a/src/lib/strings/starter-pack.ts
+++ b/src/lib/strings/starter-pack.ts
@@ -99,3 +99,7 @@ export function createStarterPackUri({
 }): string | null {
   return new AtUri(`at://${did}/app.bsky.graph.starterpack/${rkey}`).toString()
 }
+
+export function startUriToStarterPackUri(uri: string) {
+  return uri.replace('/start/', '/starter-pack/')
+}
diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts
index 4c75f47ad..b88b77f73 100644
--- a/src/lib/strings/url-helpers.ts
+++ b/src/lib/strings/url-helpers.ts
@@ -2,6 +2,7 @@ import {AtUri} from '@atproto/api'
 import psl from 'psl'
 import TLDs from 'tlds'
 
+import {logger} from '#/logger'
 import {BSKY_SERVICE} from 'lib/constants'
 import {isInvalidHandle} from 'lib/strings/handles'
 
@@ -285,3 +286,13 @@ export function createBskyAppAbsoluteUrl(path: string): string {
   const sanitizedPath = path.replace(BSKY_APP_HOST, '').replace(/^\/+/, '')
   return `${BSKY_APP_HOST.replace(/\/$/, '')}/${sanitizedPath}`
 }
+
+export function isShortLink(url: string): boolean {
+  try {
+    const urlp = new URL(url)
+    return urlp.host === 'go.bsky.app'
+  } catch (e) {
+    logger.error('Failed to parse possible short link', {safeMessage: e})
+    return false
+  }
+}
diff --git a/src/view/com/composer/useExternalLinkFetch.ts b/src/view/com/composer/useExternalLinkFetch.ts
index 2e0297a47..743535a5e 100644
--- a/src/view/com/composer/useExternalLinkFetch.ts
+++ b/src/view/com/composer/useExternalLinkFetch.ts
@@ -12,11 +12,13 @@ import {
   getPostAsQuote,
 } 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,
+  isShortLink,
 } from 'lib/strings/url-helpers'
 import {ImageModel} from 'state/models/media/image'
 import {ComposerOpts} from 'state/shell/composer'
@@ -94,6 +96,17 @@ export function useExternalLinkFetch({
             setExtLink(undefined)
           },
         )
+      } else if (isShortLink(extLink.uri)) {
+        if (isShortLink(extLink.uri)) {
+          resolveShortLink(extLink.uri).then(res => {
+            if (res && res !== extLink.uri) {
+              setExtLink({
+                uri: res,
+                isLoading: true,
+              })
+            }
+          })
+        }
       } else {
         getLinkMeta(agent, extLink.uri).then(meta => {
           if (aborted) {
diff --git a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
index 3b2a12c24..f5f220c62 100644
--- a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
+++ b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
@@ -2,11 +2,17 @@ import React, {useCallback} from 'react'
 import {StyleProp, View, ViewStyle} from 'react-native'
 import {Image} from 'expo-image'
 import {AppBskyEmbedExternal} from '@atproto/api'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
 
 import {usePalette} from 'lib/hooks/usePalette'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {shareUrl} from 'lib/sharing'
 import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player'
+import {
+  getStarterPackOgCard,
+  parseStarterPackUri,
+} from 'lib/strings/starter-pack'
 import {toNiceDomain} from 'lib/strings/url-helpers'
 import {isNative} from 'platform/detection'
 import {useExternalEmbedsPrefs} from 'state/preferences'
@@ -28,10 +34,16 @@ export const ExternalLinkEmbed = ({
   style?: StyleProp<ViewStyle>
   hideAlt?: boolean
 }) => {
+  const {_} = useLingui()
   const pal = usePalette('default')
   const {isMobile} = useWebMediaQueries()
   const externalEmbedPrefs = useExternalEmbedsPrefs()
 
+  const starterPackParsed = parseStarterPackUri(link.uri)
+  const imageUri = starterPackParsed
+    ? getStarterPackOgCard(starterPackParsed.name, starterPackParsed.rkey)
+    : link.thumb
+
   const embedPlayerParams = React.useMemo(() => {
     const params = parseEmbedPlayerFromUrl(link.uri)
 
@@ -47,15 +59,19 @@ export const ExternalLinkEmbed = ({
   return (
     <View style={[a.flex_col, a.rounded_sm, a.overflow_hidden, a.mt_sm]}>
       <LinkWrapper link={link} onOpen={onOpen} style={style}>
-        {link.thumb && !embedPlayerParams ? (
+        {imageUri && !embedPlayerParams ? (
           <Image
             style={{
               aspectRatio: 1.91,
               borderTopRightRadius: 6,
               borderTopLeftRadius: 6,
             }}
-            source={{uri: link.thumb}}
+            source={{uri: imageUri}}
             accessibilityIgnoresInvertColors
+            accessibilityLabel={starterPackParsed ? link.title : undefined}
+            accessibilityHint={
+              starterPackParsed ? _(msg`Navigate to starter pack`) : undefined
+            }
           />
         ) : undefined}
         {embedPlayerParams?.isGif ? (
@@ -63,35 +79,37 @@ export const ExternalLinkEmbed = ({
         ) : embedPlayerParams ? (
           <ExternalPlayer link={link} params={embedPlayerParams} />
         ) : undefined}
-        <View
-          style={[
-            a.flex_1,
-            a.py_sm,
-            {
-              paddingHorizontal: isMobile ? 10 : 14,
-            },
-          ]}>
-          <Text
-            type="sm"
-            numberOfLines={1}
-            style={[pal.textLight, {marginVertical: 2}]}>
-            {toNiceDomain(link.uri)}
-          </Text>
-
-          {!embedPlayerParams?.isGif && !embedPlayerParams?.dimensions && (
-            <Text type="lg-bold" numberOfLines={3} style={[pal.text]}>
-              {link.title || link.uri}
-            </Text>
-          )}
-          {link.description ? (
+        {!starterPackParsed ? (
+          <View
+            style={[
+              a.flex_1,
+              a.py_sm,
+              {
+                paddingHorizontal: isMobile ? 10 : 14,
+              },
+            ]}>
             <Text
-              type="md"
-              numberOfLines={link.thumb ? 2 : 4}
-              style={[pal.text, a.mt_xs]}>
-              {link.description}
+              type="sm"
+              numberOfLines={1}
+              style={[pal.textLight, {marginVertical: 2}]}>
+              {toNiceDomain(link.uri)}
             </Text>
-          ) : undefined}
-        </View>
+
+            {!embedPlayerParams?.isGif && !embedPlayerParams?.dimensions && (
+              <Text type="lg-bold" numberOfLines={3} style={[pal.text]}>
+                {link.title || link.uri}
+              </Text>
+            )}
+            {link.description ? (
+              <Text
+                type="md"
+                numberOfLines={link.thumb ? 2 : 4}
+                style={[pal.text, a.mt_xs]}>
+                {link.description}
+              </Text>
+            ) : undefined}
+          </View>
+        ) : null}
       </LinkWrapper>
     </View>
   )