GMailのアドレス帳(Contacts)にPerlでアクセスする

とあるCGIを作っている最中に、メールアドレスをGMailのアドレス帳(Contactsというらしい)に同期したくなった。何かいい方法はないかな、と思い、例によってCPANをあさってみた。

すると、WWW::Google::ContactsというCPANモジュールがあった。
http://search.cpan.org/~fayland/WWW-Google-Contacts-0.05/lib/WWW/Google/Contacts.pm

今回少しいじったので、使い方をメモっておく。
まずは、アドレス帳の中身を読む方法。なお、GMail側から取得できるデータ構造の詳細は割愛します。知りたい場合は、サンプルコードのData::Dumperの使い方を参考に中身をダンプしてみてください。

#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use WWW::Google::Contacts;
use Data::Dumper;

# インスタンス作成
my $gcontacts = WWW::Google::Contacts->new();

# 何をするにもまずはログインしなくては。
$gcontacts->login('ログイン名','パスワード') or die $@;

# アドレス帳のリストを取得
my @contacts = $gcontacts->get_contacts();

# アドレス帳で名前が載っている人だけメールアドレスを表示する
foreach my $contact (@contacts){
  # アドレス帳に載っている人の名前を取得
  my $name = $$contact{'name'}->{'gd:fullName'};
  # 名前が明示的につけられているか?
  if( defined($name)){
    print "Name : " , $name,"\n";
  }
  else{
    print "Name : No Name\n";
    next;
  }

  # 該当する名前に紐づいているメールアドレスを全て表示
  my @mailaddresses = map {$_->{address}} @{$contact->{email}};
  foreach my $mailaddr (@mailaddresses) {
    print $mailaddr,"\n";
  }
}

# グループを取得
my @groups = $gcontacts->get_groups();

# グループに関するデータ構造とデータをダンプ。
foreach my $group (@groups){
  my $dump = Dumper($group);
  $dump =~ s/\\x{([0-9a-z]+)}/chr(hex($1))/ge;
  print $dump;
}

ついで、アドレス帳に新たなエントリを載せる方法。

#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use WWW::Google::Contacts;
use Encode;

my $gcontacts = WWW::Google::Contacts->new();

# 何はともあれ、まずログイン
$gcontacts->login('ログイン名','パスワード') or die $@;

# 注意。文字コードはUTF-8にした上でUTF-8フラグを落とすこと。
# 具体的には設定したい文字に対して以下のようにEncode::encode()
# を呼ぶ。さもないと、はまります。
# 詳細は以下「ハマった箇所」に記載。
my $notes = '';
# displaynameもfullnameも区別していないみたいだ....?
# もし違うのであれば、ご一報を。
my $displayname = Encode::encode('utf8','サンプルさん');
my $fullname = Encode::encode('utf8','サンプルさん');

# アドレス帳に追加!なお、givenname,familynameは日本ではあまり意味をもたない。
my $ret = $gcontacts->create_contact(
  {
    givenName => $fullname,
    familyName => $fullname,
    fullName => $fullname,
    Notes => $notes,
    primaryMail => 'メールアドレス',
    displayName => $displayname,
  });

# 非ゼロの戻り値で追加成功。
if($ret){
  print "created!\n";
}
else{
  print "not created\n";
}

あとは、ハマった箇所について。
コメントにもあるように、encode()を呼んでUTF8フラグを落としてバイトストリームとして扱う必要がある。なぜか。

実はこれをしないと、create_contact()呼び出しにより例外が発生する。
例外発生時のメッセージ$@を表示すると、

HTTP::Message content must be bytes at /usr/share/perl5/HTTP/Request/Common.pm line 91

となっている。ソースコードを早速あさると、該当個所は

$req->content($content);

で$reqは

my $req = HTTP::Request->new(POST => $url);

ならば、ということでHTTP::Requestのcontent()メソッドの仕様をCPANのドキュメント(http://search.cpan.org/~gaas/libwww-perl-5.836/lib/HTTP/Request.pm)で調べると

Note that the content should be a string of bytes. Strings in perl can contain characters outside the range of a byte. The Encode module can be used to turn such strings into a string of bytes

となっており、コンテンツがバイトストリームでなければいけないとわかる。
よって、Encode::encocde()が必要なのである。

なお、update系のI/Fもありますが、それはまたの機会にさせてください。