はてなダイアリーにEmacsから投稿

Emacsからはてなダイアリーに投稿できるようにする試みはやり尽くされている感じがするけれど, 古すぎたりどうもしっくりこなかったりしたので, 自分で一から実装した.


やりたいこと

必須
  • はてなダイアリーEmacsから投稿したい
  • 下書きの読み書きもしたい
  • Emacsのバッファを保存したら反映されてほしい
  • ローカルディスクにはダウンロードしなくていい
    (やりたかったらバッファの保存をフックしてごにょごにょすれば済む)
  • はてな記法シンタックスハイライトしてほしい
  • Emacs以外のものをできる限り必要としない

基本的にまずは下書きとしてプレビューしながら書いていって, 書き上がったら公開というワークフローなので, 下書き機能への対応は必須. 公開済みの文章の誤植を修正した場合などは, 保存したら即時反映でかまわない.

あったら嬉しい
  • はてなブログに対応
    • 今はAPIもないので仕方ないけれど, そのうち移行したいので
  • はてなグループ対応
    • そんなに長文を書くわけでもないのでそこまで必要ではない

既存のやり方

hatena-mode

hatena-modeによれば, http://d.hatena.ne.jp/hikigaeru/20040617#p1を参照せよということだけど, プライベートモードになっていて閲覧できないので使えず.

hatena-diary-mode

http://hatena-diary-el.sourceforge.jp/

  • hatena-modeから派生したプロジェクトらしい.
  • 下書きは未対応?

はてな記法をハイライトするために, メジャーモード部分だけでも使おうかと思ったけれど, なんか2色くらいしか定義されてなくて割と残念な感じに表示されたのでやめた.

simple-hatena-mode

http://coderepos.org/share/wiki/SimpleHatenaMode

  • はてダラをインストールする必要あり
  • 下書きは未対応でFAQには運用でカバーせよとある
  • グループ日記にも対応
  • 複数アカウントに対応

下書きの対応が微妙なところがつらい. はてな記法のハイライトはだいぶましだけど(ソースコードを見た限りは)個人的にはやっぱり気き入らない感じだった.

あとは最初試そうとしたときにCodeReposが落ちていて, あんまりCodeReposに上がっているパッケージは使いたくないな, と思った. なので実際に試してはいない. これを使うせいで, はてダラCodeReposに依存してしまうのはリスクだと判断した.

hatedara-mode

http://d.hatena.ne.jp/amt/20070828/HatedaraMode

simple-hatena-modeがこれを参考に機能拡張されている感じなので試さなかった.

ya-hatena-mode

https://github.com/takaishi/ya-hatena-mode

  • AtomPubで投稿
  • 下書きに対応
  • 必要なものはEmacsだけ
  • メジャーモードは無い
  • 日記一覧などはAnythingを使う

設計思想としてはこれが一番しっくりくる. メジャーモードはないけれど, 欲しかったら別のパッケージを使ってね, というのもよい. ただ, 実装を洗練させる前に開発者が別のブログに移行してしまったようなので完成度があと一歩なのが残念. たとえばふつうのバッファの保存でアップロードしてくれるようなことはない. あと個人的には無駄にAnythingを使ったインタフェースは操作しにくかった.

最終的には, 最初はこれのAPI部分を使い回しつつインタフェースのプロトタイプを実装して, 動くようになったらAPI部分もインタフェースとあわせやすいように再設計して実装しなおした*1.

HatenaDiaryFS

http://tarao.hatenablog.com/entry/20091102/1257201079

  • fuseとAtomPubを使って日記エントリをローカルファイルのように見せる
    • Emacsでふつうにファイルとして保存すると反映される
  • 下書きに対応

今まではこれで書いていた. すべてのものがファイルとして表現されて, アプリケーションはファイルさえうまく扱えればよい, というのは思想的には非常に素晴らしい. ただこれは実際のところ以下の点が結構めんどくさかった.

  • 使うときにマウントして終わったらマウント解除
  • 新規作成と編集が異なる操作
  • 保存がけっこうもっさりする

つくったもの

hatena-diary.el
はてなダイアリーを操作するユーザインタフェース
hatena-diary-api.el
はてなダイアリーをAtomPubで操作するAPI
hatena-markup-mode.el
はてな記法のためのメジャーモード
hatena-multi-mode.el
スーパーpre記法部分だけ別のメジャーモードにする
特徴
  • AtomPubで日記を投稿
  • 日記や下書きを一覧できる
    • buffer-menuのようなインタフェース
    • 一覧しながら簡易プレビューも可能
  • 編集したらふつうに保存(save-buffer)するだけで反映される
    • 新規作成で保存するとそこからは自動的に既存記事の編集になる
  • サブアカウントにも対応
  • 実装の手抜きの関係でEmacs 24でないと動かない
    • 具体的にはtabulated-list-modeに依存
配布場所と依存パッケージおよびインストール方法
ファイル URL 依存パッケージ
hatena-diary.el https://gist.github.com/4465244
hatena-diary-api.el https://gist.github.com/4465244 flim
hatena-markup-mode.el https://gist.github.com/4428666
hatena-multi-mode.el https://gist.github.com/4475652 multi-mode, multi-mode-util

上記ファイル(のうち必要な機能のもの)と依存パッケージをダウンロードしてload-path上のどこかに置く. el-getを使っている人のために末尾にレシピを記載.

flimへの依存はsha1-el.elだけなので, flim全体をインストールしなくてもこれだけ入れれば問題ない.

設定

パッケージ

基本的にはrequireするだけ.

(require 'hatena-diary)

hatena-markup-modeを編集時に使いたい場合は, 続けて

(require 'hatena-markup-mode)
(setq hatena:d:major-mode 'hatena:markup-mode)

などとしておく. これをしなかった場合の初期値はhtml-modeになっている.

スーパーpre記法の内部で別のメジャーモードを自動的に有効にする場合は

(require 'hatena-multi-mode)
(add-hook 'hatena:markup-mode-hook #'hatena:multi-mode)

などとしておく. この機能自体はhatena-markup-modeとは独立しているので, hatena:markup-mode-hookの部分は使いたいメジャーモードに合わせて適宜変更する.

アカウント

アカウント情報は, 最初にサーバにアクセスする際に入力を促される(一度入力すればEmacsを終了するまで記憶される). もし毎回入力するのが嫌な場合は, 以下のようにして設定できる.

(setq hatena:username "ユーザ名"
      hatena:password "パスワード")

実際には上記のように書いてパーミッション600等で保存したファイルをロードするようにした方がよい.

サブアカウントを常に使いたい場合は

(setq hatena:d:username "ユーザ名")

とすると認証は本アカウントのままでサブアカウントの日記を操作できる. (一時的にサブアカウントを使う方法は後述.)

追記: 2014-11-30

2014-03-05時点で, 通常のログインパスワードでの認証は廃止されています. hatena:d:passwordAPIキーを指定することで, 引き続き利用可能です. 自分の日記のAPIキーは, メール投稿の設定画面から確認できます. 「投稿用メールアドレス」の@の前の部分がAPIキーです.

アカウント設定を暗号化する

EmacsではEasyPG Assistantでファイルを簡単に暗号化できるので, これを使うのが簡単.

まず, ~/.emacs.d/.hatena-credentials.gpgというファイルを作成して,

(setq hatena:username "ユーザ名"
      hatena:password "パスワード")

の設定を書いておく. 保存すると, 暗号鍵を訊かれるので適当に選択する.

アカウント情報を読み込む設定では

(load "~/.emacs.d/.hatena-credentials.gpg")

とすれば.elでなくても読み込めて, 読み込むときに復号化される.

復号化のためのパスフレーズEmacs起動時に入力したくないというような場合は, hatena-diary.elをrequireせずにautoloadで読み込むようにして, eval-after-loadでアカウント情報を読み込むのがよい.

(autoload 'hatena:d:list "hatena-diary"
  "List Hatena::Diary blog entries in a buffer." t)
(autoload 'hatena:d:list-draft "hatena-diary"
  "List Hatena::Diary draft entries in a buffer." t)
(eval-after-load 'hatena-diary
  '(load "~/.emacs.d/.hatena-credentials.gpg"))
Evilユーザのための設定

Evilを使っている場合のみ必要な設定.

日記や下書きの一覧でのキーがEvilのキーと衝突しないようにするには以下の設定が必要(Evil本体に書かれているBuffer-menu-modeの設定と同じことをする).

(push 'hatena:d:list-mode evil-motion-state-modes)
(evil-make-overriding-map hatena:d:list-mode-map)
(evil-add-hjkl-bindings hatena:d:list-mode-map 'motion)

hatena-multi-mode.elを使う場合は

(require 'multi-mode+evil)

も忘れずに. (ViperやVimpulseを使っている場合はmulti-mode+viper.)

使い方

コマンド
M-x hatena:d:list
日記一覧を開く
M-x hatena:d:list-draft
下書き一覧を開く
C-u M-x hatena:d:list
ユーザ名(サブアカウント)を指定して日記一覧を開く
C-u M-x hatena:d:list-draft
ユーザ名(サブアカウント)を指定して下書き一覧を開く
M-x hatena:d:new
新しい日記を書く
M-x hatena:d:new-draft
新しい下書きを書く

一覧を開いてしまえば, エントリを選択して編集したり, ショートカットキーで新規作成したりできるので, 最悪最初の2つだけ覚えておけばいい.

日記一覧

日記一覧では以下のキーが利用可能.

q
一覧を閉じる
g
一覧を更新
p
前のエントリを選択
n
次のエントリを選択
N
次のページを読み込む
RET
エントリを編集
V
view-modeでエントリを開く
v
プレビューモードのon/off
d
削除マークをつける
P
公開マークをつける(下書き一覧の場合のみ)
u
マークを消す
x
マークのついたエントリに操作を適用
c
新しい日記を書く
C
新しい下書きを書く

一覧は最初に開いた時点では直近20件しか取得しない. 以降はNキーを押すか, あるいは一覧の一番下までカーソルを持っていくと, 次の20件を読み込む.

プレビューモードがonになると, 別ウィンドウに選択中のエントリのプレビューを表示するようになる. 日記一覧の場合, プレビューはHTML断片で, w3mがインストールされていればw3mを使ってレンダリングし, そうでなければhtml-modeでソースを表示する. 下書き一覧の場合, プレビューは元のはてな記法のソースで, 編集時に使うメジャーモードで表示する.

編集

日記エントリの場合は~/.emacs.d/hatena/diary-USER-YYYYMMDD-1234567890, 下書きエントリの場合は~/.emacs.d/hatena/diary-draft-USER-1234567890のような仮想的なファイル名が付与される(1234567890の部分はUNIX時刻). また新規作成時はnew-hatena-blog-entry, new-hatena-draft-entryのような名前でバッファを開いて, 初回保存時に日付を伴ったファイル名に変更される. 実際にこれらのファイル名が使われるのは, AtomPubによるアップロードが失敗した場合と, hatena:d:no-auto-savenilに設定していて自動バックアップが作られる場合のみなので, ディレクトリ~/.emacs.d/hatena/を作らないでおいても動作する(アップロードが失敗した場合は単に保存に失敗したことになる).

保存は通常のバッファの保存(save-buffer)でできる. 保存によってアップロードが成功した場合は, "HTTP/1.1 200 OK""HTTP/1.1 201 Created"のようなメッセージが表示される.

編集時に使うメジャーモードはhatena:d:major-mode変数で設定すること.

スーパーpre記法ごとにメジャーモードを変える


hatena-multi-mode.elを使うと, スーパーpre記法の内部では指定した言語のメジャーモードを使うようにできる. ただし, スーパーpre記法の言語の名前とEmacsのメジャーモードの名前は必ずしも対応しないので調整が必要な場合もある*2.


スーパーpre記法で指定した言語に対応するメジャーモードが見つからない場合は, その言語指定部分をハイライトした上で"Cannot find major mode for file type 'LANG'"のようなメッセージが出るので, その場合は以下のいずれかの対処が必要.

  1. LANGと同名(もしくはLANG-modeという名前)のメジャーモードは存在するけれど, Emacs標準では入っていないという場合は, そのメジャーモードをインストールする
  2. LANGとは別名のメジャーモードを使いたい場合はhatena:mm:filetype-alist変数に指定する

たとえば,

 >|ocaml|
 ...
 ||<

というスーパーpre記法でtuareg-modeが使いたいなら,

(push '(ocaml . tuareg) hatena:mm:filetype-alist)

とする.

あるいは, そもそもocaml-modeという名前の関数をtuareg-modeの別名として扱ってしまっても問題ない場合は, hatena:mm:filetype-alistには手を加えずに,

(fset 'ocaml-mode 'tuareg-mode)

のようにした方がいい.

ちなみに, LANGがファイルの拡張子そのもので, auto-mode-alistでその拡張子のためのメジャーモードを指定している場合は, 何もしなくても正しくメジャーモードが選択されるので, 気にしなくてもいい.

メジャーモードはカーソル位置によって切り替わり, 裏側では別のメジャーモードでは別の間接バッファに移動するようになっているので, カーソルがスーパーpre記法の内部に入らないとハイライトされない. 加えて, font-lock-modeの一般的なやり方を想定しているため, メジャーモードがあまりに特殊な場合はうまくいかないこともある.

既知の問題や未対応な部分

  • パスワードを間違えて入力するとどうしようもなくなる
  • 更新日時を指定したアップロードができない(APIはあるけれどインタフェースがない)
  • はてなブログ未対応
  • はてなグループ日記未対応

1つ目はそのうち修正する予定. 2番目は要望があればなんとかするかも. 下2つはAPIが提供されない限り対応するつもりはない.

所感

この日記をさっそくhatena-diary.elで書いてみたところ, HatenaDiaryFSと比べて保存時の動作が圧倒的に軽快で, とても心地よかった. 以前書いた日記を参照するのも楽で助かる.

実装は細かな修正を除けば2日くらいでできてしまって楽ちんだった. tabulated-list-modeべんり.

追記

2013-01-10T22:14+0000 ブックマークコメントへの返信

id:serian ちょっとした更新モードあるかな?

ちょっと未確認なのですが, AtomPub APIの仕様で言うところのupdated要素が「ちょっとした更新」ではない更新のために指定するものだとすると, 現状の実装ではすべての更新は「ちょっとした更新」になる(updatedは指定しない)ようになっています. 逆にupdatedを指定した更新は現状ではできません(要望があればできるようにするかもしれません).

確認しました. AtomPub APIで「ちょっとした更新」を扱う方法はないようです(少なくともupdatedを指定したかどうかで違いはありませんでした). 保存した場合は必ずふつうの更新になります.

2013-01-11T14:26+0000 機能追加
  • C-u M-x hatena:d:listサブアカウントの日記一覧を開けるようにした
  • M-x hatena:d:save-as-draftで日記エントリを下書きとして保存できるようにした
2013-01-12T10:54+0000
  • アカウント情報を暗号化して保存しておく方法を記載
2013-01-26T02:25+0000 機能修正
  • アカウント情報を設定していない場合は入力を促すようにした
  • 編集バッファでメジャーモードを変更してもアップロードができるようにした
  • スーパーpre記法内で保存すればアップロードされるようにした
2013-03-25T09:57+0000 バグ修正
  • ユーザIDが「-」を含むとうまく動作しなかったのを修正

*1:細かいところでは, ya-hatena-modeAPIでは日記一覧が1ページしか読み込めないというのも, API部分を再実装するに至った理由

*2:はてなスーパーpre記法のシンタックスハイライトは, サーバ側でVimを起動してハイライトした上で, それをHTMLに書き出すという方法でやっているはずなので, 言語の名前もVimのsyntax/*.vimの名前と同じものになっている