Google Apps Script で LINE Bot を作成する

Google Apps Script を使うと無料で簡単に LINE Bot (LINE で会話できるプログラム) を作ることができます。

LINE Bot のプログラムを Web アプリとして公開しておくと、その URL に LINE からリクエストが来るという形でメッセージが届きます。 このように他システムからの通知を Web の仕組みで受け取ることを webhook と呼びます。 こちらのプログラムからメッセージを発信するときは逆に LINE 側が Web アプリとして待ち構えているので UrlFetchApp でその URL にリクエストを送ります。 前回前々回 の内容も思い出しつつ進めていきましょう。

「私は LINE なんか使っていません!」という人は同じような方法で Discord や Slack などのボットを作ることができますので挑戦してみてください。

目次

下準備

Bot のプログラムを作り始める前に LINE にアクセスするための準備が必要です。 今回は LINE Messaging API という仕組みを使います。

  1. LINE Deveplopers に LINE アカウントでログインする (QRコードログインが簡単です)
  2. 開発者情報を登録する
  3. 英語で表示されていて日本語に切り替えたい場合は切り替える (右上に切り替えメニューがあります。以下、日本語に設定したものとして説明します。)
  4. プロバイダー (Provider) を作成する
    Bot の提供者として表示される名前です。何個でも作れるので練習している間は適当でOKです。Bot を公開することになったらちゃんとしたのを考えましょう。
  5. チャネル (Channel) を作成する
    プロバイダー管理画面の「チャネル設定」から作成します。チャネルは 1 Bot につき 1 個作ります。
  6. チャネル管理画面の「Messaging API設定」を以下の通りに変更する

トークン というのはアクセスを許可してよい相手かどうかを識別するためのパスワードのような情報です。自動生成されたすごく長い文字列を使います。 パスワードと同様、みだりに他者に教えてはいけません。

LINE からのメッセージを受信する

まずは LINE からのメッセージを受け取るところまでを見ていきます。今まで通り、新しいスプレッドシートを作成して Google Apps Script のエディタを開いてください。受信したデータをそのスプレッドシートに書き込んでみることにします。

冒頭で説明した通り webhook を使うために Web アプリに似たプログラムを作るのですが、ここでは doGet(e) の代わりに doPost(e) を使います

// doGet(e) ではなく doPost(e) を使う
function doPost(e){
  let sheet = SpreadsheetApp.getActive().getActiveSheet();
  sheet.appendRow([new Date(), e.postData.contents]); // e.postData.contents に LINE からの json 形式データがある
}

Web の通信方法 (HTTP) では主なリクエストの種類 (method) が GET と POST の 2 種類あってそれに対応して関数も 2 種類あります。 極めて単純化して説明すると GET は「ください」、POST は「送ります」で webhook では普通 POST を使います。 LINE Bot にメッセージを送ると、LINE のサーバーから GAS の Web アプリに対して POST リクエストが送られてきます。 最初に載せた図を再掲します。

POST リクエストが届くと、リクエスト情報を引数 e として doPost(e) が実行されます。 POST で送られたデータには e.postData というオブジェクトとしてアクセスできます。 送られてきたデータは e.postData.contents にテキストとして入っています。 先ほどのプログラムはそのデータをスプレッドシートに記録しますので、具体的にどんなテキストが送られてきているかは、この後実際に確認します。 まずは Bot のテストまで進めてください。

普通の Web アプリでは HTML 文書を作って最後に return するのですが、LINE bot を作る今回はする必要がありません。

Webhook の設定

プログラムが書けたら Web アプリとして公開してください。「デプロイをテスト」の方ではなく「新しいデプロイ」の方を使います。「アクセスできるユーザー」を「全員」にするのをお忘れなく。 やり方を忘れてしまった人は 前回資料の「Web アプリを公開する」 を見てください。

得られた公開用の URL を「Webhook設定」の Webhook URL に設定してください。下図のようになります。 Webhook URL を設定するとその下に「Webhookの利用」というスイッチが表示されますのでオンにしてください(オンだったらそのままでOKです)。 「検証」ボタンを押して「成功」と表示されればOKです。

「検証が成功しない!」と困っている人は、貼り付けている URL が間違っていないか、テスト用の URL を使っていないか確認してください。 URL をコピーするときに端の文字が抜けてしまったりすることがありますので、コピーするときには URL の下にあるコピーボタンを使うようにしましょう。

テスト用URLは開発に使っている Google アカウントにログインした状態でのアクセスしか受け付けないため、 LINE のサーバーからのアクセスを受け付ける必要がある今回のようなケースでは使えません。 プログラムを少し書き換えるだけでも「デプロイの編集」をして動作確認する必要があるのでとても面倒です。

Bot のテスト

Messaging API設定ページの上の方にある QR コードから Bot を友達として追加してメッセージを送ってみましょう。さらにスタンプも送ってみましょう。スプレッドシートにデータが追記されたら受信までは成功です。

イベントの形式

次はスプレッドシートに記録された受信データを見ていきましょう。 下に引用したようなデータがあるはずです (userId などは *** と黒塗りしています)。 メッセージを送ったときの分に加えて、友達に追加したときの分など多くの情報が記録されていると思います。 (送った覚えのないメッセージはおそらく Webhook URL の設定のときに「検証」ボタンを押したときに送られたものです) これら通知されることがらをすべてひっくるめて「イベント」と呼んでいます。

{
  "events": [
    {
      "type": "message",
      "replyToken": "********************************",
      "source": {
        "userId": "U********************************",
        "type": "user"
      },
      "timestamp": 1547713760772,
      "message": {
        "type": "text",
        "id": "*************",
        "text": "こんにちは"
      }
    }
  ],
  "destination": "U********************************"
}

このテキストデータ形式は json と呼ばれるもので JavaScript のオブジェクトと高い互換性があって相互に変換することができます。 送受信のために json を使うことが多いです。 json 形式のテキストを JavaScript のオブジェクトへ変換するには JSON.parse(...) を使います。

イベントが { "events": [...] } と配列になっていることに注意してください(名前も複数形になっています)。 一回の受信で複数のイベントが通知されることがあるためです。正しく処理するには繰り返しが必要になるということです。

次は下のように JSON.parse(...) を使ってプログラムを書き換えて、通知されたイベントの種類によって条件分岐するプログラムにします。 届いたのがテキストメッセージだったなら(条件)、そのメッセージを英訳してスプレッドシートに追記する機能を追加しています。 送られてきた内容に反応するのが Bot の基本で、条件分岐を増やすことで Bot の機能を増やしていくことができます。

追加した部分をハイライトしています。全体をコピペして doPost(e){ ... } が二つになるのは間違いですのでご注意ください。

function doPost(e){
  let sheet = SpreadsheetApp.getActive().getActiveSheet();
  sheet.appendRow([new Date(), e.postData.contents]);
  let data = JSON.parse(e.postData.contents); // LINE から来た json データを JavaScript のオブジェクトに変換する
  let events = data.events;
  for(let i = 0; i < events.length; i++){ // すべてのイベントについて繰り返し処理をする
    let event = events[i];
    if(event.type == 'message'){ // メッセージ受信イベントであるか判定
      if(event.message.type == 'text'){ // 受信したのが普通のテキストメッセージであるか
        let translatedText = LanguageApp.translate(event.message.text, 'ja', 'en'); // 英訳して
        sheet.appendRow([new Date(), translatedText]); // スプレッドシートに追記
      }
    }
  }
}

プログラムを更新したら「デプロイを管理→編集」として更新するのをお忘れなく!「新しいデプロイ」をするとURLが変わるので webhook の設定も変えなければならなくなりますので要注意!

LINE からメッセージを送って動作確認を行いましょう。 まだ LINE アプリ上で返事は帰ってきませんが、スプレッドシートに記録される内容が増えていると思います。 Bot を作るにあたってスプレッドシートへの記録が必須なわけではありませんが、プログラムが動作している様子を確認するためには便利です。 バグがあって Bot の返信がないとき、自分が書いたプログラムのどこまでが正しく動いているのかを確認するのに役立ちます。

「メッセージが届いた」というのはイベントの一種で、ほかにもフォロー・フォロー解除など様々な場面でイベント通知が届きます。 詳しく知りたい人は公式ドキュメント Webhookイベントオブジェクト を参照してください。

返信する

次は受け取ったメッセージを英訳して返信するようにしてみましょう。 「受信したのが普通のテキストメッセージだったとき」の条件判定は上と同様で、返信データを作成し、返信するプログラムに書き換えていきます。

データの形式や送り方は、受信する側の指定に合わせてあげる必要があります。 それについて説明するドキュメントが用意されているはずなので探し出して読み解きます。 今回は LINE の Messaging API リファレンスに 「応答メッセージを送る」 という項目がありました。探し出すのも読み解くのも慣れないうちは難しいかもしれません。今回は一緒に読み解いてみましょう。

Web API リファレンスを読む時のチェックポイント

HTTPリクエストの種類と宛先 URL は?
今回の場合「データを送る必要があるのでおそらく POST だろう」と予測しつつ確認する。
認証方法 (authorization) は?
「必要なトークンはどうやって入手するのかな?」と予測しつつ確認する。どのアプリからの通信かを認証するトークン、そのアプリを使っているどのユーザかを認証するトークンなど、2つのトークンを組み合わせる場合が多いです。
送るべきデータとその形式は?
HTTPで送るデータには大まかに分けて2種類(リクエストヘッダー、リクエストボディ)あります。 ヘッダーにはデータの形式についての情報や認証トークンといった通信を成立させるのに必要な情報、ボディには送りたいデータそのものを含めるのが一般的です。 それ以外に、送るデータが小さいときには URL の最後にオマケのように付け足して送るときもあります。

ドキュメントを読んでもピンとこないところがあったら、ドキュメント中のサンプルを見るのがお勧めです。

チェックポイントに従って「応答メッセージを送る」を読んでみる

HTTPリクエストの種類と宛先 URL は?
「POST https://api.line.me/v2/bot/message/reply」 → 予想通り POST だ。
認証方法 (authorization) は?
「応答メッセージを送るには、Webhookイベントオブジェクトに含まれる応答トークンが必要です。」→ 受信したデータをスプレッドシートに記録したから見てみよう。replyToken というのが確かにあるぞ。これを使うのか。
アプリの認証はどうなっているのかな?他のトークンについて説明がないかな?
送るべきデータとその形式は?
Content-Type (必須ヘッダー) は「application/json」 → json 形式のテキストで送れってことね。
Authorization (必須ヘッダー) は「Bearer {channel access token}」 → チャンネルアクセストークンをヘッダーに入れて認証するパターンか。さっきチャンネルを作ったからトークンもそこかな?
ボディには replyToken と messages が必須なのか。メッセージはどんな形式にすればいいのかな?お!「リクエストの例」がわかりやすいな。

ドキュメントによると返信には2種類のトークンが必要です。 ひとつは 応答トークン で、受信したメッセージに含まれるものをそのまま送り返すことでどのメッセージへの返信かを示すためのものです。 もうひとつは自分の開発したボットからの通信だと認証するための チャンネルアクセストークン で Messaging API 設定のページで発行ボタンを押したら表示されたものです。

どんな HTTP リクエストを送ればいいかがわかったら次は Google Apps Script で HTTP リクエストを送る方法を知る必要があります。 説明する方が難しくなってしまいそうなので実際のプログラムを見ていきたいと思います。 詳しい説明は Google Apps Script の UrlFetchApp のドキュメント を読んでください。

LINE から GAS への通信も、GAS から LINE への通信も、ある決まった URL に対するアクセスになっています。 誰からのどんな内容のメッセージなのか、どのメッセージへの返信なのかといった通信毎に変わってくる情報は POST するデータの方に含めることになります。 1つの関数と1つのURLが対応していて、その関数への引数が POST するデータになると考えるとわかりやすいかもしれません。

次のプログラムの最初のハイライト部分 (10-13行目)を見てください。ここではドキュメントの「リクエストボディ」の項目の通りに送信データを準備しています。 ここでは JavaScript のオブジェクトを作成しておいて後で json 形式のテキストに変換します。 元のメッセージに含まれる replyToken を送信データに含めていること、返信するメッセージが1つでも配列にすること、等に注目しましょう。

function doPost(e){
  let data = JSON.parse(e.postData.contents); // LINE から来た json データを JavaScript のオブジェクトに変換する
  let events = data.events;
  for(let i = 0; i < events.length; i++){
    let event = events[i];
    if(event.type == 'message'){
      if(event.message.type == 'text'){ // 受信したのが普通のテキストメッセージだったとき
        let translatedText = LanguageApp.translate(event.message.text, 'ja', 'en');
        // 送信するデータをオブジェクトとして作成する
        let contents = {
          replyToken: event.replyToken, // event.replyToken は受信したメッセージに含まれる応答トークン
          messages: [{ type: 'text', text:  translatedText }],
        };
        reply(contents); // 下で説明
      }
    }
  }
}

function reply(contents){
  let channelAccessToken = "チャンネルアクセストークンの長い文字列をここに貼り付ける";
  let replyUrl = "https://api.line.me/v2/bot/message/reply"; // LINE にデータを送り返すときに使う URL
  let options = {
    method: 'post',
    contentType: 'application/json',
    headers: {
      Authorization: 'Bearer ' + channelAccessToken
    },
    payload: JSON.stringify(contents) // リクエストボディは payload に入れる
  };
  UrlFetchApp.fetch(replyUrl, options);
}

実際に返信を行う処理は reply(contents) という関数にまとめています。 UrlFetchApp のドキュメント の説明に従って、 POST リクエストのヘッダーとボディは URLFetchApp.fetch(url, options) の第二引数にオブジェクトとして渡しています。 2つ目のハイライト部分 (23-30行目) が、先に読み解いた通りの送信方法になるようオブジェクトを作成している部分です。 送るデータを JSON 形式に変換するために JSON.stringify(...) を用いています。

プログラムを更新したらデプロイの更新をお忘れなく!(not 新しいデプロイ) LINE からメッセージを送って動作確認を行いましょう。

「デプロイの更新もちゃんとしたのに返信が来ない!」と困っている人は、トークンが正しく貼り付けられているか確認してください。 LINE Developers のページでトークンをコピーするときに端の1文字を漏らしてしまうことなどがありますので、 横にある小さなコピーボタンを使うとよいでしょう (再発行ボタンではないので注意)。 ブラウザの翻訳機能を使っていると表示されているトークンが部分的に翻訳されてしまっていて…ということがあるようですが、コピーボタンを使えばこの問題も起きません。 プログラムに貼り付けるときには " がトークンの両端に必要なのでうっかり消してしまわないように、余計な空白文字などが入らないように気を付けてください。

トークンのような仕組みは Google Apps Script や LINE Bot に限らず、様々な外部システムとのデータ連携で登場します。 1文字でも間違っていると動かかないのでコピー時に注意が必要なこと、公開してはいけないことなどの注意点も同様ですので覚えておきましょう。

「トークンはパスワードのようなものなのにプログラムに貼り付けてしまって大丈夫なのか?誰かにみられてしまうのではないか?」と心配した方、大変的確な心配ができていてすばらしいと思います。 トークンが含まれているプログラムのコードを公開してはいけません。 Web アプリを公開してもコードが公開されるわけではないので大丈夫です。 また、万が一トークンが流出しても LINE アカウントを完全に乗っ取られることにはなりませんのでご安心ください (Bot は乗っ取られます)。 うっかりトークンを漏らしてしまったときには LINE Developers の設定画面からトークンを再発行 (Reissue) すれば、元のトークンは無効になります。

「トークンを使うプログラムを公開したい時はどうしたらいいの?」の答えは語るとなかなか長くなりますのでひとまずキーワードだけ置いておきます。 一般には「環境変数」というものを使います。Google Apps Script では「スクリプトプロパティ」というものを使います。

演習

こんな bot を作ってみましょう。