理系学生日記

おまえはいつまで学生気分なのか

しょぼいカレンダーを iCal 化して iPhone でアニメ時間を確認する

YAPC::Asia Tokyo 2011id:hakobe932 さんが WebService::SyoboiCalendarLT をされていました。
くわしくはこちらをご覧ください。

これはしょぼいカレンダー というアニメ番組表サイトから情報を引っ張ってくる超便利モジュールなのですが、ぼくはそもそも"しょぼいカレンダー"というものを知らなかった。こんな便利なサイトをぼくは四半世紀に渡り知らなかったし、だれも教えてくれなかった。おかげで人生の 1/3 くらいを無駄にすごした。
これを知っただけでも YAPC に行った価値があった。ありがとう YAPC。YAPC がぼくを救ってくれた。

そういうわけですから、まずは 3G の届かないところでもアニメ放映予定を見て一日の予定を組めるように、iCal 化してみることにしました。
https://img.skitch.com/20111023-ca6ftwbyfan5ke8qup25s8u1d9.jpg

1 スクリプトにまとめようとしてみたら、エラい長くなった感じがします。
未来一ヶ月分を取得するかたちですけど、しょぼいカレンダーに与える負荷が心配で、とりあえず 1 秒の sleep を入れてます。あと、Encode どこでかけるかっていうタイミングがよくわかりませんでした。

とにかくだ、これを cron にしかけ出力される iCalendar ファイルを HTTP Server の配下に配置すれば、iPhone や Mac の iCal から参照できるようになってハッピーライフ実現する。

#!/usr/bin/env perl
use strict;
use warnings;
use WebService::SyoboiCalendar;
use Smart::Args;
use Config::Pit;
use Data::ICal;
use Data::ICal::Entry::TimeZone::Standard;
use Data::ICal::Entry::TimeZone;
use Data::ICal::Entry::Event;
use Encode;
use DateTime;
use Log::Minimal;

binmode STDOUT => ':utf8';

my ($filename) = @ARGV;

die "usage: $0 filename\n" unless $filename;
run(filename => $filename);


sub run {
    args my $filename;

    my $conf = pit_get('cal.syoboi.jp', require => {
	user => 'username on SyoboiCalendar',
	pass => 'password on SyoboiCalendar',
    });
    infof("successfully got account info on SyoboiCalendar.");

    my $from = DateTime->now;
    my $to   = DateTime->now->add( months => 1 );

    my $calendar = setup_calendar(filename => $filename);
    infof("iCalendar successfully set up.");

    eval {
	infof("query from " . $from->ymd('') . "-" . $to->ymd('') );
	my $syoboi  = WebService::SyoboiCalendar->new($conf);
	my $results = $syoboi->search_program({
	    range => $from->ymd('/') . '-' . $to->ymd('/'),
	});
	infof("got search results from SyoboiCalendar.");
	update_calendar(calendar => $calendar, programs => $results);
	infof("successfully update $filename.");
    };
    if ( my $e = Exception::Class->caught('WebService::SyoboiCalendar::APIRequestError') ) {
	errorf($e->res);
    }
}

sub update_calendar {
    args my $calendar,
	 my $programs;

    for my $anime (@$programs) {
	my $program = $anime->program;
	my $title   = $anime->title;

	my $vevent = Data::ICal::Entry::Event->new();
	debugf("got " . $title->title . ".");
	my $summary = $title->title . " (" . $program->ch_name() . ")";

	# iso-8601 format
	my ($dtstart, $dtend, $dtstamp) = map {
	    my $a =  $_->datetime;
	    $a =~ s/[-:]//g;
	    $a;
	} ($program->start_time, $program->end_time, DateTime->now);

	$vevent->add_properties(
	    summary     => $summary,
	    dtstart     => $dtstart,
	    dtend       => $dtend,
	    dtstamp     => $dtstamp,
	    transp      => 'TRANSPARENT',
	);
	$calendar->add_entry($vevent);
	sleep 1;
    }
    debugf("got all programs.");

    open my $fh, '>', $filename or die $!;
    print {$fh} $calendar->as_string;
}

sub create_calendar {
    my $calendar = Data::ICal->new;

    my $standard = Data::ICal::Entry::TimeZone::Standard->new;
    $standard->add_properties(
 	'TZOFFSETFROM' => '+0900',
	'TZOFFSETTO'   => '+0900',
	'TZNAME'       => 'JST',
	'DTSTART'      => '19700101T000000'
    );
    my $timezone = Data::ICal::Entry::TimeZone->new;
    $timezone->add_property(
	'TZID'           => 'Asia/Tokyo',
	'X-LIC-LOCATION' => 'Asia/Tokyo',
    );

    $calendar->add_properties(
	'X-WR-CALNAME'  => 'Anime Calendar',
	'CALSCALE'      => 'GREGORIAN',
	'X-WR_TIMEZONE' => 'Asia/Tokyo',
	'X-WR-CALDESC'  => 'kiririmode Anime Calendar',
    );

    $timezone->add_entry($standard);
    $calendar->add_entry($timezone);

    return $calendar;
}

sub setup_calendar {
    args my $filename;

    infof("create $filename.");
    my $calendar = create_calendar();

    my $standard = Data::ICal::Entry::TimeZone::Standard->new;
    $standard->add_properties(
 	'TZOFFSETFROM' => '+0900',
	'TZOFFSETTO'   => '+0900',
	'TZNAME'       => 'JST',
	'DTSTART'      => '19700101T000000'
    );
    my $timezone = Data::ICal::Entry::TimeZone->new;
    $timezone->add_property(
	'TZID'           => 'Asia/Tokyo',
	'X-LIC-LOCATION' => 'Asia/Tokyo',
    );

    $calendar->add_properties(
	'X-WR-CALNAME'  => 'Anime Calendar',
	'CALSCALE'      => 'GREGORIAN',
	'X-WR_TIMEZONE' => 'Asia/Tokyo',
	'X-WR-CALDESC'  => 'kiririmode Anime Calendar',
    );

    $timezone->add_entry($standard);
    $calendar->add_entry($timezone);

    return $calendar;
}

追記

で、しょぼいカレンダーもう一度よく確認してみたら、しょぼいカレンダー側で iCal 用出力できるんですね。

ぼくの場合、http://cal.syoboi.jp/rss2.php?days=14&usr=kiririmode&alt=ical を iCal に登録しておけばいい…はずなんですが、なんか iCal から読み込めないので、だれか理由知ってる人はおしえてください。