https://a.storyblok.com/f/270183/320556/4f1449b359/group-chat-sms-terminal.png

Nexmo SMS APIとPHPによるテキストメッセージグループチャットの構築

最終更新日 May 13, 2021

所要時間:1 分

新しいPHPクライアント・ライブラリを少し練習するために、ユーザーの受信メッセージがチャットの他のメンバー全員に送信されるシンプルなSMSグループ・チャットを作ろうと思います。私についてきて一緒に作ることもできますし、Nexmo SMS Group Chat リポジトリの Nexmo SMS グループチャットリポジトリをクローンしてください。

レポをクローンするだけなら批判はしない。本当に。

私たちが作っているもの

これから簡単なスクリプトを組み立てる:

  • ユーザーがテキスト JOINと自分の名前(例 JOIN tlytle)を電話番号に送ることができます。電話番号は「グループ」を表します。

  • 一度参加すると、ユーザーが送信したメッセージはグループの他のメンバーにも伝わります。また、他のユーザーが送信したメッセージはすべてユーザーに届きます。

  • ユーザーがグループの一員になりたくないと判断した場合、送信 LEAVEを送ると登録解除されます。

次の投稿では、グループのメッセージのログを見ることができるシンプルなウェブ・インターフェイスも紹介する。

セットアップ

まずは Nexmoクライアント・ライブラリとMongoデータベースを用意します。データベースのセットアップはこのチュートリアルの範囲外ですが、無料のMongoホストがいくつかあるので、そのうちのひとつにデータベースをセットアップするのは簡単でしょう。また PHP 用 Mongo ドライバをインストールする必要があります。

これとNexmoクライアント・ライブラリをComposerに含めることができる:

$ composer require nexmo/client 1.0.*@beta
 $ composer require mongodb/mongodb

シンプルな設定ファイルを定義することで、API認証情報とデータベース接続を1つのファイルにまとめることができる。

以下に例を挙げる。 config.phpをテンプレートとして使用します:

<?php
return [
    'mongo' => [
        'uri' => 'mongodb://user:password@host:port',
        'database' => 'groupchat'
    ],
    'nexmo' => [
        'key' => 'key',
        'secret' => 'secret'
    ]
];

非常にシンプルなブートストラップ(bootstrap.php)ファイルがオートロードを行い、コンフィギュレーション・ファイルを渡す:

<?php
$autoloader = require __DIR__ . '/vendor/autoload.php';
$config = require  __DIR__ . '/config.php';
$config['autoloader'] = $autoloader;

return $config;

この2つのファイルが揃えば、グループチャットアプリケーションを構築する準備が整いました。

受信テキストメッセージの処理

以下のスクリプトが必要だ。 受信ウェブフックを受け取り、メッセージを処理するスクリプトが必要です。そこで public/inbound.phpを作成し、その中に ../bootstrap.phpを作成し、Nexmoクライアントとmongoクライアントを作成します。

<?php
$config = require __DIR__ . '/../bootstrap.php';
$nexmo = new \Nexmo\Client(new \Nexmo\Client\Credentials\Basic($config['nexmo']['key'], $config['nexmo']['secret']));
$mongo = new \MongoDB\Client($config['mongo']['uri']);
$db = $mongo->selectDatabase($config['mongo']['database']);

次に、インバウンドリクエストからインバウンドメッセージを作成し、それが有効かどうかをチェックしたい。クライアントライブラリはこれを行う簡単な方法を提供しています。

$inbound = \Nexmo\Message\InboundMessage::createFromGlobals();
if(!$inbound->isValid()){
    error_log('not an inbound message');
    return;
}

これでファイルのセットアップが完了しました。 Nexmoダッシュボードにアクセスして そのスクリプトにCallback URLフィールドに

Phone Number SMS Callback Webhook Settings

これにより、Nexmoアカウントが、その番号にメッセージが送信されるたびにスクリプトにウェブフックリクエストを行うように設定されます。ローカルで開発している場合は、次のようなものを使用する必要があります。 ngrokなどを使用して、Nexmoプラットフォームが到達できるパブリックURLを持つローカルトンネルを作成する必要があります。

番号を設定したら、メッセージを送ることができる。それでは、送信者に何か指示を返信してみましょう。クライアントライブラリによって作成された受信メッセージオブジェクトには createReplyメソッドがあります。 tofrom.

$nexmo->message()->send($inbound->createReply('Use JOIN [your name] to join this group.'));

Nexmoの番号にメッセージを送ると、すぐに返事が返ってくるはずだ。

インバウンド・ウェブフックで送信されたパラメータを使用しているため、コードは送信者として使用する番号を知る必要はありません。なぜそれが重要なのでしょうか?コードや設定を追加することなく、Nexmo番号の数だけ、ひいてはグループチャットに対応するシンプルなオートレスポンダーが完成しました。

処理コマンド

コマンドを処理する前に JOINコマンドを実際に処理する前に、ユーザーがすでにシステムとやりとりしたかどうかを知る必要がある。そこで、クエリーを書いてみよう。ここでは usersコレクションで設定します。各ドキュメントには groupプロパティが設定されていることを期待します。そして userはユーザーの番号(メッセージが送信された番号)に設定されます。

$user = $db->selectCollection('users')->findOne([
    'group' => $inbound->getTo(), // the group's number
    'user'  => $inbound->getFrom() //the user's  number
]);

必要に応じてトラブルシューティングができるように、簡単なエラーログを追加してみよう:

if($user){
    error_log('found user: ' . $user['name']);
} else {
    error_log('no user found');
}

データベースにデータがないので、この時点でのメッセージはログに記録されるはずだ。 no user found.使用チェックのコードができたので、コマンド・キーワードを探し始めることができる。

最初の単語を使って、ユーザーがコマンドを送信しているかどうかをチェックする。なぜなら JOINコマンドは名前も想定しているので、メッセージを解析して、最初の単語を1つのコマンド、その後にオプションの引数を指定する必要があります。正規表現を使って任意のスペースで分割し、それを2つの要素に制限することで、必要なものが得られます。解析されたコマンドで switchを指定することで、最初の単語を処理することができます:

$command = preg_split('#\s+#', $inbound->getBody(), 2);
switch(strtolower(trim($command[0]))){

手始めに、期待される第2引数が提供されているかどうかをチェックしよう-少なくとも新規ユーザーの場合は。もしそうでなければ、返信を送るのは簡単だ:

case 'join';
    error_log('got join command');

    if(!$user && empty($command[1])){
        $nexmo->message()->send($inbound->createReply('Use JOIN [your name] to join this group.'));
        break;
    }

新規ユーザーで(既存ユーザーが見つからなかった)、名前を入力した場合 ($command['1']empty())であれば、基本的なユーザーデータをセットアップしなければなりません:

if(!$user){
    $user = [
        'group' => $inbound->getTo(),
        'user' => $inbound->getFrom(),
        'actions' => []
    ];
}

そして、その名前を忘れてはならない。なぜ 外でをするのか?既存のユーザーが新しい名前を入力した場合に JOINコマンドを使って名前を更新できるようにするためです。新しいユーザーが2番目の引数を持っていることを確認しているので、新しいユーザーも同様に名前が設定されていることがわかります:

if(isset($command[1])){
    $user['name'] = $command[1];
}

これは JOINコマンドなので、ユーザーのステータスをアクティブに設定し、アクションのログエントリーを作成する必要もあります。

$user['status'] = 'active';
$user['actions'][] = [
    'command' => 'join',
    'date' => new \MongoDB\BSON\UTCDatetime(microtime(true))
];

あとはユーザーを保存(または作成)するだけです。Mongo の replaceOneコマンドを使います。upsert) を挿入させます。 breakを追加して、アクションが実行されたら処理を停止するようにします:

$db->selectCollection('users')->replaceOne([
    'group' => $inbound->getTo(), // the group's number
    'user'  => $inbound->getFrom() //the user's  number
], $user, ['upsert' => true]);

error_log('added user');
break;

JOINグループへのユーザーを許可する必要がある。 LEAVEグループへのユーザーを許可する必要がある。例えば JOINのように、少しログを取って、ユーザーが実際に購読しているかどうかをチェックします。もし購読していなければ、ヘルプを返信するだけです。それはとても簡単なことです:

case 'leave';
    error_log('got leave command');

    if(!$user){
        $nexmo->message()->send($inbound->createReply('Use JOIN [your name] to join this group.'));
        break;
    }

彼らが購読している場合、購読ステータスを更新し、アクションが行われたことを記録する必要があります。これは statusプロパティを変更し、新しいメンバーを actions配列に新しいメンバーを追加します。もちろん、この変更をデータベースに書き込むことも重要です:

//update the user's status
$user['status'] = 'inactive';
$user['actions'][] = [
    'command' => 'leave',
    'date' => new \MongoDB\BSON\UTCDatetime(microtime(true))
];

//update the database
$db->selectCollection('users')->replaceOne([
    'group' => $inbound->getTo(), // the group's number
    'user'  => $inbound->getFrom() //the user's  number
], $user);

グループからユーザーを削除したら、退会したことと、今後どのように再参加できるかを知らせる必要があります:

//let them know they've left
$nexmo->message()->send($inbound->createReply('You have left. Use JOIN to join this group again.'));

error_log('removed user');
break;

メッセージのリレーによるSMSグループチャット

グループへの参加と脱退はうまくいったので、次はユーザーがコマンドではなくメッセージを送るのを処理する必要がある。コマンドでないメッセージはすべてグループへのメッセージである。ここでのロジックは単純で、もしユーザーが購読していてアクティブであれば、そのメッセージは他のすべての 他のメンバーに送られなければならない。

そのユーザーがグループにメッセージを投稿できるかどうかをチェックする必要があります。データベースでユーザが見つかった場合、そのユーザはグループに加入していたことになりますが、退会したかどうかをチェックする必要があります。データベースで見つからなかったり、グループで活動していなかったり、どちらかのケースに当てはまらない場合は、すぐに役立つ返信を送ります:

default:
    error_log('no command found');

    if(!$user || 'active' != $user['status']){
        $nexmo->message()->send($inbound->createReply('Use JOIN [your name] to join this group.'));
        break;
    }

もしそのユーザーが購読していてアクティブであれば、メッセージのアーカイブを作成します。これには、テキスト、送信先のグループ、ユーザー自身(名前が必要になるたびにユーザーを調べる必要がないように、名前も)、その他のメタデータが含まれます。

また、グループ内の他のユーザーに送られたメッセージを記録するために、空の sends配列も作成します:

error_log('user is active');

$log = [
    '_id'   => $inbound->getMessageId(),
    'text'  => $inbound->getBody(),
    'date'  => new \MongoDB\BSON\UTCDatetime(microtime(true)),
    'group' => $inbound->getTo(),
    'user'  => $inbound->getFrom(),
    'name'  => $user['name'],
    'sends' => []
];

メッセージの中継が必要なすべてのメンバーを見つけるために、アクティブとマークされたこの特定のグループのユーザーをコレクションに問い合わせます。 usersコレクションに問い合わせます。現在のユーザーを除外することを忘れないようにする必要があります。 $neは'not equal'を意味する)が、テスト目的でこれを削除すると便利である:

$members = $db->selectCollection('users')->find([
    'group'  => $inbound->getTo(),
    'user'   => ['$ne' => $inbound->getFrom()],
    'status' => 'active'
]);

リストを手に入れたら、それを反復処理して各メンバーにメッセージを送ることができる。単純な配列を send()メソッドに渡すことができます。 Messageオブジェクトと同様に)。この配列は、メンバーの番号を toとして、グループの番号を fromとして使用し、メッセージを投稿したユーザーの名前を textに追加します。

これは完全なメッセージ・オブジェクトを返します。配列として扱うこともできますが、ゲッターメソッドを使ってメッセージ ID とメンバーの番号を送信ログに追加する方が簡単です。

foreach($members as $member) {
    $sent = $nexmo->message()->send([
        'to'   => $member['user'],
        'from' => $inbound->getTo(),
        'text' => $user['name'] . ': ' . $inbound->getBody()
    ]);

    $log['sends'][] = [
        'user' => $sent->getTo(),
        'id'   => $sent->getMessageId()
    ];
}

すべてのメッセージが送信されたら、新しいメッセージをデータベースのログコレクションに追加し、受信メッセージの処理を終了します。

    $db->selectCollection('logs')->insertOne($log);

    error_log('relayed message');
    break;
} // end of switch

次のステップ

そして、インバウンドメッセージを受け取り、そのうちのいくつかに返信し、他のメッセージをグループにリレーするシンプルなスクリプトをセットアップした。一元的には、このコマンドのコンセプトをより複雑でインタラクティブな自動応答ボットに拡張したり、グループリレーをユーザーの番号だけを隠す2つのユーザープロキシにしたり、あるいはこれをSMS配信リストとして再利用して、誰でもインバウンドメッセージをグループに送ることができるようにすることもできる。

Group SMS Chat in the terminal

PHPクライアントライブラリとNexmoのAPIを使えば、インバウンドメッセージの処理とアウトバウンドメッセージの送信は簡単です。

また、このチュートリアルのパート2では、グループチャットへのウェブインターフェースを構築します。

有用なリソース

シェア:

https://a.storyblok.com/f/270183/384x384/f71222cc5f/tjlytle.png
Tim Lytleヴォネージの卒業生