天気予報(今の天気)のBOTをCakePHP3を利用して作ってみた


### CakePHP3で動かす
はい、前回の公開から時間が経ってますが、書いていきます。
CakePHP3を使って、LINE BOTを使うというところからですね。

CakePHP3の使い方がわからない人は、調べてください。

### BotController作成

```php:BotController.php
<?php
namespace App\Controller;

use App\Controller\AppController;

class BotController extends AppController
{

  // アクセストークン
  protected $ACCESS_TOKEN = 'developerの[メッセージ送受信設定]の[アクセストークン]';

  public function index()
  {
    // autoRenderは使用しません
    $this->autoRender = false;
    
    // 返却する文字列
    $message_text = "";

    $messageData = array();
    $request = $this->request->getData();

    // ユーザーから送られてきたデータ
    $event = $request['events'][0];
    $type  = $event['type'];
    $replyToken = $event['replyToken'];

    $userId = $event['source']['userId'];

    if($type == 'follow'){
      // 登録された時に文言を返す
      $message_text = "登録ありがとう。よろしくね。";
    }elseif($type == 'unfollow'){
      // 削除された場合の処理
    }elseif($type == "message"){
      // メッセージ
      $messageType = $event['message']['type'];
      if($messageType == 'text') {
        // text そのまま返す
        $message_text = $event['message']['text'];
      }
    }

    // 返信可能な場合に処理する
    if(!empty($message_text)) {
      //レスポンスフォーマット
      $response_format_text = [
        "type" => 'text',
        "text"  => $message_text
      ];

      //ポストデータ
      $post_data = [
        "replyToken" => $replyToken,
        "messages" => [$message_text]
      ];

      //curl実行
      $ch = curl_init("https://api.line.me/v2/bot/message/reply");
      curl_setopt($ch, CURLOPT_POST, true);
      curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
      curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data));
      curl_setopt($ch, CURLOPT_HTTPHEADER, array(
       'Content-Type: application/json; charser=UTF-8',
        'Authorization: Bearer ' . $ACCESS_TOKEN
      ));
      $result = curl_exec($ch);
      curl_close($ch);
    }
    
    echo 200;
  }
}
```

コレで、`https://hogehoge/bot`とかをWebhook URLに指定すれば、
前回と同じようなことができますね。


### 天気予報
では次に。天気予報。
今回は[OpenWeatherMap](https://openweathermap.org/api)を利用します。
登録して、API KEYを取得したら、「天気予報」と言う言葉に反応して、
[沼津市](http://www.city.numazu.shizuoka.jp/)=`Numazu, JP`の現在の天気を取得します。


### WeatherMapComponent作成
まずは、天気予報関連の処理をまとめたいので
`WeatherMapComponent.php`を作りましょう。

```php:WeatherMapComponent.php
<?php
namespace App\Controller\Component;

use Cake\Controller\Component;

class WeatherMapComponent extends Component
{
    // 現在の天気を取得
    protected $WEATHER_MAP_WEATHER_URL = "http://api.openweathermap.org/data/2.5/weather";
    // 沼津市を指定
    protected $CITY = "Numazu, JP";
    // api key
    protected $WEATHER_MAP_API = "[OpenWeatherMap の API KEY]";
    
  /**
   * 天気予報文字列を返す
   * @return string
   */
    public function getWeather(){
      $url = self::getWeatherUrlFromCity();
      $weather = json_decode(file_get_contents($url), true);
      
      return self::getWeatherText($weather);
    }
    
  /**
   * 都市名検索URL(現在の天気)
   * @return string
   */
    public function getWeatherUrlFromCity(){
      return $this->WEATHER_MAP_WEATHER_URL. "?q=".$this->CITY."&units=metric&appid=".$this->WEATHER_MAP_API;
    } 
    

  /**
   * objectから天気予報を取得して文字列を返す
   * @param $weather
   * @return string
   */
   public function getWeatherText($weather){
      $text = '';

      if(isset($weather['weather'][0]['id'])){
        $main = $this->getMainText($weather['weather'][0]['main']);
        $description = $this->getWeatherDescription($weather['weather'][0]['id']);
        $text .= "今の天気は".$main."で、".$description."です。\n";
      }

      // degが風向きを、speedが風速を表してます。
      if(isset($weather['wind']['deg']) && isset($weather['wind']['speed'])){
        $digger = $this->getWindDigger($weather['wind']['deg']);
        $text .= $digger."向きの風、風速".$weather['wind']['speed']."メートル。\n";
      }

      // tempが気温を示しています
      if(isset($weather['main']['temp'])){
        $text .= "気温は".$weather['main']['temp']."度です。";
      }

      return $text;
    }
    
  /**
   * @param $main
   * @return mixed
   */
    public function getMainText($main){
      $inEnglish = array('Clear', 'Clouds', 'Rain', 'Snow','Mist');
      $inJapanese = array('晴れ', 'くもり', '雨', '雪','霧');
      $key = array_search($main,$inEnglish);
      if($key !== false){
        return $inJapanese[$key];
      }
      return $main;
    }

  /**
   * 天気詳細
   * @param $id
   * @return string
   */
    public function getWeatherDescription($id){
      $description[200] = '雨が降る雷雨';
      $description[201] = '雨が降る雷雨';
      $description[202] = '豪雨による雷雨';
      $description[210] = '雷雨';
      $description[211] = '雷雨';
      $description[212] = '重い雷雨';
      $description[221] = '荒れた雷雨';
      $description[230] = '軽い霧雨で雷雨';
      $description[231] = '雷雨と霧雨';
      $description[232] = '重い霧雨で雷雨';
      $description[300] = '光度の霧雨';
      $description[301] = '霧雨';
      $description[302] = '重い霧雨の霧';
      $description[310] = '光度降雨雨';
      $description[311] = '霧雨';
      $description[312] = '強い霧雨';
      $description[313] = 'にわか雨と霧雨';
      $description[314] = '重いにわか雨と霧雨';
      $description[321] = 'にわか霧雨';
      $description[500] = '小雨';
      $description[501] = '中程度の雨';
      $description[502] = '強い雨';
      $description[503] = '非常に豪雨';
      $description[504] = '極端な雨';
      $description[511] = '雨氷';
      $description[520] = '強度のにわか雨';
      $description[521] = 'にわか雨';
      $description[522] = '激しいにわか雨';
      $description[531] = '不規則なにわか雨';
      $description[600] = '小雪';
      $description[601] = '雪';
      $description[602] = '大雪';
      $description[611] = 'みぞれ';
      $description[612] = 'にわかみぞれ';
      $description[615] = '明るい雨と雪';
      $description[616] = '雨や雪';
      $description[620] = '雷とにわか雪';
      $description[621] = 'にわか雪';
      $description[622] = '重いにわか雪';
      $description[701] = 'ミスト';
      $description[711] = '煙';
      $description[721] = 'ヘイズ';
      $description[731] = '砂、ほこり旋回する';
      $description[741] = '霧';
      $description[751] = '砂';
      $description[761] = 'ほこり';
      $description[762] = '火山灰';
      $description[771] = 'スコール';
      $description[781] = '竜巻';
      $description[800] = '晴天';
      $description[801] = '薄い雲';
      $description[802] = '雲';
      $description[803] = '曇りがち';
      $description[804] = '厚い雲';
      $description[900] = '竜巻';
      $description[901] = '熱帯低気圧';
      $description[902] = 'ハリケーン';
      $description[903] = '寒い';
      $description[904] = '暑い';
      $description[905] = '風が強い';
      $description[906] = '雹';
      $description[951] = '落ち着いた';
      $description[952] = 'そよ風';
      $description[953] = 'そよ風';
      $description[954] = '中風';
      $description[955] = '新鮮な風';
      $description[956] = '強い風';
      $description[957] = '高風、近くの暴風';
      $description[958] = 'ガール';
      $description[959] = '深刻な暴風';
      $description[960] = '嵐';
      $description[961] = '暴風雨';
      $description[962] = 'ハリケーン';

      if(isset($description[$id])){
        return $description[$id];
      }

      return '';
    }

  /**
   * 風向き計算
   *
   * @param $digree
   * @return mixed
   */
    public function getWindDigger($digree){
      $dname = ["北","北北東","北東", "東北東", "東", "東南東", "南東", "南南東", "南", "南南西", "南西", "西南西", "西", "西北西", "北西", "北北西", "北"];
      $dindex = round($digree / 22.5);

      return $dname[$dindex];
    }
}
```

### BotController修正
今度はこのComponentを使えるようにしないとですね。

```php:BotController.php
class BotController extends AppController
{
  public $components = [ "WeatherMap" ];
  
  // アクセストークン
  protected $ACCESS_TOKEN = 'developerの[メッセージ送受信設定]の[アクセストークン]';
```

componentsに"WeatherMap"を指定します。

```php:BotController.php
    }elseif($type == "message"){
      // メッセージ
      $messageType = $event['message']['type'];
      if($messageType == 'text') {
        // 文言に「天気予報」が含まれていたら天気予報を返す
        if(strpos("天気予報",$event['message']['text']) !== false){
          $message_text = $this->WeatherMap->();
        }else{
          // text そのまま返す
          $message_text = $event['message']['text'];
        }
      }
    }
```

`if(strpos("天気予報",$event['message']['text']) !== false){`
このようにして、文言の中に「天気予報」が含まれていたら、天気を返します。
とはいえ、今回返しているのは現在の天気なわけですが…

### まとめ
このようにして、Controller、Componentを作成して、BOTに天気を返させる処理ができました。
今回は現在の天気を返していますが、URLの「weather」を「forecast」にすれば、予報の処理も作成できます。
(返ってくる値が違うので、気をつけてください)

APIと連携したら色々と面白いBOTが作れそうだなぁーと言うのがこちらを作った際の感想でした。
でも、現在はPUSH機能を使ったBOTがかなり面白いので、そちらの処理も後々書きたいと思います。


この機能を使ったおしゃべりBOT [堕天使YOHANE](https://line.me/R/ti/p/%40vov9636t)を公開しています。
メインはおしゃべりなので、何か言うと返ってきます。
また、占いとか…現在の沼津の天気を教えてくれるそんなBOTです。