LINE BOT APIでBOTのお友達全員にメッセージを送ってみる

LINEの[BOT API Trial Account 先着10,000名様まで先行申し込み受付中](https://business.line.me/services/products/4/introduction)、という言葉につられて登録したので試してみました。

基本的にはbotとお友達になって、botに何か話しかけたら答える、みたいな1対1の対話が想定のようで、メッセージを受信したら送信者に何かを送るという流れのようです。
なので、お友達全員にメッセージを一斉送信、ということがしたかったのですが、友達のID一覧取得、みたいのがないので、お友達になった時(operation requestを受信した時)に保存しておくようにしました。(配列をjson_encodeしてファイルに保存してます。)

[LINEのChannels管理画面みたいなの](https://developers.line.me/channels/)で自分のBOTのChannel IDとかSecretとかMIDとかを確認して、Callback URL に `https://myhost.mydomain.jp:443/linebot/rcv` みたいに受信するURLを設定します。

諸事情によりCodeIgniter2で書いてます。

```php:controllers/linebot.php
class Linebot extends CI_Controller {

	private $_channel_id = '自分のBOTのChannel ID';
	private $_channel_secret = '自分のBOTのChannel Secret';
	private $_channel_mid = '自分のBOTのMID';

	private $_allow_ip = 'メッセージ送信を許可するIPアドレス(自分のIPとか)';
	private $_save_path;

	public function __construct()
	{
		parent::__construct();
		$this->_save_path = APPPATH . 'logs/line_friends';
	}

	public function rcv()
	{
		$input = file_get_contents('php://input');
		log_message('debug', $input);
		$json = json_decode($input);
		if ( ! isset($json->result) OR ! is_array($json->result))
		{
			log_message('error', 'no result');
			$this->output->set_status_header('400');
			return;
		}
		foreach ($json->result as $result) {
			if ( ! isset($result->content->opType))
			{
				// operation requestじゃない(メッセージとかが送られてきた)場合はスタンプをランダムで返す
				$this->_send_sticker(rand(1, 17), 1, $result->content->from);
				continue;
			}
			// operation requestの場合
			switch ($result->content->opType)
			{
				case 4:
					// added as friend
					if ($this->_set_friend($result->content->params[0]) !== FALSE) {
						$this->_send_text('ようこそ！', $result->content->params[0]);
					}
					break;
				case 8:
					// blocked account
					$this->_del_friend($result->content->params[0]);
					break;
			}
		}
		$this->output->set_status_header('200');
	}

	public function snd($msg)
	{
		// 指定したIPからのアクセスのみ許可する
		$remote_addr = $this->input->server('HTTP_X_FORWARDED_FOR');
		if ($remote_addr === FALSE)
		{
			$remote_addr = $this->input->server('REMOTE_ADDR');
		}
		if (empty($remote_addr))
		{
			log_message('error', 'no remote address');
			show_error('no remote address');
		}
		if ($remote_addr !== $this->_allow_ip)
		{
			log_message('error', 'invalid access: ' . $remote_addr);
			show_error('invalid access: ' . $remote_addr);
		}
		$this->_send_text(urldecode($msg));
		$this->output->set_status_header('200');
	}

	private function _get_friends()
	{
		if ( ! file_exists($this->_save_path))
		{
			return array();
		}
		return json_decode(file_get_contents($this->_save_path));
	}

	private function _set_friend($mid)
	{
		$friends = $this->_get_friends();
		if ( ! in_array($mid, $friends))
		{
			$friends[] = $mid;
			return file_put_contents($this->_save_path, json_encode($friends));
		}
	}

	private function _del_friend($mid)
	{
		$friends = $this->_get_friends();
		if (($key = array_search($mid, $friends)) !== FALSE)
		{
			unset($friends[$key]);
			return file_put_contents($this->_save_path, json_encode(array_values($friends)));
		}
	}

	private function _send_text($message, $mid_arr = NULL)
	{
		$content = array(
				'contentType' => 1,
				'toType' => 1,
				'text' => $message
		);
		return $this->_send_message($content, $mid_arr);
	}

	private function _send_sticker($stkid, $stkpkgid, $mid_arr = NULL)
	{
		$content = array(
				'contentType' => 8,
				'toType' => 1,
				'contentMetadata' => array(
						'STKID' => (string)$stkid,
						'STKPKGID' => (string)$stkpkgid
				)
		);
		return $this->_send_message($content, $mid_arr);
	}

	private function _send_message($content, $mid_arr = NULL)
	{
		if (is_string($mid_arr))
		{
			$mid_arr = array($mid_arr);
		}
		elseif (is_null($mid_arr)) {
			$mid_arr = $this->_get_friends();
		}
		if (empty($mid_arr))
		{
			return FALSE;
		}
		$url = 'https://trialbot-api.line.me/v1/events';
		$headers = array(
				'Content-Type: application/json; charser=UTF-8',
				'X-Line-ChannelID: ' . $this->_channel_id,
				'X-Line-ChannelSecret: ' . $this->_channel_secret,
				'X-Line-Trusted-User-With-ACL: ' . $this->_channel_mid
		);
		$post = array(
				'to' => $mid_arr,
				'toChannel' => 1383378250,
				'eventType' => '138311608800106203',
				'content' => $content
		);

		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_POST, TRUE);
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
		curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post, JSON_UNESCAPED_UNICODE));
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
		$ret = curl_exec($ch);
		log_message('debug', $ret);
		return $ret;
	}
}
```

なにか話しかけられた時に何もないのも寂しいので、17種類のスタンプをランダムで返すようにしました。
(スタンプのIDとかは、[API reference](https://developers.line.me/bot-api/api-reference)にsticker listのExcelファイルがあります。)

`https://myhost.mydomain.jp/linebot/snd/Hello`
とかで『Hello』をBOTのお友達全員に送ることができます。
日本語とか記号はURLエンコードする必要があります。(日本語はブラウザがやってくれるかも)

toTypeが『1 = user』って書いてあって、1しか書いてないんだけど、他にも違うタイプがあるのか、これからできるようになるのか…。
グループにも送信できるといいんだけどなー。