巷では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のプロフィール情報を取得し返す、と言う事で適当に取得します。
exportfunctiongetTwitterData({id})=>{constheaders={Authorization:`Bearer ${process.env.TWITTER_BEARER_TOKEN}`}constparams={screen_name:id}constuserShowEndoPoint='https://api.twitter.com/1.1/users/show.json'returnnewPromise((resolve,reject)=>{axios.get(userShowEndoPoint,{headers,params}).then((response)=>resolve(response.data)).catch(async(err)=>{returnreject(err.response)})})}
データをスタイリングし、Puppeteerでスクリーンショットを撮る
Vercel
はAWS Lambda
上で動いてる為、デプロイパッケージのサイズに制限があります。Puppeteer
に同梱さているChrome単体パッケージが単体で250MBもあるので、そのままVercel
にデプロイしてしまうと、サイズ上限に引っかかってしまいます。
そこで、サイズ上限を回避するためにchrome-aws-lambda
とpuppeteer-core
を利用します。2
この2つはバージョンを合わせる必要があるので注意が必要です。
importchromefrom'chrome-aws-lambda'importpuppeteerfrom'puppeteer-core'
また、ローカル環境ではchrome-aws-lambda
が働いてくれないので、サーバー上で動いているかを条件分けし、ローカルでは自分のPCに入っているChromeを使うようにします。
constbrowser=awaitpuppeteer.launch(process.env.AWS_REGION?{args:chrome.args,executablePath:awaitchrome.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*asReactfrom'react'exportfunctioncreateElement(tweetData){constheader={height:'33%',width:'100%',overflow:'hidden'}constheaderImage={height:'100%',width:'100%',objectFit:'cover'}return(<divstyle={header}><imgsrc={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'
constelement=createElement(tweetData)constpage=awaitbrowser.newPage()awaitpage.setContent(`<html>
<head>
<style>
body {
width: "${width}";
height: "${height}";
}
</style>
</head>
<body>${renderToString(element)}</body>
</html>
`)constimage=awaitpage.$('body')constbuffer=awaitimage.screenshot({encoding:'base64'})
補足
日本語が混じったDOMをそのままスクリーンショットしてしまうと、日本語のフォントがPuppeteer
に存在しないので文字化けしてしまいます。
ですのでgooglefontsなどのcdnからfontを読み込む様にします。
awaitchrome.font('https://rawcdn.githack.com/googlefonts/noto-cjk/be6c059ac1587e556e2412b27f5155c8eb3ddbe6/NotoSansCJKjp-Regular.otf')awaitchrome.font('https://rawcdn.githack.com/googlefonts/noto-fonts/ea9154f9a0947972baa772bc6744f1ec50007575/hinted/NotoSans/NotoSans-Regular.ttf')
撮ったスクリーンショットをクライアントに返す
上述しましたが、クライアント側のエンドポイントは/api
になります。
(いくつかのレポジトリでこうなってたので参考にしました。)./api/index.ts
で、リクエストを受けレスポンスを返す処理を行います。
exportdefaultasync(req,res)=>{const{id}=req.queryres.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'exportdefaultasync(req,res)=>{constresult=awaitgetTwitterData(req.query)// Twitterのデータ取得constsvgImage=awaitcreateCard()// 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のプロフィールを表示させると悲しくなるので注意が必要
— ivgtr (@iVgtr) January 22, 2021