Fix types, port getPostData to new interface

This commit is contained in:
Eric Bailey
2025-01-09 16:25:47 -06:00
parent cd2ddd21ef
commit 71aa7e2fd2
8 changed files with 292 additions and 243 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
"install-fonts": "node --loader ts-node/esm scripts/install-fonts.ts"
},
"dependencies": {
"@atproto/api": "^0.13.4",
"@atproto/api": "0.14.0-next.2",
"@atproto/common": "^0.4.0",
"@resvg/resvg-js": "^2.6.2",
"express": "^4.19.2",
+5 -6
View File
@@ -49,7 +49,7 @@ export function Post({
data: PostData
moderatorData: ModeratorData
}) {
if (AppBskyFeedPost.isRecord(post.record)) {
if (AppBskyFeedPost.isValidRecord(post.record)) {
const avatar = data.images.get(post.author.avatar)
const rt = post.record.text
? new RichTextApi({
@@ -325,8 +325,8 @@ export function Embeds({
}
if (
AppBskyGraphDefs.isStarterPackViewBasic(embed.record) &&
AppBskyGraphStarterpack.isRecord(embed.record.record)
AppBskyGraphDefs.isValidStarterPackViewBasic(embed.record) &&
AppBskyGraphStarterpack.isValidRecord(embed.record.record)
) {
const uri = getStarterPackImageUri(embed.record)
const {name, description} = embed.record.record
@@ -550,9 +550,8 @@ export function QuoteEmbed({
moderatorData: ModeratorData
}) {
if (
AppBskyEmbedRecord.isViewRecord(embed.record) &&
AppBskyFeedPost.isRecord(embed.record.value) &&
AppBskyFeedPost.validateRecord(embed.record.value).success
AppBskyEmbedRecord.isValidViewRecord(embed.record) &&
AppBskyFeedPost.isValidRecord(embed.record.value)
) {
const {author, value: post, embeds} = embed.record
const avatar = data.images.get(author.avatar)
+1 -2
View File
@@ -1,5 +1,4 @@
/* eslint-disable bsky-internal/avoid-unwrapped-text */
import React from 'react'
import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api'
import {Butterfly} from './Butterfly.js'
@@ -18,7 +17,7 @@ export function StarterPack(props: {
images: Map<string, Buffer>
}) {
const {starterPack, images} = props
const record = AppBskyGraphStarterpack.isRecord(starterPack.record)
const record = AppBskyGraphStarterpack.isValidRecord(starterPack.record)
? starterPack.record
: null
const imagesArray = [...images.values()]
+91 -207
View File
@@ -1,14 +1,8 @@
import {
AppBskyEmbedExternal,
AppBskyEmbedImages,
AppBskyEmbedRecord,
AppBskyEmbedRecordWithMedia,
AppBskyFeedDefs,
AppBskyGraphDefs,
} from '@atproto/api'
import {AppBskyFeedDefs} from '@atproto/api'
import {httpLogger} from '../logger.js'
import {getStarterPackImageUri} from '../util/getStarterPackImageUri.js'
import {Embed,parseEmbed} from '../util/parseEmbed.js'
import {getImage} from './getImage.js'
export type Metadata = {
@@ -39,13 +33,99 @@ function normalizeAspectRatio(aspectRatio?: {
return aspectRatio
}
export function getEmbedData(embed: Embed, images: Map<string, Metadata>) {
switch (embed.type) {
case 'images': {
for (const image of embed.view.images) {
images.set(image.fullsize, {
aspectRatio: normalizeAspectRatio(image.aspectRatio),
})
}
break
}
case 'link': {
if (embed.view.external.thumb) {
images.set(embed.view.external.thumb, {
aspectRatio: {
width: 1200,
height: 630,
},
})
}
break
}
case 'video': {
if (embed.view.thumbnail) {
images.set(embed.view.thumbnail, {
aspectRatio: normalizeAspectRatio(embed.view.aspectRatio),
})
}
break
}
case 'feed': {
if (embed.view.avatar) {
images.set(embed.view.avatar, {
aspectRatio: {
width: 1000,
height: 1000,
},
})
}
break
}
case 'list': {
if (embed.view.avatar) {
images.set(embed.view.avatar, {
aspectRatio: {
width: 1000,
height: 1000,
},
})
}
break
}
case 'starter_pack': {
const uri = getStarterPackImageUri(embed.view)
images.set(uri, {
aspectRatio: {
width: 1200,
height: 630,
},
})
break
}
case 'post': {
if (embed.view.author.avatar) {
images.set(embed.view.author.avatar, {
aspectRatio: {
width: 1000,
height: 1000,
},
})
}
for (const _e of embed.view.embeds) {
getEmbedData(parseEmbed(_e), images)
}
break
}
case 'post_with_media': {
getEmbedData(embed.view, images)
getEmbedData(embed.media, images)
break
}
case 'post_blocked':
case 'post_detached':
case 'post_not_found':
default:
break
}
}
export async function getPostData(
post: AppBskyFeedDefs.PostView,
): Promise<PostData> {
const images: Map<string, Metadata> = new Map()
// console.log(JSON.stringify(post, null, 2))
if (post.author.avatar) {
images.set(post.author.avatar, {
aspectRatio: {
@@ -56,203 +136,7 @@ export async function getPostData(
}
if (post.embed) {
if (AppBskyEmbedImages.isView(post.embed)) {
// get OPs media
for (const image of post.embed.images) {
images.set(image.fullsize, {
aspectRatio: normalizeAspectRatio(image.aspectRatio),
})
}
}
if (AppBskyEmbedExternal.isView(post.embed)) {
if (post.embed.external.thumb) {
images.set(post.embed.external.thumb, {
aspectRatio: {
width: 1200,
height: 630,
},
})
}
}
if (AppBskyEmbedRecord.isView(post.embed)) {
if (AppBskyEmbedRecord.isViewRecord(post.embed.record)) {
if (post.embed.record.author.avatar) {
images.set(post.embed.record.author.avatar, {
aspectRatio: {
width: 1000,
height: 1000,
},
})
}
for (const embed of post.embed.record.embeds) {
if (AppBskyEmbedImages.isView(embed)) {
for (const image of embed.images) {
images.set(image.fullsize, {
aspectRatio: normalizeAspectRatio(image.aspectRatio),
})
}
}
if (AppBskyEmbedExternal.isView(embed)) {
if (embed.external.thumb) {
images.set(embed.external.thumb, {
aspectRatio: {
width: 1200,
height: 630,
},
})
}
}
if (AppBskyEmbedRecordWithMedia.isView(embed)) {
if (AppBskyEmbedImages.isView(embed.media)) {
for (const image of embed.media.images) {
images.set(image.fullsize, {
aspectRatio: normalizeAspectRatio(image.aspectRatio),
})
}
} else if (AppBskyEmbedExternal.isView(embed.media)) {
if (embed.media.external.thumb) {
images.set(embed.media.external.thumb, {
aspectRatio: {
width: 1200,
height: 630,
},
})
}
}
}
if (AppBskyGraphDefs.isListView(embed.record)) {
if (embed.record.avatar) {
images.set(embed.record.avatar, {
aspectRatio: {
width: 1000,
height: 1000,
},
})
}
}
if (AppBskyFeedDefs.isGeneratorView(embed.record)) {
if (embed.record.avatar) {
images.set(embed.record.avatar, {
aspectRatio: {
width: 1000,
height: 1000,
},
})
}
}
if (AppBskyGraphDefs.isStarterPackViewBasic(embed.record)) {
const uri = getStarterPackImageUri(embed.record)
images.set(uri, {
aspectRatio: {
width: 1200,
height: 630,
},
})
}
}
}
if (AppBskyGraphDefs.isListView(post.embed.record)) {
if (post.embed.record.avatar) {
images.set(post.embed.record.avatar, {
aspectRatio: {
width: 1000,
height: 1000,
},
})
}
}
if (AppBskyFeedDefs.isGeneratorView(post.embed.record)) {
if (post.embed.record.avatar) {
images.set(post.embed.record.avatar, {
aspectRatio: {
width: 1000,
height: 1000,
},
})
}
}
if (AppBskyGraphDefs.isStarterPackViewBasic(post.embed.record)) {
const uri = getStarterPackImageUri(post.embed.record)
images.set(uri, {
aspectRatio: {
width: 1200,
height: 630,
},
})
}
}
if (AppBskyEmbedRecordWithMedia.isView(post.embed)) {
// get OPs media
if (AppBskyEmbedImages.isView(post.embed.media)) {
for (const image of post.embed.media.images) {
images.set(image.fullsize, {
aspectRatio: normalizeAspectRatio(image.aspectRatio),
})
}
}
if (AppBskyEmbedExternal.isView(post.embed.media)) {
if (post.embed.media.external.thumb) {
images.set(post.embed.media.external.thumb, {
aspectRatio: {
width: 1200,
height: 630,
},
})
}
}
// get media from embedded post
if (AppBskyEmbedRecord.isViewRecord(post.embed.record.record)) {
for (const embed of post.embed.record.record.embeds) {
if (AppBskyEmbedImages.isView(embed)) {
for (const image of embed.images) {
images.set(image.fullsize, {
aspectRatio: normalizeAspectRatio(image.aspectRatio),
})
}
}
if (AppBskyEmbedExternal.isView(embed)) {
if (embed.external.thumb) {
images.set(embed.external.thumb, {
aspectRatio: {
width: 1200,
height: 630,
},
})
}
}
if (AppBskyEmbedRecordWithMedia.isView(embed)) {
if (AppBskyEmbedImages.isView(embed.media)) {
for (const image of embed.media.images) {
images.set(image.fullsize, {
aspectRatio: normalizeAspectRatio(image.aspectRatio),
})
}
} else if (AppBskyEmbedExternal.isView(embed.media)) {
if (embed.media.external.thumb) {
images.set(embed.media.external.thumb, {
aspectRatio: {
width: 1200,
height: 630,
},
})
}
}
}
}
}
}
getEmbedData(parseEmbed(post.embed), images)
}
const deduped = Array.from(images.entries())
-1
View File
@@ -1,4 +1,3 @@
import React from 'react'
import {AppBskyFeedPost, AtUri} from '@atproto/api'
import resvg from '@resvg/resvg-js'
import {Express} from 'express'
+8 -3
View File
@@ -1,7 +1,6 @@
import assert from 'node:assert'
import React from 'react'
import {AppBskyGraphDefs, AtUri} from '@atproto/api'
import {AppBskyActorDefs,AppBskyGraphDefs, AtUri} from '@atproto/api'
import resvg from '@resvg/resvg-js'
import {Express} from 'express'
import satori from 'satori'
@@ -37,7 +36,13 @@ export default function (ctx: AppContext, app: Express) {
return res.status(404).end('not found')
}
const imageEntries = await Promise.all(
[starterPack.creator]
(
[] as (
| AppBskyActorDefs.ProfileViewBasic
| AppBskyActorDefs.ProfileView
)[]
)
.concat([starterPack.creator])
.concat(starterPack.listItemsSample.map(li => li.subject))
// has avatar
.filter(p => p.avatar)
+152
View File
@@ -0,0 +1,152 @@
/**
* This file is a copy of what exists in the social-app
*/
import {
AppBskyEmbedExternal,
AppBskyEmbedImages,
AppBskyEmbedRecord,
AppBskyEmbedRecordWithMedia,
AppBskyEmbedVideo,
AppBskyFeedDefs,
AppBskyGraphDefs,
AppBskyLabelerDefs,
} from '@atproto/api'
export type Embed =
| {
type: 'post'
view: AppBskyEmbedRecord.ViewRecord
}
| {
type: 'post_not_found'
view: AppBskyEmbedRecord.ViewNotFound
}
| {
type: 'post_blocked'
view: AppBskyEmbedRecord.ViewBlocked
}
| {
type: 'post_detached'
view: AppBskyEmbedRecord.ViewDetached
}
| {
type: 'feed'
view: AppBskyFeedDefs.GeneratorView
}
| {
type: 'list'
view: AppBskyGraphDefs.ListView
}
| {
type: 'labeler'
view: AppBskyLabelerDefs.LabelerView
}
| {
type: 'starter_pack'
view: AppBskyGraphDefs.StarterPackViewBasic
}
| {
type: 'images'
view: AppBskyEmbedImages.View
}
| {
type: 'link'
view: AppBskyEmbedExternal.View
}
| {
type: 'video'
view: AppBskyEmbedVideo.View
}
| {
type: 'post_with_media'
view: Embed
media: Embed
}
| {
type: 'unknown'
view: null
}
export type EmbedType<T extends Embed['type']> = Extract<Embed, {type: T}>
export function parseEmbedRecordView({record}: AppBskyEmbedRecord.View): Embed {
if (AppBskyEmbedRecord.isViewRecord(record)) {
return {
type: 'post',
view: record,
}
} else if (AppBskyEmbedRecord.isViewNotFound(record)) {
return {
type: 'post_not_found',
view: record,
}
} else if (AppBskyEmbedRecord.isViewBlocked(record)) {
return {
type: 'post_blocked',
view: record,
}
} else if (AppBskyEmbedRecord.isViewDetached(record)) {
return {
type: 'post_detached',
view: record,
}
} else if (AppBskyFeedDefs.isGeneratorView(record)) {
return {
type: 'feed',
view: record,
}
} else if (AppBskyGraphDefs.isListView(record)) {
return {
type: 'list',
view: record,
}
} else if (AppBskyLabelerDefs.isLabelerView(record)) {
return {
type: 'labeler',
view: record,
}
} else if (AppBskyGraphDefs.isStarterPackViewBasic(record)) {
return {
type: 'starter_pack',
view: record,
}
} else {
return {
type: 'unknown',
view: null,
}
}
}
export function parseEmbed(embed: AppBskyFeedDefs.PostView['embed']): Embed {
if (AppBskyEmbedImages.isView(embed)) {
return {
type: 'images',
view: embed,
}
} else if (AppBskyEmbedExternal.isView(embed)) {
return {
type: 'link',
view: embed,
}
} else if (AppBskyEmbedVideo.isView(embed)) {
return {
type: 'video',
view: embed,
}
} else if (AppBskyEmbedRecord.isView(embed)) {
return parseEmbedRecordView(embed)
} else if (AppBskyEmbedRecordWithMedia.isView(embed)) {
return {
type: 'post_with_media',
view: parseEmbedRecordView(embed.record),
media: parseEmbed(embed.media),
}
} else {
return {
type: 'unknown',
view: null,
}
}
}
+34 -23
View File
@@ -2,18 +2,19 @@
# yarn lockfile v1
"@atproto/api@^0.13.4":
version "0.13.4"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.13.4.tgz#47631e622760b2a18e4aee75b728e9cf8952e221"
integrity sha512-Fwn37hP+Xr9YjA/hadvn7ZKbUPQhJiUus1+govgNKF3/jNdyk2ICoEe0z+hxaO3xX8LCU5yARbgt3SRoXbIwrg==
"@atproto/api@0.14.0-next.2":
version "0.14.0-next.2"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.14.0-next.2.tgz#c42313217dc5cbbd81438706018963d4232a4ba6"
integrity sha512-1FADZQAklDozrGDxrb6ydkrlBBES4sKP7wJZTtwzqb1bSUOqRD8vjuvVvklt2ahhAQvslsBUC1wBrMa7VGS6hA==
dependencies:
"@atproto/common-web" "^0.3.0"
"@atproto/lexicon" "^0.4.1"
"@atproto/syntax" "^0.3.0"
"@atproto/xrpc" "^0.6.1"
"@atproto/common-web" "^0.3.2"
"@atproto/lexicon" "^0.4.5"
"@atproto/syntax" "^0.3.1"
"@atproto/xrpc" "^0.6.6"
await-lock "^2.2.2"
multiformats "^9.9.0"
tlds "^1.234.0"
zod "^3.23.8"
"@atproto/common-web@^0.3.0":
version "0.3.0"
@@ -25,6 +26,16 @@
uint8arrays "3.0.0"
zod "^3.21.4"
"@atproto/common-web@^0.3.2":
version "0.3.2"
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.3.2.tgz#4cf78ad4d24fed801882f3d35afc39bceccdff51"
integrity sha512-Vx0JtL1/CssJbFAb0UOdvTrkbUautsDfHNOXNTcX2vyPIxH9xOameSqLLunM1hZnOQbJwyjmQCt6TV+bhnanDg==
dependencies:
graphemer "^1.4.0"
multiformats "^9.9.0"
uint8arrays "3.0.0"
zod "^3.23.8"
"@atproto/common@^0.4.0":
version "0.4.0"
resolved "https://registry.npmjs.org/@atproto/common/-/common-0.4.0.tgz"
@@ -37,28 +48,28 @@
multiformats "^9.9.0"
pino "^8.15.0"
"@atproto/lexicon@^0.4.1":
version "0.4.1"
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.1.tgz#19155210570a2fafbcc7d4f655d9b813948e72a0"
integrity sha512-bzyr+/VHXLQWbumViX5L7h1NKQObfs8Z+XZJl43OUK8nYFUI4e/sW1IZKRNfw7Wvi5YVNK+J+yP3DWIBZhkCYA==
"@atproto/lexicon@^0.4.5":
version "0.4.5"
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.5.tgz#4fcf3731193c674286e9e8d677bbab5dd530b817"
integrity sha512-fljWqMGKn+XWtTprBcS3F1hGBREnQYh6qYHv2sjENucc7REms1gtmZXSerB9N6pVeHVNOnXiILdukeAcic5OEw==
dependencies:
"@atproto/common-web" "^0.3.0"
"@atproto/syntax" "^0.3.0"
"@atproto/common-web" "^0.3.2"
"@atproto/syntax" "^0.3.1"
iso-datestring-validator "^2.2.2"
multiformats "^9.9.0"
zod "^3.23.8"
"@atproto/syntax@^0.3.0":
version "0.3.0"
resolved "https://registry.npmjs.org/@atproto/syntax/-/syntax-0.3.0.tgz"
integrity sha512-Weq0ZBxffGHDXHl9U7BQc2BFJi/e23AL+k+i5+D9hUq/bzT4yjGsrCejkjq0xt82xXDjmhhvQSZ0LqxyZ5woxA==
"@atproto/syntax@^0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@atproto/syntax/-/syntax-0.3.1.tgz#4346418728f9643d783d2ffcf7c77e132e1f53d4"
integrity sha512-fzW0Mg1QUOVCWUD3RgEsDt6d1OZ6DdFmbKcDdbzUfh0t4rhtRAC05KbZYmxuMPWDAiJ4BbbQ5dkAc/mNypMXkw==
"@atproto/xrpc@^0.6.1":
version "0.6.1"
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.1.tgz#dcd1315c8c60eef5af2db7fa4e35a38ebc6d79d5"
integrity sha512-Zy5ydXEdk6sY7FDUZcEVfCL1jvbL4tXu5CcdPqbEaW6LQtk9GLds/DK1bCX9kswTGaBC88EMuqQMfkxOhp2t4A==
"@atproto/xrpc@^0.6.6":
version "0.6.6"
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.6.6.tgz#28f58270ef4a8056f7f718bd52512e74bcd3702f"
integrity sha512-umXEYVMo9/pyIBoKmIAIi64RXDW9tSXY+wqztlQ6I2GZtjLfNZqmAWU+wADk3SxUe54mvjxxGyA4TtyGtDMfhA==
dependencies:
"@atproto/lexicon" "^0.4.1"
"@atproto/lexicon" "^0.4.5"
zod "^3.23.8"
"@cbor-extract/cbor-extract-darwin-arm64@2.2.0":