How to generate images for frame with satori?
Closed this issue · 2 comments
Hi! I'm trying to build a simple frame which will show some info for the user on one of the frames
In my api/frame/route.ts
I have this:
import { fdk } from "@/app/page"
import { NextRequest, NextResponse } from "next/server"
import { getConnectedAddressForUser } from "@/utils/fc"
async function getResponse(req: NextRequest): Promise<NextResponse> {
const searchParams = req.nextUrl.searchParams
const id: any = searchParams.get("id")
const idAsNumber = parseInt(id)
/* ----------------------------- GETTING ADDRESS ---------------------------- */
const body = await req.json()
const fid = body.untrustedData.fid
const address = await getConnectedAddressForUser(fid)
const nextId = idAsNumber + 1
if (idAsNumber === 1) {
try {
const frameMetadata = await fdk.getFrameMetadata({
post_url: `${process.env.BASE_URL}/api/frame?id=${nextId}`,
buttons: [{ label: "Show my role", action: "post" }],
aspect_ratio: "1:1",
image: { url: `${process.env.BASE_URL}/loading.png`, ipfs: false }
})
return new NextResponse(frameMetadata)
} catch (error) {
console.log(error)
return NextResponse.json({ error: error })
}
} else if (idAsNumber === 2) {
try {
const frameMetadata = await fdk.getFrameMetadata({
post_url: `${process.env.BASE_URL}/api/frame?id=${nextId}`,
buttons: [
{ label: "Mint Role", action: "post", target: `${process.env.BASE_URL}/api/frame?id=${4}` },
{ label: "Why?", action: "post", target: `${process.env.BASE_URL}/api/frame?id=${3}` }
],
aspect_ratio: "1:1",
image: { url: `${process.env.BASE_URL}/role.png`, ipfs: false }
})
return new NextResponse(frameMetadata)
} catch (error) {
console.log(error)
return NextResponse.json({ error: error })
}
} else if (idAsNumber === 3) {
try {
const frameMetadata = await fdk.getFrameMetadata({
post_url: `${process.env.BASE_URL}/api/frame?id=${nextId}`,
buttons: [{ label: "Mint Role", action: "post" }],
aspect_ratio: "1:1",
image: { url: `${process.env.BASE_URL}/api/image` }
})
return new NextResponse(frameMetadata)
} catch (error) {
console.log(error)
return NextResponse.json({ error: error })
}
} else {
try {
const frameMetadata = await fdk.getFrameMetadata({
post_url: `${process.env.BASE_URL}/api/end`,
buttons: [
{ label: "Mint NFT", action: "post" },
{ label: "Button 2", action: "post_redirect" },
{ label: "Button 3", action: "post_redirect" },
{ label: "Button 4", action: "post_redirect" }
],
aspect_ratio: "1:1",
image: { url: `${process.env.BASE_URL}/mint.png`, ipfs: false }
})
return new NextResponse(frameMetadata)
} catch (error) {
console.log(error)
return NextResponse.json({ error: error })
}
}
}
export async function POST(req: NextRequest): Promise<Response> {
return getResponse(req)
}
export const dynamic = "force-dynamic"
In the third steps I'm referring to image: { url: `${process.env.BASE_URL}/api/image` }
for my image which is api/image.tsx
:
import type { NextApiRequest, NextApiResponse } from "next"
import sharp from "sharp"
import { Data } from "@/app/types"
import satori from "satori"
import { join } from "path"
import * as fs from "fs"
const fontPath = join(process.cwd(), "Roboto-Regular.ttf")
let fontData = fs.readFileSync(fontPath)
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
let data = { address: "user address" }
const svg = await satori(
<div
style={{
justifyContent: "flex-start",
alignItems: "center",
display: "flex",
width: "100%",
height: "100%",
backgroundColor: "f4f4f4",
padding: 50,
lineHeight: 1.2,
fontSize: 24
}}
>
<div style={{ display: "flex", flexDirection: "column", padding: 20 }}>
<h2 style={{ textAlign: "center", color: "lightgray" }}>Your Data</h2>
<div
style={{
backgroundColor: "#007bff",
color: "#fff",
padding: 10,
marginBottom: 10,
borderRadius: 4,
width: `100%`,
whiteSpace: "nowrap",
overflow: "visible"
}}
>
{data.address}
</div>
</div>
</div>,
{
width: 400,
height: 400,
fonts: [
{
data: fontData,
name: "Roboto",
style: "normal",
weight: 400
}
]
}
)
// Convert SVG to PNG using Sharp
const pngBuffer = await sharp(Buffer.from(svg)).toFormat("png").toBuffer()
// Set the content type to PNG and send the response
res.setHeader("Content-Type", "image/png")
res.setHeader("Cache-Control", "max-age=10")
res.send(pngBuffer)
} catch (error) {
console.error(error)
res.status(500).send("Error generating image")
}
}
However it gives 500 error on this frame in the test environment and I have no idea why.... Can somebody please advise me on how to fix this? I've seen analytics-frame
project in this repo and satori.ts
file but I would like to keep the images locally so I don't really know how to adjust it for my needs....
Assuming the output isn’t larger than 256 bytes, you can convert the png buffer to a dataURI and return that. For the open graph images to work, then need to be URIs but dataURIs work.
Fixed it with this:
export const uploadToIpfs = async (image: Buffer): Promise<string> => {
try {
const tempPath = path.join("/tmp", "image.png")
fs.writeFileSync(tempPath, image)
const file = fs.readFileSync(tempPath)
const formData = new FormData()
formData.append("file", new Blob([file]), "image.png")
const metadata = JSON.stringify({ name: `image.png` })
formData.append("pinataMetadata", metadata)
const imageUpload = await fetch("https://api.pinata.cloud/pinning/pinFileToIPFS", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PINATA_JWT}`
},
body: formData
})
const { IpfsHash } = await imageUpload.json()
const url = `${process.env.GATEWAY_URL}/ipfs/${IpfsHash}?filename=image.png`
console.log({ url })
return url
} catch (error) {
console.log(error)
throw error
}
}
export const generateImage = async (stat: any): Promise<Buffer> => {
const monoFontReg = await fetch(
"https://api.fontsource.org/v1/fonts/inter/latin-400-normal.ttf",
);
const monoFontBold = await fetch(
"https://api.fontsource.org/v1/fonts/inter/latin-700-normal.ttf",
);
const template: any = html(`<div>Demo html</div>`);
// convert html to svg
const svg = await satori(template, {
width: 1200,
height: 630,
fonts: [
{
name: "Roboto Mono",
data: await monoFontReg.arrayBuffer(),
weight: 400,
style: "normal",
},
{
name: "Roboto Mono",
data: await monoFontBold.arrayBuffer(),
weight: 700,
style: "normal",
},
]
});
const pngBuffer = await sharp(Buffer.from(svg)).toFormat("png").toBuffer()
return pngBuffer
}
export const getAnalyticsImageUrl = async (stat: wrappedStatType) => {
try {
const image = await generateImage(stat);
const url = await uploadToIpfs(image);
return url;
} catch (error) {
console.log(error);
throw error;
}
}