詩と創作・思索のひろば

ドキドキギュンギュンダイアリーです!!!

Fork me on GitHub

コマンドラインでメールの内容に基づいた処理をするツールを書いた: letterknife

メールに対する jq みたいなやつ……というと強力すぎるけど、そういう感じにメールを入力に受け取って何かしらの処理をした上で出力してくれるツールです。ここでいうメールとは MIME 形式のメール全体。Gmail なら "Show original" で見られるようなもの。

結局ターミナルでメールを読むことにした に書いたとおり最近はターミナルでメールを読むようになりそこそこ快適なんだけど、メールとの接し方がプログラマブルになったからには楽をしたい。だいたい通知のメールのこの部分をクリックするだけ(そして承認したりコメントしたりする)、みたいなパターンが決まってるものには DWIM(do what I mean)的にキー入力一発で対応したいわけです。それをやるためのツールとして書いた。珍しく名前を気に入ってる。

GitHub - motemen/letterknife

使い方

$ cat <email> | letterknife [条件] [パートの選択] [アクション]

というふうに標準入力に MIME メッセージ全体を渡し、適当な出力を得る、という感じ。

たとえば、

  • @example.com からのメールの場合
  • HTML メールのパートを選択して
  • 表示する

ってことをしたければ

$ letterknife --match-address "From:*@example.com" --select-part text/html --print-content < eml

という感じ。省略形も用意していて

$ letterknife --from "*@exmaple.com" --html

だけでもいい。ちなみに From の条件がマッチしなかった場合や HTML なパートが存在しなかった場合は、コマンドは失敗する。

もう一つ例を用意すると

  • 件名に「請求」が含まれていたら
  • 添付の PDF を
  • ファイルとして保存する

であれば

$ letterknife --subject "*請求*" --select-attachment application/pdf --save-file

とする。標準出力には一時的に保存されたファイルへのパスが出力されるのでこれを mv するなどしたらよろしい。

オプションの全体はこんな感じ。まあやりたいことはだいたいできるんじゃないでしょうか。

% letterknife --help
Usage of letterknife:
      --from <pattern>                     Shortcut for --match-address 'From:<pattern>'
      --subject <pattern>                  Shortcut for --match-header 'Subject:<pattern>'
      --html                               Shortcut for --select-part text/html
      --plain                              Shortcut for --select-part text/plain
      --match-address <header>:<pattern>   Filter: address header <header>:<pattern> eg. "From:*@example.com"
      --match-header <header>:<pattern>    Filter: header <header>:<pattern> eg. "Subject:foobar"
      --select-part <content-type>         Select: non-attachment parts by <content-type>
      --select-attachment <content-type>   Select: attachments by <content-type>
      --print-content                      Action: print decoded content
      --print-header <header>              Action: print <header>
      --print-raw                          Action: print raw input as-is
      --save-file                          Action: save parts as files and print their paths
      --debug                              enable debug logging

letterknife でメールの定形処理をシェルスクリプト化する

あとはこれを使ってちょっとしたシェルスクリプトを書いて、neomutt からメールをパイプするキーを割り当てればキー一発で DWIM 的な処理を行える。

サンプルとして書くとこういう感じ:

#!/bin/bash

set -eo pipefail

mail=$(cat)

url=$(
  { letterknife --from '*@github.com' --html | pup 'a:contains("view it on GitHub") attr{href}'; } <<< "$mail" || \
  { letterknife --from '*@example.com' --plain <<< "$mail" | grep https://... | tail -1; } <<< "$mail" || \
  false
)

if [ -n "$url" ]; then
  if [ -n "$DEBUG" ]; then
    echo "$url"
  else
    open "$(echo "$url" | tr -d '\r\n')"
  fi
fi

letterknife を || でつなぎつつ、条件にマッチするまで何度も呼び出している。すでに手元だと10件以上の条件があってだんだん遅くなっていくんだろうなーと思うけどマウスでクリックするよりはだいぶマシ。HTML メールは pup で処理するとやりやすい。最終的にはメール中のひとつの URL を得て、ブラウザで開くだけ、というわけなのさ……。

結局ターミナルでメールを読むことにした

最近は集中力を高める活動をしていて手をキーボードから離したくないんだけど、Gmail のウェブビューは Tab でフォーカスしにくいんじゃ~! メール上でやりとりすることってあんまりなくて、アップデートとして送られてきたメール中のリンクをたどりたいことが主なのでこのキーボードナビゲーションの悪さは割と困る。HTML メールを表示する難しさってのがあるんだろうなあ。

ともあれ毎回マウスやトラックパッドに手を伸ばすのはやめたいので、いっそターミナルでメール読んだらいいじゃんってことで Neomutt を使ってみることにした。ターミナルの MUA(Mail User Agent)である mutt のフォークらしい。ハッカーっぽくてかっこいいよな! 以下、macOS 向け設定メモ。

アカウント設定

Gmail にアクセスするために、アプリパスワードを発行してキーチェーンに登録しておく。neomuttrc の設定には生の値のほかにコマンドを書けるのでこういう感じでいいと思う。

set imap_pass = "`security find-generic-password -a *** -s *** -w ~/Library/Keychains/login.keychain-db`"

あとは Gmail 側で IMAP に表示、としたものを全て同期することにする。最初下書きフォルダなどが何も同期されなかったんだけど、Gmail の言語設定を英語にしたら出てきた。ここは特にこだわりなかったのでこれで済ませている。日本語のラベルも英語にすると neomutt 側から確認できるようになった。

set imap_check_subscribed

HTML メールを見る

ターミナルで見るゾ! って言ってもテキストメールよりは HTML メールのほうが情報量多いのであるならこっちを見たい。auto_view と mailcap って仕組みを使って、HTML メールをいい感じに読むことができる。

# neomuttc
set mailcap_path = ~/.config/neomutt/mailcap
# text/html なパートがあったらそれを優先して描画
auto_view text/html
# mailcap
# 'v' を使って個別にパートを見るとき。w3m をインタラクティブに使う
text/html; w3m -I %{charset} -T text/html; needsterminal
# 一覧からメールを開いたとき。テキストとして描画
text/html; w3m -I %{charset} -T text/html; copiousoutput

w3m では Alt-Shift-M で外部ブラウザを起動できるので主にこれで Chrome を開く。

ほかにもメール中のリンクを開くのに urlscan というプログラムを使う方法もある。これは mutt のコンパニオンである urlview の置き換えらしいぜ。

macro index,pager \cB "<pipe-message>urlscan<Enter>"

GUI で読みたいメールの処理

などなど設定していればターミナルで十分メールは読める。とはいえ視覚的に確認したいメールもある。

毎日確認したいダッシュボードのスナップショットのようなものであれば Google Data Studioのスクリーンショットを定期的にSlackに投稿するツールを作った - 日直地獄 のような方法を使って Slack で確認する、でいい。

突然やってきたメールの場合はもう Gmail ウェブで読むしかない。Gmail API を利用すればメールの Message-Id から Gmail 上のパーマリンクを取得することもできるんだけど、認証したり API 叩いたりとややまだるっこしい。簡単にやるならメールから Meesage-Id を取得して、こんな URL を開くといい。

https://mail.google.com/mail/u/0/#search/rfc822msgid:<msgid>

Message-Id で検索した結果を開く。ここから Enter もう一回押さなきゃいけないけどねー。

メッセージを外部プログラムに渡せるのが便利

ともあれ、<pipe-message>(キーボードショートカットだと |)でメッセージの全体を外部プログラムに渡せるのが便利。上のように Message-Id から URL を開く、みたいなのもちょっとコードを書いてしまえば実現できるわけだし。

そういうわけでメールを条件に必要なアクションを起こすための部品プログラムを書いたりしている。そのうち紹介します。


ちなみに GUI アプリだと id:cockscomb に教えてもらった Mimestream ってのがよさそうだった。

ターミナルからTickTickのタスクを確認する

TickTick はまだ使ってるぞよ! しかし新年の高揚感もとうに果てて、なんとか新鮮新奇さを演出してモチベーションを維持しようとしている状態。またできるだけタスクを確認するためのコストをゼロに近づけていく必要がある。最近はやっぱなんでもキーボードでできるの大事だなって気持ちが高まっているので、そうする。なんかウィンドウやタブの切り替えが下手になってきたような気がするんだよなー。

CLI で TickTick のタスクを一覧するツールを書いてみたのだけど、今回は API を使わずに macOS の Open Scripting Architecture を使ってみた。AppleScript を書いて使うことが多いあれだ。長(おさ)ともかかっている(ここまで同僚情報)。

Raycast の extension がやってたのでその真似なんだけど、OSA 経由でアプリに問い合わせるということにすると面倒な認証系の作業が不要なので非常に楽だった。手元に TickTick のアプリケーションがインストールされている必要があるけど、かなり有力な手段だと思う。

osascript -l JavaScript -e 'Application("TickTick").next7daysTasks()' | jq '.[0].tasks[0]'

なんてことができる。アプリがどんな API を提供しているかは、スクリプト エディタ.app を開いて Cmd+Shift+O すると開く「用語説明(英語だと scripting dictionary?)」から確認できる模様。

で、作った GitHub - motemen/cli-ticktick-osa を使うと、こんな感じに見える。サンプルです。

これをさらに oh-my-zsh の magic-enter で呼び出すように設定していて、ホームディレクトリで enter したら即タスク一覧が表示されるようになっている。ここまでやれば否が応でもタスクを直視するだろう。だよな……?

はてなで一緒に働きませんか?