メールが届いたらその内容をslack/mattermostに投稿する

# TL;DR
メールが届いたらこんな感じのメッセージをslack/mattermostにpostする仕組みを作りました．
![qiita3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419995/3e064f8a-4c3a-24a0-62c2-b27248dfba35.png)

# はじめに
slack / mattermostを始めとするチャットツールは普及してきていますが，様々な事情により，完全にメールを使わないで仕事をすることは難しいと思います．とはいえ定期的にメーラをチェックするのもめんどくさいですね．なので，メールが届いたらその内容をslack / mattermostに通知し，通知内容を見て必要があれば返信すればよいのではと考えました．

何らかの事情によりメールを見る環境と普段作業をする環境が分断されている方にもおすすめです．[^1]
[^1]: 自己責任でお願いします．

# 前提
- メーラとしてThunderBirdを利用していること

# 具体的な手順
## incoming webhookの取得
今回は届いたメールの内容をpostするため，incoming webhookを取得します．具体的な手順は既に解説記事がたくさんあるので省略します．
slackの場合は[こちら](https://qiita.com/vmmhypervisor/items/18c99624a84df8b31008)，mattermostの場合は[こちら](https://qiita.com/terukizm/items/4524249dd7f1298fdc06)をご参照ください．

## FiltaQuillaの導入
ThunderBirdのアドオン，FiltaQuillaを[インストール](https://addons.thunderbird.net/ja/thunderbird/addon/filtaquilla/)します．
FiltaQuillaは，メール受信時のフィルタやそれに対するアクションを拡張するアドオンです．フィルタ条件として正規表現が使えるため，その用途で利用されることが多いですが，今回はアクションとして任意のスクリプトを実行できる機能を主に利用します．

上記のアクション機能は，デフォルトではオフになっています．そのため， `ハンバーガーメニュー > アドオン > FiltaQuilla` から，`JavaScript Action with Body` にチェックを入れ，Thunderbirdを再起動します．
![qiita1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419995/e261c45b-2a31-13e5-4441-80a0960f66d1.png)

`ハンバーガーメニュー > メッセージフィルター > メッセージフィルター` より，新しくフィルタを作り，`以下の動作を実行する` の選択肢の中に `Javascript Action with Body` があればOKです．アクション選択の右側にあるアイコンをクリックするとウインドウが開き，任意のjavascriptを書くことができます．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419995/036a3bfe-b675-6cca-173c-0a21fdb23e8b.png)

例えば，以下のようなプログラムを書くと，受信したメールのタイトルの前に `[Hello, World] ` とprefixをつけることができます．

```javascript
for (let index = 0; index < msgHdrs.length; index++){
  let hdr = msgHdrs.queryElementAt(index, Ci.nsIMsgDBHdr);
  hdr.subject = "[Hello, World] " + hdr.subject;
}
```

ちなみに，アクションの選択肢の中には `Run Program` というものがあり，ローカルにある任意の実行ファイルを実行することができます．しかしこの動作は，メールのヘッダを受信した段階で起動するようで，プログラムに対してヘッダに関する情報を渡すことができますが，本文等のヘッダに含まれない情報は渡すことができません [参考](http://www.mesquilla.net/mesquillacom/extensions/filtaquilla/index.html)．
一方， `JavaScript Action with Body` に関しては，[公式サイト](http://www.mesquilla.net/mesquillacom/extensions/filtaquilla/index.html)に以下のような記述があります．

> The “Javascript Action with Body” delays the application of the filter until the body has been downloaded.

つまり，メールの受信が完了してからJavascriptが起動するようなので，こちらのアクションを使えばメール本文を取得することができます．

## 実行するjavascriptを書く

ここからは上記のアクションに指定するjavascriptを書いていきます．とりあえず動かしたい方は[最終的なjavascriptコード](#最終的なjavascriptコード)まで読み飛ばしてください．
コードを実行して動かなくなった場合には，開発ツールボックス (Ctrl + Shift + I) の `コンソール` タブを開くとデバッグしやすいかと思います．

### postする要素の取得
まずhdrオブジェクトを取得します．

```javascript
let hdr = msgHdrs.queryElementAt(index, Ci.nsIMsgDBHdr);
```

hdrオブジェクトにはメールヘッダに関する情報が含まれており，簡単にSubjectやAuthor, Cc, Bcc情報を取得することができます．これらの他に取得できる項目については[公式ドキュメント](https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIMsgDBHdr)を参照してください．

```javascript
subject = hdr.mime2DecodedSubject
author = hdr.mime2DecodedAuthor
cc = hdr.ccList
bcc = hdr.bccList
```

hdrオブジェクトの要素として，`hdr.subject` や `hdr.author` がありますが，これらの要素にはMIMEエンコードされた文字列が格納されています．そのため，日本語が含まれる件名や長い件名を扱う場合には `hdr.mime2DecodedSubject`, `hdr.mime2DecodedAuthor` を使う点に注意してください．
何かの事情でMIMEデコードをする場合には[こちら](http://0xcc.net/jsescape/strutil.js) や [こちら](https://slack-redir.net/link?url=https%3A%2F%2Fgithub.com%2FOne-com%2Frfc2047) を利用するのが良いと思います．

bodyについては，[このサイト](https://superuser.com/questions/210047/script-thunderbird-filters)を参考に，以下のようにして取得しました．

```javascript
let messenger = Components
    .classes["@mozilla.org/messenger;1"]
    .createInstance(Components.interfaces.nsIMessenger);
let listener = Components
    .classes["@mozilla.org/network/sync-stream-listener;1"]
    .createInstance(Components.interfaces
        .nsISyncStreamListener);
let uri = hdr.folder.getUriForMsg(hdr);
messenger.messageServiceFromURI(uri)
    .streamMessage(uri, listener, null, null, false, "");
let body = hdr.folder.getMsgTextFromStream(listener.inputStream,
    hdr.Charset, 65536, 32768, false, true, {});
```

### postするjsonの作成

取得した要素を並べてpostするjsonを作成します．mattermostの場合だとmarkdown記法を利用することもできます．

```javascript
const data = {
    "text": "件名：" + subject + "\n"
            + "送信元: " + author + "\n"
            + "Cc: " + cc + "\n"
            + "Bcc: " + bcc + "\n"
            + "本文\n"
            + body
};
```

### postする
XMLHTTPRequestを用いてデータをpostします．mattermostでうまく動かない場合は，HTTPヘッダの `content-type` に `application/json` を指定していないのが原因かもしれません．

```javascript
const xml = new XMLHttpRequest();
xml.open("POST", url, false);
xml.setRequestHeader("content-type", "application/json");
xml.send(`${JSON.stringify(data)}`)
```
## 最終的なjavascriptコード

最終的には下記のようなjavascriptコードになります．デバッグをしやすくするため，consoleへ出力するコードも残してあります． 利用される際は，1-2行目の `url` を取得したwebhook urlに置き換えてください．

```javascript
const url = "https://your.mattermost.url/hooks/xxxxxxxxxxx"; // mattermost
const url = "https://hooks.slack.com/services/xxxxxxxxxxx";  // slack

for (let index = 0; index < msgHdrs.length; index++) {
    let hdr = msgHdrs.queryElementAt(index, Ci.nsIMsgDBHdr);

    // subject
    subject = hdr.mime2DecodedSubject
    Cu.reportError(" flt log1: " + subject);

    // message body
    let messenger = Components
        .classes["@mozilla.org/messenger;1"]
        .createInstance(Components.interfaces.nsIMessenger);
    let listener = Components
        .classes["@mozilla.org/network/sync-stream-listener;1"]
        .createInstance(Components.interfaces
            .nsISyncStreamListener);
    let uri = hdr.folder.getUriForMsg(hdr);
    messenger.messageServiceFromURI(uri)
        .streamMessage(uri, listener, null, null, false, "");
    let body = hdr.folder.getMsgTextFromStream(listener.inputStream,
        hdr.Charset, 65536, 32768, false, true, {});
    Cu.reportError(" flt log2: " + body.length + " " + body);

    // author
    author = hdr.mime2DecodedAuthor
    Cu.reportError(" flt log3: " + author);

    // Cc, Bcc
    cc = hdr.ccList
    bcc = hdr.bccList

    // formatting data
    const data = {
        "text": "## 件名：" + subject + "\n"
                + "## 送信元: " + author + "\n"
                + "## Cc: " + cc + "\n"
                + "## Bcc: " + bcc + "\n"
                + "## 本文\n"
                + body
    };

    // POST
    const xml = new XMLHttpRequest();
    xml.open("POST", url, false);
    xml.setRequestHeader("content-type", "application/json");
    xml.send(`${JSON.stringify(data)}`)
}
```

# 動かしてみる
`プロジェクトA` が件名に含まれる場合にアクションを起こすよう設定して，メールを送ってみます．すると…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419995/0c728dae-8908-b31f-47e9-d069c995eddd.png)

通知が届きました！
![qiita3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419995/3e064f8a-4c3a-24a0-62c2-b27248dfba35.png)

さらに，mattermostではmarkdown記法が使えるので，もうちょっとリッチな表現にすることができます．例えばpostするjsonを書き換えて，`緊急` という文字列がタイトルに含まれているときにこのようなデータをpostする，という新しいルールを加えると...

```javascript
const data = {
    "attachments": [{
        "color": "#FF8000",
        "text": "件名: " + subject + "\n"
            + "送信元: " + author + "\n"
            + "Cc: " + cc + "\n"
            + "Bcc: " + bcc + "\n"
            + "本文\n"
            + body
    }]
};
```

緊急性の高いものだけを目立たせることができます．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/419995/226c3c25-b651-414b-a8d4-abf9cea62ec9.png)

# まとめ
ThunderbirdのプラグインFiltaQuillaを使って，slack/mattermostに新着メールの情報をpostする方法について説明しました．本記事では説明しませんでしたが，postするチャンネルを条件によって変えて，そのチャンネルへのpostだけスマホに通知するようにすれば，重要なメールが来たときにだけスマホを鳴らすようにすることもできます．また，今回はslack/mattermostへの通知でしたが，FiltaQuillaを使えば任意のjavascriptを動かすことができるため，もっと面白い使い方があるかもしれません．

# 参考サイト
- FiltaQuilla: https://addons.thunderbird.net/ja/thunderbird/addon/filtaquilla/
- https://superuser.com/questions/210047/script-thunderbird-filters
- https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIMsgDBHdr