Hugoでブログカードに対応する

こんにちはnasustです。

今回は、Hugoでブログカードに対応する方法を紹介します。

私のもう1つのブログは、はてなブログを使用しています。 そこで便利なのがブログカードです。

こういう風にカード状のデザインでリンクを表現してくれます。

残念なことにHugoの標準の機能では対応していないです。

ブログカードの仕組み

ブログカードはリンク先のheadタグの中のOGPタグを読み取って表示しています。

OGP(Open Graph protocol)とは元々Facebookで作成されたタグです。 現在では幅広く使用されています。

The Open Graph protocol

このブログのページにも以下の様に設定しています。

<meta property="og:title" content="Hugoでブログカードに対応する | nasust dev blog" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://nasust.com/hugo/tips/blogcard/" />
<meta property="og:image" content="https://nasust.com/images/hugo_eyecache.59b40f2ad83fc37df27606b4237783817f6b1193f93149fe56985f158f70be51.png" />
<meta property="og:site_name" content="nasust dev blog" />
<meta property="og:description" content="こんにちはnasustです。 今回は、Hugoでブログカードに対応する方法を紹介します。 私のもう1つのブログは、はてなブログを使用しています。..." />
html

Twitterで表示されるTwitter CardもこのOGPタグを読み取って表示しています。

Hugoでブログカードを実現するには

ショートコード作れば出来ると思われるかもしれませんがHugo単体では出来ません。

Hugoのテンプレートの関数には外部のHTMLを読み取ってパースする機能がありません。

ですが別の外部とやり取りする関数があります。

getJSON

この関数は指定したURLからJSONのレスポンスを取得します。 つまりOGPを取得してJSONを返すAPIサーバーを作成すれば実現出来ます。

Data Templates | Hugo

JSON APIサーバー

import * as express from 'express';
import * as client from 'cheerio-httpcli';

const app = express();

app.get("/getogp", (expressRequest, expressResponse, expressNext) => {
    const url = expressRequest.query.url;
    client.fetch(url, (err, $, res, body) => {
        if (err) {
            expressNext(err)
            return;
        }

        const result = {
            exists: false,
            title: "",
            description: "",
            url: "",
            image: "",
            site_name: "",
            type: "",
        }

        const ogTitleQuery = $("meta[property='og:title']");

        if (ogTitleQuery.length > 0) {
            result.exists = true;
            result.title = $("meta[property='og:title']").attr("content");
            result.description = $("meta[property='og:description']").attr("content");
            result.url = $("meta[property='og:url']").attr("content");
            result.image = $("meta[property='og:image']").attr("content");
            result.site_name = $("meta[property='og:site_name']").attr("content");
            result.type = $("meta[property='og:type']").attr("content");
        } else {
            result.title = $("head title").text()
            result.description = $("meta[name='description']").attr("content");
        }

        expressResponse.json(result);
    });

})

app.listen(6060, () => console.log('Listening on port 6060'));
typescript

Node.jsとTypeScriptでogpタグを返すスクリプトを書きました。

このスクリプトを起動して、getJSON http://localhost:6060/getogp?url=WebページのURLでOGPタグのJSONを取得します。 以下のコードはgetJSONの使用例です。querify関数はquery stringの文字列を返します。

{{ $getURL := printf "http://localhost:6060/getogp?%s" (querify "url" $url ) }}
{{ $json := getJSON $getURL }}
html

querify | Hugo

$jsonはサーバーが返したJSONをそのまま格納されています。$json.titleはog:titleのcontentの文字列が入っています。

この$jsonを使用してブログカードのショートコードを作成します。

warningWarning
getJSONは高速化の為に結果をキャッシュします。 キャッシュにヒットすると通信を行わず結果のJSONを返します。 デバックの為に必ず通信したい場合は、hugo –ignoreCacheを使用するかurlにタイムスタンプを付与します。

ショートコード

{{ $url := .Get 0 }}
{{ $json := getJSON printf "http://localhost:6060/getogp?%s" (querify "url" $url )  }}

<a class="m-blog-card" href="{{$json.url | safeHTMLAttr }}">
    {{ with $json.image }}
        <div class="m-blog-card__eyecache">
            <img src="{{ .  | safeHTMLAttr }}" />
        </div>
    {{ end }}
    <div class="m-blog-card__title">
        {{ $json.title }}
    </div>
    {{ with $json.description }}
        <div class="m-blog-card__description">
            {{.}}
        </div>
    {{ end }}
    {{ with $json.site_name }}
        <div class="m-blog-card__site_name">
            {{.}}
        </div>
    {{ end }}
</div>
html

ブログカードのショートコードテンプレートの例です。

{{< blogcard “WebページのURL” >}} で上記のHTMLがレンダリングされます。

まとめ

このブログカードのショートコードの様に外部のプログラムと連携する場合はgetJSONを利用します。 ローカルでAPIサーバーを立ち上げる事により、プラグインっぽい事が出来るので色々応用出来ます。

例えばAPIサーバーがコマンドを実行して、その結果をJSONを返すなど出来ます。 あるページのサムネイル画像が取得したい場合はAPIサーバーがヘッドレスブラウザを立ち上げてスクリーンショットを撮るコマンドを実行するなど。

色々と連携出来そうです。

prevnext