simフリー版ドコモメールの着信をIMAP4 IDLE→IFTTT→PushBullet でプッシュ通知

ドコモ回線で SIMフリー端末(Android)へ変更すると、ドコモメール(spモードメール)の着信が特殊SMSで通知されなくなる
simフリー端末用の公式ドコモメールアプリは、15分間隔でのポーリング受信
連絡は他のツールに移りつつあるため、あまり支障はないが、実験的メモです

注意
キャリア端末での着信通知は SMSで起動するため、Android端末が Doze 状態でも通知されますが、今回の方法は Androidのプッシュを最終的に利用しているため、Dozeの影響を受けます
着信トリガーで、SMSや電話着信を起動すれば、リアルタイム性を保つことは可能かと思います(今回はそこまで突っ込んでません)

試したけどNGだった事
  • ドコモメール設定(spモード接続)から user-agentを iPhone へ変更して、着信お知らせメール(SMS)をオンにしてみる → 通常SMSが到着した形跡なし
  • ドコモ公式端末から DcmWapPushHelper.apk と jp.nttdocomo.carriermail-2.apk を移植 → jp.nttdocomo.carriermail-2.apk(ドコモメール)は、起動と強制終了を繰り返して利用できず、DcmWapPushHelper.apk + ドコモメール(simフリー用)は、プッシュの反応なし
ということで、IMAP4 の IDLEを用いて、メール着信イベントを取得するしかないという事になりました

IMAP4 IDLE は早い話、セッションをつないだままにしてボックスに変化があったらサーバからクライアントへメッセージを送る --- という方法なため、端末アプリで IMAP4 IDLE を用いると、常にセッションを張り続ける→電池を消耗する という事は容易に想像されます。
今回は、常時起動したサーバ(今回は手持ちのVPS)で、IMAP4 IDLEを監視して、メール受信イベント →  IFTTT WebHook → PushBullet で端末に「何か来たよ」の通知を行う事としました
メール受信イベント以下は、Gmailへ通知を送るなどでも同じです

※ imap.select('inbox') で受信フォルダを選択しています
それ以外のフォルダの通知を受ける場合、他のを指定する必要があります
日本語の場合 utf7でエンコード?(未確認)

Pyhotnソース(終了は ctrl + d) 同じ場所に log ディレクトリを作成してください
import datetime
import imaplib
import httplib2
import urllib
import time
import signal
import os

signal.signal(signal.SIGINT, signal.SIG_DFL) # ctrl + d

# imap4へ接続して imapオブジェクトを返す(失敗は False)
def connect_iamp_server():
    imap_server = 'imap.spmode.ne.jp'
    imap_port = '993'
    imap_user = '******@docomo.ne.jp'
    imap_pass = '******'

    try:
        imap = imaplib.IMAP4_SSL(imap_server,imap_port)
        sock = imap.socket()
        # sock.settimeout(60 * 15) # NATタイマーにかかる場合タイムアウトを設定
        imap.login(imap_user,imap_pass)
    except imaplib.IMAP4_SSL.error as e:
        print(e)
        return False
    else:
        return imap


# Http GET or POST
def httpExecute(url, user = '', passwd = '', postData = ''):
    for key in postData:
        if postData[key] == None:
            postData[key] = ''

    http = httplib2.Http(timeout=8)
    if user != '':
        http.add_credentials(user, passwd)

    if postData != '':
        try:
            content = http.request(url,
                            method="POST",
                            headers={'Content-type': 'application/x-www-form-urlencoded'},
                            body=urllib.parse.urlencode(postData) )[1]
            return content.decode()
        except httplib2.ServerNotFoundError:
            return ''
    else:
        try:
            (resp, content) = http.request(url, 'GET')
            return content.decode()
        except httplib2.ServerNotFoundError:
            return ''


def printtime(s):
    now = datetime.datetime.now()
    writeLn = now.strftime("%Y-%m-%d %H:%M:%S ") + s
    print(writeLn)

    logFile = os.path.dirname(os.path.abspath(__file__)) + os.sep + "log" + os.sep + now.strftime("%Y%m%d") + ".log"
    with open(logFile, mode='a') as f:
        f.write(writeLn + '\n')


def main():
    while True:
        mailExists = False
        imap = connect_iamp_server()
        if imap != False:
            printtime('M: Connected IMAP4 Host')
            imap.select('inbox')
            imap_idletag = imap._new_tag()
            printtime('C: %s IDLE'%(imap_idletag.decode('utf-8')))
            imap.send(b'%s IDLE\r\n'%(imap_idletag))

            imap_line = imap.readline().strip().decode('utf-8')
            printtime('S: ' + imap_line)
            if imap_line == '+ .':
                try:
                    while True:
                            imap_line = imap.readline().strip().decode('utf-8')
                            printtime('S: ' + imap_line)
                            if imap_line.startswith('* BYE ') or (len(imap_line) == 0):
                                mailExists = False
                                break
                            if imap_line.endswith('EXISTS'):
                                mailExists = True
                                printtime('C: DONE')
                                imap.send(b'DONE\r\n')
                                imap_line = imap.readline().strip().decode('utf-8')
                                printtime('S: ' + imap_line)
                                break
                            # 上記以外のコマンドはループ(IMAP4を操作してもレスポンスが返ってくる)
                except:
                     mailExists = False
                     pass
                finally:
                    # imap.close() # タイムアウト時にエラーが発生
                    imap.logout()
                    printtime('M: Disconnect IMAP4 Host')

            if mailExists:
                printtime('M: mail Exists')
                # メール到着処理
                postData = {'value1': 'ドコモメール'}
                httpresponse = httpExecute('https://maker.ifttt.com/trigger/*******/with/key/*******', '', '', postData)
                printtime('M: IFTTT > ' + httpresponse)
        else:
            # 接続NG -> 60秒待ち
            time.sleep(60)


if __name__ == '__main__':
    main()
  

ドコモの仕様書どおり IDLE 30分で BYE切断になるが、NATタイマーがそれ以下の場合、タイムアウトを設定する(コメントアウトを外す)

コマンドラインでうまくいったら、pythonをデーモン化するメモ あたりでデーモンとして監視させればOK


参考文献
imap4libでpush通知と遊んでみる --- デーモン移行時、改行コードに注意

コメント