「楽天ブログに自動で記事を投稿したい」というタイトルから脱線してきたのでそのまんまの件名に変えました。
前回はIMAPプロトコルで狙ったメールの受信を行うプログラムを書きました。
メールを解析した後は不要なのでメーラを使って手作業で消していたのですが、毎回はさすがに面倒なので解析の流れでプログラムで削除まで行うことにしました。
IMAPプロトコルではフォルダ間のメッセージ移動が可能なので、受信箱に届いたメッセージをゴミ箱へ移動させることにします。
String host = 'imap.mail.yahoo.co.jp'
String user = '******'
String pass = '******'
String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory"
Properties properties = System.properties
properties.setProperty('mail.imap.socketFactory.class', SSL_FACTORY)
properties.setProperty('mail.imap.ssl.trust', '*')
properties.setProperty('mail.debug', 'true')
Session session = Session.getInstance(properties)
URLName urln = new URLName('imap', host, 993, null, user, pass)
Store store = session.getStore(urln)
store.connect()
IMAPFolder inbox = store.getFolder('Inbox')
inbox.open(Folder.READ_WRITE)
SearchTerm[] stlst = [
new SubjectTerm('日の日記'),
new ReceivedDateTerm(ComparisonTerm.EQ, new Date()),
new FromStringTerm('no-reply@plaza.rakuten.co.jp')
]
SearchTerm st = new AndTerm(stlst)
List<Message> target = []
inbox.search(st).each { Message msg->
println("Subject : ${msg.getSubject()}")
println(" From : ${msg.getFrom()}")
println(" ReplyTo : ${msg.getReplyTo()}")
target.add(msg)
}
if (!target.isEmpty()) {
Message[] targetArray = target.toArray(new Message[0])
inbox.moveMessages(targetArray, store.getFolder('Trash'))
}
inbox.close()
store.close()
ポイントは store.getFolder('Inbox')
をcom.sun.mail.imap.IMAPFolder
で受けているところです。
メッセージの移動はIMAP固有の機能なのでjavax.mail.Folder
にはmoveMessage
sメソッドが定義されていません。
問題発生
これで課題解決かと思ったのですが実際に実行してみると例外が発生します。うーん?デバッグ出力を有効にしてみるとMOVEコマンドでエラーが発生していました。
A6 MOVE 15,33 Trash A6 NO [CANNOT] MOVE It's not possible to perform specified operation
プログラムの書き方の問題ではなくIMAPサーバ側でコマンドを拒否しているようです。メッセージの移動を一切禁止するとかあるのだろうか。
ちなみに上記の A6
というのはコマンドとその応答を紐づけるものです。並列実行を考慮した実装となっているそうです。
原因の調査
さてプロトコル系でうまくいかないときは正常に動いているものを参考にするのが定石です。Thunderbirdという無料メーラーはログを出力する機能があるのでそれを利用することにしました。
環境変数 NSPR_LOG_MODULES
にプロトコル種別とログレベルを設定(IMAP:3 など)、NSPR_LOG_FILE
に保存先ファイルパスを設定した上でThunderbirdを起動します。(※ログは起動のたびにクリアされます)
Thunderbirdの画面からメッセージの移動を行ったところ以下のコマンドが発行されていました。(抜粋)
65 uid move 394141 "Trash" * OK [COPYUID 4 394141 359316] * 2 EXPUNGE 65 OK UID MOVE completed
単なる MOVEコマンドではなく、UID MOVEコマンドを使用していました。
MOVEコマンドで指定しているのはメッセージ番号、これはメールボックス内の連番でセッション内で有効な番号。一方、UID MOVEコマンドで指定しているのはUIDというものでメールボックス内で一意で永続的であることが保証された番号。
どうやらYahoo!メールのIMAPではメッセージ番号を使った移動はサポートしていないということらしい。
解決方法
さてメッセージを移動させる方法は分かりました。しかし UID MOVE
コマンドをjavamail-1.6.2がサポートしているのかというとどうも期待できなさそうです。moveuid
とかcopyuid
とか思わせぶりなメソッドは存在するのですが実際に発行しているコマンドは MOVE
コマンドでした。
そうなると自前で発行するしかありません。
javamailには自前のプロトコル処理に差し替える機能があるようですが(プロパティmail. + プロトコル名 +.classなど)、正直そこまでやるほどの労力を使いたくありません。
そこでimap関連の機能を利用しつつ UID MOVE
コマンド発行することにしました。
SearchTerm[] stlst = [
new SubjectTerm('日の日記'),
new ReceivedDateTerm(ComparisonTerm.EQ, new Date()),
new FromStringTerm('no-reply@plaza.rakuten.co.jp')
]
SearchTerm st = new AndTerm(stlst)
IMAPMessage[] msgs = inbox.search(st)
FetchProfile fp = new FetchProfile()
fp.add(FetchProfile.Item.ENVELOPE)
fp.add(UIDFolder.FetchProfileItem.UID)
inbox.fetch(msgs, fp)
msgs.each { IMAPMessage msg->
println("Subject : ${msg.getSubject()}")
println(" From : ${msg.getFrom()}")
println(" ReplyTo : ${msg.getReplyTo()}")
println(" UID : ${msg.getUID()}")
}
if (msgs.length > 0) {
inbox.getProtocol().simpleCommand('UID MOVE '
+ UIDSet.toString(Utility.toUIDSet(msgs))
+ ' "Trash"', null)
}
UID MOVE
コマンドを発行するにあたりUIDが必要となるので、まずはfecthメソッドにてUIDを取得します。そしてそのあとにsimpleCommand
メソッドを用いてコマンドを発行しています。
UIDSet.toString(Utility.toUIDSet(msgs))は幾つかのUIDをまとめてくれる便利メソッドです。(123,124,125といった連番であれば123:125のようにまとめてくれる)
Yahoo!メールではゴミ箱は使用容量のカウント外となるのでひとまず移動だけでオッケーですね。
おまけ
IMAPでメッセージ削除は論理削除→物理削除の順で行います。
論理削除はメッセージに対してDeleted
フラグを立てる形で行われるのですが、こちらも単なるSTORE
コマンドではエラーになり、UID STORE
コマンドを用いる必要がありました。
if (msgs.length > 0) {
inbox.getProtocol().simpleCommand('UID STORE '
+ UIDSet.toString(Utility.toUIDSet(msgs))
+ ' +FLAGS '
+ inbox.getProtocol().createFlagList(new Flags(Flags.Flag.DELETED))
, null)
}
inbox.expunge()
+FLAGS
でフラグON、-FLAGS
でフラグOFFを意味します。UID STORE
コマンドで削除フラグを立てて、expunge
メソッドで物理削除しています。
関連記事
参考資料
- IMAP4のコマンド一覧 (itmedia.co.jp)
- IMAP4(Internet Mail Access Protocol version 4)~前編:インターネット・プロトコル詳説(8) – @IT (itmedia.co.jp)
- IMAP4(Internet Mail Access Protocol version 4)~後編:インターネット・プロトコル詳説(9) – @IT (itmedia.co.jp)
- java – move (copy) IMAPMessage to another folder on the mail server – Stack Overflow
- java – Cannot delete emails of domain .co.jp type – Stack Overflow
- Thunderbirdの動作ログを取る – Yatao.net
- thunderbird の SMTP,POP,IMAPサーバとのやりとりのログを記録する|プログラムメモ (pgmemo.tokyo)