mohapinkepane / driver-whatsapp-cloud

BotMan的WABA Cloud API驱动程序

1.0.6 2024-08-31 18:34 UTC

This package is auto-updated.

Last update: 2024-10-01 00:11:38 UTC


README

BotMan驱动程序,用于将WhatsApp Business Cloud API与BotMan连接

WhatsApp Business Cloud API

请阅读官方文档Meta开发者文档

安装和设置

首先,您需要获取Whatsapp驱动程序

composer require mohapinkepane/driver-whatsapp-cloud

然后,您需要将以下条目添加到您的.env文件中

WHATSAPP_ACCESS_TOKEN=your-whatsapp-access-token
WHATSAPP_VERIFICATION=your-whatsapp-verification-token
WHATSAPP_APP_SECRET=your-whatsapp-app-secret
WHATSAPP_PHONE_NUMBER_ID=your-whatsapp-phone-number-id

此驱动程序需要有效的安全URL来设置webhook并接收聊天用户的events和information。这意味着您的应用程序应通过HTTPS URL可访问。

ngrok 是创建此类公共HTTPS URL的出色工具。如果您使用Laravel Valet,可以使用“valet share”创建它。如果您使用Laravel Herd,也可以使用“herd share”创建它。Serveo也是一个优秀且无头痛的替代方案 - 它完全是免费的。

要将BotMan与WhatsApp Business连接,您首先需要遵循官方快速入门指南创建您的WhatsApp Business应用程序,并获取访问令牌以及应用程序密钥。将它们与BotMan .env文件中的占位符值进行交换。

之后,您可以设置webhook,它将WhatsApp应用程序与您的BotMan应用程序连接起来。这在上面的快速入门指南中有说明。

配置包

您可以使用以下命令发布配置文件

php artisan vendor:publish --provider="Botman\Drivers\Whatsapp\Providers\WhatsappServiceProvider"

此文件将包含在config/botman/whatsapp.php中发布的文件内容

<?php

return [
    /*
    |--------------------------------------------------------------------------
    | Whatsapp url
    |--------------------------------------------------------------------------
    | Your whatsapp cloud api base url
    */
    'url' => env('WHATSAPP_PARTNER', 'https://graph.facebook.com'),

    /*
    |--------------------------------------------------------------------------
    | Whatsapp Token
    |--------------------------------------------------------------------------
    | Your Whatsapp access token  you received after creating
    | the application on Whatsapp(Facebook Portal).
    */
    'token' => env('WHATSAPP_ACCESS_TOKEN'),


    /*
    |--------------------------------------------------------------------------
    | Whatsapp App Secret
    |--------------------------------------------------------------------------
    |
    | Your Whatsapp application secret, which is used to verify
    | incoming requests from Whatsapp.
    |
    */
    'app_secret' => env('WHATSAPP_APP_SECRET'),

    /*
    |--------------------------------------------------------------------------
    | Whatsapp Verification
    |--------------------------------------------------------------------------
    | Your Whatsapp verification token, used to validate the webhooks.
    */
    'verification' => env('WHATSAPP_VERIFICATION'),

    /*
    |--------------------------------------------------------------------------
    | Whatsapp Phone Number ID
    |--------------------------------------------------------------------------
    | Your Whatsapp phone_number_id
    */
    'phone_number_id'=>env('WHATSAPP_PHONE_NUMBER_ID'),


    /*
    |--------------------------------------------------------------------------
    | Passphrase for whatsapp key pair
    |--------------------------------------------------------------------------
    | Only required if flows with end point are used,otherwise leave as is.
    */
    'passphrase'=>env('WHATSAPP_KEYS_PASSPHRASE'),
    

    /*
    |--------------------------------------------------------------------------
    | Whatsapp  Public Key
    |--------------------------------------------------------------------------
    | Public key uploaded to  whatsapp for encryption of flow end point data.
    | Only required if flows with end point are used,otherwise leave as is.
    */
    'public_key'=>env('WHATSAPP_PUBLIC_KEY'),


    /*
    |--------------------------------------------------------------------------
    | Whatsapp  Private Key
    |--------------------------------------------------------------------------
    | Private key used for decryption of flow end point data.
    | Only required if flows with end point are used,otherwise leave as is.
    */
    'private_key'=>env('WHATSAPP_PRIVATE_KEY'),


    /*
    |--------------------------------------------------------------------------
    | Whatsapp Cloud API Version
    |--------------------------------------------------------------------------
    */
    'version' => 'v20.0',

    /*
    |--------------------------------------------------------------------------
    | throw_http_exceptions
    |--------------------------------------------------------------------------
    | Do you want the driver to throw custom(driver) exceptions or the default exceptions
    */
    'throw_http_exceptions' => true,

    /*
    |--------------------------------------------------------------------------
    | restrict_inbound_messages_to_phone_number_id
    |--------------------------------------------------------------------------
    | Restrict inbound messages to this phone number id - ingnore all others
    */
    'restrict_inbound_messages_to_phone_number_id' => true,


    /*
    |--------------------------------------------------------------------------
    | Conversational Components
    |--------------------------------------------------------------------------
    | Configure whatsapp conversational components
    | See https://developers.facebook.com/docs/whatsapp/cloud-api/phone-numbers/conversational-components
    */
    'conversational_components' => [
        /*
        | Enable or disable whatsapp welcome messages
        */
        'enable_welcome_message' => false,

        /*
        | Whatsapp commands list
        */
    "commands"=> [
            [
            "command_name"=> "hello",
                "command_description"=> "Say hello",
            ],
            [
            "command_name"=> "help",
                "command_description"=> "Request help",
            ]
        ],

        /*
        | Whatsapp prompts (Ice breakers) list
        */
    "prompts"=> ["Book a flight","plan a vacation"],
    ]

];

支持的功能

  • 文本消息
  • 联系人消息
  • 位置消息
  • 反应消息
  • 模板消息
  • 图片附件
  • 文档附件
  • 位置附件
  • 视频附件
  • 音频附件
  • 贴纸附件
  • 行动号召
  • 交互式消息
    • 列表
    • 回复按钮
    • 位置请求
    • 流程

发送WhatsApp消息

Facebook仍在对其WhatsApp功能进行大量实验。这就是为什么它们在特定平台上的行为不同。一般来说,可以很容易地说,所有这些都在您手机上的原生WhatsApp应用程序中工作。例如,列表消息在WhatsApp桌面应用程序中不起作用。

文本

您可以按以下方式发送文本

$bot->reply(
    TextMessage::create('Please visit https://youtu.be/hpltvTEiRrY to inspire your day!')
    ->previewUrl(true)//Allows whatsapp to show the preview of the url(video in this case)
 );

或者,在以下对话中更强大地发送

$this->ask('Hello! What is your firstname?', function(Answer $answer) {
    $this->firstname = $answer->getText();
    $this->say(
            TextMessage::create('Nice to meet you '.$this->firstname)
            ->contextMessageId($answer->getMessage()->getExtras('id'))
        );
});

媒体

您仍然可以像文档中说明的那样将媒体附加到消息中这里,但这将限制您仅限于图片、视频、音频和文件。

ALTERNATIVELY,有一个MediaMessage类。它支持视频、图片、文档、贴纸和音频。令人兴奋的是,您可以在适用的情况下添加标题和文件名。您还可以通过链式调用contextMessageId()方法来提供上下文。

它可以以两种方式使用

1. MediaMessage::create('media-type-here')
    ->url('media-url-here')

2. MediaMessage::create('media-type-here')
    ->id('media-id-here')//Whatsapp media id

以下是一些示例

$bot->reply(
    MediaMessage::create('image')
->url('https://images.pexels.com/photos/1266810/pexels-photo-1266810.jpeg')
->caption('This is a cool image!')
);

$bot->reply(
    MediaMessage::create('audio')
->url('https://samplelib.com/lib/preview/mp3/sample-15s.mp3')
);

$bot->reply(
    MediaMessage::create('document')
->url('https://pdfobject.com/pdf/sample.pdf')
->caption('This is a cool Document!')
);

$bot->reply(
    MediaMessage::create('sticker')
->url('https://stickermaker.s3.eu-west-1.amazonaws.com/storage/uploads/sticker-pack/meme-pack-3/ sticker_18.webp')
);

$bot->reply(
    MediaMessage::create('video')
->url('https://sample-videos.com/video321/mp4/480/big_buck_bunny_480p_10mb.mp4')
->caption('This is a cool Video!')
);

列表

您可以按以下方式发送列表消息(在对话中)

 $this->ask(InteractiveListMessage::create("Here is your  list of current Ticketbox listings",'View listings')
        ->addHeader('Ticketbox listings')
        ->addFooter('Powered by Ticketbox.co.ls')
        ->addSection(ElementSectionList::create('Events',[
                    ElementSectionListRow::create(
                    1,//List item id
                    'Selemo Sa Basotho'////List item title
                    )
                    ->description('In 2024, we commemorate 200 years since the Basotho nation arrived..')
                    ,
                    ElementSectionListRow::create(2,'Winterfest')
                    ->description('vibrant cultural performances, music, food and a colossal Bonfire '),
            ])
        )
        ->addSection(ElementSectionList::create('Vouchers',[
                ElementSectionListRow::create(3,'Kobo ea Seanamarena'),
            ])
 ),function(Answer $answer) {
    $payload = $answer->getMessage()->getPayload();//Get Payload
    $choice_id=$answer->getMessage()->getExtras('choice_id');//You can the select choice ID like this
    $choice_text=$answer->getMessage()->getExtras('choice_text');
    $choice=$answer->getText();
    $this->say(
            TextMessage::create('Nice.You choose '.$choice)
            ->contextMessageId($answer->getMessage()->getExtras('id'))
        );
 });

回复按钮

您可以按以下方式发送回复按钮消息(在对话中)

$this->ask(InteractiveReplyButtonsMessage::create('How do you like BotMan so far?')
        ->addFooter('Powered by BotMan.io')
        ->addHeader(
                ElementHeader::create('image',[
                    'link'=>"https://botman.io/img/botman.png",
                ])
        )
        ->addButtons([
        ElementButton::create(1,'Quite good'),
        ElementButton::create(2,'Love it')
    ]),function(Answer $answer) {
        $payload = $answer->getMessage()->getPayload();//Get Payload
        $choice_id=$answer->getMessage()->getExtras('choice_id');//You can the get choice ID like this
        $choice_text=$answer->getMessage()->getExtras('choice_text');
        $choice=$answer->getText();
        $this->say(
                TextMessage::create('Nice.You choose '.$choice)
                ->contextMessageId($answer->getMessage()->getExtras('id'))
            );
    });

标题可以是文本、图片、视频或文档类型

流程

您可以按以下方式发送流程消息(在对话中)

$this->ask(FlowMessage::create(
        'FLOW_ID',//Unique ID of the Flow provided by WhatsApp
        'FLOW_TOKEN',//Generated by the business to serve as an identifier
        'Take a quick survey',//Text on the CTA button.
        'How do you like BotMan so far?',//flow body text
        'draft'// flow mode -> published is default
        'navigate'// Flow action -> navigate is default
    )
    ->addFooter('Powered by BotMan.io')
    ->addHeader(
                ElementHeader::create('image',[
                    'link'=>"https://botman.io/img/botman.png",
                ])
        )
    ->addActionPayload(
        ElementFlowActionPayload::create('RECOMMEND' //First screen name
        ,[
            'title' => 'hello',
        ]//Payload)
    )
    ,function(Answer $answer) {
     $payload = $answer->getMessage()->getPayload();
     $this->say('Thanks!');
});

标题可以是文本、图片、视频或文档类型

具有端点的流程

(1) 生成RSA密钥对

php artisan botman:whatsapp:generate:keypair {passphrase}

(2) 将密钥和密码短语复制到.env文件中

WHATSAPP_KEYS_PASSPHRASE=passpharase_here
WHATSAPP_PUBLIC_KEY=public_key_here
WHATSAPP_PRIVATE_KEY=private_key_here

可能需要缓存配置

php artisan config:cache

(3) 将密钥添加到whatsapp

php artisan botman:whatsapp:add-public-key

(4) 实现流数据处理逻辑 - 使用自定义控制器

  1. 在web.php中添加自定义路由

      Route::post('/custom-url', [CustomController::class, 'handleFlow']);//The method must be handleFlow
    
  2. 排除该路由的CSRF保护

  3. 创建一个扩展Flowprocessor的自定义控制器

      <?php
    
      namespace App\Http\Controllers;
      use Botman\Drivers\Whatsapp\Http\FlowProcessor;
    
      class FlowController extends FlowProcessor
      {
      
          private const SCREEN_RESPONSES = [];
    
          /**
          * @param array $decrypted_body
          * @return array
          */
          public function getNextScreen($decrypted_body) {
    
                  $screen = $decrypted_body['screen'] ?? null;
                  $data = $decrypted_body['data'] ?? [];
                  $version = $decrypted_body['version'] ?? null;
                  $action = $decrypted_body['action'] ?? null;
                  $flow_token = $decrypted_body['flow_token'] ?? null;
    
                  //Custom code here
                  if ($action === 'INIT') {
                      return [];
                  }
    
    
                  if ($action === 'data_exchange') {
                      return [];
                  }
                  //Custom code here
    
              \Log::error('Unhandled request body:', $decrypted_body);
              throw new \Exception('Unhandled endpoint request. Make sure you handle the request action & screen logged above.');
    
          }
      }
    

(5) 实现流数据处理逻辑 - 使用Spatie webhook客户端

  1. 在此阅读和阅读包文档

  2. 实现自定义RespondsToWebhook类

      <?php
    
      namespace App\WebHooks;
    
      use Illuminate\Http\Request;
      use Spatie\WebhookClient\WebhookConfig;
      use Symfony\Component\HttpFoundation\Response;
      use Botman\Drivers\Whatsapp\Http\FlowProcessor;
      use Spatie\WebhookClient\WebhookResponse\RespondsToWebhook;
    
      class CustomFlowRespondsTo  extends FlowProcessor implements RespondsToWebhook
      {
    
          private const SCREEN_RESPONSES = [];
    
      
          public function respondToValidWebhook(Request $request, WebhookConfig $config): Response
          {
              return $this->handleFlow($request);//Leave as it is
          }
    
          /**
          * @param array $decrypted_body
          * @return array
          */
          public function getNextScreen($decrypted_body) {
    
              $screen = $decrypted_body['screen'] ?? null;
              $data = $decrypted_body['data'] ?? [];
              $version = $decrypted_body['version'] ?? null;
              $action = $decrypted_body['action'] ?? null;
              $flow_token = $decrypted_body['flow_token'] ?? null;
    
              //Custom code here
              if ($action === 'INIT') {
                  return [];
              }
    
              if ($action === 'data_exchange') {
                  return [];
              }
              //Custom code here
    
          \Log::error('Unhandled request body:', $decrypted_body);
          throw new \Exception('Unhandled endpoint request. Make sure you handle the request action & screen logged above.');
    
          }
      }
    
  3. 实现自定义WebhookProfile类

      <?php
          namespace App\WebHooks;
          use Log;
          use Illuminate\Http\Request;
          use Botman\Drivers\Whatsapp\Traits\MatchesFlowProfile;
          use Spatie\WebhookClient\WebhookProfile\WebhookProfile;
    
          class WhatsappFlowWebhookProfile implements WebhookProfile
          {
              use MatchesFlowProfile;
              
              public function shouldProcess(Request $request): bool
              {
                  return $this->matchesFlowProfile($request);
              }
          }
    
  4. 实现自定义SignatureValidator类

          <?php
              namespace App\WebHooks;
    
              use Illuminate\Http\Request;
              use Spatie\WebhookClient\WebhookConfig;
              use Spatie\WebhookClient\Exceptions\InvalidConfig;
              use Botman\Drivers\Whatsapp\Traits\ValidatesFlowSignature;
              use Spatie\WebhookClient\SignatureValidator\SignatureValidator;
    
              class WhatsappSignatureValidator implements SignatureValidator
              {
                  use ValidatesFlowSignature;
                  public function isValid(Request $request, WebhookConfig $config): bool
                  {
                      return $this->validatesSignature($request);
                  }
              }
    

模板

您可以根据以下示例发送模板消息。当然,这些只是示例,但您可以实现几乎任何您想要的功能。

示例(A)(使用默认的hello_world模板)

 $this->say(TemplateMessage::create('hello_world','en_us')
 ->addComponents(
     [
         ElementComponent::create('header',[]),
         ElementComponent::create('body',[]),
     ]
 ));

示例(B)(使用默认的purchase_receipt_1模板)

$this->say(TemplateMessage::create('purchase_receipt','en_us')
->addComponents(
    [
        ElementComponent::create('header',[
                [
                'type'=>'document',
                'document'=>[
                    "link"=>"https://pdfobject.com/pdf/sample.pdf"
                ]
            ]
        ]),
        ElementComponent::create('body',[
            [
                "type"=> "currency",
                "currency"=>[
                    "fallback_value"=> "$100.99",
                    "code"=> "USD",
                    "amount_1000"=> 100990
                ]
            ],
            [
                "type"=>"text",
                "text"=>"Ticketbox-Thetsane Office Park,Maseru,Lesotho.",
            ],
            [
                "type"=>"text",
                "text"=>"ticket",
            ]
        ]),
    ]
));

示例(C)(使用默认的fraud_alert模板 - 在对话中)

$this->ask(TemplateMessage::create('fraud_alert','en_us')
->addComponents(
    [
        ElementComponent::create('header',[]),
        ElementComponent::create('body',[
            [
                "type"=>"text",
                "text"=>"John Miller Doe",
            ],
            [
                "type"=>"text",
                "text"=>"Dummy Company",
            ],
            [
                "type"=>"text",
                "text"=>"Spooky",
            ],
            [
                "type"=>"text",
                "text"=>"Dummy Company",
            ],
            [
                "type"=>"text",
                "text"=>"D4SRT",
            ],
            [
                "type"=> "date_time",
                "date_time" => [
                    "fallback_value"=> "February 25, 1977",
                ]
            ],
            [
                "type"=>"text",
                "text"=>"Dummy Merchant",
            ],
            [
                "type"=> "currency",
                "currency"=>[
                    "fallback_value"=> "$100.99",
                    "code"=> "USD",
                    "amount_1000"=> 100990
                ]
            ]
        ]),
    ]
),
function(Answer $answer) {
    $payload = $answer->getMessage()->getPayload();
    \Log::info('PAYLOAD'.\json_encode($payload));
    $this->say('Thanks!');
});

行动号召

您可以使用以下方式发送行动号召

$bot->reply(InteractiveCallToActionURLButtonMessage::create(
    'Do you want to know more about BotMan?',//Call to action body
    "Visit us", //Call to action button text
    "https://botman.io"//Call to action url
)
->addFooter('Powered by BotMan.io')
->addHeader(
    ElementHeader::create('image',[
        'link'=>"https://botman.io/img/botman.png",
    ])
));

标题可以是文本、图片、视频或文档类型

反应

您可以使用以下方式对消息进行反应

$this->ask('Hello! Do you read me?', function(Answer $answer) {
    $message_id=$answer->getMessage()->getExtras('id');
    $this->say(
        ReactionMessage::create($message_id,'😀')
    );
});

联系人

您可以使用以下方式发送联系人

$addresses = [
    Address::create("Menlo Park", "United States"),
    // Address::create("Menlo Park", "United States", "us", "CA", "1 Hacker Way", "HOME", "94025"),
    // Address::create("Menlo Park", "United States", "us", "CA", "200 Jefferson Dr", "WORK", "94025")
];

$emails = [
    Email::create("test@whatsapp.com"),
    Email::create("test@fb.com", "WORK")
];

$name = Name::create("John", "John Smith", "Smith");

$org = Organization::create("WhatsApp","Manager");

$phones = [
    Phone::create("+1 (940) 555-1234"),
    Phone::create("+1 (940) 555-1234", "HOME"),
    Phone::create("+1 (650) 555-1234", "WORK", "16505551234")
];

$urls = [
    URL::create("https://www.google.com"),
    URL::create("https://#", "WORK")
];

$person = Contact::create($addresses, "2012-08-18", $emails, $name, $org, $phones, $urls);

$bot->reply(
    ContactsMessage::create([
        $person
    ])
);

位置

您可以使用以下方式发送位置

$bot->reply(
    LocationMessage::create(-122.425332, 37.758056, "Facebook HQ", "1 Hacker Way, Menlo Park, CA 94025")
);

位置请求

您可以使用以下方式发送位置请求

$this->ask(LocationRequestMessage::create('Please share your location'), function(Answer $answer) {
        $payload = $answer->getMessage()->getPayload();
        \Log::info('PAYLOAD'.\json_encode($payload));
        $this->say('Thanks!');
});

消息上下文

您可以将任何类型的消息作为对先前消息的回复发送。先前消息将出现在新消息的顶部,并带引号在上下文气泡中。限制在此讨论。

您可以通过调用方法:contextMessageId('message-id-here')来链式调用方法实现此功能。以下提供了示例。

$this->say(
        TextMessage::create('reply-here')
        ->contextMessageId('message-id-here')
    );

消息ID

IncomingMessage类在其extras中包含消息ID。您可以通过在类的实例上调用方法:getExtras('id')来获取它。

标记为已读

markSeen()方法接受一个类型为IncomingMessage的参数,并且可以用几种方式使用

在接收(收到)中间件中

public function received(IncomingMessage $message,$next, BotMan $bot)
    {
        if($bot->getDriver()->getName()=='Whatsapp'){
            $bot->markSeen($message);
        }
        return $next($message);
    }

在会话中

$this->ask('Hello! What is your firstname?', function(Answer $answer) {
    $this->bot->markSeen($answer->getMessage());
    $this->firstname = $answer->getText();
    $this->say('Nice to meet you '.$this->firstname);
});

对话组件

对话组件是您可以在企业电话号码上启用的聊天内功能。它们使WhatsApp用户更容易与您的企业互动。您可以配置易于使用的命令,提供用户可以点击的预写破冰者,并使用欢迎消息问候新用户。

欢迎消息

如果您启用了此功能并且用户给您发消息,WhatsApp客户端会检查用户与您的企业电话号码之间是否存在现有的消息线程。如果没有,客户端会触发类型设置为request_welcome的消息webhook。

要启用/禁用您的机器人中的欢迎消息,首先编辑您的config/botman/whatsapp.php文件中的变量enable_welcome_message以满足您的需求。

然后使用Artisan命令

php artisan botman:whatsapp:add-conversational-components

您可以按照以下方式读取和操作request_welcome消息

在接收(收到)中间件中

public function received(IncomingMessage $message,$next, BotMan $bot)
{
    if($bot->getDriver()->getName()=='Whatsapp'){
        
          if($message->getExtras('type') == 'request_welcome'){
            $bot->say('Hello ! Welcome to my bot',[$message->getSender()]);
          }
    }
    return $next($message);
}

在会话中

if($this->bot->getMessage()->getExtras('type') == 'request_welcome'){
    $this->say('Hello ! Welcome to my bot');
}

如果您不处理它,则消息将由botman全局回退路由处理 - 如果有可用的回退路由。

命令

要将命令添加到您的机器人中。首先在您的config/botman/whatsapp.php文件中定义您的命令结构。在那里您将找到一个命令演示有效负载。只需将其编辑为您的需求。

然后使用Artisan命令

php artisan botman:whatsapp:add-conversational-components

提示 - 破冰者

将提示添加到您的机器人。首先在您的 config/botman/whatsapp.php 文件中定义您的提示。在那里您将找到一个提示示例负载。只需编辑以满足您的需求。

然后使用Artisan命令

php artisan botman:whatsapp:add-conversational-components

贡献

有关详细信息,请参阅 CONTRIBUTING

鸣谢

安全漏洞

如果您在 BotMan 中发现安全漏洞,请将电子邮件发送至 Marcel Pociot,邮箱地址为 m.pociot@gmail.com。所有安全漏洞都将得到及时处理。

许可证

BotMan 是在 MIT 许可证条款下免费分发的软件。