ブログにツイートを自動で埋め込みできるようにした。markdown の中にツイートへの直リンクがある場合、以下のように表示される。
https://twitter.com/ushiro_noko/status/123772055698153472
通常の埋め込みツイートと少し違うが後述する API を用いていることが理由。
tweetic.io というツイートを静的な HTML に変換して返してくれる API をつかった。HTML 文字列の他にツイート URL など以下の形式の付属情報も返してくれる。
{
html: string;
meta: {
url: string;
author_name: string;
author_url: string;
html: string;
width: number | null;
height: number | null;
type: string;
cache_age: string;
provider_name: string;
provider_url: string;
version: string;
}
}
yarn generate
時のフックに以下のような処理を書くことで、レンダリング前の HTML に API から返却された HTML を差し込む形で実装した。
ざっくり言えば yarn generate
時に html に含まれるツイートへの直リンクを正規表現で取得して、tweetic.io の API を Promise.all する。帰ってきた HTML を page.html
に対して順番に差し込んでいくようにしている。
nuxt.config.ts
generate: {
page: async (page: { route: any; path: any; html: string }) => {
const matchers = /"https:\/\/twitter.com\/.+?\/status\/[0-9]+?"/gi
const matchedUrls = page.html.match(matchers)
if (!matchedUrls) return
const targetUrls = matchedUrls.map((url) => url.replace(/"/g, ''))
if (!targetUrls.length) return
await Promise.all(
targetUrls.map(async (url) => {
return await axios.get<TweeticResponse>(
'https://www.tweetic.io/api/tweet',
{
params: {
layout: '',
url,
css: '',
enable_twemoji: true,
show_media: true,
show_quoted_tweet: true,
show_info: true,
} as TweeticParams,
}
)
})
)
.then((res) => {
const dom = new JSDOM(page.html)
const aTags = dom.window.document.querySelectorAll('a')
const replaceList: { target: string; staticHtml: string }[] = []
aTags.forEach((aTag) => {
if (aTag.parentElement?.tagName === 'P') {
res.forEach(({ data }) => {
const {
html: staticHtml,
meta: { url },
} = data
if (aTag.parentElement && aTag.href === url) {
replaceList.push({
target: aTag.parentElement.outerHTML,
staticHtml: `${staticHtml}<p><a href="${url}" target="_blank" rel="noopener noreferrer">${url}</a></p>`,
})
}
})
}
})
page.html = replaceList.reduce((acc, { target, staticHtml }) => {
return acc.replace(target, staticHtml)
}, dom.serialize())
})
.catch((err) => {
consola.error(err)
})
},
},
なぜこうしたかというと API を使ってみたかったから以外ない。普通のツイート埋め込みに比べてツイートに対するアクションができなかったり、スレッドを作れなかったり、動画が再生できなかったりして不便ではある。また generate フックでやっているので yarn dev
中には結果を見ることができない。
https://twitter.com/miyaoka/status/1346664808402206721
https://twitter.com/miyaoka/status/1346853138259611648
またビルドするごとに API コールするためエコではない。Nuxt 3 に移行して ISR できるようになればこの問題は解決するが、その他の不便さは API が頑張ってくれるしかない気もしている。
当初は hast や rehype プラグインを使って ast からごにょごにょしようとしていたが、やっていくうちに DOM API が使いたくなり、hast to DOM をしてみたが jsdom で良い感が強くこのやり方に落ち着いた。
また miyaoka.dev や zenn.dev のように WebComponents で実装することもできた(このブログも js を全部抜いている)が、二番煎じは面白くないので没にした。
https://miyaoka.dev/posts/2021-01-07-tweet-component
2022/07 tweeticio のアップデートでメディア表示ができるようになっていた。リクエストパラメータでフラグを付与すればおk。