HerokuでLINEとDiscordをつないでみる

# はじめに
[KC3 2020](https://kc3.me/conf/)の勉強会で話した内容です。
DiscordとLINEでテキストメッセージのやり取りができるようにします。
画像のメッセージのやり取りの実装などは別記事でやります。

- [LINE Botの準備](https://qiita.com/sanbasan/items/8145140bb67c4c6ef516#line-bot%E3%81%AE%E6%BA%96%E5%82%99)
- [Discord Botの準備](https://qiita.com/sanbasan/items/8145140bb67c4c6ef516#discord-bot%E3%81%AE%E6%BA%96%E5%82%99)
- [コードを書いてHerokuにデプロイ](https://qiita.com/sanbasan/items/8145140bb67c4c6ef516#%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%9B%B8%E3%81%84%E3%81%A6heroku%E3%81%AB%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4)
- [環境変数の登録などの諸設定](https://qiita.com/sanbasan/items/8145140bb67c4c6ef516#%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%81%AE%E7%99%BB%E9%8C%B2%E3%81%AA%E3%81%A9%E3%81%AE%E8%AB%B8%E8%A8%AD%E5%AE%9A)

LINEでメッセージ送信 → LINE BotがHerokuにメッセージの内容を送信 → HerokuがDiscord Webhookにメッセージの内容を送信 → Discordでメッセージが表示される

Discordでメッセージ送信 → Discord BotがHerokuにメッセージの内容を送信 → HerokuがLINE Botにメッセージの内容を送信 → LINE Botが発言
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/be3aa3e5-7783-c339-38c1-ad2c771422db.png">大まかにはこのような構成になっています。

# LINE Botの準備
### LINE Developersにログイン
[LINE Developers](https://developers.line.biz/)にログイン。LINEアカウントでログインできる。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/e4af0b64-cd2a-2167-0c24-23f30749f040.png">

### Providerを作成
Providers一覧の上の”Create”ボタンを押し、名前を決めて新規作成。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/f1b15ca9-dedd-cd4c-d971-2977923c90ce.png">

### Channelを作成
作成したProviderのChannelを作成する。Create a Messaging API Channelを選択。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/d16790c2-8711-c71c-9ce9-950d3bc4c426.png">
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/85746039-6d78-b572-a5e5-d5fb6af3ecaf.png">
入力項目を埋めてChannelを作成する。
このとき、ブラウザの拡張機能が干渉してインプットが消えてしまうことがあった。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/7b9c5a33-a210-8b55-0731-a9336babc296.png">

### LINE Botの設定を変更
[LINE Official Account Manager](https://manager.line.biz/)の応答設定からLINE Botの設定のWebhookをオンにする。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/a466c83b-d2cd-48f7-168c-fea65e734976.png">

### LINE Botと友だちになる/グループに招待
グループに参加させるには[LINE Developers](https://developers.line.biz/)のChannel設定のMessaging APIタブからグループ参加を有効にする必要がある。
友だち追加はBasic settingsタブのQRコードなどから可能。
グループの各メンバーはBotを友だち追加する必要がある。

これでLINE Botの準備は完了。

[環境変数の登録などの諸設定](https://qiita.com/sanbasan/items/8145140bb67c4c6ef516#%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%81%AE%E7%99%BB%E9%8C%B2%E3%81%AA%E3%81%A9%E3%81%AE%E8%AB%B8%E8%A8%AD%E5%AE%9A)をするために[LINE Developers](https://developers.line.biz/)を開く。

# Discord Botの準備
### Discord Developer Portalにログイン
[Discord Developer Portal](https://discord.com/developers)にログインする。

### Applicationを作成
右上の"New Application"を押して名前を指定し、Applicationを新規作成する。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/d286f30d-aae5-074d-ff5f-68d43eb73ad6.png">

### Discord Botの作成
BotタブからBotを新規作成する。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/7dae8994-042b-b64d-036b-2acff7fece09.png">

### Discord Botをサーバーに招待
OAuth2タブで適宜必要なチェックを入れて生成されたURLを開く。
そのページでBotをサーバーに招待する。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/02521f5b-b3c5-c9cc-b3b6-13cbbf570186.png">
これでDiscord Botの準備は完了。Botはサーバーに参加していてオフラインの状態。

# コードを書いてHerokuにデプロイ
Node.js(今回は12.8.3を使用)でHerokuで行う処理を記述する。

### 下準備
`git init`や`npm init`など叩いてgit・npmが使える状態にする。ここは割愛。
[Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli)を使用するのでインストールしておく。

### 必要なパッケージの導入
- [@line/bot-sdk](https://www.npmjs.com/package/@line/bot-sdk)
- [discord.js](https://www.npmjs.com/package/discord.js)
- [express](https://www.npmjs.com/package/express)
- [axios](https://www.npmjs.com/package/axios)

```
$ npm install @line/bot-sdk discord.js express axios
```
を実行してパッケージを導入。

### LINE → Discordの処理
jsファイルを作成してコードを書いていく。

```linebot.js
const express = require('express');
const axios = require('axios');
const line = require('@line/bot-sdk');

const PORT = process.env.PORT || 5000;

const lineConfig = {
  channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.LINE_CHANNEL_SECRET,
};
const lineClient = new line.Client(lineConfig);

const discordWebhookUrl = process.env.DISCORD_WEBHOOK_URL;
const discordWebhookConfig = {
  headers: {
    'Accept': 'application/json',
    'Content-type': 'application/json',
  },
};

const generatePostData = (event, profile) => {
  const type = event.message.type;
  if (type !== 'text') {
    return {
      username: profile.displayName,
      avatar_url: `${profile.pictureUrl}.png`,
      content: 'テキスト以外のメッセージを送信しました。',
    };
  }
  return {
    username: profile.displayName,
    avatar_url: `${profile.pictureUrl}.png`,
    content: event.message.text,
  };
};

const lineBot = async (req, res) => {
  res.status(200).end(); // 'status 200'をLINEのAPIに送信

  const events = req.body.events;
  events.forEach(async (event) => {
    try {
      const profile =  await lineClient.getProfile(event.source.userId);
      const postData = await generatePostData(event, profile);
      // DiscordのWebHookにPOST
      await axios.post(discordWebhookUrl, postData, discordWebhookConfig);
    } catch(error) {
      console.error(error);
    }
  });
};

const app = express();
app.post('/linehook/', line.middleware(lineConfig), (req, res) => lineBot(req, res));
app.listen(PORT, () => console.log(`Listening on ${ PORT } for LINE Messaging API.`));
```
expressでLINE BotからのPOSTを受けてDiscord WebhookにPOSTしている。
LINEのアイコンと名前をDiscordで出力する際に反映させている。

### Discord → LINEの処理
おなじくjsファイルを作成してコードを書いていく。

```discordbot.js
const line = require('@line/bot-sdk');
const discord = require('discord.js');

const lineConfig = {
  channelAccessToken: process.env.LINE_CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.LINE_CHANNEL_SECRET,
};
const lineClient = new line.Client(lineConfig);

const discordClient = new discord.Client();

const generateMessage = (event) => {
  const content = event.content;
  if (content.length === 0) {
    return {
      type: 'text',
      text: 'Discordで非対応の形式のメッセージを送信しました。',
      sender: {
        name: event.author.username,
        iconUrl: event.author.displayAvatarURL().replace('.webp', '.png'),
      },
    };
  }
  return {
    type: 'text',
    text: `${content}`,
    sender: {
      name: event.author.username,
      iconUrl: event.author.displayAvatarURL().replace('.webp', '.png'),
    },
  };
};

discordClient.on('message', async (event) => {
  try {
    // BOTのメッセージはスルー
    if (!event.author.bot) {
      const message = generateMessage(event);
      await lineClient.pushMessage(
        // 送信先のトークルームのグループ/ユーザーID
        process.env.LINE_TARGET_ID,
        message,
      );
    }
  } catch(error) {
    console.error(error);
  } 
});

discordClient.login(process.env.DISCORD_ACCESS_TOKEN);

```
Discordのアイコンと**ユーザーID**をLINE Botが発言するときに使用する。

### Procfileの記述
Herokuでjsファイルが実行できるようにProcfileを書いておく。

```Procfile
line: node linebot.js
dicord: node discordbot.js

```

### Herokuにデプロイ
[Heroku スターターガイド (Node.js)](https://devcenter.heroku.com/ja/articles/getting-started-with-nodejs#-4)にあるようにHeroku CLIでデプロイする。

まだこの時点では動作しない。

# 環境変数の登録などの諸設定
[Heroku Dashboard](https://dashboard.heroku.com/)のSettingsのConfig Varsから環境変数を設定できる。
Heroku CLIからも可能。[Herokuで環境変数を設定する方法 - Qiita](https://qiita.com/kazuhikoyamashita/items/2c3c31155e98675f780f)
上のコードだと以下の5つを設定する。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/b50191f7-dfd4-ce26-edf6-57e03a993999.png">

### LINEのChannel access token
[LINE Developers](https://developers.line.biz/)のChannelの設定画面のMessaging APIタブの一番下にChannel access token (long-lived)がある。
上のコードでは`LINE_CHANNEL_ACCESS_TOKEN`として設定する。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/94c874bc-f5f6-81be-be8e-c3af0910c861.png">

### LINEのChannel secret
[LINE Developers](https://developers.line.biz/)のChannel設定画面のBasic settingsタブにChannel secretがある。
上のコードでは`LINE_CHANNEL_SECRET`として設定する。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/8455b831-abc5-3efa-0624-1e3976129c62.png">

### LINE Messaging APIのWebhook URLを設定
[LINE Developers](https://developers.line.biz/)のChannelの設定画面のMessaging APIタブでWebhook URLを"https://＜Herokuのアプリ名＞.heroku.com/linehook"に設定する。
設定するとVerifyというボタンが出る。押してSuccessと出れば成功。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/2cdbea41-d410-84a4-fc39-aea2f4d2514f.png">

### DiscordのWebhook URL
[タイトル: Webhooksへの序章 – Discord - Discord Support](https://support.discord.com/hc/ja/articles/228383668-%E3%82%BF%E3%82%A4%E3%83%88%E3%83%AB-Webhooks%E3%81%B8%E3%81%AE%E5%BA%8F%E7%AB%A0)のようにWebhook URLを取得し、それを上のコードでは`DISCORD_WEBHOOK_URL`として設定する。

### DiscordのToken
[Discord Developer Portal](https://discord.com/developers)の作成したアプリケーションの画面のBotタブからTokenを取得し、上のコードでは`DISCORD_ACCESS_TOKEN`として設定する。
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/537455/373b9d77-d619-f4e3-fba2-2b725a2e446e.png">

### Botが発言するトークルームのID
LINE Botが発言するトークルームのIDを指定しておく必要がある。
[グループチャット | LINE Developers](https://developers.line.biz/ja/docs/messaging-api/group-chats/#%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B7%E3%82%99%E3%82%A4%E3%83%98%E3%82%99%E3%83%B3%E3%83%88%E5%88%A9%E7%94%A8%E6%99%82%E3%81%AE%E3%83%92%E3%83%B3%E3%83%88)にあるようにメッセージイベントの`source`プロパティからIDを取得できる。`linebot.js`にコードを書き足してログに表示するようにしておけばHerokuの実行ログから確認できる。それを上のコードでは`LINE_TARGET_ID`として設定する。

### dynoを起動
[Heroku Dashboard](https://dashboard.heroku.com/)からdynoを起動する。
これでテキストメッセージのやり取りはできるようになる。

# 参照
- [ドキュメントトップ | LINE Developers
](https://developers.line.biz/ja/docs/)
- [Discord Developer Portal](https://discord.com/developers/docs)
- [Heroku Dev Center](https://devcenter.heroku.com/articles/free-dyno-hours#dyno-sleeping)
- [@line/bot-sdk - npm](https://www.npmjs.com/package/@line/bot-sdk)
- [discord.js - npm](https://www.npmjs.com/package/discord.js)
- [express - npm](https://www.npmjs.com/package/express)
- [axios - npm](https://www.npmjs.com/package/axios)
- [Herokuで環境変数を設定する方法 - Qiita](https://qiita.com/kazuhikoyamashita/items/2c3c31155e98675f780f)
- [Heroku Schedulerの設定方法 - Qiita](https://qiita.com/Taro_man/items/2eab3e3acad88c5b759e)

これらの記事も参考になると思います。

- [LINE BOTをHeroku + Node.jsでやるまで - Qiita](https://qiita.com/TakuTaku04/items/cb71f10669a9e9cbf71b)
- [Heroku + LINE + node.js (Express.js)でオウム返しボットを作成する - Qiita](https://qiita.com/abemaki/items/44d2015561c31ea9628e)
- [Discord.jsを使って開発したDiscordボットをHerokuで動かす方法 - Qiita](https://qiita.com/InkoHX/items/590b5f15426a6e813e92)