DeepL API ProをSlackのアプリとして連動する

# はじめに
ココネ株式会社のサービス基盤開発室クライアントチームでツール開発している鞠(クク)です。

このページではDeepLでSlack用の翻訳ツールを作成してみました。

#### 仕様
本ツールは、Slack上の文言に対してDeepLを利用して自動翻訳してくれるツールになります。
やり方はSlackのメッセージに対して、国旗のリアクションを行うと、自動翻訳されたメッセージがスレッドに投稿されます。

![deepl_sample3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/31e7cdeb-6aa6-d9b8-4bd9-55fe62ef3792.png)


# DeepLとは？

:::note info
DeepL翻訳は、
2017年8月28日にサービスを開始したニューラル機械翻訳サービス。
ドイツのケルンに本拠地を置く DeepL GmbH が開発。

Google 翻訳よりも精度が高く、微妙なニュアンスのある翻訳ができると肯定的な報道と評価を受けている。
:::

https://ja.wikipedia.org/wiki/DeepL%E7%BF%BB%E8%A8%B3

#### 2023年1月31日に韓国語とノルウェー語がリリースされました。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/d518dc2e-9a1d-f566-4ece-dcab7bec0dcc.png">

https://www.deepl.com/ja/blog/welcome-korean-and-norwegian

# 開発環境

* DeepL API Pro（for Developers）
* Slack ワークスペース
* AWS API Gateway
* AWS Lambda

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/8c2e0df1-5195-29db-7ba0-f39f225bdfac.png">

# DeepL API Pro
:::note warn
無料版でも利用可能ですが、有料版のメリットとしては、
機密性が高い社内の情報を一瞬でもDeepLのサーバー側に保存されない上、
機械学習にも使われないようになります。
:::

#### 1. 契約プラン

https://www.deepl.com/ja/pro-api?cta=header-pro-api/

月使用量と、1,000,000文字あたり￥2,500 

<img width="400" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/aa443ce7-62ac-fca7-ce78-1533ca68aad5.png">

登録料金　￥630 /月　+　従量課金制　100万文字あたり￥630

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/cc2150ea-87a9-852b-b684-ee278296f4a3.png)

#### 2. DeepL API関連技術ドキュメント

https://www.deepl.com/ja/docs-api/

#### 3. DeepL APIで使用する認証キー
正常に登録を完了すると、次のアカウント情報ページに認証キーとAPIのドメインが表示されます。
LambdaでAPIコールするとき、必要になります。

https://www.deepl.com/ja/account/summary


#### 4. 使用量確認（文字数＆料金）

<img width="800" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/a92a935d-c8df-0be0-56db-4abcfbeae7fa.png">

次のURLで文字数＆料金が確認できます。

https://www.deepl.com/ja/account/usage


# AWS API Gateway

#### 1. 概要

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/a079c26e-3d6d-706f-5cee-28877cb218f0.png)

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/getting-started.html

#### 2. REST APIを作成

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-create-api.html

#### 3. POSTメソッドを作成
Resourcesの **[Actions]->[Create Method]->[POST]** メソッドを作成します。

#### 4.  Method Execution / - POST - Integration Request設定

Integration typeでLambda関数を選びます。
**Use Lambda Proxy integration**をチェックします。

![スクリーンショット 2023-03-07 21.46.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/25e2c149-f446-e1a3-bd08-5f86196f5c96.png)

#### 4. REST API のステージのセットアップ

[Actions]->[Deploy API]でDeployment stageで[New Stage]を指定して入力後デプロイします。
Stage名は自由に入力してください。

![スクリーンショット 2023-03-07 21.55.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/db1a3b09-abd2-4e37-f0c8-c3ce3edae77f.png)

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-stages.html

#### 6. REST API のデプロイ

デプロイ後、生成されたURLを下記のSlack APIのアプリに入力します。
(Slack API -> 登録アプリ -> Event Subscriptions -> Request URL)

https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-deploy-api.html

# AWS Lambda
:::note alert
こちらのコードはPython 3.9 Runtimeで動作確認しました。
:::
``` diff_python

import json
import os
import requests


CURRENT_PROJECT_VERSION = "0.0.1"
SLACK_CONVERSATIONS_REPLIES_URL = "https://slack.com/api/conversations.replies" 
SLACK_CHAT_POST_MESSAGE_URL = "https://slack.com/api/chat.postMessage" 

SLACK_TOKEN = os.environ.get('SLACK_TOKEN')
DEEPL_KEY = os.environ.get('DEEPL_KEY')
DEEPL_URL = os.environ.get('DEEPL_URL')
DEBUG_MODE = os.environ.get('DEBUG_MODE')

#data mapping between reaction names and language codes
LANG_CODES = {
  'us': 'en-us', #英語アメリカ
  'gb': 'en-gb', #英語イギリス
  'cn': 'ZH', #中国語
  'de': 'de', #ドイツ語
  'fr': 'fr', #フランス語
  'it': 'it', #イタリア語
  'jp': 'ja', #日本語
  'es': 'es',#スペイン語
  'nl': 'nl', #オランダ語
  'pl': 'pl', #ポーランド語
  'ru': 'ru', #ロシア語
  'flag-ua': 'uk', #Ukrainian
  'kr': 'ko', #韓国語
};
                
def is_challenge(event):
    if 'body' in event:
        body = json.loads(event['body'])
        if ('type' in body) & (body['type'] == 'url_verification'):
            return body['challenge']

def process_message(event):
    if 'body' in event:
        body = json.loads(event['body'])
        event_body = body['event']
        if ('type' in event_body) & (event_body['type'] == 'reaction_added') & ('reaction' in event_body) & (event_body['reaction'] in LANG_CODES):
            event_reaction = event_body['reaction']
            event_item = event_body['item']
            event_channel = event_item['channel']
            event_ts = event_item['ts']
            
            header_message={
                "Authorization": "Bearer {}".format(SLACK_TOKEN)
            }
            
            payload  = {
                "channel" : event_channel,
                "ts" : event_ts
            }
            res = requests.get(SLACK_CONVERSATIONS_REPLIES_URL, headers=header_message, params=payload)
            
            t_data = res.json()
            original_message = t_data['messages'][0]['text']
            
            if(DEBUG_MODE):
                print("original_message : ", original_message)
                
            trans_lang = LANG_CODES[event_reaction]

            # パラメータの指定
            deepl_params = {
                        "auth_key" : DEEPL_KEY,
                        "text" : original_message,
                        "target_lang": trans_lang  # 翻訳後の言語
                    }
            
            deepl_response = requests.post(DEEPL_URL, data=deepl_params) # URIは有償版, 無償版で異なるため要注意 -> 個人用

            trans_data = deepl_response.json()
            translate_messages = trans_data['translations'][0]['text']

            if(DEBUG_MODE):
                print("translate_messages : ", translate_messages)
                
            #判別ロージック追加
            marge_translate_messages = ":"+event_body['reaction']+": "+trans_lang+"\n"+translate_messages
                
            for i in t_data['messages']:
                if ('bot_id' in i) & (i['text'] == marge_translate_messages):
                    return

            chatpayload  = {
                "channel" : event_channel,
                "text" : marge_translate_messages,
                "thread_ts" : event_ts
            }
            
            if(DEBUG_MODE):
                print("marge_translate_messages : ", marge_translate_messages)
            
            res_chat = requests.post(SLACK_CHAT_POST_MESSAGE_URL, headers=header_message, params=chatpayload)
            return res.json()
    return

def lambda_handler(event, context):
    # Return Slack challenge parameter
    clg = is_challenge(event = event)
    
    process_message(event = event)

    return {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json"
        },
        "body": json.dumps({
            "challenge ": clg
        })
    }

```

_※コメントなどのリアクションがあったら、現ソースコードをリファクタリングする予定です。_


# Slack アプリケーション登録


#### 1. アプリを登録

https://api.slack.com/apps

Slackにログインした状態で、上記のURLにアクセスします。
[Create an App]をクリックしてアプリの名前と
下記の文言の後にアプリを使用するWorkspaceを選択します。
`Pick a workspace to develop your app in:`


#### 2. アプリの権限を設定

左上のメニューから **Features -> OAuth & Permissions** を選択

![スクリーンショット 2023-03-10 14.20.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/e2448104-4aa6-855f-7766-75b44d71d2a7.png)

上記の画面の Scopes -> Bot Token Scopesの[Add an OAuth Scope]で権限をを追加します。

https://api.slack.com/scopes

| 権限 | 詳細 |
|:-:|:-:|
| channels:history | メッセージの参照、履歴の参照 |
| groups:history | プライベート チャンネルのメッセージの参照、履歴の参照 |
| chat:write | チャンネルに投稿 |
| reactions:read | メッセージにつけられたリアクションを参照 |


https://api.slack.com/changelog/2020-09-15-events-api-truncate-authed-users

https://api.slack.com/search?query=post

#### 3. ボットの設定

こちらではボットの名とメンション名の設定します。
左のメニューから **Features -> App Home** 選択
**App Display Name -> Edit** ボタンを押して、設定を行います。

![スクリーンショット 2023-03-10 14.57.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/cf57348b-7bd6-7403-e838-f971eb472291.png)
![スクリーンショット 2023-03-10 14.58.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/37340a25-a9c6-8187-ff17-facb27f1fb38.png)

#### 4. アプリの外見設定

左のメニューから **Settings -> Basic Information -> Display Information**
アプリの設定画面で、アプリ名、説明文、バックグラウンドカラー、アイコン画像など設定します。

![スクリーンショット 2023-03-10 15.12.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/e0dea6ca-79b0-d34a-6c4c-527ff8cc8ae6.png)

https://api.slack.com/start/distributing/guidelines

#### 5. アプリをワークスペースにインストール

左のメニューから **Settings -> Install App** を選択します。
(**Features -> OAuth & Permissions**からも同様の操作が可能です。)

![スクリーンショット 2023-03-10 15.29.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/f0ea92ed-44ce-72a2-a077-6980f2958a1b.png)

:::note warn
権限によって実装内容が次のように変わります。
**A. 管理者権限がある場合**
　**[Install App to Workspace]** でインストール後そのまま許可できます。
**B. 一般権限の場合**
　**[Request to Install]** でインストールを依頼して管理者の承認待ちになります。
:::

**Slack Enterprise Grid Plan**の場合
複数のワークスペースで共通して使える **Org-Wide App**をインストールできます。

https://api.slack.com/enterprise/apps

#### 5. アプリのトークンを取得

インストール後には　左のメニューの 
**Settings -> Install App 又は Features -> OAuth & Permissions**で
xoxb- から始まる **Bot User OAuth Access Token** が取得できます。

また、セキュリティなどの必要に応じていつでもトークンが再発行できます。
**Features -> OAuth & Permissions -> Revoke All OAuth Tokens -> [Revoke Tokens]**

https://api.slack.com/authentication/oauth-v2

#### 6. Event Subscriptions設定

翻訳するときリアクションが付けることになりますが、
その時の確認のためイベントのサブスクライブ機能を使用します。

https://api.slack.com/apis/connections/events-api

左のメニューの **Features -> Event Subscriptions　-> Enable Events -> [Off]** クリックします。

![スクリーンショット 2023-03-10 16.23.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/07d21e66-0518-9b98-cf5c-8d7344684df7.png)

**Request URL**にAWS HTTP GatewayからデプロイしたURLを記入します。

![スクリーンショット 2023-03-10 16.23.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/08ba7c24-2a40-eb19-0c40-173c9026af55.png)

最初にURLを登録するには**challenge認証**が必要です。

https://api.slack.com/events/url_verification

Lambdaの次の関数で実装されています。

``` diff_python
def is_challenge(event):
```

設定内容としては以下のように機能を有効にして **Request URL** を設定し、**bot events** の方に **reaction_added** を追加し **[Save Changes]** ボタンを押して保存するだけです。

![スクリーンショット 2023-03-10 16.38.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/78a46770-8e36-6c2e-1f2a-3f3a2501bf9e.png)

https://api.slack.com/legacy/oauth-scopes

**Basic Information -> Building Apps for Slack -> Add features and functionality**の
設定画面は最終的に次のようになります。

![スクリーンショット 2023-03-08 16.49.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/af8f78cb-da54-a782-6d2b-4085d459cf45.png)

#### 7. アプリをチャンネルに招待して動作確認

対象Slack チャンネルアプリのボットユーザーを招待します。
**チャンネル詳細 -> インテグレーション -> App -> [アプリを追加する]**
アプリを検索して **[追加]**

![スクリーンショット 2023-03-10 16.41.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/85313f4f-f561-7892-3a09-d9b7cbe1ff5b.png)


メッセージに翻訳したい国の国旗の絵文字をリアクションでつけてください。
**:flag-fr:**

![スクリーンショット 2023-03-10 16.17.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3235832/f3402a5f-c6a9-a697-cc16-d0c456982ceb.png)


# 参照コード一覧　
:::note warn
**Python以外の言語(JS, TS)** で実装する方はこちらをご参考ください。
:::
https://github.com/seratch/deepl-for-slack

https://github.com/slack-samples/deno-message-translator

https://github.com/slackapi/reacjilator

# まとめ
一通り実装してみた感想はDeepL API周りは割と分かりやすくて、特に問題なかったんですが、
Slackの設定とAWS実装で色々試行錯誤しました。

今回の実装の開発環境で必須になる**Slack**と**DeepL**とは違って
**AWS**環境は自作サーバーでも他のサーバーレスクラウドでも何でも使いやすい環境で各自試してみてください。
* * *
:::note info
サーバー周りの環境を**Heroku**でご利用の方は次の**Kazuhiro Sera様**の次のドキュメントをご参考ください。
:::
https://qiita.com/seratch/items/a001985ee1dccaf95727