clasp + typescript:[gas] 特定のGmailの受信をslack通知する

メールをみるの無精なので、来たメールで拾いたいものがあったらslackに通知するようにするスクリプトを書いてみた。来たことがわかればいいので、来た件数だけを通知するという簡単仕様で 雑にでも gas + clasp + typescript の一連の流れを追えたらと思っています。

プロジェクトの準備

前回もやったようなプロジェクトの準備をやっていきます。

# ディレクトリ作成
$ mkdir kyuka-notification
$ cd kyuka-notification/
# ログインアカウントを確認
$ clasp login --status
(node:48940) ExperimentalWarning: The fs.promises API is experimental
You are logged in as xxxx@gmail.com
# gasの環境準備
$ npm i -S @types/google-apps-script
npm WARN saveError ENOENT: no such file or directory, open '/YOUR/DIR/kyuka-notification/package.json'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN enoent ENOENT: no such file or directory, open '/YOUR/DIR/kyuka-notification/package.json'
npm WARN kyuka-notification No description
npm WARN kyuka-notification No repository field.
npm WARN kyuka-notification No README data
npm WARN kyuka-notification No license field.
+ @types/google-apps-script@1.0.20
$ clasp create --title "Kyuka Notification" --type standalone
# timezoneを変更
$ cat appsscript.json
{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}

コードを書いていく

コードが書ける状態になったので、ローカルのエディタでコードを書いていきます。

まずは一旦動作だけ確認できるようにログにメモを残す数行のプログラム。

// 一旦console.logに出る状態で clasp push
// Compiled using ts2gas 3.6.4 (TypeScript 4.1.3)
function main() {
    notify();
}
function notify() {
    console.log("log");
}

この状態で clasp push してWEB上で動作することを確認。

実際に動かすコードを書く

typescriptのコードがわからないので、特にライブラリを使わないこちらを写経させてもらいました。incoming webhookの設定手順は省略しています。

// incoming webhookを使ってslackに通知するプログラム
let webhook = "https://hooks.slack.com/services/xxxxxx"
function main() {
    notify();
}
function notify() {
    let message = '私は気になります => 休暇申請';
    let jsonData = {
        "username" : '気になるタイプの民',
        "text" : message,
    };
    let payload =  JSON.stringify(jsonData);
    let options = {
        "method" : "post",
        "contentType" : "application/json",
        "payload" : payload,
    };
    UrlFetchApp.fetch(webhook, options);
    console.log("notify done");
}

これでclasp pushしてslackに通知されることを確認した。次は実際にGmailからデータを見て処理をするところを時間を見つけて書いていきたい。

Gmail から特定のタイトルのメール件数を取る

GmailAppというオブジェクトが用意されていて、searchというファンクションにqueryを与えるとGmailの検索窓のように動くらしくそれを愚直に書いている感じです。

returnで 返しているのは通知用に使うデータになっています。

function searchMail() {
    const beforeOneHour = dayjs.dayjs().subtract(1, 'hour').unix();
    const query = `承諾 after:${beforeOneHour}`;
    const threads = GmailApp.search(query);
    const length = threads.length;
    console.log(length);
    return { text: `承諾: ${length}件`, length: length };
}

「一時間前までの間に」という判定をしたかったため、day.jsを読み込んでいます。appscript.jsonに下記を追加するとグローバルオブジェクトに入ってくる様子(ここがちゃんと理解できてなくてハマった)

  "dependencies": {
    "libraries":[
      {
        "userSymbol": "dayjs",
        "libraryId": "1ShsRhHc8tgPy5wGOzUvgEhOedJUQD53m-gd8lG2MOgs-dXC_aCZn9lFB",
        "version": "1"
      }
    ]
  },

あとは通知をするだけという感じです。

定期実行

この画面のトリガーを設定すれば良さそう。

定期処理は明日調べて追加して、全体のスクリプトをはろうと思います。

テストで1分おきで実行にしてみたところ動いてそうだった。

ちょっとわからなかった

ちゃんと調べればいいんだけど、デプロイが以下の選択肢がなくてバッチで実行する場合は特にデプロイしなくてもいいんだろうか?(トリガーで動いているし、バージョン管理は手元でできているのでまぁという気持ちになってる)

最終的なコード

こんな感じになりました。js不勉強なのをこれを気に少しずつ解消できるとよいけども(ES2015すらそもそも危うい為)

// main.ts
const webhook = "https://hooks.slack.com/services/xxx"
function main() {
    const mailResult = searchMail();
    console.log(mailResult);
    notify(mailResult);
}
function notify(result) {
    const message = `わたし、きになります! → ${result.text}`;
    const jsonData = {
        "username" : '気になるタイプの民',
        "text" : message,
    };
    const payload =  JSON.stringify(jsonData);
    const options = {
        "method" : "post",
        "contentType" : "application/json",
        "payload" : payload,
    };
    if (result.length != 0) {
        UrlFetchApp.fetch(webhook, options);
        console.log("notify done");
    }
}
function searchMail() {
    const beforeOneHour = dayjs.dayjs().subtract(1, 'hour').unix();
    const query = `<検索したい文字列> after:${beforeOneHour}`;
    const threads = GmailApp.search(query);
    const length = threads.length;
    console.log(length);
    return { text: `お知らせ: ${length}件`, length: length };
}
// appscriot.json
{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
    "libraries":[
      {
        "userSymbol": "dayjs",
        "libraryId": "1ShsRhHc8tgPy5wGOzUvgEhOedJUQD53m-gd8lG2MOgs-dXC_aCZn9lFB",
        "version": "1"
      }
    ]
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8"
}

文言は違いますが一応こんな感じで動いたので一旦満足。

これから本当にやりたかったことをやっていこうと思います。

※実際にgoogle app scriptにデプロイされている.gsファイルとは違うためご注意ください。

参考URL