Paralellize image uploads (#5535)
* Remove unsafe type coercions from posting embed * Extract resolveEmbed into a separate function * Refactor to if-else because these are mutually exclusive * Refactor resolveEmbed to early returns * Separate resolving embed and media * Parallelize image upload * Prioritize not dropping media
This commit is contained in:
+117
-137
@@ -13,6 +13,8 @@ import {
|
|||||||
RichText,
|
RichText,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
|
|
||||||
|
import {isNetworkError} from '#/lib/strings/errors'
|
||||||
|
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {ComposerImage, compressImage} from '#/state/gallery'
|
import {ComposerImage, compressImage} from '#/state/gallery'
|
||||||
import {writePostgateRecord} from '#/state/queries/postgate'
|
import {writePostgateRecord} from '#/state/queries/postgate'
|
||||||
@@ -22,8 +24,6 @@ import {
|
|||||||
threadgateAllowUISettingToAllowRecordValue,
|
threadgateAllowUISettingToAllowRecordValue,
|
||||||
writeThreadgateRecord,
|
writeThreadgateRecord,
|
||||||
} from '#/state/queries/threadgate'
|
} from '#/state/queries/threadgate'
|
||||||
import {isNetworkError} from 'lib/strings/errors'
|
|
||||||
import {shortenLinks, stripInvalidMentions} from 'lib/strings/rich-text-manip'
|
|
||||||
import {LinkMeta} from '../link-meta/link-meta'
|
import {LinkMeta} from '../link-meta/link-meta'
|
||||||
import {uploadBlob} from './upload-blob'
|
import {uploadBlob} from './upload-blob'
|
||||||
|
|
||||||
@@ -60,13 +60,6 @@ interface PostOpts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function post(agent: BskyAgent, opts: PostOpts) {
|
export async function post(agent: BskyAgent, opts: PostOpts) {
|
||||||
let embed:
|
|
||||||
| AppBskyEmbedImages.Main
|
|
||||||
| AppBskyEmbedExternal.Main
|
|
||||||
| AppBskyEmbedRecord.Main
|
|
||||||
| AppBskyEmbedVideo.Main
|
|
||||||
| AppBskyEmbedRecordWithMedia.Main
|
|
||||||
| undefined
|
|
||||||
let reply
|
let reply
|
||||||
let rt = new RichText({text: opts.rawText.trimEnd()}, {cleanNewlines: true})
|
let rt = new RichText({text: opts.rawText.trimEnd()}, {cleanNewlines: true})
|
||||||
|
|
||||||
@@ -77,134 +70,7 @@ export async function post(agent: BskyAgent, opts: PostOpts) {
|
|||||||
rt = shortenLinks(rt)
|
rt = shortenLinks(rt)
|
||||||
rt = stripInvalidMentions(rt)
|
rt = stripInvalidMentions(rt)
|
||||||
|
|
||||||
// add quote embed if present
|
const embed = await resolveEmbed(agent, opts)
|
||||||
if (opts.quote) {
|
|
||||||
embed = {
|
|
||||||
$type: 'app.bsky.embed.record',
|
|
||||||
record: {
|
|
||||||
uri: opts.quote.uri,
|
|
||||||
cid: opts.quote.cid,
|
|
||||||
},
|
|
||||||
} as AppBskyEmbedRecord.Main
|
|
||||||
}
|
|
||||||
|
|
||||||
// add image embed if present
|
|
||||||
if (opts.images?.length) {
|
|
||||||
logger.debug(`Uploading images`, {
|
|
||||||
count: opts.images.length,
|
|
||||||
})
|
|
||||||
|
|
||||||
const images: AppBskyEmbedImages.Image[] = []
|
|
||||||
for (const image of opts.images) {
|
|
||||||
opts.onStateChange?.(`Uploading image #${images.length + 1}...`)
|
|
||||||
|
|
||||||
logger.debug(`Compressing image`)
|
|
||||||
const {path, width, height, mime} = await compressImage(image)
|
|
||||||
|
|
||||||
logger.debug(`Uploading image`)
|
|
||||||
const res = await uploadBlob(agent, path, mime)
|
|
||||||
|
|
||||||
images.push({
|
|
||||||
image: res.data.blob,
|
|
||||||
alt: image.alt,
|
|
||||||
aspectRatio: {width, height},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.quote) {
|
|
||||||
embed = {
|
|
||||||
$type: 'app.bsky.embed.recordWithMedia',
|
|
||||||
record: embed,
|
|
||||||
media: {
|
|
||||||
$type: 'app.bsky.embed.images',
|
|
||||||
images,
|
|
||||||
},
|
|
||||||
} as AppBskyEmbedRecordWithMedia.Main
|
|
||||||
} else {
|
|
||||||
embed = {
|
|
||||||
$type: 'app.bsky.embed.images',
|
|
||||||
images,
|
|
||||||
} as AppBskyEmbedImages.Main
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add video embed if present
|
|
||||||
if (opts.video) {
|
|
||||||
const captions = await Promise.all(
|
|
||||||
opts.video.captions
|
|
||||||
.filter(caption => caption.lang !== '')
|
|
||||||
.map(async caption => {
|
|
||||||
const {data} = await agent.uploadBlob(caption.file, {
|
|
||||||
encoding: 'text/vtt',
|
|
||||||
})
|
|
||||||
return {lang: caption.lang, file: data.blob}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
if (opts.quote) {
|
|
||||||
embed = {
|
|
||||||
$type: 'app.bsky.embed.recordWithMedia',
|
|
||||||
record: embed,
|
|
||||||
media: {
|
|
||||||
$type: 'app.bsky.embed.video',
|
|
||||||
video: opts.video.blobRef,
|
|
||||||
alt: opts.video.altText || undefined,
|
|
||||||
captions: captions.length === 0 ? undefined : captions,
|
|
||||||
aspectRatio: opts.video.aspectRatio,
|
|
||||||
} as AppBskyEmbedVideo.Main,
|
|
||||||
} as AppBskyEmbedRecordWithMedia.Main
|
|
||||||
} else {
|
|
||||||
embed = {
|
|
||||||
$type: 'app.bsky.embed.video',
|
|
||||||
video: opts.video.blobRef,
|
|
||||||
alt: opts.video.altText || undefined,
|
|
||||||
captions: captions.length === 0 ? undefined : captions,
|
|
||||||
aspectRatio: opts.video.aspectRatio,
|
|
||||||
} as AppBskyEmbedVideo.Main
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add external embed if present
|
|
||||||
if (opts.extLink && !opts.images?.length) {
|
|
||||||
if (opts.extLink.embed) {
|
|
||||||
embed = opts.extLink.embed
|
|
||||||
} else {
|
|
||||||
let thumb
|
|
||||||
if (opts.extLink.localThumb) {
|
|
||||||
opts.onStateChange?.('Uploading link thumbnail...')
|
|
||||||
|
|
||||||
const {path, mime} = opts.extLink.localThumb.source
|
|
||||||
const res = await uploadBlob(agent, path, mime)
|
|
||||||
|
|
||||||
thumb = res.data.blob
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.quote) {
|
|
||||||
embed = {
|
|
||||||
$type: 'app.bsky.embed.recordWithMedia',
|
|
||||||
record: embed,
|
|
||||||
media: {
|
|
||||||
$type: 'app.bsky.embed.external',
|
|
||||||
external: {
|
|
||||||
uri: opts.extLink.uri,
|
|
||||||
title: opts.extLink.meta?.title || '',
|
|
||||||
description: opts.extLink.meta?.description || '',
|
|
||||||
thumb,
|
|
||||||
},
|
|
||||||
} as AppBskyEmbedExternal.Main,
|
|
||||||
} as AppBskyEmbedRecordWithMedia.Main
|
|
||||||
} else {
|
|
||||||
embed = {
|
|
||||||
$type: 'app.bsky.embed.external',
|
|
||||||
external: {
|
|
||||||
uri: opts.extLink.uri,
|
|
||||||
title: opts.extLink.meta?.title || '',
|
|
||||||
description: opts.extLink.meta?.description || '',
|
|
||||||
thumb,
|
|
||||||
},
|
|
||||||
} as AppBskyEmbedExternal.Main
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add replyTo if post is a reply to another post
|
// add replyTo if post is a reply to another post
|
||||||
if (opts.replyTo) {
|
if (opts.replyTo) {
|
||||||
@@ -313,3 +179,117 @@ export async function post(agent: BskyAgent, opts: PostOpts) {
|
|||||||
|
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resolveEmbed(
|
||||||
|
agent: BskyAgent,
|
||||||
|
opts: PostOpts,
|
||||||
|
): Promise<
|
||||||
|
| AppBskyEmbedImages.Main
|
||||||
|
| AppBskyEmbedVideo.Main
|
||||||
|
| AppBskyEmbedExternal.Main
|
||||||
|
| AppBskyEmbedRecord.Main
|
||||||
|
| AppBskyEmbedRecordWithMedia.Main
|
||||||
|
| undefined
|
||||||
|
> {
|
||||||
|
const media = await resolveMedia(agent, opts)
|
||||||
|
if (opts.quote) {
|
||||||
|
const quoteRecord = {
|
||||||
|
$type: 'app.bsky.embed.record',
|
||||||
|
record: {
|
||||||
|
uri: opts.quote.uri,
|
||||||
|
cid: opts.quote.cid,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if (media) {
|
||||||
|
return {
|
||||||
|
$type: 'app.bsky.embed.recordWithMedia',
|
||||||
|
record: quoteRecord,
|
||||||
|
media,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return quoteRecord
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (media) {
|
||||||
|
return media
|
||||||
|
}
|
||||||
|
if (opts.extLink?.embed) {
|
||||||
|
return opts.extLink.embed
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resolveMedia(
|
||||||
|
agent: BskyAgent,
|
||||||
|
opts: PostOpts,
|
||||||
|
): Promise<
|
||||||
|
| AppBskyEmbedExternal.Main
|
||||||
|
| AppBskyEmbedImages.Main
|
||||||
|
| AppBskyEmbedVideo.Main
|
||||||
|
| undefined
|
||||||
|
> {
|
||||||
|
if (opts.images?.length) {
|
||||||
|
logger.debug(`Uploading images`, {
|
||||||
|
count: opts.images.length,
|
||||||
|
})
|
||||||
|
opts.onStateChange?.(`Uploading images...`)
|
||||||
|
const images: AppBskyEmbedImages.Image[] = await Promise.all(
|
||||||
|
opts.images.map(async (image, i) => {
|
||||||
|
logger.debug(`Compressing image #${i}`)
|
||||||
|
const {path, width, height, mime} = await compressImage(image)
|
||||||
|
logger.debug(`Uploading image #${i}`)
|
||||||
|
const res = await uploadBlob(agent, path, mime)
|
||||||
|
return {
|
||||||
|
image: res.data.blob,
|
||||||
|
alt: image.alt,
|
||||||
|
aspectRatio: {width, height},
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
$type: 'app.bsky.embed.images',
|
||||||
|
images,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (opts.video) {
|
||||||
|
const captions = await Promise.all(
|
||||||
|
opts.video.captions
|
||||||
|
.filter(caption => caption.lang !== '')
|
||||||
|
.map(async caption => {
|
||||||
|
const {data} = await agent.uploadBlob(caption.file, {
|
||||||
|
encoding: 'text/vtt',
|
||||||
|
})
|
||||||
|
return {lang: caption.lang, file: data.blob}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
$type: 'app.bsky.embed.video',
|
||||||
|
video: opts.video.blobRef,
|
||||||
|
alt: opts.video.altText || undefined,
|
||||||
|
captions: captions.length === 0 ? undefined : captions,
|
||||||
|
aspectRatio: opts.video.aspectRatio,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (opts.extLink) {
|
||||||
|
if (opts.extLink.embed) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
let thumb
|
||||||
|
if (opts.extLink.localThumb) {
|
||||||
|
opts.onStateChange?.('Uploading link thumbnail...')
|
||||||
|
const {path, mime} = opts.extLink.localThumb.source
|
||||||
|
const res = await uploadBlob(agent, path, mime)
|
||||||
|
thumb = res.data.blob
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
$type: 'app.bsky.embed.external',
|
||||||
|
external: {
|
||||||
|
uri: opts.extLink.uri,
|
||||||
|
title: opts.extLink.meta?.title || '',
|
||||||
|
description: opts.extLink.meta?.description || '',
|
||||||
|
thumb,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user