deno compileでTrelloの期限切れタスクをSlackに通知するバイナリを作成してみた

この記事は株式会社富士通システムズウェブテクノロジーが企画する[いのべこ（富士通システムズウェブテクノロジー） Advent Calendar 2020](https://qiita.com/advent-calendar/2020/fsweb)  の24日目の記事です。
本記事の掲載内容は私自身の見解であり、所属する組織を代表するものではありません。

#はじめに

以前、Trelloの期限切れタスクをSlackに通知するアプリを作ったのですが、やりたいことを実現するためにWebAPIを作る必要性は全くないのにも関わらず、denoでwebAPIってどう作るんだろう？
という興味がまさってしまい、ついWebAPIで作ってしまいました。その記事はこちら↓です。
[キャラが可愛いので、DenoでTrelloの期限切れタスクをSlackに通知する \- Qiita](https://qiita.com/George22e/items/22ff225e172ed8f65788)

作ってみたもののWebサービスを常に起動しておかなければならず、使い勝手がいま一つでしたので、今回、普通にバッチのように関数を叩いて起動するようにしてみました。
また、折角なので、つい先日2020/12/8にリリースされたばかりの[Deno v1.6.0](https://deno.land/posts/v1.6)の新機能、`deno compile`も試してみました。

作成したソースコードはGithubにアップしています。
[George22e/NotifyTrelloToSlack](https://github.com/George22e/NotifyTrelloToSlack)

#やりたいこと

私が所属する部門では、部門運営のタスクをTrelloでチケット管理し、コミュニケーションツールとしてSlackを活用しています。
![OMQ9MRmrrhVrQs71598882771_1598882901.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/220817/84e2543e-09a3-7fac-1e26-61c7bb642b91.png)

社内タスクの対応漏れを減らし、期限が切れタスクがあればすぐに気づいて対応できるようにしたい。
そのために作りたいものは以下です。

- 毎朝、期限当日のTrelloのタスクをSlackに通知する
- 毎朝、期限切れのTrelloのタスクをSlackに通知する

# ソースコードの構成
- main.ts
    - エントリーポイント。主処理はslack_notificationに全て実装しているので、slack_notificationの関数を呼び出すのみ。
- slack_notification.ts
    - Trelloのタスクを取得し、期限当日／期限切れのTrelloのタスクをSlackに通知する関数を実装。
- settings.js
    - 各種設定値を保持する設定ファイル。

# ソースコードの中身
## main.ts
もともとWebAPIで作った時はserver.tsとして、Webサーバを立ち上げる処理を実装していましたが、slack_notification の notify 呼び出すだけに変更しました。

```javascript:main.ts
import { notify } from './slack_notification.ts'

notify()
```

## slack_notification.ts
POSTのWebAPIで作っていたものをただの関数にしています。

```javascript:slack_notification.ts
import { soxa } from 'https://deno.land/x/soxa/mod.ts'
import { format } from 'https://deno.land/x/date_fns/index.js'
import settings from './settings.js'

export async function notify(): Promise<void> {
    let target_list_on_cards = null;
    for (const listid of settings.trello.listids) {
        let list_on_cards = await soxa.get('https://api.trello.com/1/lists/' + listid + '/cards?key=' + settings.trello.auth.key + '&token=' + settings.trello.auth.token)
        target_list_on_cards = (target_list_on_cards == null) ? list_on_cards.data : target_list_on_cards.concat(list_on_cards.data)
    }

    let today = format(new Date(), 'yyyy-MM-dd', {})
    //today = '2020-09-02'
    console.log('today : ' + today)

    const tasks_due_today = target_list_on_cards.filter((v: any) => (v['due'] != null && v['due'].slice(0, 10) === today && !v['dueComplete']))
    const overdue_tasks = target_list_on_cards.filter((v: any) => (v['due'] != null && v['due'].slice(0, 10) < today) && !v['dueComplete'])

    console.log("#tasks_due_today");
    console.log(tasks_due_today);
    console.log("#overdue_tasks");
    console.log(overdue_tasks);

    for (const task of tasks_due_today) {
        await notifySlackOfTasksDueToday(task)
    }

    for (const task of overdue_tasks) {
        await notifySlackOfOverdueTasks(task)
    }
}

async function notifySlackOfTasksDueToday(task: any): Promise<void> {
    let mention = ''
    let idMembers = task['idMembers']
    for (const memberId of idMembers) {
        const slack_user_id = await getSlackUserId(memberId)
        mention += '<@' + slack_user_id + '>'
    }

    let message = ''
    if (mention != null) message += mention + "\n"
    message += 'チケットの対応期限が本日迄です。対応をお忘れなく。'+ "\n"
    message += "<" + task['shortUrl'] + "|" + task['name'] + ">"
    let response = await soxa.post(settings.slack.notification_caution.webhook_url, {}, {
      headers: {'Content-type': 'application/json'},
      data: {
          "text": message
      }
    });
}

async function notifySlackOfOverdueTasks(task: any): Promise<void> {
    let mention = ''
    let idMembers = task['idMembers']
    for (const memberId of idMembers) {
        const slack_user_id = await getSlackUserId(memberId)
        mention += '<@' + slack_user_id + '>'
    }

    let message = ''
    if (mention != null) message += mention + "\n"
    message += 'チケットの対応期限が切れています。急ぎ対応をお願いします。'+ "\n"
    message += "<" + task['shortUrl'] + "|" + task['name'] + ">"
    let response = await soxa.post(settings.slack.notification_alert.webhook_url, {}, {
      headers: {'Content-type': 'application/json'},
      data: {
          "text": message
      }
    });
}

async function getSlackUserId(memberId: string): Promise<string> {
    let response = await soxa.get('https://api.trello.com/1/members/' + memberId + '?key=' + settings.trello.auth.key + '&token=' + settings.trello.auth.token)
    const usr = settings.users.find((user: any) => user.trello_username === response.data['username'])
    if (usr == null) {
        console.log(memberId + "のslackメンバーIDが未定義です")
        return ''
    }
    return usr.slack_member_id
}
```

## settings.js
Trello開発者向けトークンやAPIキーなどの設定値をこのファイルで管理します。
具体的な設定内容は[以前の記事](https://qiita.com/George22e/items/22ff225e172ed8f65788)を参照ください。

```javascript:settings.js
export default {
    trello : {
      auth : {
        token : "{Trello開発者向けトークン}",
        key : "{Trello開発者向けAPIキー}"
      },
      listids : ["{通知したいTrelloのカードが含まれるリストID}", "{通知したいTrelloのカードが含まれるリストID}"]
    },
    slack : {
      notification_caution : {
        webhook_url : "{期限当日のカードを知らせるSlackのwebhook_url}"
      }, 
      notification_alert : {
        webhook_url : "{期限切れのカードを知らせるSlackのwebhook_url}"
      }
    },
    users : [{
        "name" : "{名前}",
        "trello_username" : "{Trelloのプロフィール名}",
        "slack_member_id" : "{SlackのメンバーID}"
      }, {
        "name" : "{名前}",
        "trello_username" : "{Trelloのプロフィール名}",
        "slack_member_id" : "{SlackのメンバーID}"
      }
    ]
}
```

# deno compile してみる
`deno complie` によって、denoのアプリケーションを単一の実行可能なバイナリとして作成できるようになりました。
以下の記事を参考にさせていただきました。ありがとうございました。

[Deno v1\.6\.0で実装された新機能の紹介 \- Qiita](https://qiita.com/uki00a/items/86eae971286d2bff515a)
[Deno1\.6\.0 で入った deno compile のコードを読んでみる \- Qiita](https://qiita.com/pocari/items/f25ff01d48b6a2ebd5f8)

まずは、denoのバージョンを最新にアップグレードします。

```
deno upgrade
```

正常に1.6.2にアップグレードされました。

```
Checking for latest version
downloading https://github.com/denoland/deno/releases/download/v1.6.2/deno-x86_64-unknown-linux-gnu.zip
Version has been found
Deno is upgrading to version 1.6.2
downloading https://github-production-release-asset-2e65be.s3.amazonaws.com/133442384/ca8a0180-4470-11eb-8015-6accae0f814e?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20201230%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20201230T071018Z&X-Amz-Expires=300&X-Amz-Signature=62f0c082c7a7eba69756a66ae9e9e2c1e1569edbc22f04edc5d5eb8c1650b4c5&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=133442384&response-content-disposition=attachment%3B%20filename%3Ddeno-x86_64-unknown-linux-gnu.zip&response-content-type=application%2Foctet-stream
Version has been found
Deno is upgrading to version 1.6.2
Archive:  /tmp/.tmpnMaS2Q/deno.zip
  inflating: deno                    
Upgrade done successfully
```

続いて、`deno compile`をしてみます。
新機能のコマンド利用する場合は、`--unstable`のオプションが必要なようです。
また`--output`で生成するバイナリのファイル名を指定できます。

```
deno compile --unstable main.ts --output trello_notify
```

正常終了し、バイナリファイルが生成されました。

```
Check file:///home/george/trello_notify/main.ts
Bundle file:///home/george/trello_notify/main.ts
Compile file:///home/george/trello_notify/main.ts
Emit trello_notify
```

# cron設定

`deno compile`で生成したバイナリファイルをUbuntuのサーバに配備し、平日朝7時に実行されるようにcronの設定しました。

```
# m h  dom mon dow   command
00  7  *  *  1-5  /usr/local/bin/trello_notify
```

朝7時に容赦なくSlack通知されるようになりました＾＾
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/220817/ec0e1de7-6a7e-df3a-3ece-bdd23972622c.png)

main.ts を実行するときは、`deno run --allow-net main.ts` のように
`--allow-net` オプションを指定する必要がありますが、バイナリ実行では特に気にすることなく動いてくれました。

# 終わりに

初めてdenoに触ったのは2020年8月末のことでした。
それからずっと触っていなかったのですが、denoのバージョンは `v.1.3.1` から `v.1.6.2`にまで上がっており、今回試してみた `deno compile` といった新機能が実装されていて、その進化のスピードに驚きました。
こうして新しく実装された機能を使ってみて楽しむことができるのも、出始めのツール（ランタイム環境と言った方が正しいのかな？）ならではかもしれません。
denoの更なる進化、普及に期待したいと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/220817/23c6ec87-c87d-1bd7-78d4-92a7f4455733.png)

それにしても、denoのマスコットキャラクターはかわいいなぁ:relaxed: