Python初心者がチャットワークのRESTful APIで遊んでみた

たぶん何番煎じかわからないけど、最小構成Red Hat系OSの上でREST APIを叩く機会があり、手持ちの言語だと追加ライブラリなしで実装できなくてcurlでゴリゴリ書いて凌いだことがあったけど、Pythonだと標準ライブラリでできるらしいので試してみた。

想定している読者

- Pythonは初心者
- 制御構文とか関数とかプログラミングの基本的なことは完全にマスター()
- Pythonの配列とか辞書(連想配列)は何となく知っている
- RESTful APIが何かは知っている

2019.05.29追記: ビジネスプランの場合のAPI利用申請について追記

# TL;DR

- Python2.7 (OSインストール後の標準環境はまだ2.7系の模様)
- 追加ライブラリなし
- API Tokenはチャットワークの設定メニューから取得
- HTTPアクセスには標準のurllib2ライブラリを使用
- レスポンスのJSON解析は標準のjsonライブラリを使用
- 今回実行したAPIは以下の2つ
    - ルーム一覧の取得(`GET`)
    - 「マイチャット」にメッセージ投稿(`POST`)

# API Tokenの取得

小難しいOAuthなどは不要。
Webのチャットワークにログインし、設定メニューから「API設定」を選択。

![2018-08-16_07h25_38.png](https://qiita-image-store.s3.amazonaws.com/0/256859/d7ffdb29-8937-cafc-2289-5755d5ae5c8b.png)

画面にチャットワークのログインパスワードを入力すればTokenの文字列が表示される。

![2018-08-16_06h41_47.png](https://qiita-image-store.s3.amazonaws.com/0/256859/b8b2402a-4273-11c2-20c0-2d261777962b.png)

---

2019.05.29 追記

上記はフリープラン(おそらくパーソナルプランも)の場合。
ビジネスプランの場合は、APIトークンの取得には「管理者」の許諾が必要。

![2019-05-14_21h55_32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256859/9cffb349-5601-e16d-3c7b-ec1cb84b1f20.png)

利用申請画面で[API利用申請]ボタン押下すれば、設定されている組織の管理者へ利用申請の通知が届く。
そして管理者が許諾の手続きを行えば、APIトークンが作成可能になる。
(フリープランのときに作っていたAPIトークンがあっても、すべて破棄される)

# Pythonのソースコードを書く

## 大枠

Python2系だとこんな感じ。
ソースコードは`utf-8`で書く。
変数の頭に`$`とかつかないし、行末の`;`もないし、ブレース`{...}`はなくてブロックの始まりに`:`を書く模様。
mainの書き方が独特ですね。

- `def myfunction(arg):`でmyfunction関数(引数はarg)を定義
- `if __name__ == '__main__':`でメイン関数を定義

```python

#!/usr/bin/python
# -*- coding: utf-8 -*-

def myfunction(arg):
    return arg + 1

if __name__ == '__main__':
    result = myfunction(arg)
```

## 設定ファイル読み込み

API Tokenをソースファイル内にハードコーディングしてもいいけど、せっかくなので外部ファイルに保存し、それをスクリプトから読み込むようにする。
Pythonの標準ライブラリに`ConfigParser`というものがあり、Windowsのiniファイルに近い形式の設定ファイルを簡単に読み込める。

```ini:config.ini
[chatwork]
token = ********
```

```python
import ConfigParser

ini = ConfigParser.SafeConfigParser()
ini.read('./config.ini')
token = ini.get('chatwork', 'token')
```
これで`token`にはconfig.iniに記述しているtokenの値がセットされる。

## 「マイチャット」のID取得(GETリクエスト)

- [チャット一覧を取得するAPI](http://developer.chatwork.com/ja/endpoint_rooms.html#GET-rooms)

解説は見当たらなかったけど「マイチャット」であるかどうかは、チャットの`type`が`my`になっているので、その項目を探す。

チャット一覧を取得するには`https://api.chatwork.com/v2/rooms`に対してGETリクエストを投げればOK。
標準ライブラリでHTTPアクセスを行うには、`urllib2`を使用する。

- [20.6. urllib2 --- URL を開くための拡張可能なライブラリ — Python 2.7.15 ドキュメント](https://docs.python.org/ja/2.7/library/urllib2.html)
- [urllib2 を使ってインターネット上のリソースを取得するには — Python 2.7.15 ドキュメント](https://docs.python.org/ja/2.7/howto/urllib2.html)

基本的な構文は`urllib2.urlopen("http://www.example.org")`となるが、今回はAPI Tokenをヘッダに付加するため、Requestオブジェクトを作成(`urllib2.Request()`)し、それにヘッダ設定を追加(`request.add_header()`)している。
エラーの場合は例外が発生するのでハンドリングしておく。

レスポンスはJSON形式なので、これもPythonの`json`ライブラリを使って対象を探す。
`json.loads()`することで、Pythonの配列・辞書形式のオブジェクトに変換してくれる。(PowerShellの`ConvertFrom-Json`と同じ！)
チャット一覧は配列形式なので`for`で地道に探す。

[18.2. json --- JSON エンコーダおよびデコーダ — Python 2.7.15 ドキュメント](https://docs.python.org/ja/2.7/library/json.html)

```python
def get_mychat_id(token):
    url = 'https://api.chatwork.com/v2/rooms'
    request = urllib2.Request(url)
    request.add_header('X-ChatWorkToken', token)
    try:
        resp = urllib2.urlopen(request)
    except urllib2.HTTPError, e:
        print e.code
        print e.reason
        print e.read()
        return ""
    j = json.loads(resp.read())
    for item in j:
        if item['type'] == 'my':
            room_id = item['room_id']
    return room_id
```

## マイチャットへメッセージ送信(POSTリクエスト)

- [対象にメッセージを送信するAPI](http://developer.chatwork.com/ja/endpoint_rooms.html#POST-rooms-room_id-messages)

チャットのID取得と同様に`urllib2`を使用する。
今回はメッセージの送信のため、HTTPリクエストボディも追加する(`request.add_data()`)。
リクエストボディを追加すると、メソッドは勝手にPOSTになる。

リクエストボディはURLエンコードをしておく必要があるため、(urllib2とは別の)`urllib`を使用する(`urllib.urlencode()`)。

[20.5. urllib --- URL による任意のリソースへのアクセス — Python 2.7.15 ドキュメント](https://docs.python.org/ja/2.7/library/urllib.html#urllib.urlencode)

```python
def post_message(token, room_id, message):
    url = "https://api.chatwork.com/v2/rooms/" + str(room_id) + "/messages"
    request = urllib2.Request(url)
    request.add_header('X-ChatWorkToken', token)
    body = {
        'body' : message
    }
    request.add_data(urllib.urlencode(body))
    try:
        resp = urllib2.urlopen(request)
    except urllib2.HTTPError, e:
        print e.code
        print e.reason
        print e.read()
        return ""
    print resp.read()
```

あと、他言語やってたせいで引っかかったのが、intの値(room_idは文字列でなく数値)は`+`で文字列連結しようとしても型エラーになるので、明示的に`str()`で文字列にしてやる必要がある。


## ソース全体

チャットワークは[Google APIと違って](https://qiita.com/zaki-lknr/items/97c363c12ede4c1f25d2#%E3%82%A2%E3%83%AB%E3%83%90%E3%83%A0%E3%81%AE%E4%BD%9C%E6%88%90)日本語がそのまま送れました。。𩸽も問題なし。
(念のためPowerShellでも書いてみたけどShift_JISのソースコードなら問題なかった。𩸽は`?`になったけど)

```python:chatwork.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

import ConfigParser
import urllib
import urllib2
import json

def read_token():
    ini = ConfigParser.SafeConfigParser()
    ini.read('./config.ini')
    return ini.get('chatwork', 'token')

def get_mychat_id(token):
    url = 'https://api.chatwork.com/v2/rooms'
    request = urllib2.Request(url)
    request.add_header('X-ChatWorkToken', token)
    try:
        resp = urllib2.urlopen(request)
    except urllib2.HTTPError, e:
        print e.code
        print e.reason
        print e.read()
        return ""
    j = json.loads(resp.read())
    for item in j:
        if item['type'] == 'my':
            room_id = item['room_id']
    return room_id

def post_message(token, room_id, message):
    url = "https://api.chatwork.com/v2/rooms/" + str(room_id) + "/messages"
    request = urllib2.Request(url)
    request.add_header('X-ChatWorkToken', token)
    body = {
        'body' : message
    }
    request.add_data(urllib.urlencode(body))
    try:
        resp = urllib2.urlopen(request)
    except urllib2.HTTPError, e:
        print e.code
        print e.reason
        print e.read()
        return ""
    print resp.read()

if __name__ == '__main__':
    token = read_token()
    #print token
    id = get_mychat_id(token)
    print id
    post_message(token, id, "hello chatwork api!\n日本語\n改行も多バイトコードもそのまま書ける…？\nほっけ / 𩸽 / ホッケ")
```

※ urlopen()の例外はハンドリングしてるけど、そのあとは素通りしているので注意