はてブのブクマを livedoor クリップにクリップしてみる件 - にぽたん研究所

June 28, 2006

このエントリーをはてなブックマークに追加
livedoor クリップとかいうソーシャルブックマークがオープンしていたので、はてブユーザの自分としては、はてブのブクマを全部 livedoor クリップにデータ移行が出来たらなぁとか思った。
なので、はてブから livedoor クリップに移すのを作ってみた。

どうも livedoor クリップにはまだ API 的なものが用意されていないっぽいので、WWW::Mechanize を使って、フォームから post するという、あまり綺麗じゃない方法を取ってみた。

で、WebService::SyncSBS::D2H にあやかって WebService::SyncSBS::H2L という名前にしようと思ったけど、メソッドとか互換させたわけでもないし、はてブは Web Service と呼べるが、livedoor クリップはそうではないので、WWW::SyncSBS::H2L という名前にしてみた。

まぁ、良かったら使ってみてくだちい。




package WWW::SyncSBS::H2L;

use strict;
use Carp;
use HTML::Entities ();
use WWW::Mechanize;
use URI;
use base qw(Class::Accessor::Fast);

__PACKAGE__->mk_accessors(qw(mech hatena_id livedoor_id password debug));

sub new {
    my($class, $args) = @_;
    my $self = bless {
        debug => 0,
    }, $class;
    if ($args && ref($args) eq 'HASH') {
        for my $method (keys %$args) {
            if ($class->can($method)) {
                $class->$method($args->{$method});
            }
        }
    }
    return $self->_init;
}

sub sync {
    my $self = shift;
    $self->login_livedoor_clip unless $self->{_logged_in};
    my $posted = $self->parse_hatena_bookmark;
    $self->clip_to_livedoor($posted);
}

sub login_livedoor_clip {
    my $self = shift;
    unless ($self->livedoor_id && $self->password) {
        croak('set your livedoor_id and password before login.');
    }
    unless ($self->_has_clip_account) {
        croak('register to livedoor clip before using this module.');
    }
    my $res = $self->mech->get('http://clip.livedoor.com/register/');
    $self->mech->submit_form(
        form_name => 'loginForm',
        fields => {
            livedoor_id => $self->livedoor_id,
            password    => $self->password,
        },
    );
    # XXX login checking (WWW::Mechanize->uri() doesn't work correct).
    $self->mech->get('http://clip.livedoor.com/register/');
    $self->{_logged_in} =
        $self->mech->uri =~ m{^http://clip\.livedoor\.com/} ? 1 : 0;
    unless ($self->{_logged_in}) {
        croak("failed to login to livedoor clip.");
    }
}

sub _has_clip_account {
    my $self = shift;
    my $myclip_url =
        sprintf('http://clip.livedoor.com/clips/%s', $self->livedoor_id);
    my $res = $self->mech->get($myclip_url);
    return $res->is_success ? 1 : 0;
}

sub _init {
    my $self = shift;
    unless ($self->mech) {
        my $mech = WWW::Mechanize->new;
        $mech->agent_alias('Windows IE 6');
        $self->mech($mech);
    }
    if ($self->livedoor_id && $self->password) {
        $self->mech->login_livedoor_clip;
    }
    return $self;
}

sub parse_hatena_bookmark {
    my $self = shift;
    unless ($self->hatena_id) {
        croak('set your hatena_id before sync.');
    }
    my $hatena_feed =
        sprintf('http://b.hatena.ne.jp/%s/atomfeed', $self->hatena_id);
    my $recent_feed = $self->_get_content($hatena_feed);
    my $count = $self->get_bookmark_count($recent_feed);
    unless ($count) {
        croak($self->hatena_id . ' is not using Hatena::Bookmark.');
    }
    my $last = $count - $count % 20;
    my @recent_posted = $self->_parse_feed($recent_feed);
    my @posted = ();
    if ($last) {
        for (my $of = $last; $of >= 0; $of -= 20) {
            my $uri = URI->new($hatena_feed);
            if ($of) {
                $uri->query_form(of => $of);
            }
            my @piece =
                $self->_parse_feed($self->_get_content($uri->as_string));
            push @posted, reverse(@piece);
        }
    }
    push @posted, reverse(@recent_posted);
    return \@posted;
}

# XXX parse using regexp
sub _parse_feed {
    my($self, $feed) = @_;
    my @posted_data = ();
    while ($feed =~ m{<entry>\s*(.+?)\s*</entry>}gs) {
        my $content = $1;
        my($title) = $content =~ m{<title>\s*(.+?)\s*</title>};
        my($url)   = $content =~ m{<link rel="related".+?href="(.+?)"\s*/>};
        my($summary) = $content =~ m{<summary[^<>]+>(.*?)</summary>};
        my @tags =
            map { _decode_entities($_) }
                $content =~ m{<dc:subject>(.*?)</dc:subject>}g;
        my $data = {
            title   => (_decode_entities($title)   || ''),
            url     => (_decode_entities($url)     || ''),
            summary => (_decode_entities($summary) || ''),
            tags    => \@tags,
        };
        push @posted_data, $data;
    }
    return @posted_data;
}


sub get_bookmark_count {
    my($self, $feed) = @_;
    my $count;
    if ($feed =~ m{<feed version=}) {
        ($count) = $feed =~
            m{<openSearch:totalResults>(\d+)</openSearch:totalResults>};
        unless ($count) {
            $count = scalar($feed =~ m{<entry>(.+?)</entry>}sg);
        }
    }
    return ($count || 0);
}

sub _get_content {
    my($self, $url) = @_;
    my $success = 0;
    for (1 .. 10) { # try to get feed 10 times.
        my $res = $self->mech->get($url);
        if ($res->is_success) {
            $success = 1;
            last;
        }
    }
    unless ($success) {
        croak("faild fetching: $url");
    }
    return $self->mech->content;
}

sub clip_to_livedoor {
    my($self, $data) = @_;
    for my $posted (@$data) {
        my $uri = URI->new('http://clip.livedoor.com/clip/add');
        $uri->query_form(
            link  => $posted->{url},
            jump  => 'page',
            tags  => join(' ', @{$posted->{tags}}),
            title => $posted->{title},
            notes => $posted->{summary},
        );
        my $add_url = $uri->as_string;
        my $res = eval { $self->mech->get($add_url) };
        if ($res && $res->is_success) {
            eval { $self->mech->submit_form(form_name => 'clip') };
            if ($@) {
                carp("can't submit: " . $posted->{url});
            }
        }
        else {
            carp("fail to clip $add_url HTTP Status: " . $res->code);
        }
    }
}

# XXX Hatena's feed has any strange entities
sub _decode_entities {
    my $data = shift;
    $data =~ s/&amp;/&/g;
    return HTML::Entities::decode_entities($data);
}

1;

__END__

=head1 NAME

WWW::SyncSBS::H2L - Sync Hatena::Bookmark to livedoor clip

=head1 SYNOPSIS

 use WWW::SyncSBS::H2L;
 
 my $h2l = WWW::SyncSBS::H2L->new;
 $h2l->hatena_id('hatena_id');
 $h2l->livedoor_id('livedoor_id');
 $h2l->password('password');
 $h2l->sync;

=head1 AUTHOR

nipotan E<lt>nipotan@gmail.comE<gt>

=head1 COPYRIGHT

Copyright (c) 2006 nipotan. All rights reserved.

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=head1 SEE ALSO

L<WWW::Mechanize>, L<WebService::SyncSBS::D2H>

=cut


nipotan at 16:06 | Comments(3) | TrackBack(4) | 技術 
このエントリーをはてなブックマークに追加

Trackback URL for this entry

Trackbacks

1. nipotanさんの「はてブ-&gt;livedoorクリップ」の使用方法  [ cloned.log ]   June 29, 2006 10:28
nipotanさんのはてブのブクマを livedoor クリップにクリップしてみる件のやさしい使用法を。というか自分自身がPerlのど素人なのでやってみたことを書くだけ。OSはFedoraCore5で。(Perl使いの人からみてコラって内容があったらご指摘下さい。修正します。) デフォルトイ
2. livedoor クリップのクリップをはてブにブクマしてみる件  [ にぽたん研究所 ]   July 03, 2006 17:46
livedoor クリップとかいうソーシャルブックマークがオープンしていたので、使ってみたんだけど、元々はてブユーザの自分としては、livedoor クリップに登録したクリップを全部はてブにデータ移行が出来たらなぁとか思った。なので、livedoor クリップからはてブに移すのを作...
3. nipotanさんの「livedoorクリップ-&gt;はてブ」の使用方法  [ cloned.log ]   July 03, 2006 20:00
nipotanさんのlivedoor クリップのクリップをはてブにブクマしてみる件のやさしい使用法を。というか自分自身がPerlのど素人なのでやってみたことを書くだけ。OSはFedoraCore5で。(Perl使いの人からみてコラって内容があったらご指摘下さい。修正します。) デフォルトイン
4. 「公開APIを利用したサンプルサイトを作っていくよ」公開  [ [Z]ZAPAブロ〜グ2.0 ]   September 14, 2006 21:58
公開APIを利用したサンプルサイトを作っていくよを作りました。 AWS、BWS、DWSを利用した[Z]ZAPAnetサーチ2.0や、サン・マイクロシステムズ - サン×リクルート Mash up アワードに応募した中古車、自動車カタログ検索-ZAPAnet-、宿・ホテル検索-ZAPAnet-、物件...

Comments

1. Posted by koko   June 28, 2006 22:01
ソーシャルバックマーク北ね。
なんか、目新しい機能ないけど
2. Posted by 国際結婚 diary   June 29, 2006 14:47
こんにちは

RSSと、クリップの使い方の勉強
をしていたところ、こちらの
ブログにつきました。

又、時間をつくり勉強にきます
3. Posted by r4   April 26, 2012 16:27
こんにちは

RSSと、クリップの使い方の勉強
をしていたところ、こちらの
ブログにつきました。

又、時間をつくり勉強にきます

Post a comment

Name:
URL:
  Remember info?: Rate: Face    Star