🎉

Qiita/Zennの投稿をGitHubプロフィールに自動反映するためのツールを作った

2021/02/21に公開
3

概要

こんなツイートを見ました。

天才か! かっこいい!こういうことをシュッと出来るの尊敬しかない。自分でもまねっこしてみたかったので Go でツール化してみました ╭( ・ㅂ・)و ̑̑。

https://github.com/ikawaha/feedsnippet

インストール

go install github.com/ikawaha/feedsnippet@latest

コマンド

$ feedsnippet --help
Usage of feedsnippet:
  -config string
    	config file
  -debug
    	print raw fees for debug (optional)
  -diff
    	replace snippets only when there is a difference (optional)
  -file string
    	target file (optional)
  -verbose
    	print snippets to stdout (optional)

使い方

config にフィードの取得設定を書いていきます。config は yaml ファイルになってます。

項目 内容
urls フィードのURL。複数指定可能
template フィードを表示するためのテンプレート。省略可能。省略時はデフォルトのテンプレートが利用されます
sort_by_published trueのとき、フィードを作成日時でソートします。省略可能
reverse trueのとき、フィードを逆順に並べ直します。省略可能
limit 上位何件を表示対象にするかを指定できます。0の場合は全てを対象とします。省略可能

template は Go のテンプレートの記法を利用します。

最小構成

Zenn と Qiita のフィードをそれぞれ5件ずつ取得して並べる最小構成の設定は次のように書けます。

- urls:
    - https://zenn.dev/ikawaha/feed
  limit: 5
- urls:
    - https://qiita.com/ikawaha/feed
  limit: 5

この設定での出力はこんな感じです。

テンプレートを利用してフィードの表示をカスタマイズする

テンプレートを使えば出力する項目を整形できます。テンプレートは Go のテンプレート記法です。テンプレートでは、対象レポジトリにアイコン用の画像ファイルを用意しておいてフィード元ごとにアイコンを指定しています。アイコンはレポジトリの適当なところに先に置いておきました(画像は @mikkame san のレポジトリからいただきました。)

- urls:
    - https://zenn.dev/ikawaha/feed
  template: |-
      **Zenn**
      {{range . -}}
        * ![](./icon/zenn.png) [{{ .Title }}]({{ .Link }})
      {{ end }}
  limit: 5
- urls:
    - https://qiita.com/ikawaha/feed
  template: |-
      **Qiita**
      {{range . -}}
        * ![](./icon/qiita.png) [{{ .Title }}]({{ .Link }})
      {{ end }}
  limit: 5

出力例:

複数のフィードを混ぜ合わせて表示する

Zenn と Qiita のフィードを混ぜ合わせて更新日でソートして上位10件を取り出します。

- urls:
    - https://zenn.dev/ikawaha/feed
    - https://qiita.com/ikawaha/feed
  template: |-
    {{range . -}}
      * {{ if eq .Header.FeedLink "https://zenn.dev/ikawaha/feed" -}}
            ![](./icon/zenn.png)
        {{- else }}{{ if eq .Header.FeedLink "https://qiita.com/ikawaha/feed" -}}
            ![](./icon/qiita.png)
        {{- end }}{{ end -}}
      [{{ .Title }}]({{ .Link }})
    {{ end }}
  limit: 10
  sort_by_published: true

出力例:

テンプレートの設定方法

フィードのアイテムが与えられるので、基本的にはそれらを range で回して表示していきます。テンプレートの書き方は Go のドキュメント を参考にして下さい。ちょっと結構クセがあります。フィードのアイテムのフィールドは gofeed.Item の項目になります。Header という項目に gofeed.Feed へのリンクがあります。

ファイル内の Feed Snippet を置き換える

コマンドの file オプションにファイルを指定すると、ファイルの Feed の該当位置を新しいもので置き換えます。仕組みとしては

<!--[START github.com/ikawaha/feedsnippet]-->

<!--[END github.com/ikawaha/feedsnippet]-->

というタグを目印にこの範囲を置き換えます。正規表現でマッチさせて置き換えるだけなので、入れ子にしたりとか出来ません。更新すると日付を一緒に埋め込むので、フィードが更新された日時を知ることが出来ます(コミットの日時を見ればいい気もしますが)。新規に埋め込むときは埋め込む範囲に上の STARTEND を埋め込んでおいて下さい。

GitHub Actions で README.md の更新を自動化

name: Update feed snippet

on:
  workflow_dispatch:
  schedule:
    - cron:  '0 0 * * *'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Set up Go
        uses: actions/setup-go@v2
        with:
          go-version: 1.16

      - name: Install feedsnippet
        run: go install github.com/ikawaha/feedsnippet@latest

      - name: Update README.md
        run: feedsnippet -config feedsnippet.yml -diff -file README.md

      - name: git commit
        run: |
          git config --local user.email "ikawaha@users.noreply.github.com"
          git config --local user.name "ikawaha"
          git add README.md
          git diff --cached --quiet || (git commit -m "Update feed snippet" && git push origin main)

こういう感じで仕掛けておくと1日に1回差分があるときだけ更新してくれます(これも mikkame san) のを参考にさせていただきました。)。差分がなくても更新してしまいますが、そこは愛嬌です。 追記(2021-02-27 18:12):修正されています -diff オプションを追加しました

付録

feedsnippet でいうところのフィードは、gofeed でいうところの gofeed.Item に相当します。フィード全体の情報はフィードから Header として参照できます。

type Feed struct {
        Header *gofeed.Feed
        *gofeed.Item
}

以下に gofeed で扱えるフィールドをツールのフィードの項目と対応させて転記しておきます。gofeed ホントにすばらしいライブラリ!

フィード情報

gofeed.Item RSS Atom JSON
Title /rss/channel/item/title
/rdf:RDF/item/title
/rdf:RDF/item/dc:title
/rss/channel/item/dc:title
/feed/entry/title /items/title
Description /rss/channel/item/description
/rdf:RDF/item/description
/rss/channel/item/dc:description
/rdf:RDF/item/dc:description
/feed/entry/summary /items/summary
Content /rss/channel/item/content:encoded /feed/entry/content /items/content_html
Link /rss/channel/item/link
/rdf:RDF/item/link
/feed/entry/link[@rel=”alternate”]/@href
/feed/entry/link[not(@rel)]/@href
/items/url
Updated /rss/channel/item/dc:date
/rdf:RDF/rdf:item/dc:date
/feed/entry/modified
/feed/entry/updated
/items/date_modified
Published /rss/channel/item/pubDate
/rss/channel/item/dc:date
/feed/entry/published
/feed/entry/issued
/items/date_published
Author /rss/channel/item/author
/rss/channel/item/dc:author
/rdf:RDF/item/dc:author
/rss/channel/item/dc:creator
/rdf:RDF/item/dc:creator
/rss/channel/item/itunes:author
/feed/entry/author /items/author/name
GUID /rss/channel/item/guid /feed/entry/id /items/id
Image /rss/channel/item/itunes:image
/rss/channel/item/media:image
/items/image
/items/banner_image
Categories /rss/channel/item/category
/rss/channel/item/dc:subject
/rss/channel/item/itunes:keywords
/rdf:RDF/channel/item/dc:subject
/feed/entry/category /items/tags
Enclosures /rss/channel/item/enclosure /feed/entry/link[@rel=”enclosure”] /items/attachments

ヘッダ情報

gofeed.Feed RSS Atom JSON
Title /rss/channel/title
/rdf:RDF/channel/title
/rss/channel/dc:title
/rdf:RDF/channel/dc:title
/feed/title /title
Description /rss/channel/description
/rdf:RDF/channel/description
/rss/channel/itunes:subtitle
/feed/subtitle
/feed/tagline
/description
Link /rss/channel/link
/rdf:RDF/channel/link
/feed/link[@rel=”alternate”]/@href
/feed/link[not(@rel)]/@href
/home_page_url
FeedLink /rss/channel/atom:link[@rel="self"]/@href
/rdf:RDF/channel/atom:link[@rel="self"]/@href
/feed/link[@rel="self"]/@href /feed_url
Updated /rss/channel/lastBuildDate
/rss/channel/dc:date
/rdf:RDF/channel/dc:date
/feed/updated
/feed/modified
/items[0]/date_modified
Published /rss/channel/pubDate /items[0]/date_published
Author /rss/channel/managingEditor
/rss/channel/webMaster
/rss/channel/dc:author
/rdf:RDF/channel/dc:author
/rss/channel/dc:creator
/rdf:RDF/channel/dc:creator
/rss/channel/itunes:author
/feed/author /author/name
Language /rss/channel/language
/rss/channel/dc:language
/rdf:RDF/channel/dc:language
/feed/@xml:lang
Image /rss/channel/image
/rdf:RDF/image
/rss/channel/itunes:image
/feed/logo /icon
Copyright /rss/channel/copyright
/rss/channel/dc:rights
/rdf:RDF/channel/dc:rights
/feed/rights
/feed/copyright
Generator /rss/channel/generator /feed/generator
Categories /rss/channel/category
/rss/channel/itunes:category
/rss/channel/itunes:keywords
/rss/channel/dc:subject
/rdf:RDF/channel/dc:subject
/feed/category

Happy hacking!

Discussion

ikawahaikawaha

差分があるときだけ更新できるようにコマンドに -diff オプションを追加して、GitHub Workflow の例を修正しました

ikawahaikawaha

GitHub Workflow の記述に間違いがあって、差分があるのに push されないというバグを修正しました。

正:

          git diff --cached --quiet || git commit -m "Update feed snippet"; git push origin main

誤:

          git diff --cached --quiet || git commit -m "Update feed snippet" || git push origin main
ikawahaikawaha

記事中の上の修正箇所を微調整して、cron の更新頻度を1日1回に修正

修正

git diff --cached --quiet || (git commit -m "Update feed snippet" && git push origin main)