Better starterpack embed (#4659)

zio/stable
Hailey 2024-06-26 17:24:33 -07:00 committed by GitHub
parent da4dfeb9cf
commit 878b0476dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 103 additions and 32 deletions

View File

@ -1,8 +1,10 @@
import {BskyAgent} from '@atproto/api' import {BskyAgent} from '@atproto/api'
import {isBskyAppUrl} from '../strings/url-helpers'
import {extractBskyMeta} from './bsky'
import {LINK_META_PROXY} from 'lib/constants' import {LINK_META_PROXY} from 'lib/constants'
import {getGiphyMetaUri} from 'lib/strings/embed-player' 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 { export enum LikelyType {
HTML, HTML,
@ -28,7 +30,7 @@ export async function getLinkMeta(
url: string, url: string,
timeout = 15e3, timeout = 15e3,
): Promise<LinkMeta> { ): Promise<LinkMeta> {
if (isBskyAppUrl(url)) { if (isBskyAppUrl(url) && !parseStarterPackUri(url)) {
return extractBskyMeta(agent, url) return extractBskyMeta(agent, url)
} }

View File

@ -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)
}
}

View File

@ -99,3 +99,7 @@ export function createStarterPackUri({
}): string | null { }): string | null {
return new AtUri(`at://${did}/app.bsky.graph.starterpack/${rkey}`).toString() return new AtUri(`at://${did}/app.bsky.graph.starterpack/${rkey}`).toString()
} }
export function startUriToStarterPackUri(uri: string) {
return uri.replace('/start/', '/starter-pack/')
}

View File

@ -2,6 +2,7 @@ import {AtUri} from '@atproto/api'
import psl from 'psl' import psl from 'psl'
import TLDs from 'tlds' import TLDs from 'tlds'
import {logger} from '#/logger'
import {BSKY_SERVICE} from 'lib/constants' import {BSKY_SERVICE} from 'lib/constants'
import {isInvalidHandle} from 'lib/strings/handles' import {isInvalidHandle} from 'lib/strings/handles'
@ -285,3 +286,13 @@ export function createBskyAppAbsoluteUrl(path: string): string {
const sanitizedPath = path.replace(BSKY_APP_HOST, '').replace(/^\/+/, '') const sanitizedPath = path.replace(BSKY_APP_HOST, '').replace(/^\/+/, '')
return `${BSKY_APP_HOST.replace(/\/$/, '')}/${sanitizedPath}` 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
}
}

View File

@ -12,11 +12,13 @@ import {
getPostAsQuote, getPostAsQuote,
} from 'lib/link-meta/bsky' } from 'lib/link-meta/bsky'
import {getLinkMeta} from 'lib/link-meta/link-meta' import {getLinkMeta} from 'lib/link-meta/link-meta'
import {resolveShortLink} from 'lib/link-meta/resolve-short-link'
import {downloadAndResize} from 'lib/media/manip' import {downloadAndResize} from 'lib/media/manip'
import { import {
isBskyCustomFeedUrl, isBskyCustomFeedUrl,
isBskyListUrl, isBskyListUrl,
isBskyPostUrl, isBskyPostUrl,
isShortLink,
} from 'lib/strings/url-helpers' } from 'lib/strings/url-helpers'
import {ImageModel} from 'state/models/media/image' import {ImageModel} from 'state/models/media/image'
import {ComposerOpts} from 'state/shell/composer' import {ComposerOpts} from 'state/shell/composer'
@ -94,6 +96,17 @@ export function useExternalLinkFetch({
setExtLink(undefined) 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 { } else {
getLinkMeta(agent, extLink.uri).then(meta => { getLinkMeta(agent, extLink.uri).then(meta => {
if (aborted) { if (aborted) {

View File

@ -2,11 +2,17 @@ import React, {useCallback} from 'react'
import {StyleProp, View, ViewStyle} from 'react-native' import {StyleProp, View, ViewStyle} from 'react-native'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {AppBskyEmbedExternal} from '@atproto/api' import {AppBskyEmbedExternal} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {shareUrl} from 'lib/sharing' import {shareUrl} from 'lib/sharing'
import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player' import {parseEmbedPlayerFromUrl} from 'lib/strings/embed-player'
import {
getStarterPackOgCard,
parseStarterPackUri,
} from 'lib/strings/starter-pack'
import {toNiceDomain} from 'lib/strings/url-helpers' import {toNiceDomain} from 'lib/strings/url-helpers'
import {isNative} from 'platform/detection' import {isNative} from 'platform/detection'
import {useExternalEmbedsPrefs} from 'state/preferences' import {useExternalEmbedsPrefs} from 'state/preferences'
@ -28,10 +34,16 @@ export const ExternalLinkEmbed = ({
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
hideAlt?: boolean hideAlt?: boolean
}) => { }) => {
const {_} = useLingui()
const pal = usePalette('default') const pal = usePalette('default')
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const externalEmbedPrefs = useExternalEmbedsPrefs() const externalEmbedPrefs = useExternalEmbedsPrefs()
const starterPackParsed = parseStarterPackUri(link.uri)
const imageUri = starterPackParsed
? getStarterPackOgCard(starterPackParsed.name, starterPackParsed.rkey)
: link.thumb
const embedPlayerParams = React.useMemo(() => { const embedPlayerParams = React.useMemo(() => {
const params = parseEmbedPlayerFromUrl(link.uri) const params = parseEmbedPlayerFromUrl(link.uri)
@ -47,15 +59,19 @@ export const ExternalLinkEmbed = ({
return ( return (
<View style={[a.flex_col, a.rounded_sm, a.overflow_hidden, a.mt_sm]}> <View style={[a.flex_col, a.rounded_sm, a.overflow_hidden, a.mt_sm]}>
<LinkWrapper link={link} onOpen={onOpen} style={style}> <LinkWrapper link={link} onOpen={onOpen} style={style}>
{link.thumb && !embedPlayerParams ? ( {imageUri && !embedPlayerParams ? (
<Image <Image
style={{ style={{
aspectRatio: 1.91, aspectRatio: 1.91,
borderTopRightRadius: 6, borderTopRightRadius: 6,
borderTopLeftRadius: 6, borderTopLeftRadius: 6,
}} }}
source={{uri: link.thumb}} source={{uri: imageUri}}
accessibilityIgnoresInvertColors accessibilityIgnoresInvertColors
accessibilityLabel={starterPackParsed ? link.title : undefined}
accessibilityHint={
starterPackParsed ? _(msg`Navigate to starter pack`) : undefined
}
/> />
) : undefined} ) : undefined}
{embedPlayerParams?.isGif ? ( {embedPlayerParams?.isGif ? (
@ -63,35 +79,37 @@ export const ExternalLinkEmbed = ({
) : embedPlayerParams ? ( ) : embedPlayerParams ? (
<ExternalPlayer link={link} params={embedPlayerParams} /> <ExternalPlayer link={link} params={embedPlayerParams} />
) : undefined} ) : undefined}
<View {!starterPackParsed ? (
style={[ <View
a.flex_1, style={[
a.py_sm, a.flex_1,
{ a.py_sm,
paddingHorizontal: isMobile ? 10 : 14, {
}, 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 ? (
<Text <Text
type="md" type="sm"
numberOfLines={link.thumb ? 2 : 4} numberOfLines={1}
style={[pal.text, a.mt_xs]}> style={[pal.textLight, {marginVertical: 2}]}>
{link.description} {toNiceDomain(link.uri)}
</Text> </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> </LinkWrapper>
</View> </View>
) )