Go でモブログシステム作った
この記事は Go Advent Calendar 2020 24日目の記事です。
はじめに
皆さんは「モブログ」ってご存じでしょうか。
モブログとは、携帯電話などの携帯通信端末からインターネットに接続して閲覧・投稿ができるブログ(ウェブログ)のことである。
携帯電話などを利用して書き留められた短い文章や、あるいはカメラ機能で撮影された写真などが記事の主な内容となる。常に携帯するものなので、ふと思いついた所感を臨場感が損なわれないうちに記すこと、あるいは街角で見かけた面白い光景や偶然出会った有名人などを逃さず撮影することなどが可能となる。1日のうち特定の時間にパソコンの前に構えて書かれる通常のブログよりも一層、リアルタイム性が色濃く、独特の臨場感や軽快さがある。
ちなみに、モブログはモバイル(mobile)とブログ(blog)が組み合わされた造語である。和製英語ではないので、英語圏でもmoblogといえば通用する。
まだ皆がガラケーを使っていた頃や、スマートフォンでまだリッチなコンテンツを表示できなかった頃、ブログを投稿する方法として活用された仕組みです[1]。人々は手持ちのガラケーやスマホで写真を撮り、決められたメールアドレスに写真を添付しメールを送信します。ブログサービスはメールを受信すると以下の様に振る舞います。
- 送信元が決められたメールアドレスかどうかチェックする
- メールの表題をブログのタイトルにする
- メールの本文をブログの本文にする
- メールに添付された画像ファイルを本文の末尾に付け加える
この仕組みは当初、多くのブログサービスが提供し、多くのユーザが利用してきました。しかしながらスマートフォンが普及しブラウザでリッチなコンテンツを表示できる様になった最近ではあまり名前を聞かなくなってしまいました。
メールは死んだプロトコルなのか
そんな事はありません。多くの人は毎日の様に Gmail を開き、メールを確認します。メールアドレスという個人に割り当てられた識別は、多くのウェブサービスでの認証にも使われ、良くも悪くも今や欠く事のできないプロトコルになってしまいました。
メールと連携するシステムも未だに健在します。モブログだけではありません。例えば皆さんがお使いの GitHub issues、あれもメールに返信すると issue にコメントが追加される仕組みになっています。
モブログは今でも有益か
さてこのモブログ、何故名前を聞かなくなってしまったのでしょうか。考えられる可能性としては各ブログサービスがスマホ向けのブログ投稿画面を提供する様になった事だと言えるでしょう。
しかしブログサービスを使っていない、例えば自分で GitHub Pages の様なブログシステムを運用しておられる方は、今も尚 PC を開き Markdown を扱えるテキストエディタで記事を書き、git を操作しないといけません。
モブログが登場したきっかけである「ふと思いついた所感を臨場感が損なわれないうちに記すこと」「あるいは街角で見かけた面白い光景や偶然出会った有名人などを逃さず撮影すること」「リアルタイム性」「独特の臨場感や軽快さ」全てが無くなってしまいます。
ご自分で Markdown を WYSIWYG[2] で編集できる画面を用意する事もできますが、わりと難易度が高かったりもします。
GitHub Pages の様なブログシステムを使っている人はモブログを諦めないといけないのでしょうか。そんな事はありません。無いならば作る、それが我々プログラマの命題ですね。
メールを受信してプログラムを動かす仕組み
メールを送信し、そのメールを元にブログを更新するにはメールサーバと連携する何かが必要になります。これを実現する簡単な方法が postfix[3] と procmail[4] です。
Postfix は言わずと知れたメールサーバ(MTA: Mail Transfer Agent)です。このメールサーバにはスパムメールを削除したり独自のプログラムを動かす為に procmail というプログラムと連携する仕組みが用意されています。Postfix で procmail を使うには main.cf
に以下を足すだけです。
mailbox_command = /usr/bin/procmail
procmail は /etc/procmailrc
でグローバルな設定を行う事も可能ですが、各ユーザのホームディレクトリに .procmailrc
を置く事でユーザ毎の設定を行う事も可能です。UNIX でメールを扱うには一般的に mbox もしくは Maildir という2つの形式が使われます。僕は Maildir なので以下の様に設定しました。
PATH=/bin:/usr/bin
MAILDIR=$HOME/Maildir
DEFAULT=$MAILDIR/
SPAM=$MAILDIR/.spam/
LOCKFILE=$HOME/.lockmail # Maildir形式の場合はコメントアウトする(パフォーマンスが下がる)
LOGFILE=$HOME/.procmail.log # ログ出力先
#VERBOSE=ON # 詳細ログ出力
# メールヘッダー中に「 X-Spam-Status: Yes 」の記述があれば、「 .Spam 」ディレクトリにメールを格納する
:0
*^X-Spam-Status: Yes
$SPAM
# 送信先が「XXXXXXXX@example.com」の場合は moblog コマンドを実行する
:0
* ^X-Original-To: XXXXXXXX@example.com
| /home/mattn/dev/moblog/moblog
先頭に |
を付けてコマンド名を指定すると、そのコマンドの標準入力に RFC2822 で決められたメールテキストが渡ります。
あとはメールを解析してブログを投稿する仕組みを作れば良いことになります。
ちなみに sendgrid や mailgun といったメール配信サービスの多くでは、受信したメールを外部のウェブサーバに REST 形式で POST してくれる機能を持っている物もありますが、多くのサービスは有料です。お金の無いお小遣い制のお父さんや、学生さんには厳しい世の中なのです。
Go でメールを解析するには
Go にも net/mail というパッケージが用意されています。ReadMessage
という関数を使い、本文やヘッダを得る事はできるのですが、メールプロトコルには現代では信じられない様なしきたりが今も多く存在し、当たり前の様に使われています。7ビットだけで通信する為に作られた quoted-printable
[5] や、base64
を用いて表現されたメールの表題(Subject)や、添付ファイルを表現した MIME Multipart[6] の本文などがそれにあたり、ReadMessage で得られる Message は生の値がそのまま格納されています。内容を見てパースしないといけないので面倒ですね。そこで便利なのが enmime
というパッケージです。
enmime
を使うと前述の様な、quoted-printable
や base64
でエンコードされた表題や本文が UTF-8 のまま扱え、添付ファイルもバイト列で扱えます。procmail
からは標準入力を介してメールテキストが渡されますので、以下の様にメールをパースします。
env, err := enmime.ReadEnvelope(os.Stdin)
if err != nil {
log.Fatalf("cannot parse e-mail: %v", err)
}
Markdown を扱いたい
今回ターゲットにする「ブログサービスを使っていない人達」の多くは HTML を直接書いているか、もしくは GitHub Pages 等を使い Markdown を書いておられる方です。メールで送られてきた HTML メールをそのまま Markdown の本文として表現しても良いのですが、既存の投稿もありますし、出来れば Markdown で統一したいですよね。そこで便利なのが godown
というパッケージです。
godown
は HTML を Markdown に変換する為のパッケージです。元々、何の為に作られたかというと僕のブログは blosxom[7] という Perl で出来たブロギングツールを使っているのですが、そろそろ Markdown ベースの物に移行しようと考え、既存の HTML 記事を Markdown に変換する目的で作りました。結果としては未だに blosxom を使っているので用無しになってしまいましたが、今回の記事で有効活用できる事になりました。
if env.HTML != "" {
var buf bytes.Buffer
if err := godown.Convert(&buf, strings.NewReader(env.HTML), nil); err == nil {
env.Text = buf.String()
}
}
body := strings.ReplaceAll(strings.ReplaceAll(env.Text, "\r", ""), "\n", "\n\n")
こんな感じに、HTML メールであれば Markdown に変換する事にしました。
こうする事で
- WYSIWYG なエディタで作成されたメールの場合はそちらを Markdown として
- プレインテキストなエディタで作成されたメールはそのまま Markdown として
どちらも Markdown として扱える様になりました。Gmail の WYSIWYG エディタを使ってもいいですし、テキストでそのまま Markdown を書いて貰っても良いです。
画像の添付ファイル
さてモブログと言えば写真ですね。enmime は良く出来ていて、メールのインライン画像と添付画像を個別に扱えます。画像ファイルの場合はファイルとして保存し、本文を差し替えます。
n := 1
for _, attachment := range env.Inlines {
if !strings.HasPrefix(attachment.ContentType, "image/") {
continue
}
file := filepath.ToSlash(filepath.Join("assets", slug+fmt.Sprintf("-%03d.jpg", n)))
err = saveJpeg(file, attachment)
if err != nil {
log.Fatalf("cannot write attachment file: %v", err)
}
marker := "[image: " + attachment.FileName + "]"
text = strings.ReplaceAll(text, marker, fmt.Sprintf(`![%s](%s)`, attachment.FileName, "/"+file))
n++
}
添付画像の場合は本文の後ろに Markdown の画像タグを付け加えます。
Git で push
さてこれで本文と添付画像が保存できたので、あとは記事を投稿するだけです。メールの添付ファイルから取り出した本文と画像ファイルを既存の Jekyll の _posts
に記事を置き、assets
に画像を置きます。ゴミ掃除の為に以下を実行してから git add & git commit & git push しています。
- git reset
- git checkout .
- git reset --hard HEAD
- git clean -fdx
動いている物
実はずいぶん前に無料ドメインを取った際にジョークで登録した unko.ga
というドメインがあり、Jekyll のリポジトリとして構築し Netlify に deploy していました。
Netlify は MX を設定できるので、postfix の my_network
にこの unko.ga
も登録しました。もちろん SMTP プロトコルはメールアドレスを知っていると誰でも偽装できてしまうので(DKIM 等を使う事もできますが)、今回は以下の様なランダムな文字列だけに反応します。
[ランダムな文字列]@unko.ga
gmail は WYSIWYG なエディタで HTML メールを送信する事ができるので、試してみます。
インライン画像を貼り付け送信します。
すると Netlify でデプロイが開始されます。
正常にデプロイが環境しブログ記事が投稿されました。
無事記事が投稿されました。本当はちゃんとスマホから写真付きで投稿すべきでしたが、さすがに💩を撮影する訳にはいかないので(ry
今回工夫した点としては以下の通り。
- 画像をそのまま保存せず image パッケージで一度読み込む様にした。これにより EXIF を削除できる[8]。また大きい画像をリサイズできる。
- HTML をそのまま使わず Markdown にする事で利便性を上げた。
- オプション指定を可能にして汎用的な物に仕上げた。
今回作った物は以下の GitHub リポジトリに置いてあります。ご自分で運用されてみたいと思われた方はご自由にお使い下さい。
まとめ
Go でモブログシステムを作ってみました。一見、既に廃れてしまった技術の様にも感じますが、応用方法は沢山あります。外部のシステムと連動させる事も出来るでしょう。e-mail という技術的負債の塊を使う事に少し辛さがありますが、おそらく e-mail は今後も使われ続けていくでしょう。その中で古い技術を掘り起こし、新たなイノベーションを作り出すのも楽しい事だったりします。
-
もはやモブログを知っているだけで老人扱いされる可能性も微レ存 ↩︎
-
WYSIWYG: What You See Is What You Get の略 ↩︎
-
=0C
の様に=
を用いて8ビット文字を表現するプロトコル https://ja.wikipedia.org/wiki/Quoted-printable ↩︎ -
https://ja.wikipedia.org/wiki/Multipurpose_Internet_Mail_Extensions ↩︎
-
昔は幾らかのブログサービスで EXIF を消してなくて住所バレする案件などあった。 ↩︎
Discussion