巷ではGithubのプロフィールをデコるのが流行っています。
具体的にはGitHub Readme Stats を利用してGitHubプロフィールをカッコよくするのような記事を読んでもらえばいいんですが、ここで紹介されている物以外を載せたい場合があると思います。
そこで、今回は試しにTwitterのプロフィール情報を取得し画像にして返すtwitter-profile-cardというものを書きました。
Vercel
にデプロイされており、URL
にidのクエリを付けて叩くとTwitterのプロフィールの画像が返ってきます。
Twitterの情報を取得する手順で他のデータを取得すれば応用が利くはずです。
記事では書いたCSSの詳細は省いていますが、この様な感じで表示されます。(画像はpngにした物ですが、リンク先は生成された物になっています。)
詳しくはリポジトリを見て下さい。
やりたいこと
- Twitter APIを叩いて、プロフィールを取得
- HTMLElementにデータを埋め込んでスタイリング1
-
Puppeteer
でHTMLElementのスクリーンショットを撮る - svgの配下のimageタグにスクリーンショットを埋め込んだレスポンスを返す
- これらをデプロイ
構成
.
├── README.md
├── package.json
├── vercel.json
├── api
│ └── index.ts
├── src
│ ├── createCard.ts
│ ├── createElement.tsx
│ └── getTwitterData.ts
└── tsconfig.json
-
/api
がエンドポイントになります
データの取得
好きなように取得してください。
今回はTwitterのプロフィール情報を取得し返す、と言う事で適当に取得します。
export function getTwitterData({ id }) => {
const headers = {
Authorization: `Bearer ${process.env.TWITTER_BEARER_TOKEN}`
}
const params = {
screen_name: id
}
const userShowEndoPoint = 'https://api.twitter.com/1.1/users/show.json'
return new Promise((resolve, reject) => {
axios
.get(userShowEndoPoint, { headers, params })
.then((response) => resolve(response.data))
.catch((err) => {
return reject(err.response)
})
})
}
データをスタイリングし、Puppeteerでスクリーンショットを撮る
Vercel
はAWS Lambda
上で動いてる為、デプロイパッケージのサイズに制限があります。
Puppeteer
に同梱さているChrome単体パッケージが単体で250MBもあるので、そのままVercel
にデプロイしてしまうと、サイズ上限に引っかかってしまいます。
そこで、サイズ上限を回避するためにchrome-aws-lambda
とpuppeteer-core
を利用します。2
この2つはバージョンを合わせる必要があるので注意が必要です。
import chrome from 'chrome-aws-lambda'
import puppeteer from 'puppeteer-core'
また、ローカル環境ではchrome-aws-lambda
が働いてくれないので、サーバー上で動いているかを条件分けし、ローカルでは自分のPCに入っているChromeを使うようにします。
const browser = await puppeteer.launch(
process.env.AWS_REGION
? {
args: chrome.args,
executablePath: await chrome.executablePath,
headless: chrome.headless
}
: {
args: [],
executablePath:
process.platform === 'win32'
? 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
: process.platform === 'linux'
? '/usr/bin/google-chrome'
: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
}
)
データを表示する為に、html
を書きます。
スタイリングにインテリセンスが効かないと辛いので、.tsx | .jsx
拡張子のファイルを作り、JSX.Element
を返す関数を書きます。
全てを書くと長いので、ここではヘッダーを表示する部分だけ書きます。
import * as React from 'react'
export function createElement(tweetData) {
const header = {
height: '33%',
width: '100%',
overflow: 'hidden'
}
const headerImage = {
height: '100%',
width: '100%',
objectFit: 'cover'
}
return (
<div style={header}>
<img
src={tweetData.profile_banner_url}
alt="header image"
height="100px"
width="300px"
style={headerImage}
/>
</div>
)
}
この様にinline-style
でスタイリングしていきます。
このJSX.Element
をreact-dom/server
のrenderToString
を使う事でstring型に変換でき、それをPuppeteer
で読み込み、スクリーンショットを撮ります。
import { renderToString } from 'react-dom/server'
import { createElement } from './createElement'
const element = createElement(tweetData)
const page = await browser.newPage()
await page.setContent(
`<html>
<head>
<style>
body {
width: "${width}";
height: "${height}";
}
</style>
</head>
<body>${renderToString(element)}</body>
</html>
`
)
const image = await page.$('body')
const buffer = await image.screenshot({ encoding: 'base64' })
補足
日本語のフォントがPuppeteer
に存在しないので文字化けしてしない為、elementに日本語が混じっていると文字化けしてしまうのでgooglefontsなどのcdnからfontを読み込む様にします。
await chrome.font(
'https://rawcdn.githack.com/googlefonts/noto-cjk/be6c059ac1587e556e2412b27f5155c8eb3ddbe6/NotoSansCJKjp-Regular.otf'
)
await chrome.font(
'https://rawcdn.githack.com/googlefonts/noto-fonts/ea9154f9a0947972baa772bc6744f1ec50007575/hinted/NotoSans/NotoSans-Regular.ttf'
)
撮ったスクリーンショットをクライアントに返す
上述しましたが、クライアント側のエンドポイントは/api
になります。
(いくつかのレポジトリでこうなってたので参考にしました。)
./api/index.ts
で、リクエストを受けレスポンスを返す処理を行います。
export default async (req,res) => {
const { id } = req.query
res.send(id)
}
req.query
に叩いたURLのqueryが入ります。
(例えば/api?id=Twitter
とすると、req.query.idにTwitterが入ります。)
res.send
でクライアントにレシポンスを返すことができ、res.setHeader
でHeaderを設定します。
先ほど撮ったスクリーンショットをsvgに埋め込み、クライアントに返します。
return `
<svg
xmlns="http://www.w3.org/2000/svg"
width="${width}"
height="${height}"
viewport="0 0 ${width} ${height}"
fill="none"
>
<image href="data:image/jpeg;base64,${buffer}" x="0" y="0" width="100%" height="100%"/>
</svg>
`
import { createCard } from '../src/createCard'
import { getTwitterData } from '../src/getTwitterData'
export default async (req,res) => {
const result = await getTwitterData(req.query) // Twitterのデータ取得
const svgImage = await createCard() // svg画像のHTMLelementを取得
res.setHeader('Content-Type', 'image/svg+xml') // svgを指定
res.setHeader('Cache-Control', `public, max-age=${60 * 60 * 12}`) // データの変化があまりないのでキャッシュを12時間に
res.send(svgImage) // データを返す
}
これでクライアントにsvg画像を返すことができました。
確認、デプロイ
vercel.json
を設定し、Vercel dev
でローカルサーバーを起動できます。
http://localhost:3000/api?id=Twitter
で画像が表示されれば完成です。
あとはvercel
コマンドでデプロイできます、簡単ですね。
まとめ
フォロワーが居ないアカウントでTwitterのプロフィールを表示させると悲しくなるので注意が必要