to pagetop
:)
spec5zigen Creator's Lab.

鷺谷です。関東の大雪、だいじょうぶでしたか?
去年の大雪では尻もちついたので今回はおとなしくしていました。

冬のicsファイル書き出し

「サイボウズからもっと簡単にiPhoneのカレンダーに予定を持ってこれないか?」

会社ではスケジュール管理にサイボウズを利用しています。
個人的に一部の予定をiPhoneのカレンダーで利用できるようにしたかったので、
Macのカレンダー.appに手作業で再登録して、iCloud経由でiPhoneに同期して利用していました。
この作業をしなくて済むようにサイボウズの予定をMacのカレンダー.appに取り込みができるようなしくみを作ってみました。
カレンダー.appの取り込みにはiCalendar形式のファイル(.ics)が使えます。
サイボウズからicsファイルが取得できれば一連の作業がずいぶん簡単になります。

ありがたいことに、サイボウズには予定をメール経由で他のアプリと連携できる機能があります。
予定の登録情報をメールの添付書類で受け渡しする仕組みです。
この仕組みをうまく利用してブラウザから簡単にiPhoneのカレンダーに入れる方法を試してみました。

目標

サイボウズの予定をPCのブラウザで表示した状態で
選択した予定のicsファイルを簡単に書きだせるようにする。

完成イメージ

ブラウザでサイボウズの予定一覧を表示します。

サイボウズの予定

サイボウズの予定



予定のリンクを右クリックします。

予定のリンクを右クリック

予定のリンクを右クリック、コンテキストメニューに[スケジュールを書き出す]が表示



[スケジュールを書き出す]を選ぶとschedule.icsファイルが自動的にダウンロードされます。

icsファイル

icsファイル



ダウンロードされたschedule.icsファイルを開くとカレンダー.appが起動して、スケジュール追加の確認をするダイアログが表示されます。

イベントを追加

イベントを追加



予定の登録完了。あとはiCloudでiPhoneにまで届きます。

カレンダーappに登録完了

カレンダーappに登録完了



制限事項

以下の前提条件があります。

  • ・記事記載時点のchrome拡張機能の仕様・サイボウズの仕様であること。
  • ・開始・終了時間が決まっていること。
     (時間が空欄だとカレンダー.appで取り込みに失敗する)
  • ・一日だけの予定であること。(複数日に渡る予定が反映できない)

もっと踏み込んで解析すれば解決できそうですが、
今回は個人的な要望を満たしたのでここまでの実装にしています。

機能の分担

選択したスケジュールのリンク先のURLをパラメタにして、
ics書き出しプログラムを呼び出すところまでをchromeの拡張機能で行います。
普段使っているブラウザにあわせてchromeの拡張機能を利用しました。
スケジュールデータ取得、変換、ダウンロード周りはPHPで処理します。

PHPの実装

PHPが受け取るパラメタは予定詳細画面のURLです。
このURLにはサイボウズのユーザーIDや日付、予定の識別子情報が含まれています。

https://<サイボウズのURL>/o/ag.cgi?page=ScheduleView&UID=<ユーザーID>&GID=&Date=<選択した日>&BDate=<開始日?>&sEID=<イベント識別子>&CP=

まずこの情報を元にメール送信フォームにアクセスするために必要なパラメタでセットしてリクエストを組み立てます。

https://<サイボウズのURL>/o/ag.cgi?page=MailSend&sp=sv&Date=<選択した日>&BDate=<開始日?>&UID=<ユーザーID>&GID=&sEID=<イベント識別子>&SavePath=1

ログインパスワードなどもPOSTのパラメタに入れておきます。
プログラム側でパスワードをセットする仕組みにしているので、
配置するPHPにはアクセス制限をかけておきましょう。
出来上がったリクエストをPOST送信してフォーム情報を入手します。

フォームのソース中にvcsファイルにアクセスに必要なファイルのIDと思われるデータを見つけます。


上記の例では1677の値を知りたいので、「”>schedule.vcs」を手がかりに寸前の数値部分を抜き出しました。
IDがわかればやっとvcsファイルにアクセスすることができます。

フォームと同じ要領でvcsダウンロードのリクエストを組み立てます

https://<サイボウズのURL>/o/ag.cgi/schedule.vcs?page=PersonalDownload&id=<見つけたID>&from=3&notimecard=1&type=text&subtype=calendar&ct=1&ext=.vcs

これでPOSTでアクセスしするとicsが取得できます。
URLを見る限りvcsがダウンロードできると思ったら拡張子icsで取得できました。
レスポンスのvcsファイルはまだ加工が必要なので変数で保持します。

取得した生のics(vcs)ファイルはこのような内容です。

BEGIN:VCALENDAR
PRODID:-//Cybozu, Inc.//Cybozu Office 8.0//JP
VERSION:1.0
BEGIN:VEVENT
UID:000
DTSTAMP:20150205T120000 
X-CY-EVENT;ENCODING=QUOTED-PRINTABLE:=E4=BD=9C=E6=A5=AD
SUMMARY;ENCODING=QUOTED-PRINTABLE:=E3=81=94=E9=A3=AF=E3=82=92=E8=85=B9=E3=81=84=E3=81=A3=E3=81=B1=E3=81=84=E9=A3=
=9F=E3=81=B9=E3=82=8B
DTSTART:20150205T120000 
DTEND:20150205T130000 
CLASS:PUBLIC
ATTENDEE;CN="なまえ";ROLE=REQ-PARTICIPANT;RSVP=TRUE:MAILTO:info@spec5zigen.com
TZ:+09:00
END:VEVENT
END:VCALENDAR

これをカレンダー.appに取り込める形にPHPで変換します。
まず完成形のファイルのひな形を作っておいてここに取得したファイルの内容をセットします。

$result =            'BEGIN:VCALENDAR'
            ."\r\n".'PRODID:Cybozu2iCal'
            ."\r\n".'VERSION:2.0'
            ."\r\n".'METHOD:PUBLISH'
            ."\r\n".'CALSCALE:GREGORIAN'
            ."\r\n".'BEGIN:VEVENT'
            ."\r\n".'UID:#UID#'
            ."\r\n".'DTSTART;TZID=Asia/Tokyo:#DTSTART#'
            ."\r\n".'DTEND;TZID=Asia/Tokyo:#DTEND#'
            ."\r\n".'DESCRIPTION:#DESCRIPTION#'
            ."\r\n".'SUMMARY:#SUMMARY#'
            ."\r\n".'END:VEVENT'
            ."\r\n".'BEGIN:VTIMEZONE'
            ."\r\n".'TZID:Asia/Tokyo'
            ."\r\n".'BEGIN:STANDARD'
            ."\r\n".'DTSTART:19700101T000000'
            ."\r\n".'TZOFFSETFROM:+0900'
            ."\r\n".'TZOFFSETTO:+0900'
            ."\r\n".'END:STANDARD'
            ."\r\n".'END:VTIMEZONE'
            ."\r\n".'END:VCALENDAR';
//
<略>
$dataStr = <vcsから取り出したSUMMARYの内容>;
$dataStr = quoted_printable_decode($dataStr);
$result = str_replace('#SUMMARY#',$dataStr,$result);
<略>

SUMMARYとDESCRIPTIONにはエンコードした文字列が入っているので
quoted_printable_decode()でデコードします。

内容が完成したらダウンロードの出力を実行します。

$fileName = "schedule.ics";
header('Content-Type: text/calendar;charset=utf-8',TRUE);
header('Content-Disposition:attachment;filename='.$fileName,TRUE);
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
echo $result;

PHPはここで完成。

chromeの拡張機能の実装

次にPHPにURLを伝えるchromeの拡張機能を作ります。
今回は2ファイルを用意します。
manifest.json(このファイル名で定義を記述)
exec.js(命名は自由、処理を記述)
2個をフォルダにまとめておきます。

manifest.jsonはこんな内容です

{
  "name": "スケジュールを書き出す",
  "description": "サイボウズのスケジュールをicsファイルに書き出す",
  "background": {
    "scripts": ["exec.js"]
  },
  "permissions": [
       "<PHPのURLまたはドメイン>"
      ,"contextMenus"
      ,"tabs"
  ],
  "version": "1.0",
  "manifest_version": 2
}

backgroundに処理を行うjsファイル名を定義。
permissionsには
許可ドメイン/URL情報を記載します。
それに加えて、許可する機能を記載します。
コンテキストメニューを利用するので”contextMenus”、
phpにリダイレクトするためにタブを使ったので”tabs”を記載しました。

exec.jsはメニューを出して選択したURLをPHPに渡す処理を記載します。

function doClick() {
    return function(clickEvtInfo, infoTab) {
				var checkStr = 'https://<サイボウズのURL>/o/ag.cgi?page=ScheduleView&UID=';
				var url = clickEvtInfo.linkUrl;
				if(url.indexOf(checkStr) != 0){
					alert('サイボウズのスケジュールリンクを選んでください');
					return;
				}

				//
				var destURL = '<PHPのURL>?url='+encodeURIComponent(url);
				//
 				chrome.tabs.create({url: destURL});
    };
};
//
var parentId = chrome.contextMenus.create({
    "title" : "スケジュールを書き出す",
    "type" : "normal",
    "contexts" : ["link"],
    "onclick" : doClick()
});
//
chrome.browserAction.onClicked.addListener(function(tab) {
    chrome.tabs.update(tab.id, {
        url: url
    });
});

getClickHandler()では無関係なリンクを除外してリダイレクトするだけの処理を記載
chrome.contextMenus.createではメニューの準備
chrome.browserAction.onClicked.addListenerでリダイレクトを実装

chromeの拡張機能のインストール

出来上がったらchromeの拡張機能画面を開いて
manifest.jsonとjsの入ったフォルダを画面にドロップするとインストールを促す表示に切り替わります。

拡張機能に登録

拡張機能に登録

インストールすると自作の拡張機能が一覧に表示されます。

登録された拡張機能

登録された拡張機能

サイボウズの予定一覧で右クリックして動作を確認してみます。jsonのpermissionsの記述がちゃんとできていないとPHPまでたどり着けません。

やっかいなところ

PHPでvcsの中身から必要な情報を抜き出すのは結構手間がかかりました。先に加工しないとデコードに失敗するので必要に応じて改行を取ったり、トリミングする必要があります。
vcsのID抜き出し方法もエイヤッと現状仕様決め打ちでやっているので個人使用レベルですが、自分で使えりゃいいいや、と自己満足。
なのでスケジュールのヘビーユーザーであるディレクターさんにはあまりウケてないです。