AWS Lambdaでtodoistのタスク一覧を定期通知する

Lambdaやりたいと言いながら放置していたので、このGWという連休でLambdaの簡単なスクリプトに手を付けよう!と意気込み、連休最終日(明日も休みにしちゃったけど)に作った。

モチベーション

単純にtodoistがAPIを公開しており、APIのリファレンスが比較的わかりやすかったことがある。
todoist api reference
あと、Slackのtodoist appで特定プロジェクトのタスクの一覧表示とかできなかったのが大きい。(調べてみたけどわからなかった、、)
本当はLambdaでSlack botまで作ろうかと思ったけど、まずは通知するところからやることにした。

コード

pythonを選んだ理由は,最近pythonのboto3で色々スクリプトを書いていたから。

# coding: utf-8

import json
import requests
import os
import sys
from todoist.api import TodoistAPI

SLACK_CHANNEL = os.environ['SLACK_CHANNEL']
SLACK_POSTURL = os.environ['SLACK_POSTURL']
TDIAPI = TodoistAPI(os.environ['TODOISTAPITOKEN'], cache=False)
TDIAPI.sync()
name = os.environ['TODOIST_PJT']

def tasklist(name):
    list = TDIAPI.state['projects']
    for projects_id in list:
        if projects_id['name'] == name:
            tasks_project_id = projects_id['id']
            break

    try:
        tasks_project_id
    except NameError:
        print("プロジェクト名が正しくありません。プロジェクト名を正しく入力してください。")
        sys.exit()

    items = TDIAPI.state['items']
    slackmessage = []
    for name in items:
        if name['checked'] == 0 and name['project_id'] == tasks_project_id:
            taskcontent = '- ' + name['content']
            slackmessage.append(taskcontent)
    message = '\n'.join(slackmessage)
    return message

def lambda_handler(event, context):
    msg = tasklist(name)
    title = "*[定期通知] プロジェクト " + name + " のタスクリスト*\n"
    slack_message = {
        'channel': SLACK_CHANNEL,
        'icon_emoji': ":todoist:",
        'text': title,
        "attachments": [
            {
                "color": "#36a64f",
                "fields": [
                    {
                        "value": msg,
                    },
                ],
            }
        ]
    }
    requests.post(SLACK_POSTURL, data=json.dumps(slack_message))

lambda.json

{
    "name": "todoist-notify",
    "description": "Notify task list to slack",
    "region": "ap-northeast-1",
    "handler": "lambda.lambda_handler",
    "runtime": "python3.7",
    "role": "arn:aws:iam::111111111111:role/MyLambdaExecRole",
    "timeout": 300,
    "memory": 128,
     {
      "SLACK_POSTURL":"https://SLACKURL/XXX",
      "SLACK_CHANNEL":"#SLACK_CHANNEL_NAME",
      "TODOIST_PJT":"Inbox",
      "TODOISTAPITOKEN": "XXXXX"
    }
}

こんな感じでとてもシンプルなものです。
projectテーブルにはtaskはなく、taskテーブルにはproject_idがあるのでまずは引数名からproject_idを取ってくる。
次にそのproject_idにマッチして、かつ未完了のタスクをリストにして、Slackに通知するというよくあるもの。
これをCloudwatch ruleに突っ込んで平日定期通知させることにした。

学び

上記スクリプト自体は過去にcliで近しいものを作っていたので、そこまで時間はかかっていないけど、開発環境作ったりデバッグ方法を学ぶのに少し時間を要した。
特にライブラリを使う場合、一緒にzipに固めてAWS Lambdaへアップロードが必要なわけだけど、lambda-uploaderを使えば、コードのあるディレクトリで、

 $ lambda-uploader --profile=PROFILENAME
λ Building Package
λ Uploading Package
λ Fin

とやれば勝手にzip圧縮してアップロードしてくれることを知り、更新はとても簡単にできた。

また、手元のテストも、python-lambda-localを使えば、event.jsonにテストのjsonファイルを置いて、疑似テストまでできるというのを知ることができた。(本コードの場合は空データだけど)
例えばテストするときは、envchainを使って、Slackの通知先とかを環境変数で変えてテストするなどした。

$ TODOISTAPITOKEN=XXXXXXXXXXX envchain slack python-lambda-local -f lambda_handler lambda.py event.json

envchain

python-lambda-localやlambda-uploaderあたりはクラメソさんの記事がとても参考になりました。(いつもありがとうございます!)
aws-lambda-python-local

そう言えば、Lambdaにアップロードしてテストを実行したところ、「OS Error, Readonly filesysrtem. /home/XXXX」みたいなエラーが出た。
どうもtodoistのAPIが ~/.todoist みたいなホームディレクトリにキャッシュを書き込むような仕様になっているようで、Lambdaに限らず同じようなことがGoogle App Engineでも発生する様子。

https://github.com/Doist/todoist-python/issues/48

todoiste-apiでは、cacheを無効化できたので良かったが、同じようにホームディレクトリに書き込みを行うような作りだったら、同様の事象が発生するので注意が必要。

Lambdaを使って

既にあるものを直したりはやったことがあったけど、ちゃんと1から作ったのは初めて。
CircleCIのときにもなんて便利〜っておもったけど、AWS Lambdaもやはり便利〜!
AWS Lambdaは個人で開発しているレベル(上記ちょっと通知したりあれこれする程度)であればほぼ無料枠内で収まるので、ガシガシ作っていきたい。
とりあえず簡単なスクリプトから開発スタイルが作れたので、次はAWSのeventデータを取ってきて色々やっていく。