Ribbit's works

【Excelだけ】サーバー無しでチャットボットを作る

#VBA #Excel
にメンテナンス済み
記事のトップ画像

最近ではクラウドサービスやフレームワークが多く普及してきたことで、チャットボットを作るのがかなり容易になりました。

ただ手段は増えましたが、サービス間のデータをやり取りするためにサーバーが必要だったり、環境を整えることができない。という方もいらっしゃるかと思います。

そういった方のために、今回は Excel を使ってチャットボットを作る方法をご紹介します。

ちなみに今回紹介するのは、プラットフォームに Chatwork を使用してチャットボットを作成する手順となりますが、VBA のコードを作成したいクラウドサービス用に書き換えることで、Slack のような別のサービスでも使用可能です。

前提

チャットボットを作成する上で、ユーザーが使用する側のサービスからデータを受け取る手段として主流なのは、Webhook と呼ばれる技術です。

これはクラウドサービス側で設定するもので、クラウドサービス内でユーザが何らかのアクションを起こした際に、予め指定したサーバに POST リクエストを送信するという機能です。当然、そのクラウドサービスが Webhook に対応していない場合は使用できません。

ですが今回は、そもそもサーバがありません。

ではどうするか。

ポーリングします。

ポーリングについて

ポーリング(polling)とは、通信やソフトウェアにおいて、競合を回避したり、送受信の準備状況を判断したり、処理を同期したりするために、複数の機器やプログラムに対して順番に定期的に問い合わせを行い、一定の条件を満たした場合に送受信や処理を行う通信及び処理方式のことである。

Wikipedia

詳細に理解していただく必要はありませんが、重要なのは「プログラムに対して順番に定期的に問い合わせを行い」というところです。

サービス側からデータを受け取ることはできないので、こちら側から定期的に新しいアクションがないか問い合わせることで、チャットボットを実現します。

ポーリングのデメリット

この方法はチャットボットを実現する上で、Webhook に比べていくつも劣る部分があります。

レスポンスが悪くなる

Webhook であれば、

  1. ユーザーがアクション
  2. Chatwork が POST リクエスト
  3. リクエストを受け取り、返信
  4. ユーザーが確認

上記の流れでユーザーとのやり取りを行うこととなりますが、ポーリングの場合は、

  1. ユーザーがアクション
  2. Excel から問い合わせ
  3. アクションを起こしている場合は、返信を作成
  4. ユーザーが確認

という流れになります。

Webhook であれば、ユーザーがアクションを起こした直後に、

「こんなアクションがあったよ!」

とクラウドサービス側から教えてくれるのに対して、ポーリングでは、

「何かアクション起こしてない?」

とこちらから問い合わせるまでユーザーのアクションに気づくことができず、返事が遅くなってしまう可能性があります。

頻繁に問い合わせることで解決できるかもしれませんが、問い合わせには API を使用します。

Chatwork の場合は API の利用回数に上限が設けられており、2020 年 10 月時点では 5 分間に 300 回です。

利用規約に違反する可能性がある

上述の API 使用の観点から、クラウドサービスの利用規約に違反する可能性があります。

この方法を利用する場合は、API の使用に関して、利用規約を確認しておきましょう。

VBA の作成

前置きが長くなりましたが、ここから実際にチャットボットに使用する VBA コードを紹介していきます。

JSON の取り扱い

Chatwork API では、リクエストの返信が JSON 形式になります。

そのため、JSON ファイルを取り扱うライブラリである JsonConverter を使用します。ファイルは以下より取得できます。

VBA-JSON GitHub

Chatwork API の操作

以下のコードを、標準モジュール名「Chatwork」で作成します。

Private Const END_POINT As String = "https://api.chatwork.com/v2/"

' ここにチャットボットとして使用したいアカウントのAPIトークンを貼り付けてください
Private Const API_TOKEN As String = "xxxxxxxxxxxxxxxxxxxxxxxxxx"

Public Function Api(ByRef method As String, ByRef url As String, ByRef param As String) As String

    Dim httpRequest As Object
    Set httpRequest = CreateObject("MSXML2.XMLHTTP")

    With httpRequest
        Call .Open(method, END_POINT & url, False)
        If method = "POST" Or method = "PUT" Then
            Call .setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
        End If

         ' 認証
        Call .setRequestHeader("X-ChatWorkToken", API_TOKEN)
        Call .send(IIf(param <> "", param, Null))

        Api = .responseText
    End With
End Function

Public Function GetContacts() As String
    GetContacts = Api("GET", "contacts", "")
End Function

Public Function GetMessages(ByRef roomId As String, Optional ByRef forces As Boolean = False) As String

    Dim url As String
    url = "rooms/" & roomId & "/messages?force=" & IIf(forces, "1", "0")

    GetMessages = Api("GET", url, "")
End Function

Public Function SendMessage(ByRef roomId As String, ByRef Message As String) As String

    Dim url As String, param As String
    url = "rooms/" & roomId & "/messages"

    param = "body=" & Message

    SendMessage = Api("POST", url, param)
End Function

ここで Chatwork との各やり取りを定義しています。

定義したのは、

  • Chatwork から自分のコンタクトを取得
  • 特定のルームのメッセージを取得
  • 特定のルームへメッセージを送信

になります。これらを使って、コンタクトを取得 → 新しいメッセージを確認 → 返信。

という操作を Excel から行っていきます。

定時実行プログラム

以下のコードを、標準モジュール名「Batch」で作成します。

Private Const BOT_ACCOUNT As String = "チャットボットとして使いたいアカウント名"

Dim rooms As Object

Public Sub Initialize()
    ' 対象のチャットルームを全て取得
    Set rooms = JsonConverter.ParseJson(Chatwork.GetContacts)

    Call Refresh
End Sub

Public Sub Refresh()
    ' 前回確認時の時刻を保持(Unix Timeを変換)
    Dim lastModified As Double, targetRange As Range
    Set targetRange = ThisWorkbook.Worksheets(1).Range("A1")
    If targetRange.value = "" Then
        targetRange.value = (DateDiff("s", "1970/1/1 9:00", Now) + 32400) / 86400 + 25569
    End If
    lastModified = (CDbl(targetRange.value) - 25569) * 86400 - 32400
    targetRange.value = (DateDiff("s", "1970/1/1 9:00", Now) + 32400) / 86400 + 25569

    ' 全てのチャットルームから、依頼がないか確認します
    Call CheckRequest(lastModified)

    ' 次回実行の予約
    Dim scheduled As Date
    scheduled = DateAdd("s", 300, Time)
    Application.OnTime scheduled, "Batch.Refresh"
End Sub

Private Sub CheckRequest(ByRef lastModified As Double)

    ' イテレータの準備
    Dim room As Object
    Dim messages As Object
    Dim message As Object

    ' ルーム単位でメッセージを受信し、新着情報がないか確認します
    For Each room In rooms

        Set messages = JsonConverter.ParseJson(Chatwork.GetMessages(room("room_id"), True))

        For Each message In messages

            ' 前回更新以降のメッセージでなければ処理しません
            If CLng(message("send_time")) <= lastModified Or message("account")("name") = BOT_ACCOUNT Then
                GoTo MESSAGE_CONTINUE
            End If

            ' 返信します
            Call Chatwork.SendMessage(room("room_id"), Message("body") & "に対するExcelからの返信です")

MESSAGE_CONTINUE:
        Next
    Next
End Sub

ここまで作成し、Batch モジュールの Initialize を実行することで、5 分毎に対象アカウントへのメッセージを確認します。

実行すると、対象アカウントに対する全てのメッセージに返信を行うため、そのまま実行する際は注意してください。

例では Initialize 起動後に受け取った全てのメッセージに対して返信を行っていますが、message.body の内容に応じて返信メッセージを変更することで、チャットボットとしてやり取りを行うことができます。