GitHub の URL を自動で OGP 画像に置き換える
いつぞやから GitHub の各種 URL はいい感じの OGP 画像を埋め込んでくれるようになりました。
前に、こういう GitHub カード的なのが欲しくて Elm でいい感じにカードを構築するツールを作ってましたが、本家の方がかっこいいので置き換えることにします。 本記事はそのメモ書きです。
こんな感じ
URLを [og:image](url)
って感じに書いておくと、こんな感じになります。
og:image
の後に style="width: 500px"
などを記述すると、そのまま HTML の img タグの属性として利用してくれる仕様です。
実装
このサイトは Slick というツールを使っています。 Markdown から HTML への変換は、内部的にはファイルを読み込んで Pandoc にかけるだけです:
buildPost :: FilePath -> Action Post
= cacheAction ("build" :: T.Text, srcPath) $ do
buildPost srcPath <- readFile' srcPath
postContent <- markdownToHTML' @(Record FrontMatterParams) (T.pack postContent)
postData ...
markdownToHTML'
で Pandoc を使っています。 postContent
はただの String
型の値で、Markdown のテキストです。 なので、これに簡単な置換処理をかけます:
buildPost :: FilePath -> Action Post
= cacheAction ("build" :: T.Text, srcPath) $ do
buildPost srcPath <- replaceLinkToOGImage =<< readFile' srcPath
postContent <- markdownToHTML' @(Record FrontMatterParams) (T.pack postContent)
postData ...
replaceLinkToOGImage :: MonadIO m => String -> m String
=
replaceLinkToOGImage md fmap unlines $ forM (lines md) $ \line ->
case parseMaybe ogImageTagParser line of
Just (attrs, url) ->
maybe line (buildEmbedImage attrs url) <$> fetchOGImage url
Nothing ->
pure line
各行に対してまず、[og:image attrs](url)
をパースして属性とURLを取り出します。 その URL 先の HTML を取得し、og:image
のメタタグから画像の URL を取り出します。 そして、それらを元にして Markdown をいい感じに置き換えます。
パース
パースには megaparsec パッケージを使います:
type Parser = Parsec Void String
ogImageTagParser :: Parser (String, String)
= do
ogImageTagParser <- Parser.string "[og:image"
_ <- Parser.printChar `manyTill` Parser.char ']'
attrs <- Parser.char '('
_ <- Parser.printChar `manyTill` Parser.char ')'
url pure (attrs, url)
manyTill
を使うことで2つ目のパーサーが成功するまで1つ目のパーサーを繰り返し実行します。 結果として ]
が出るまでの文字列をパースするなどができ、この方法で属性とURLを取得しました。
スクレイピング
HTMLの取得には req パッケージを使いました(割愛)。 そして、取得した HTML から任意の HTML 要素を取得するには scalpel-core パッケージを使います:
fetchOGImage :: MonadIO m => String -> m (Maybe String)
=
fetchOGImage url case Req.useHttpsURI =<< mkURI (Text.pack url) of
Nothing ->
pure Nothing
Just (url', opts) -> do
<- fetchHtml url' opts
html pure $ scrapeStringLike html ogimgaeScraper
ogimgaeScraper :: (Show s, StringLike s) => Scraper s String
= toString <$> attr "content" ("meta" @: ["property" @= "og:image"]) ogimgaeScraper
property="og:image"
を持つ meta
HTML タグの content
属性を取得しているだけです。 scrapeStringLike
によって Maybe String
として、その結果を取得しています。
置き換える
最後は集めた要素を利用して、いい感じに置き換えるだけです。 画像の拡大縮小をしたいので ![](url)
ではなく <img src="url">
を使いました:
buildEmbedImage :: String -> String -> String -> String
=
buildEmbedImage attrs url image "[<img src=\"" ++ image ++ "\"" ++ attrs ++ " >](" ++ url ++ ")"
これを Pandoc に食わせるだけで、いい感じな HTML にしてくれます。
おまけ:静的ファイルのサーブ
Hakyll と異なり、Slick は静的ファイルのビルドまでで、localhost でサーブするような機能は提供していません。 slick-template には、npm の serve パッケージ を利用する例が書いてありますが、できれば Haskell だけでなんとかしたいですよね。
ということで、scotty を利用して簡単な静的ファイルをサーブするやつを作りました:
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Data.Maybe (fromMaybe, listToMaybe)
import Network.Wai.Middleware.Static (staticPolicy, addBase, (>->), noDots)
import System.Environment (getArgs)
import Web.Scotty
main :: IO ()
= do
main <- fromMaybe "docs" . listToMaybe <$> getArgs
path 8080 $ do
scotty $
middleware $ addBase path >-> noDots
staticPolicy "/" $
get "/index.html" redirect
scotty は Ruby の Sinatra にインスパイアされた極めてシンプルな Web フレームワークです。 静的ファイルをサーブするのには wai-middleware-static パッケージを使っています。