googleカレンダーとslackを利用して、会議室予約終了時刻を事前通知

2018年4月11日

会社の会議室を予約していたにも関わらず、その前に利用している方の会議が終わらず、自分の会議を短縮とか別の会議室を取り直しとかあったりしませんか?

私の会社では稀にあることです。

大体が時間を気にせずに結論が出るまで会議を伸ばしていたり、そもそも話が白熱して時間を忘れてしまっていたなどの理由はあると思いますが、前利用者の時間超過(無断延長)によりせっかく予約していた会議室が使えないとなるとイヤですよね。

Googleカレンダーで完結できれば良いのですが、開始前の通知はできますが、終了前通知はできないです。

そこで会議室利用者に対し、利用終了10分前にslackで通知し、時間超過(無断延長)を防ぐような仕組みを作成しました。

 

色々テストしてみたこと

1.Googleカレンダーの会議室予約者を自動的に取得し、それをメールで通知する
メールに気づかない!

2.Googleカレンダーの会議室予約者を自動的に取得し、それをPublicにて作成したslackチャンネルに投稿
→チャンネルに参加させるのが面倒。しかもミュートされたら意味がない

3.Googleカレンダーの会議室予約者を自動的に取得し、slackのダイレクトメッセージへ送る
→チャンネル参加も必要もない。ミュートもできない。
→これで行く!!

前提条件および前準備

・Googleとslackを同一アカウント名で利用している。(slackの認証をgoogleアカウントで行っている)

・PHP5.4以上がコマンドライン(CLI)で利用できるようにする。

・Google Calendar APIを利用できるようにしておく。

・slack api tokenを取得しておく。

 

Google Calendar APIの利用方法やSlackのAPI Token取得方法はネット内に色々書かれてますので、ここでは割愛します。

 

1.作業ディレクトリにcomposer.pharをインストール

PHP5.4以上がインストールされコマンドラインが利用できる環境の作業ディレクトリに移動。その後以下のコマンドを実行。

# curl -s http://getcomposer.org/installer | php

※作業ディレクトリにcomposer.pharが設置されます。

2.Googleクライアントライブラリのインストール

作業ディレクトリにて以下のコマンドを実行すると、composer.pharを使用してGoogleクライアントライブラリがインストールされます。

# php composer.phar require google/apiclient:^2.0

3.サンプルコードの作成

Google Calendar APIのページにあるソースコードを『quickstart.php』として保存します。
quickstart.phpでなくともgoogle_calendar_resource_check.phpでも何でも良いです。

今回はこちらのソースを修正してslackに通知させます。

<?php
require_once __DIR__ . '/vendor/autoload.php';
define('APPLICATION_NAME', 'Google Calendar API PHP Quickstart');
define('CREDENTIALS_PATH', '~/.credentials/calendar-php-quickstart.json');
define('CLIENT_SECRET_PATH', __DIR__ . '/client_secret.json');
// If modifying these scopes, delete your previously saved credentials
// at ~/.credentials/calendar-php-quickstart.json
define('SCOPES', implode(' ', array(
  Google_Service_Calendar::CALENDAR_READONLY)
));
date_default_timezone_set('America/New_York'); // Prevent DateTime tz exception
if (php_sapi_name() != 'cli') {
  throw new Exception('This application must be run on the command line.');
}
/**
 * Returns an authorized API client.
 * @return Google_Client the authorized client object
 */
function getClient() {
  $client = new Google_Client();
  $client->setApplicationName(APPLICATION_NAME);
  $client->setScopes(SCOPES);
  $client->setAuthConfig(CLIENT_SECRET_PATH);
  $client->setAccessType('offline');
  // Load previously authorized credentials from a file.
  $credentialsPath = expandHomeDirectory(CREDENTIALS_PATH);
  if (file_exists($credentialsPath)) {
    $accessToken = json_decode(file_get_contents($credentialsPath), true);
  } else {
    // Request authorization from the user.
    $authUrl = $client->createAuthUrl();
    printf("Open the following link in your browser:\n%s\n", $authUrl);
    print 'Enter verification code: ';
    $authCode = trim(fgets(STDIN));
    // Exchange authorization code for an access token.
    $accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
    // Store the credentials to disk.
    if(!file_exists(dirname($credentialsPath))) {
      mkdir(dirname($credentialsPath), 0700, true);
    }
    file_put_contents($credentialsPath, json_encode($accessToken));
    printf("Credentials saved to %s\n", $credentialsPath);
  }
  $client->setAccessToken($accessToken);
  // Refresh the token if it's expired.
  if ($client->isAccessTokenExpired()) {
    $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    file_put_contents($credentialsPath, json_encode($client->getAccessToken()));
  }
  return $client;
}
/**
 * Expands the home directory alias '~' to the full path.
 * @param string $path the path to expand.
 * @return string the expanded path.
 */
function expandHomeDirectory($path) {
  $homeDirectory = getenv('HOME');
  if (empty($homeDirectory)) {
    $homeDirectory = getenv('HOMEDRIVE') . getenv('HOMEPATH');
  }
  return str_replace('~', realpath($homeDirectory), $path);
}
// Get the API client and construct the service object.
$client = getClient();
$service = new Google_Service_Calendar($client);
// Print the next 10 events on the user's calendar.
$calendarId = 'primary';
$optParams = array(
  'maxResults' => 10,
  'orderBy' => 'startTime',
  'singleEvents' => TRUE,
  'timeMin' => date('c'),
);
$results = $service->events->listEvents($calendarId, $optParams);
if (count($results->getItems()) == 0) {
  print "No upcoming events found.\n";
} else {
  print "Upcoming events:\n";
  foreach ($results->getItems() as $event) {
    $start = $event->start->dateTime;
    if (empty($start)) {
      $start = $event->start->date;
    }
    printf("%s (%s)\n", $event->getSummary(), $start);
  }
}

コマンドラインにて『quickstart.php』を実行します。

# php quickstart.php

正常に動作すると『Open the following link in your browser:』が表示されますので、表示されたURLをブラウザを利用してアクセスします。

すると認証画面に遷移し、認証を許可するとコードが表示されますのでコピーしてください。

表示されたコードをコマンドラインの『Enter Verification code:』の場所に貼り付けます。

タイムゾーンエラーが出る場合は、以下をソース上部に入れてください。

date_default_timezone_set('Asia/Tokyo');

 

4.サンプルコードを修正し、slackに投げる仕掛けを作る

$calendarIdの指定を配列に変更します。

[修正前]
$calendarId = 'primary';
[修正後]
$calendar_arr = array("","","");
※ここに会議室のリソースメールを入れる

$results = $service->events->listEvents($calendarId, $optParams);以下をすべて変更します。
ちなみに現在の日時から最大10件まで取得可能です。それ以上はmaxResultsの値を変更してください。

[修正前]
$optParams = array(
  'maxResults' => 10,
  'orderBy' => 'startTime',
  'singleEvents' => TRUE,
  'timeMin' => date('c'),
);
$results = $service->events->listEvents($calendarId, $optParams);
[修正後]
$optParams = array(
  'maxResults' => 10,
  'orderBy' => 'startTime',
  'singleEvents' => TRUE,
  'timeMin' => date('c'),
);
foreach($calendar_arr as $resource){
	$results = $service->events->listEvents($resource, $optParams);
	if (count($results->getItems()) == 0) {
		print "No upcoming events found.\n";
	} else {
		//  print "Upcoming events:\n";
		foreach ($results->getItems() as $event) {
			$now_date = date("Y/m/d H:i");
			$location = $event->location;
			$summary = $event->summary;
			$start = date("Y/m/d H:i",strtotime($event->start->dateTime));
			$end = date("Y/m/d H:i", strtotime($event->end->dateTime));
			$min_before = date("Y/m/d H:i", strtotime($end ."-10 minute")); // 終了時刻何分前
			if($end >= $now_date && $min_before <= $now_date){ // 終了時刻と現在の時刻、終了時刻10分前と現在の時間と比較
				$attendees_arr = $event->attendees; // 会議室予約を確認
				$count = count($attendees_arr);
				$i = 0;
				$attendees = "";
				while($i < $count){ // 通知先取得
					if(strpos($attendees_arr[$i]->email,'resource.calendar.google.com') === false){
						$attendees_email = $attendees_arr[$i]->email;
						$address_count = strpos($attendees_email, "@");
						$mention = substr($attendees_email,0,$address_count);
						$attendees[] .= "@".$mention;
					}
					$i++;
				}
				//$arr = array($location, $start, $end, $min_before, $attendees);
				//print_r($arr);
				$mention_list1 = "";
				$mention_list2 = "";
				foreach($attendees as $val){
					$mention_list1 .= "<".$val.">,"; // 通知文用
					$mention_list2[] = $val; // 各ダイレクトメッセージ用
				}
				$slackApiKey = ""; // Slack API Keyを入力
				$channel = urlencode("会議室利用終了事前通知");
				$channel = "%23".$channel;
				$username = "Notice_Bot";
				$body  = "会議室の利用終了時間が近づいてきました。\n";
				$body .= "延長する場合は、スケジュール登録をお願いいたします。\n";
				$body .= "===================================================\n";
				$body .= "予約名:$summary \n";
				$body .= "会議室名:$location \n";
				$body .= "開始日時:$start \n";
				$body .= "終了日時:$end \n";
				$body .= "参加者:$mention_list1 \n";
				$body .= "\n\n";
				$body = urlencode($body);
				foreach($mention_list2 as $val){
					$url = "https://slack.com/api/chat.postMessage?token=${slackApiKey}&username=${username}&channel=${val}&text=${body}";
					file_get_contents($url);
				}
			}
		}
	}
}

修正ポイント

foreach($calendar_arr as $resource){

配列化された会議室のリソースメールをforeachで回します。

                        $now_date = date("Y/m/d H:i");
                        $location = $event->location;
                        $summary = $event->summary;
                        //$start = $event->start->dateTime;
                        //$end = $event->end->dateTime;
                        $start = date("Y/m/d H:i",strtotime($event->start->dateTime));
                        $end = date("Y/m/d H:i", strtotime($event->end->dateTime));
                        $min_before = date("Y/m/d H:i", strtotime($end ."-10 minute")); // 終了時刻何分前

現在の時刻を指定し、APIにて取得した情報から会議室の利用開始時間、終了時間を成型。
ついでに終了時間10分前の時刻も指定。

                        if($end >= $now_date && $min_before <= $now_date){ // 終了時刻と終了時刻を現在の時間と比較
                                $attendees_arr = $event->attendees; // 会議室予約を確認
                                $count = count($attendees_arr);
                                $i = 0;
                                $attendees = "";
                                while($i < $count){ // 通知先取得
                                        if(strpos($attendees_arr[$i]->email,'resource.calendar.google.com') === false){
                                                $attendees_email = $attendees_arr[$i]->email;
                                                $address_count = strpos($attendees_email, "@");
                                                $mention = substr($attendees_email,0,$address_count);
                                                $attendees[] .= "@".$mention;
                                        }
                                        $i++;
                                }

終了時刻と現在の時刻、終了時刻10分前と現在の時間と比較し、その範囲の予約状況を確認します。

その後、参加者リストも取得。slackのメンション用にユーザー名の前に"@"を付与してます。

参加者リストの中に会議室のリソースメールも混じるので『resource.calendar.google.com』は除外してます。

                                foreach($attendees as $val){
                                        $mention_list1 .= "<".$val.">,"; // 通知文用
                                        $mention_list2[] = $val; // 各ダイレクトメッセージ用
                                }

slackの仕様では、通知文に乗せるメンションは"<“と">"で囲わないとならないらしいので成型。
また、通知先を$mention_list2として配列に保管。

				$channel = urlencode("会議室利用終了事前通知");
				$channel = "%23".$channel;

 

色々いじった中でできたゴミなので、無視して良いです。
チャンネルに投稿する場合は必要です。

$slackApiKey = ""; // Slack API Keyを入力

SlackのAPI Keyを入力してください。
SlackのAPI Key取得方法はいろんな所に情報が載ってますので割愛します。

				$body  = "会議室の利用終了時間が近づいてきました。\n";
				$body .= "延長する場合は、スケジュール登録をお願いいたします。\n";
				$body .= "===================================================\n";
				$body .= "予約名:$summary \n";
				$body .= "会議室名:$location \n";
				$body .= "開始日時:$start \n";
				$body .= "終了日時:$end \n";
				$body .= "参加者:$mention_list1 \n";
				$body .= "\n\n";

slackに通知する文面です。

foreach($mention_list2 as $val){
					$url = "https://slack.com/api/chat.postMessage?token=${slackApiKey}&username=${username}&channel=${val}&text=${body}";
					file_get_contents($url);
				}

 

slackのapi(chat.postMessage)に通知文を送ります。

作成したプログラムを月~金の10~17時の毎時20分、50分にcronで動かせば自動通知可能です。

こんな感じに通知されます。

 

 

 

 

 

 

 

 

end