背景
我想做些有趣的事情,所以当我发送位置信息时,我做了一个LINE BOT,告诉我附近的饺子店和烤肉店。
饺子和烤肉的原因是我喜欢它们。
参考
[官方参考]
https://developers.line.biz/ja/docs/messaging-api
[用于PHP的LINE SDK]
https://github.com/line/line-bot-sdk-php
[[LINE Bot]我制作了一个机器人,可以通过位置信息搜索tabelog 3.5或更高版本的优秀餐厅。
(我以此为模型。)
https://qiita.com/NARI_Creator/items/f29112e6f604c86b3c0d
[如何使用可直接使用的源代码获取Gurunavi API]
https://enjoy-surfing.com/gurunavi/
[用于Laravel制作LineBot的模型]
https://qiita.com/sh-ogawa/items/2238e579d7ee538025a0
[使用Laravel在LINE上创建聊天机器人(创建QR代码)]
https://blog.capilano-fw.com/?p=4285#i-4
这很有帮助??谢谢。
规范
1.输入是否要吃饺子或烤肉。
2.发送位置信息
3.
告诉您附近的商店
简单规格如下。 ??
创建之前要做什么
申请heroku
我认为如果您在这里看看,就可以做到。
易于使用,直到您注册Heroku帐户并部署
在heroku上部署Laravel(数据库是MySQL)
如果注册您的信用卡,
heroku每月可以免费使用1000个小时。
使用免费计划时,它每30分钟睡眠一次,因此睡眠后首次访问的响应速度很慢,但是如果完成批处理,似乎没有问题。
LINE Message API注册
创建一个LINE BOT帐户。
官方文件
该公式相当仔细地解释了它,因此我认为您可以边看边做。
实作
安装LINE Messaging API SDK
1 | $ composer require linecorp/line-bot-sdk |
添加环境变量
1 2 3 | #line LINE_SECRET_TOKEN = LINE_ACCESS_TOKEN = |
您可以从帐户的"基本设置"中引用
令牌。
创建路线
当用户向LINE BOT发送消息时,LINE平台向指定应用程序的URL发送HTTP请求,因此请确定URL。
web.php
1 | Route::post('line','LineController@post')->name('line.post'); |
顺便说一下,请求是通过POST发送的。
之后,我还将创建一个Controller。
创建一个客户端
这次,我在服务类中对其进行了总结。
LineService.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | <?php namespace App\Services; use LINE\LINEBot; use LINE\LINEBot\HTTPClient\CurlHTTPClient; class LineService { /** * linesdkの使用開始 * * @return LINEBot */ public static function lineSdk() { $token = config('services.line.token'); $secret = config('services.line.secret'); $httpClient = new CurlHTTPClient($token); $bot = new LINEBot($httpClient,['channelSecret' => $secret]); return $bot; } } |
它不是用基本的官方自述文件写的吗?
验证签名
危险请求可能来自生产线平台以外的其他地方,需要进行验证。
当请求来自在线平台时,请求标头将包含称为
使用
通道密钥作为私钥,并使用HMAC-SHA256算法获取请求正文的摘要值。
确保摘要值的Base64编码值与请求标头中X-Line-Signature中包含的签名匹配。
由于它被仔细地写为
,因此请按原样进行验证。
LineController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <?php namespace App\Http\Controllers; use App\Services\LineService; use LINE\LINEBot\SignatureValidator; class LineController extends Controller { public function post() { $signarure = request()->header('X-Line-Signature'); $validateSignature = SignatureValidator::validateSignature($httpRequestBody, $channelSecret, $signarure); if ($validateSignature) { return response()->json(200); } else { abort(400); } } } |
目前,如果可以验证签名,则将返回
请求主体摘要值是base64编码的,并与请求标头中的X-Line-Signature比较。
SignatureValidator.php
1 2 3 4 5 6 7 8 | public static function validateSignature($body, $channelSecret, $signature) { if (!isset($signature)) { throw new InvalidSignatureException('Signature must not be empty'); } return hash_equals(base64_encode(hash_hmac('sha256', $body, $channelSecret, true)), $signature); } |
这是什么吗?
1 2 3 4 | $channelSecret = config('services.line.secret'); $httpRequestBody = request()->getContent(); $hash = hash_hmac('sha256', $httpRequestBody, $channelSecret, true); $signature = base64_encode($hash); |
注册Webhook URL
接下来,从LINE控制面板中注册Webhook URL。
注册heroku网址(×××××.herokuapp.com / line),然后按验证按钮。
如果出现消息
如果不阻止控制器返回200,则会收到错误消息。
发送短信后我会回复
目前,当发送短信时,我将返回
LineController.php
1 2 3 4 5 6 7 8 9 10 11 12 | $bot = LineService::lineSdk(); try{ $events = $bot->parseEventRequest($httpRequestBody, $signature); foreach ($events as $event) { if ($event instanceof LINE\LINEBot\Event\MessageEvent\TextMessage) { $bot->replyText($event->getReplyToken(), 'こんにちは!'); } } }catch(\Exception $e){ Log::debug($e); } |
webhook↓发送各种事件。
Webhook事件对象
SDK
发送的事件是否为短信
检查它是否是
响应令牌被发布到可以应答的请求,因此它由
一起返回。
尝试根据事件
的类型更改响应
暂时实施对以下事件的响应。
?关注事件
?取消关注事件
邮票消息事件
短信事件
?位置消息事件
LineController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | try{ $events = $bot->parseEventRequest($httpRequestBody, $signature); foreach ($events as $event) { //フォローイベント if($event instanceof FollowEvent){ $followMessage = '友達登録ありがとう!何が食べたいか入力してね。近くのお店を見つけるよ!(例:餃子、ぎょうざ、ぎょーざ、焼肉、焼き肉、やきにく など)'; $bot->replyText($event->getReplyToken(), $followMessage); continue; } //フォロー解除イベント else if ($event instanceof UnfollowEvent) { continue; } //スタンプメッセージイベント else if($event instanceof StickerMessage){ $stampMessage = '可愛いスタンプだね?!何が食べたいか入力してくれたら近くのお店を見つけるよ!(例:餃子、ぎょうざ、ぎょーざ、焼肉、焼き肉、やきにく など)' $bot->replyText($event->getReplyToken(), $stampMessage); continue; } //テキストメッセージイベント else if($event instanceof TextMessage){ //TODO テキストメッセージ実装 } //位置情報メッセージイベント else if($event instanceof LocationMessage){ //TODO 位置情報実装 } } }catch(\Exception $e){ Log::debug($e); } |
参考别名????? ????
回复短信
此BOT要求您先输入要吃的食物,然后索取位置信息??
我想判断是饺子还是烤肉,但是
我想支持各种形式的饺子和烤肉,例如
只需将所有内容放入if语句中。也许不好??
LineService.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /** * 入力された文字が餃子か判定 * * @param $text * @return bool */ public function isGyoza($text) { return ($text === '餃子' || $text === 'ぎょうざ' || $text === 'ぎょーざ' || $text === 'ギョーザ' || $text === '??????'); } /** * 入力された文字が餃子か判定 * * @param $text * @return bool */ public function isYakiniku($text) { return ($text === '焼肉' || $text === '焼き肉' || $text === 'やきにく' || $text === 'ヤキニク' || $text === '????'); } |
您将在此处收到烤肉或饺子的结果。 ↓
如果输入了烤肉或饺子,请使用后面所述的
同样,
如果输入其他单词,它将返回一条短信??
LineService.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | /** * 送られてきたメッセージに対するレスポンス * * @param $bot * @param $event * @return void */ public function getMessage($bot, $event) { try{ $text = $event->getText(); if ($this->isGyoza($text)) { //DBに保存 $user_id = $event->getUserId(); $category = Line::GYOZA; $this->putCategory($user_id,$category); //位置情報を求める $word = '餃子'; $this->requireLocation($bot, $event, $word); } else if ($this->isYakiniku($text)) { //DBに保存 $user_id = $event->getUserId(); $category = Line::YAKINIKU; $this->putCategory($user_id,$category); //位置情報を求める $word = '焼肉'; $this->requireLocation($bot, $event, $word); }else{ $bot->replyText($event->getReplyToken(), '焼肉か餃子しか調べられないよ、、ごめんね。'); } }catch(\Exception $e){ Log::debug($e); } } /** * カテゴリーとユーザーIDをDBに保存 * * @param $user_id * @param $category * @return void */ public function putCategory($user_id, $category) { Line::create([ 'user_id' => $user_id, 'category' => $category ]); } /** * 位置情報を求めるメッセージを送る * * @param $bot * @param $event * @param $word //餃子か焼肉か * @return void */ public function requireLocation($bot, $event, $word) { $uri = new UriTemplateActionBuilder('現在地を送る!', 'line://nv/location'); $message = new ButtonTemplateBuilder(null, $word.'が食べたいんだね!今どこにいるか教えてほしいな!', null, [$uri]); $bot->replyMessage($event->getReplyToken(), new TemplateMessageBuilder('位置情報を送ってね', $message)); } |
?UriTemplateActionBuilder
轻击与此操作关联的控件时,将打开在第二个参数中注册的URL。
?ButtonTemplateBuilder
创建一个按钮模板。参数是标题,正文,图像URL和操作顺序。
注意,动作必须排列成阵列!
?TemplateMessageBuilder
创建模板消息。参数是替代文本ButtonTemplateBuilder。
我认为,如果到目前为止,它会像这样。 ??
从发送的地址
获取经度和纬度
由于可以通过功能
LineController.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | try{ $events = $bot->parseEventRequest($httpRequestBody, $signature); foreach ($events as $event) { //フォローイベント if($event instanceof FollowEvent){ $followMessage = '友達登録ありがとう!何が食べたいか入力してね。近くのお店を見つけるよ!(例:餃子、ぎょうざ、ぎょーざ、焼肉、焼き肉、やきにく など)'; $bot->replyText($event->getReplyToken(), $followMessage); continue; } //フォロー解除イベント else if ($event instanceof UnfollowEvent) { continue; } //スタンプメッセージイベント else if($event instanceof StickerMessage){ $stampMessage = '可愛いスタンプだね?!何が食べたいか入力してくれたら近くのお店を見つけるよ!(例:餃子、ぎょうざ、ぎょーざ、焼肉、焼き肉、やきにく など)'; $bot->replyText($event->getReplyToken(), $stampMessage); continue; } //テキストメッセージイベント else if($event instanceof TextMessage){ (new LineService())->getMessage($bot,$event); continue; } //位置情報メッセージイベント else if($event instanceof LocationMessage){ (new GurunaviService())->returnGurunaviList($bot, $event,$event->getLatitude(), $event->getLongitude()); //これを追加 } else{ break; } } |
从Gurunavi
获取信息
实现
使用餐厅搜索API。
您需要多少个参数? ??↓
?keyid(Gurunavi提供的访问密钥)
?category_s(类别代码)
https://api.gnavi.co.jp/api/manual/categorysmaster/
您可以通过点击此API获取类别代码列表。
?纬度
?经度
?范围(从纬度/经度(半径)开始的搜索范围)
就像1:300m,2:500m,3:1000m,4:2000m,5:3000m。高达3000m?
GurunabiService.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | /** * * カテゴリーが存在すればぐるなびで検索をかける * * @param $bot * @param $event * @param $lat * @param $lng */ public function returnGurunaviList($bot, $event, $lat, $lng) { //DBにカテゴリーがあるか検証 $category = Line::where(['user_id' => $event->getUserId()])->latest()->first(); if ($category) { $getGurunaviResult = $this->getGurunavi($lat, $lng, $category); if (property_exists($getGurunaviResult, 'error')) { $bot->replyText($event->getReplyToken(), 'お店が見つからなかったよ?、ごめんね。'); } else { $category->delete(); (new FlexMessage())->returnFlexMessage($event, $getGurunaviResult); } } else { $bot->replyText($event->getReplyToken(), '先に何が食べたいか教えてね!'); } } /** * * ぐるなびで検索 * * @param $lat * @param $lng * @param $category * @return mixed */ public static function getGurunavi($lat, $lng, $category) { $endpoint = 'https://api.gnavi.co.jp/RestSearchAPI/v3/'; //自分のアクセスキー $keyId = config('services.gurunavi.key'); //検索範囲 $range = 5; //カテゴリーコードを取得 if ($category->category === 1) { $categoryCode = 'RSFST14008'; } else { if ($category->category === 2) { $categoryCode = 'RSFST05001'; } else { $categoryCode = 'RSFST05003'; } } $url = $endpoint.'?keyid='.$keyId.'&category_s='.$categoryCode.'&latitude='.$lat.'&longitude='.$lng.'&range='.$range; $json = file_get_contents($url); return json_decode($json); } |
验证是否存在
顺便说一句,如果类别不是饺子和烤肉,则可以按章鱼烧的类别代码进行搜索。
并且由于搜索结果以json返回,因此将其解码并返回。
通过玩
返回搜索结果
将tabelog搜索结果转换为Flex Message并发送
Flex Message似乎是用json编写的,但我认为我永远做不到,所以
我添加了LINE Bot Designer。
使用LINE Bot Designer检查Flex Message的模板设计时创建,并将json转换为数组。
使用
这里有标题或正文吗?
flexMessage.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /** * ぐるなびの情報を送信 * * @param $event * @param $gurunavi * @return void */ public function returnFlexMessage($event,$gurunavi) { $token = config('services.line.token'); $postJson = $this->generateFlexTemplateContent($gurunavi); $result = json_encode(['replyToken' => $event->getReplyToken(), 'messages' => [$postJson]]); $curl = curl_init(); //curl_exec() の返り値を文字列で返す curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); //POSTリクエスト curl_setopt($curl, CURLOPT_POST, true); //ヘッダを指定 curl_setopt($curl, CURLOPT_HTTPHEADER, array('Authorization: Bearer '.$token, 'Content-type: application/json')); //リクエストURL curl_setopt($curl, CURLOPT_URL, 'https://api.line.me/v2/bot/message/reply'); //送信するデータ curl_setopt($curl, CURLOPT_POSTFIELDS, $result); $curlResult = curl_exec($curl); curl_close($curl); return $curlResult; } |
在
模板以json编写,但是使用LINE Bot Designer,您可以在检查设计时轻松创建json。
之后,请参考此处调整json的形状。 ???♀?
flexMessage.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | /** * ぐるなびの情報をflexMessageテンプレートにあてめる * * @param $gurunavis * @return array */ private function generateFlexTemplateContent($gurunavis) { $lists = []; foreach ($gurunavis->rest as $rest) { $lists[] = $this->getFlexTemplate($rest); } $contents = ["type" => "carousel", "contents" => $lists]; return ['type' => 'flex', 'altText' => 'searchResult', 'contents' => $contents]; } /** * flexMessageテンプレート * * @param $gurunavi * @return array */ private function getFlexTemplate($gurunavi) { return [ "type" => "bubble", "hero" => [ "type" => "image", "url" => $gurunavi->image_url->shop_image1 ? $gurunavi->image_url->shop_image1: "https://scdn.line-apps.com/n/channel_devcenter/img/fx/01_1_cafe.png", "size" => "full", "aspectRatio" => "20:13", "aspectMode" => "cover", "action" => [ "type" => "uri", "label" => "Line", "uri" => $gurunavi->url_mobile ? $gurunavi->url_mobile : '不明', ] ], "body" => [ "type" => "box", "layout" => "vertical", "contents" => [ [ "type" => "text", "text" => $gurunavi->name ? $gurunavi->name : '不明', "size" => "xl", "weight" => "bold", "wrap" => true ], [ "type" => "box", "layout" => "baseline", "margin" => "md", "contents" => [ [ "type" => "text", "text" => $gurunavi->opentime ? $gurunavi->opentime : '不明', "flex" => 0, "margin" => "md", "size" => "sm", "color" => "#999999", "wrap" => true ] ] ], [ "type" => "box", "layout" => "vertical", "spacing" => "sm", "margin" => "lg", "contents" => [ [ "type" => "box", "layout" => "baseline", "spacing" => "sm", "contents" => [ [ "type" => "text", "text" => "種類", "flex" => 1, "size" => "sm", "color" => "#AAAAAA" ], [ "type" => "text", "text" => $gurunavi->code->category_name_l[0] ? $gurunavi->code->category_name_l[0] : '不明', "flex" => 5, "size" => "sm", "color" => "#666666", "wrap" => true ] ] ], [ "type" => "box", "layout" => "baseline", "spacing" => "sm", "contents" => [ [ "type" => "text", "text" => "場所", "flex" => 1, "size" => "sm", "color" => "#AAAAAA" ], [ "type" => "text", "text" => $gurunavi->access->station.'徒歩'.$gurunavi->access->walk.'分' ? $gurunavi->access->station.'徒歩'.$gurunavi->access->walk.'分' : '不明', "flex" => 5, "size" => "sm", "color" => "#666666", "wrap" => true ] ] ], [ "type" => "box", "layout" => "baseline", "spacing" => "sm", "contents" => [ [ "type" => "text", "text" => "駐車場", "flex" => 2, "size" => "sm", "color" => "#AAAAAA" ], [ "type" => "text", "text" => $gurunavi->parking_lots ? $gurunavi->parking_lots : '不明', "flex" => 5, "size" => "sm", "color" => "#666666", "wrap" => true ] ] ] ] ] ] ], "footer" => [ "type" => "box", "layout" => "vertical", "flex" => 0, "spacing" => "sm", "contents" => [ [ "type" => "button", "action" => [ "type" => "uri", "label" => "ぐるなびで見る", "uri" => $gurunavi->url_mobile ? $gurunavi->url_mobile : '不明' ], "height" => "sm", "style" => "link" ], [ "type" => "spacer", "size" => "sm" ] ] ] ]; } } |
完成??
我成为了一名工程师,但在第一个月就尝试制作它感到沮丧,因为已经过去了四个月,所以当我再次尝试时感到惊讶。
我仍然无法编写干净的源代码,而且我确定服务类和方法都搞砸了,所以我将继续致力于它???
LINE参考非常容易理解!
有些不同!请让我知道是否有任何地方??