[kz] サイボウズ Office 10のスケジュールをical形式にして何につかおう

こんにちは、@kazscapeです。

サイボウズ Office10のスケジュールをical形式にしてみました。

参考にさせていただいたのは「Kung Noi Blog」さんのサイトの「サイボウズOffice10をiCalに変換するスクリプトとCalDavサーバ(ownCloud)にアップロード」の記事と「望遠鏡ドットコム」さんの「サイボウズ9・サイボウズ10のスケジュールをiCal形式でGoogleカレンダーに同期(一方通行)」という記事。

「Kung Noi Blog」さんのサイトにあるPerlのコードでうまくスケジュールを取得できているのですが、スケジュールの時間について、どうもサイボウズの仕様が変わったのか、うまく取得できず、すべての予定が終日の予定になってしまっていました。そこを少し改良させていただいたのと、「望遠鏡ドットコム」さんのところにあったPHPのソースコードから取得できたHTMLから不必要なものを削除する部分を流用させていただきました。

いずれもこの投稿時点のものなので、いつまたサイボウズが仕様を変更するやもしれませんが、投稿してみます。

ソースコード

#!/usr/bin/perl
use strict;
use warnings;
use Getopt::Long;
use Pod::Usage;
use YAML;
use LWP::UserAgent;
use utf8;
use Encode qw(from_to);
use POSIX qw(mktime strftime);
use Data::Dumper;
my $conf_default = $0;
$conf_default =~ s@/[^/]+$@/config.yaml@;

my %opt = (
   conf => $conf_default,
);
GetOptions( \%opt, 'conf=s', 'help' ) || pod2usage(2);
pod2usage(1) if $opt{help};
my ($cfg) = YAML::LoadFile($opt{conf});
die "Faild to read yaml file: $@" if $@;

my @facility = ();

#my $ua = LWP::UserAgent->new;
my $ua = LWP::UserAgent->new(
                         ssl_opts => {
                             verify_hostname => 0,
                             SSL_verify_mode => 0x00
                            });
my $url = $cfg->{cybozu_url} . "?page=ScheduleUserMonth";
my $req = HTTP::Request->new(POST => $url);
$req->content_type('application/x-www-form-urlencoded');
my $post = '_System=login&_Login=1&LoginMethod=1&_ID='
    . $cfg->{userid} . '&Password=' . $cfg->{password};
$req->content($post);
my $res = $ua->request($req);
die "Failed to access ", $res->status_line , "url=", $url, "\n" unless $res->is_success;
my $content = $res->content;
from_to($content, $cfg->{input_encoding} || 'shiftjis', 'utf8');
$content = Encode::decode('utf-8', $content);
$content = substr($content,index($content,'<table class="schedule usermonth" id="schedulemonth" width="100%" >'));
$content = substr($content,0,index($content,'<table class="monthNavi" '));
$content = substr($content,index($content,'<tbody id="um__body">'));
$content = substr($content,0,index($content,'</table>'));
my @event = split(/(<span class="eventDateTime">.*?<\/span><span class="eventDetail"><a class="event"[^>]*>)/, $content);
if ($cfg->{output_file}) {
    open(OUTPUT, ">$cfg->{output_file}~") || die;
} else {
    open(OUTPUT, ">&STDOUT");
}
binmode OUTPUT, ":utf8";
select(OUTPUT);
print <<EOF;
BEGIN:VCALENDAR
PRODID:$cfg->{calname}
VERSION:2.0
METHOD:PUBLISH
CALSCALE:GREGORIAN
X-WR-CALNAME:$cfg->{calname}
X-WR-CALDESC:$cfg->{calname}
X-WR-TIMEZONE:$cfg->{time_zone}
EOF
for (@event) {
    if (/<span class="eventDateTime">(.*?)<\/span><span class="eventDetail"><a class="event"\s+href="ag.exe\?([^"]+)"\s+title="([^"]+)"/) {
        &event($cfg->{calname}, $ua, $cfg->{cybozu_url}, $post,
               $1, $2, $3, $cfg->{input_encoding});
    }
}
print <<EOF;
BEGIN:VTIMEZONE
TZID:$cfg->{time_zone}
BEGIN:STANDARD
DTSTART:19700101T000000
TZOFFSETFROM:+0900
TZOFFSETTO:+0900
END:STANDARD
END:VTIMEZONE
END:VCALENDAR
EOF
close(OUTPUT);
if ($cfg->{output_file}) {
    rename "$cfg->{output_file}~", $cfg->{output_file};
}

my %cont;
my %recur;

sub event {
    my ($cal, $ua, $url, $post, $time, $query, $title, $encoding) = @_;
    my $eid;
    my $dtstart;
    my $dtend;
    my $description = "";
    my $location;
    my @start;
    my @end;
    $query=~ s/\&amp\;/&/g;

    for (split(/&/, $query)) {
        if (/^BDate=da\.(\d+)\.(\d+)\.(\d+)$/) {
            @start = ($3, $2-1, $1-1900);
        } elsif (/^Date=da\.(\d+)\.(\d+)\.(\d+)$/) {
            @end = ($3, $2-1, $1-1900);
        } elsif (/^sEID=(\d+)$/) {
            $eid = $1;
        }
    }
    if ($time =~ /^\s*(\d+):(\d+)-(\d+):(\d+)&nbsp;/) {
        $dtstart = strftime "DTSTART:%Y%m%dT%H%M%S", 0, $2, $1, @start;
        $dtend = strftime "DTEND:%Y%m%dT%H%M%S", 0, $4, $3, @end;
    } elsif ($time =~ /^\s*(\d+):(\d+)-(\d+)\/(\d+)&nbsp;/) {
        $cont{$eid} = strftime "DTSTART:%Y%m%dT%H%M%S", 0, $2, $1, @start;
        return;
    } elsif ($time =~ /^\s*(\d+)\/(\d+)-(\d+):(\d+)&nbsp;/) {
        $dtend = strftime "DTEND:%Y%m%dT%H%M%S", 0, $4, $3, @end;
        if (defined $cont{$eid}) {
            $dtstart = $cont{$eid};
            delete $cont{$eid};
        } else {
            $dtstart = strftime "DTSTART;VALUE=DATE:%Y%m%d", 0, 0, 0, @start;
        }
    } elsif ($time =~ /^\s*(\d+)\/(\d+)-(\d+)\/(\d+)&nbsp;/) {
        if (defined $cont{$eid}) {
            return;
        }
        $dtstart = strftime "DTSTART;VALUE=DATE:%Y%m%d", 0, 0, 0, @start;
        $dtend = strftime "DTEND;VALUE=DATE:%Y%m%d", 0, 0, 0, @end;
    } else {
        $dtstart = strftime "DTSTART;VALUE=DATE:%Y%m%d", 0, 0, 0, @start;
        $dtend = strftime "DTEND;VALUE=DATE:%Y%m%d", 0, 0, 0, @end;
    }
    my $diff = mktime(0, 0, 0, @start) + 86400 - time();
    if (0 <= $diff && $diff < 604800) {
        my $req = HTTP::Request->new(POST => $url . '?' . $query);
        $req->content_type('application/x-www-form-urlencoded');
        $req->content($post);
        my $res = $ua->request($req);
        if ($res->is_success) {
            my $i = index $res->content, '<a name="ScheduleData">';
            if ($i > 0) {
                my $content = substr($res->content, $i);
                from_to($content, $encoding, 'utf8');
                $content = Encode::decode('utf8', $content);
                for (split(/<th align="left"[^>]*>/, $content)) {
                    if (/^メモ<\/th>/) {
                        my $i = index($_, '<td>') + 4;
                        my $j = index($_, '</td>');
                        $description = substr($_, $i, $j-$i);
                        $description =~ s/[\r\n]+\s*//g;
                        $description =~ s/<br>/\\n\n /g;
                        $description =~ s/<[^>]+>//g;
                    }
                    if (/^設備<\/th>/) {
                        my $i = index($_, '<td>') + 4;
                        my $j = index($_, '</td>');
                        my $facility = substr($_, $i, $j-$i);
                        foreach my $f (@facility) {
                            if (index($facility, $f) >= 0) {
                                $location = $f;
                                last;
                            }
                        }
                    }
                }
            }
        }
    }
    my $uid = "$cal-$eid";
    if (defined $recur{$eid}) {
        $uid .= "-" . $recur{$eid};
        $recur{$eid}++;
    } else {
        $recur{$eid} = 1;
    }
    print <<EOF;
BEGIN:VEVENT
UID:$uid
DESCRIPTION:$description
$dtstart
$dtend
SUMMARY:$title
EOF
    print "LOCATION:$location\n" if defined $location;
    print <<EOF;
END:VEVENT
EOF
}

1;
__END__

=head1 NAME

cybozu10_ical - Convert Cybozu Office8 calendar into iCalendar format

=head1 SYNOPSIS

  % cybozu10_ical
  % cybozu10_ical --conf /path/to/config.yaml

=head1 DESCRIPTION

C<cybozu10_ical> is a command line application that fetches calendar
items from Cybozu Office 8, and converts them into an
iCalendar file.

変更したのは、41行目から45行目にかけて、取得できたHTMLから予定に関わる部分以外のところを削除してしまうようにしています。

これがなくても動くと思うのですが、スケジュールとして、何を取り出せばいいかに集中できるようにしたかったので残してあります。

次の変更箇所は、65行目のif文の条件です。

予定に関わる内容を1行にしているのですが、以前までの条件ではスケジュールの時間が取得できなくなっていたので、<span class=”eventDateTime”>のタグからスケジュールの時間を追加で取得するように条件を変更しました。そのため、関数の”event”に与える引数が一つ増えてしまったので$3まで増やしました。

89行目からの関数”event”では、引数として$timeを追加して、109行めから132行目までのif文で使用している変数を全て$timeにして、$titleには何も手を加えないようにしています。

「Kung Noi Blog」さんのところのソースコードと見比べないと何のこっちゃかもしれませんが、詳しくみたい方は両方をdiffなどしていただけると。

実行方法は元のままです

実行方法は、「Kung Noi Blog」さんのところで書かれていらっしゃるとおり、設定ファイルは「getcybozu10.yaml」の名前で以下のとおりご用意いただいて、

cybozu_url: https://fogefoge.com/cgi-bin/cbag/ag.cgi
calname: Cybozu
userid: 1
password: foge
time_zone: Asia/Tokyo
input_encoding: utf-8
output_file: /tmp/cybozu10ical.ics

実行は、それぞれが保存している場所でお願いします。

perl getcybozu10.pl –conf getcybozu10.yaml

まとめ

先駆者の方々の有益な資産を利用させていただき、少しばかり改良させていただいて、目的のical形式のファイルを取得することができました。

大変ありがとうございます。

さて、このical形式のファイル、何に使おう。。。

ではっ!


[kz] サイボウズ Office 10のスケジュールをical形式にして何につかおう」への1件のフィードバック

  1. Tanaka Kouji

    情報有難う御座います。
    上記スクリプトで、サイボウズ10から予定を取り出そうとしたのですが、

    BEGIN:VCALENDAR
    PRODID:Cybozu
    VERSION:2.0
    METHOD:PUBLISH
    CALSCALE:GREGORIAN
    X-WR-CALNAME:Cybozu
    X-WR-CALDESC:Cybozu
    X-WR-TIMEZONE:Asia/Tokyo
    BEGIN:VTIMEZONE
    TZID:Asia/Tokyo
    BEGIN:STANDARD
    DTSTART:19700101T000000
    TZOFFSETFROM:+0900
    TZOFFSETTO:+0900
    END:STANDARD
    END:VTIMEZONE
    END:VCALENDAR

    となり、うまく取り込めませんでした。
    サイボウズ側に何かしらの変更は必要ですか?
    確かに、http://www.goodnai.com/blog/2014/10/19/%E3%82%B5%E3%82%A4%E3%83%9C%E3%82%A6%E3%82%BAoffice10%E3%82%92ical%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%A8caldav%E3%82%B5%E3%83%BC%E3%83%90/
    様のサイトでは、時間をうまく取り込めないようです。

    返信

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です