続々・ブログをはてなブログからHugo+Cloudflare Pages(+R2)に移行しました

Page content

ブログの移行話の続き。

Twitter投稿

  • 前回書いた通りで、IFTTTにブログのRSSを監視してもらい、更新を受けてLambdaの関数URLに通知する。IFTTTの設定は以下の通り。
    Trigger:
    20240202-0001-blog-update-after-migratio-2.png
    これはまあ簡単。RSSのURL(https://blog.ricemountainer.net/index.xml)を指定するだけ。
    Then:
    20240202-0002-blog-update-after-migratio-2.png
    URLにはLambdaの関数URLを指定。MethodはPOST、Contet-Typeはapplication/json、Bodyに{"EntryUrl":"<<<{{EntryUrl}}>>>"}と指定する。"<<<>>>"の部分は、項目の設定個所に「JSONの形を保ちたいなら<<<で囲めばエスケープできるぜ」という注意書きがあったので、とりあえずつけてみた。実際には、URLには意図しない改行やダブルクォーテーションが混入することはまず考えられないため、多分必要はないと思われる。多分これは、タイトルとか、もっと自由なテキスト項目に対して用意された仕様だろう。
  • このIFTTTの設定でLambdaに回ってくると、設定したBodyパラメータは以下の内容で送信されていることが確認できた:
    {
      ...
      "body": "{ \"EntryUrl\" : \"https://blog.ricemountainer.net/posts/2024/01-27-straightener/\"}",
      ...
    }
    
    ちょっと意外だったのは、bodyの中身自体がJSON.strigifyした後の文字列というところか。つまり、実際に値を取り出すためには、JSON.parse(event.body).EntryUrlみたいに、一回パースが必要になる。まあ、大した話ではないが。
  • このURLをもとに、fetchでブログにアクセスの後、タイトルとって(JSDOM使ってパースしてdom.getElementsByTagName('title')[0].firstChild.nodeValue;<title>タグの値を取得)、ちょっと文言成形したのち、Twitterに投稿する。Twitterに投稿するためのOAuth2トークンは、以前Instagramの投稿をTwitterに回すツールを作ったときに構築した仕組みで定期的に更新されているDynamoDBから取得する。IFTTTから記事のタイトルも回せるようだが(それができるならわざわざfetchとかする必要もなくなるわけだが)、上述したエスケープ関連の部分が若干不安だったので、やめた。テストするのも面倒くさいし。

ping送信

調べてみると、いろんなpingの通知先サーバーが死んでたり、仮に生きててもping先によって異なるリクエストヘッダやボディの準備が必要だったりして、結構面倒くさかった。とりあえず確認した限りで生きてるところへのping送信は実装したが…思うところは後述する。。今回のping先に関する調査結果はざっくりと以下。

  • にほんブログ村 : マイページで自分専用のping URLが確認でき、単純にここにGETリクエスト送るだけ(パラメータも一切不要)でXML-RPCの結果が確認できたので、これでOKとする。これは一番簡単な例。
  • 人気ブログランキング : これも日本ブログ村と同じで、マイページで自分専用のping URLが確認できるんだが、試した限りでは単純にGETリクエスト送るだけではだめで、サイトマップのデータをPOSTでリクエストボディにセットして送信しないと反応してくれなかった。curlでいえばcurl -H "Content-Type: text/xml" -d "``curl [サイトマップのURL]``" [自分専用のping url]というかんじ。
  • Bing : http://www.bing.com/ping?sitemap=エンコード変換したサイトマップアドレスに投げればいいというような情報がいろいろなところから出てくるが(こことかこことか)、いろいろ試した限りでは全部HTTP 4xx系でレスポンスが返ってきたので、まあもうだめなんだろうなと思ってあきらめることにした。(そんなに真面目に追跡していない)
  • Google : ここでXML-RPCクライアント、RESTなどいくつかの方法が紹介されているが、curlでRestのURL叩いてもHTTP 302になるだけで成功っぽいレスポンスは返ってこなかったので、これも追跡するのが面倒になってやめた。Search Consoleにはインデックスが生成されていることが確認済だったのもあきらめに拍車をかけた。
  • Yahoo : 海外を含めたいくつかのサイトで http://api.my.yahoo.com/RPC2 がpingの送信先として使えるというような情報を目にしたが、そもそも api.my.yahoo.com の名前解決ができない。(CNAMEは返ってくるんだがAが返ってこない)これもよくは知らんけどまぁもう死んでるんだろうと思って潔くあきらめた。追跡する時間がもったいない。。。
  • Pingomatic : XML-RPC形式でデータつくって http://rpc.pingomatic.com/ にPOSTすればとりあえず通った。curlのスクリプトとしては以下。curl -H "Content-Type: text/xml" -H "Content-Length: 217" -H "User-Agent: Mozilla/5.0" -d '<?xml version="1.0"?><methodCall><methodName>weblogUpdates.ping</methodName><params><param><value>ricemountainer-blog</value></param><param><value>https://blog.ricemountainer.net/</value></param></params></methodCall>' -X POST http://rpc.pingomatic.com/
    • PingomaticはUse-Agent: Mozilla/5.0をいれておかないとエラーになるという話をどっかの海外のブログで見かけて、実際エラーになったのでこのcurlの例では入れてるんだが、これ書いてる今ふと振り返ると、実際の処理(axios.post)ではあえてUser-Agentをセットするようなことはしてないので、(この場合は多分axiosのUser-Agentが自動的にセットされて使用されるんだと思うが)あんまり関係ないかもしれない。あるいはPingomaticがcurlを弾いてるだけかも??

というわけで今のところ生きていると確認できたping通信先は「にほんブログ村」と「人気ブログランキング」と「pingomatic」の3つだけだ。もうちょっと探れば「死んでる」と判断したほうにもpingできる可能性もあるが、あまりやる気もないのでいったんこれで打ち止めにする。上記の3つのping先について、axios使ってgetやらpostでリクエスト投げる処理を実装した。言ってしまえばそれだけなんだが、リクエスト先ごとにリクエストヘッダ・メソッド・パラメータ全部違うので、基本的にすべて個別の処理として実装せざるを得ず、なんとも切ない出来上がりになった。結局ping先は3つしかないし、振り返ってみると「それ、わざわざ必要なんスか??(;‘∀’)」って自問したくなるレベルだ。わかる。俺自身そう思うもん。。

いろいろ調べてなんとなくわかったが、そもそも「ブログの更新通知としてのping」っていう概念自体がもう若干オワコンになってる雰囲気を感じる。実際、今の時代、SNSからの流入のほうが多いだろうし、Googleにインデックスさえされていればほかのサービスってそんな重要か?という気もするしな、ぶっちゃけ。しかもSEO重視の若干胡散臭い(偏見)ブログが「2023年最新版!」みたいな記事書いてるの見ると、かなり流動性の激しい情報のようだし、効果不明なそれに重点的に付き合うつもりは正直、ない。さらにもっと追加で言うと、このブログに関して言えば、最早あまり大量のアクセスを求めてすらいない。技術的な好奇心(実装してみたい欲)はあるが、オワコンに付き合うほど暇でもないし、まあそんなマジになって取り組む必要もないか、というのが今の感想である。

年月集計の改造

  • 前提として、前回の記事で、すべての記事にarchives: ["2024","2024-01"]のような形でTaxonomyを付与した。これをもとに

    {{- $archives := .Site.Taxonomies.archives }}
    {{- range $name, $taxonomy := $archives }}
      ...
    {{- end}}
    

    みたいにやってたのが前回までの実装で、残課題は

    1. 年月の降順にしたい(最新の年月を上のほうにもってきたい)
    2. 最初は年(YYYY)までの表示にして、年月(YYYYMM)のほうは閉じておきたい
    3. 年(YYYY)のほうを押すと年月(YYYYMM)が開閉するつくりにしたい

    だった。

  • 1.降順にする - は以下で実装できた。

    {{- $archives := .Site.Taxonomies.archives }}
    {{- range $archives.Alphabetical.Reverse }}
      <!-- ここに諸々の処理 -->
    {{- end }}
    

    $archives.Alphabetical.Reverseで「降順にソートされたコレクションが返却される」もんだと思って$sortedArchives := $archives.Alphabetical.Reverseとかやってたんだが、実際のところよくわかってないんだが、$archives.Alphabetical.Reverseはいわゆるvoidメソッドみたいなやつらしく、これの戻り値はないようだ(printfすると<nil>とか出てくる)。一方でrangeの引数に渡すとループは可能らしく、とりあえずこれで降順は実現できた。どうも.Site.Taxonomies.archivesで取得できるのはTaxonomy Objectというやつである一方、AlphabeticalReverseを挟むとOrdered Taxnomyという「別オブジェクト」になるようで、そもそも取り扱いが異なるようだ。最初の$archives := .Site.Taxonomies.archivesの時点から「配列」が取得できているもんだと思ってたがその認識からしてもそもそも勘違いだった。

  • 2.最初は年(YYYY)までの表示にして、年月(YYYYMM)のほうは隠す - は、前回の時点でなんとなくできてはいたんだが、これは単純に「文字列の長さが4だったら表示」で制御している。前回より少しスマートにはなったけど本質的には同じで、以下のような感じで実装

    {{- range $archives.Alphabetical.Reverse }}
      {{- $p := .Page.LinkTitle }}
      {{- if eq (len .Page.LinkTitle) 4 }}
        <!-- ここにレンダリングの処理 -->
      {{- end }}
    {{- end }}
    

    $archives.Alphabetical.ReverseはOrderedTaxonomyオブジェクトなので、配下に.Pageオブジェクトを持っている。ただまあ今回は単純に「名前」という「文字列」が欲しかったので、{{- $p := .Page.LinkTitle }}で文字列変数を定義して取得し、{{- if eq (len .Page.LinkTitle) 4 }}でその文字列長が4だったら…という処理に流している。

  • 3.年(YYYY)のほうを押すと年月(YYYYMM)が開閉する - は実現までに2段階あった。まず、メインのループとなる$archives.Alphabetical.Reverseだが、単純にこれでソートすると返ってくる順が2024-01 -> 2024 -> 2023-12 -> 2023-11 -> … -> 2023という風に、YYYYMMのほうが先に来てしまう。つまり、単純にメインループを回すと、隠すはずのYYYYMMが先にレンダリングされてしまって、とてもやりづらい。というわけで、YYYYMMのほうは別にして後回しにする必要があった。このため、処理の最初にまずYYYYMM専用の配列を別途用意した。簡単のために中身はただの単純な文字列の配列である:

    {{ $childArchives := slice }}
    {{- range $archives.Alphabetical.Reverse }}
      {{- if ne (len .Page.LinkTitle) 4 }}
          {{ $childArchives = $childArchives | append .Page.LinkTitle }}
      {{- end }}
    {{- end }}
    

    で、メインループは$archives.Alphabetical.Reverse+上述したように{{- if eq (len .Page.LinkTitle) 4 }}でYYYYのほうだけをレンダリングするように制御したうえで、そのifブロックの中(各YYYYのレンダリングの途中)に追加でYYYYMMのループを組み入れる:

    {{- range $childArchives }}
      {{- $c := . }}
      {{- if eq (strings.Contains $c $p) true }}
        <!--  YYYYMMのレンダリング処理 -->
      {{- end }}
    {{- end }}
    

    ただ、$childArchivesには、すべてのYYYYMMが格納されているので、親のYYYYでループ中の年とは違う値も回ってくる。このため、子YYYYMMの現在値$c(={{- $c := . }}でセット)に、親の現在値$pが含まれるかどうか?をstrings.Contains $c $pで検査して、親の現在値に属する子供の場合だけ子YYYYMMのレンダリングの処理に入る。(親YYYYが"2024"なら子YYYYMM"2024-01"は処理対象とするが"2023-12"等は処理対象にしない、といった具合の制御)このあとは親も子も大体同じレンダリング処理(aタグつくって/archives/配下にリンク張るだけ)だが、親YYYYのほうはOrderedtaxonomyを扱ってるので、そのYYYYに何件あるかを.Countで簡単にアクセスできたのに対し、子YYYYMMのほうはただの文字列の配列なので、件数とるために一度本体{{- $archives := .Site.Taxonomies.archives }}にアクセスして、Taxonomyを取得しないとならなかった。なので子YYYYMMの件数は以下みたいに取得している:

    {{- $ca := $archives.Get $c }}
    ...
    ({{ $ca.Count }}) <!-- <- これが子YYYMMの件数 -->
    

    これ、一発で.Countにアクセスするようないいかんじのことはできないらしく、一度TaxonomyをGetでアクセスして変数に格納しておかないとエラーになった。つまり$archives.Get $c .Countみたいに書くとエラーになった。この辺、正確な言語仕様はよくわかっていない。ただまあ一応これでやりたいことはできたので、いったんOKとする。

    • 上記のHugo Templateとの格闘は、とりあえず実現できればいいやという感じで結構無理やり乗り切ったので、もうちょっとスマートにできそうな感じはする。。例えば、最初に子YYYYMMの配列をつくるところなんかは、rangeでループしながらifで弾くんじゃなく、range whereとか使ってもうちょっときれいにできないもんかなとか、子の配列をただの文字列じゃなく、Taxnomy Objectの配列にしておけば、件数とるためだけにあとでGetでわざわざTaxnomyを取得する処理なんか不要になっただろうなとか、いろいろ思うところはある。多分工夫すればできるんだろう。ただ、ここに時間かけるのは個人的な優先度からして低かったので、動くもんができたからとりあえずいいやと思うことにした。
  • で、2段階目として、フロント側の開閉処理を書く。子YYYYMMにレンダリング部全体をdisplay: none;で基本非表示にしておき、親YYYY側に適当なspanタグ用意してonClickでJavascript起動->カスタムJsファイルにtoggleの処理(display:none;display:block;を判定させる単純な処理)を記述し、それを呼び出すように実装。基本的にこれで終了。ひとつ詰まったのは、最初は子YYYYMMのレンダリングブロックに対してclass属性を付与していたdなが、Javascriptでdisplay属性を操作する場合、ベタ書きされたdisplay属性だけが対象で、classのほうはシカトされるらしく(参考)、仕方ないのでstyle=""でスタイル指定した。あんまり好きじゃないんだが。Javascipt側の操作でもすっげー久しぶりにdocument.getElementByIdとか書いたし、令和の時代にどんだけレガシーなコード書いてるんだという気にはなったが、まあ動いたからいいかという気持ちであまり深いこと考えないことにしておく。

  • この関連で、archetypeにarchives: ["{{ dateFormat "2006" .Date }}" , "{{ dateFormat "2006-01" .Date }}"]のtaxnonomy定義を追加した。これで、hugo newしたときに自動的にarchivesの値が移行で付与したのと同様の形式で追記される。

その他

基本的にこれでコアな部分でやりたいことは終わった、はず。前回「他にやりたいこと」で上げた、Preview Deploymentsを使ったステージング環境の構成や、Deploy Hookによる毎日定時再起動なんかは、まあ興味はあるけど別に必須でもないし、後回しでもいいかなという感じ。追々考える。かも。ブログの開発作業以外にもやりたいことがあるもんで、こればかりに時間をかけてもいられないので、どこかでいったん区切りをつけないととは思っていたので、まあここまでくれば一旦はいいだろう、という感じだ。一方、1月の中旬以降くらいから、かなり急ごしらえでここまでかこつけたので、気づいてないところで色々考慮漏れや課題は残ってる気はしないでもない。なるべく継続的に注視していく予定である。

そうそう、そういう意味では、記事ごとにOGP画像変えるってのも、できるならやってみたいね。はてなブログだとできてたからね。今はOGPはブログ内全記事で共通なので、たとえばライブいってきた記録でも俺の描いたアイコンがドカッとOGPに表示されてしまう。ライブハウスの写真載せたりしたいからね。調べてみるか。

そんな感じ。ではまた。