API Gateway+LambdaとIFTTTを使って、Alexaのリストに追加した項目をTodoistに追加する (文字化け対策)

Alexaのリスト機能、便利ですよね。
「○○をやらなきゃ」と思ってスマホを探してスマホのロックを解除してアプリを起動したあたりで「……なんだっけ？」となりがちな物覚えの悪さなので、思った瞬間に発話でタスクの追加が可能というのは大変ありがたいことです。

個人的にTodoistを使用していたこともあり、IFTTT経由で「Alexaへのリスト追加を検知→Todoistに追加」とやっていました。
……が、2018年の4月くらいから、Todoistに追加していたアイテムが文字化けするように。
「まあ買い物リストに追加した痕跡が残るなら、都度Todoistで直せばいいか……」みたいなことをしばらくやっていたのですが、そんな中で下記の記事を拝読。

[[解決編]AlexaからIFTTT経由でショッピングリストの情報を取得すると文字化けするのをなんとかした - Qiita](https://qiita.com/ryo_naka/items/e1cf404e70943669c502)

私もなんとかすることにしました。

## 概要
### やったこと

1. API Gateway+Lambdaで「アイテム名を変換⇒APIからTodoistにアイテムを追加」を実行するAPIを作成
1. IFTTTで「Alexaの買い物リスト/やることリストにアイテムを追加」をトリガーに上記APIを実行

### 環境

- Amazon API Gateway
- AWS Lambda (Python3.6)
- Amazon Alexa
- IFTTT

いずれも2018/10/17現在の環境で以下、記載します。
なお筆者は「AWSは勉強中、EC2はなんとなく触れるけれどもLambdaはあんまり」「Pythonは2.xが出始めたくらいの時期に1日だけ触った」とかそんな感じです。
おおむね素人です。

## 対応内容

### AWS Lambdaで処理部分を実装

Lambdaで行う処理は下記の2点です。

- Alexaから渡されてきたアイテム名をUTF-8に変換
- TodoistのAPI経由でアイテムを追加

まずはLambdaに関数を作成。
AWS Lambdaから「関数の作成」に遷移し、ランタイムは「Python 3.6」を選択、名前・ロールはよしなに入力を行い「関数の作成」を押下。
(ロール関係はもっと詳しい記事があるかと思うので、ググっていただいたほうがよいと思います)

最終的に、実装は下記のようになりました。
Todoistとの連携部分は [pytodoist](https://github.com/Garee/pytodoist) ([Documentation](https://pytodoist.readthedocs.io/en/latest/)) を使用しています。

```python:lambda_function.py
import requests
from pytodoist import todoist

TODOIST_API_TOKEN = 'your_todoist_api_token'

FROM_CHARACTER_CODE = 'ISO-8859-1'
TO_CHARACTER_CODE = 'UTF-8'

def lambda_handler(event, context):
    add_item = convert_character_code(event['item'], FROM_CHARACTER_CODE, TO_CHARACTER_CODE)

    user = todoist.login_with_api_token(TODOIST_API_TOKEN)
    project = user.get_project(event['list'])
    if project:
        project.add_task(add_item)

    return 'OK'

def convert_character_code(text, from_character_code, to_character_code):
    return text.encode(from_character_code).decode(to_character_code)
```

なお、Lambdaの関数コードにこれをそのまま書くと `requests` や `pytodoist` が読み込めないことでエラーになります。

```
"errorMessage": "Unable to import module 'lambda_function'"
```

今回は下記を参考に、pipでインストールしたライブラリごとzipに固めてアップする方法を取りました。
[【AWS】Lambdaでpipしたいと思ったときにすべきこと - Qiita](https://qiita.com/Hironsan/items/0eb5578f3321c72637b4)
何度も行うようであれば [lambda-uploader](https://github.com/rackerlabs/lambda-uploader) などを使うのがよいみたいですね。

TodoistのAPI Tokenは、「設定 > 連携機能」の最下部に「APIトークン」があるので、これを利用します。

テストには「IFTTTからLabmdaに渡すjson」を想定したjsonを記載しておきます。
今回は下記の感じ。`list` にTodoistのプロジェクト名、`item` に追加したいアイテム名が入る想定です。

```
{
  "list": "testlist",
  "item": "testitem"
}
```

余談ですが、今回は「プロジェクト名はIFTTTでべた書き」「アイテム名はAlexaから受け取った値を渡す」とするため、文字化けが発生するのはアイテム名のみです。
そのため「文字コードの変換はアイテム名にのみに行う」としないと、下記のようなエラーになります。

```
'latin-1' codec can't encode characters in position 0-2: ordinal not in range(256)
```

### Amazon API GatewayでLambdaのコードを外部から実行できるようにする

こちらも、Labmdaと同様に詳しい記事がすでに存在しているので、ググっていただくのがよいかと思います。
個人的には下記の記事をかなり参考にさせていただきました。

- [AWS API Gateway+LambdaでSlackにメッセージをPOSTする(中編) - 年中アイス](https://reiki4040.hatenablog.com/entry/2017/02/03/220214)
- [AWS API Gateway+LambdaでSlackにメッセージをPOSTする(後編) - 年中アイス](https://reiki4040.hatenablog.com/entry/2017/02/11/232357)


### IFTTTからAPIを実行する

「New Applet」より、トリガーに「Amazon Alexa > Item added to your ToDo List」、アクションに「Webhooks > Make a web requests」を指定。
以下のように設定を行います。

| 項目名       | 設定内容 |
|--------------|---------|
| URL          | API Gateway+LambdaのAPI URL |
| Method       | API Gateway+Lambdaで設定したメソッド |
| Content Type | application/json |
| Body         | {"list":"Inbox", "item": "{{AddedItem}}"} |

Bodyが先の「IFTTTからLabmdaに渡すjson」にあたります。
「Inbox」でTodoistのインボックスに追加されるので、ほかのプロジェクトに追加したい場合はよしなに名称を変更してください。
日本語のプロジェクトであれば、日本語でOKです。


## 完成

Alexaのアプリからタスクを追加して、少し待ってTodoistを確認します。
日本語でタスクが追加されていればOKです。

![oreyaru_1.png](https://qiita-image-store.s3.amazonaws.com/0/155632/97519402-d684-19b0-7b3b-a6bb27234fb4.png)
　↓
![oreyaru_2.png](https://qiita-image-store.s3.amazonaws.com/0/155632/2775150c-130a-9e1d-9e58-486710cf0dc7.png)
やったぜ！

## 追記(2018/11/12)
2018/11/09くらいからLambdaがエラーを返すようになりました。
(その前は2018/11/05に正常稼働ログを確認しているので、その間になにかあった模様？)

```
'latin-1' codec can't encode characters in position 0-5: ordinal not in range(256): UnicodeEncodeError
```

試しに、普通にIFTTT経由でAlexa → Todoist連携したら普通に日本語入りましたね。
ちょっと様子見です。