LINE WORKS トークBot をPythonで実装してみる 〜前編: API認証〜

# はじめに
　ビジネス版LINEという位置付けのLINE WORKSにチャットボットを実装できます。
　最近、LINE WORKSのチャットボットを開発することになったので、その実装方法についてまとめようと思います。

### LINEボットと同じ？
　LINEのチャットボットのAPIは Messaging APIと言いますが、LINE WORKSのAPIは __LINE WORKS Bot Platform API__ と言うそうです。
　私自身、これまでLINEのチャットボット開発をいくつか経験してきましたのでLINE WORKSの方もすぐ使えると思いましたが、やはりAPI仕様としてはほぼ別物という印象です。返答の仕方やメッセージの種類も異なります。
　ただ、UIは似たものですので使う側は馴染みやすいと思います。

LINE WORKS Developersドキュメント: https://developers.worksmobile.com/jp/document/300130001?lang=ja

### サービスAPIとサーバーAPI
LINE WORKSのAPIには「サービスAPI」と「サーバーAPI」の2種類があります。

__サービスAPI__
>ユーザーのログインが必要なAPIです。本人のデータへのみアクセスできます。

__サーバーAPI__
>ユーザーのログインが不要な APIです。

API共通ガイド: https://developers.worksmobile.com/jp/document/3002001?lang=ja

APIそれぞれの説明にどちらの種類なのか記載されてます。
LINE WORKS トークBotで利用するAPIは「サーバーAPI」になります。なので、Token発行などはサーバーAPIの利用手順に沿って行います。


# 検証環境
言語: Python 3.7

# 0. 前準備


## 0.1. アカウント権限

対象のLINE WORKSのテナントへBotを設定するために、開発用のアカウントが必要です。

- 管理画面の「メンバー > 管理者権限」で開発用の権限の作成 (権限は "サービス>Bot" および "developers") 
- 作った管理者権限にメンバーを紐付け

## 0.2. LINE WORKS Developer Consoleで各種キーの発行

LINE WORKSのAPIを利用するために必要なキーをLINE WORKS Developer Consoleで発行します。

LINE WORKS Developer Console: https://developers.worksmobile.com/jp/console/openapi/main

|項目|説明|
|-------------------|------|
| __API ID__ |API認証用のAPI ID|
| __Server API Consumer Key__ |サーバー APIのConsumer Key|
| __Server List ID__ |Server List(ID登録タイプ) のID|
| __Server List 認証キー__ | 上のServer List IDに対応する認証キー (秘密鍵)|

## 0.3. Botの登録
BotをLINE WORKS Developer Consoleで登録します。

Bot - LINE WORKS Developer Console: https://developers.worksmobile.com/jp/console/bot/view

1. 「登録」をクリックし登録画面を開く
2. 必須となっている情報を入力  
    ※ 「Callback URL」はボットのサーバーを構築後に「修正」から登録。
3. 「保存」をクリックし、Botリストへ戻る
4. 登録したBotを選択し、情報表示。記載されている __Bot No__ を控える。

## 0.4. 環境変数へ設定
上記で発行した各種キーを環境変数へ設定する。

## 0.5. Token 発行
トークBotのAPIを利用するにあたって、Tokenを発行する必要があります。

API認証の準備: https://developers.worksmobile.com/jp/document/1002002?lang=ja

### JWT生成
__使うもの__
- Server List ID
- Server List 認証キー

```python
import os
import jwt
import datetime

SERVER_LIST_ID = os.environ.get("SERVER_LIST_ID")  # Server List ID
SERVER_LIST_PRIVATEKEY = os.environ.get("SERVER_LIST_PRIVATEKEY")  # Server List 認証キー

def get_jwt():
    """
    JWTの生成
    """
    current_time = datetime.datetime.now().timestamp()

    iss = SERVER_LIST_ID
    iat = current_time
    exp = current_time + 3600 # 1時間有効
    secret = SERVER_LIST_PRIVATEKEY

    jwstoken = jwt.encode(
        {
            "iss": iss,
            "iat": iat,
            "exp": exp
        }, secret, algorithm="RS256")

    return jwstoken.decode('utf-8')
```



### Token 発行
__使うもの__
- 生成したJWT
- API ID

```python
import os
import json
import requests
import urllib

API_ID = os.environ.get("API_ID")  # API ID

def get_server_token(jwttoken):
    """
    Token発行
    """
    url = 'https://authapi.worksmobile.com/b/' + API_ID + '/server/token'
    headers = {
        'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8'
    }
    params = {
        "grant_type" : urllib.parse.quote("urn:ietf:params:oauth:grant-type:jwt-bearer"),
        "assertion" : jwttoken
    }

    form_data = params

    r = requests.post(url=url, data=form_data, headers=headers)

    body = json.loads(r.text)
    access_token = body["access_token"]

    return access_token
```

生成されたTokenを控えておく。

※ 注意
>発行された access_token が 24 時間以内利用されない場合には自動的に失効しますので、ID 登録タイプの場合は必ず一日に一回 Token を発行してください。
access_token の発行時間を token 情報と共に保存し、 Server API の利用時には　token　の有効期限を確認することを推奨します。
access_token の有効期限内であっても、token の再発行を行うと以前の token は無効になります。

https://developers.worksmobile.com/jp/document/1002002?lang=ja

### まとめ
上記のToken発行をまとめると、こんな感じです。

```python:publish_token.py
import os
import json
import jwt
import requests
import urllib
import datetime


SERVER_LIST_ID = os.environ.get("SERVER_LIST_ID")  # Server List ID
SERVER_LIST_PRIVATEKEY = os.environ.get("SERVER_LIST_PRIVATEKEY")  # Server List 認証キー
API_ID = os.environ.get("API_ID")  # API ID


def get_jwt():
    """
    JWTの生成
    """
    current_time = datetime.datetime.now().timestamp()

    iss = SERVER_LIST_ID
    iat = current_time
    exp = current_time + 3600 # 1時間有効
    secret = SERVER_LIST_PRIVATEKEY

    jwstoken = jwt.encode(
        {
            "iss": iss,
            "iat": iat,
            "exp": exp
        }, secret, algorithm="RS256")

    return jwstoken.decode('utf-8')


def get_server_token(jwttoken):
    """
    Token発行
    """
    url = 'https://authapi.worksmobile.com/b/' + API_ID + '/server/token'
    headers = {
        'Content-Type' : 'application/x-www-form-urlencoded; charset=UTF-8'
    }
    params = {
        "grant_type" : urllib.parse.quote("urn:ietf:params:oauth:grant-type:jwt-bearer"),
        "assertion" : jwttoken
    }

    form_data = params

    r = requests.post(url=url, data=form_data, headers=headers)

    body = json.loads(r.text)
    access_token = body["access_token"]

    return access_token


if __name__ == "__main__":
    jwttoken = get_jwt()
    access_token = get_server_token(jwttoken)

    print(access_token)
```

# 後編へ続く
次は実際のチャットボットの部分をまとめます。

後編: https://qiita.com/mmclsntr/items/28ba6baaf23124a53663

# 参考
https://qiita.com/leo1109/items/2e82c899891f8e771315
https://qiita.com/tokotan/items/f615f4a62219d655436f