#!/usr/bin/env perl package MixiEchoPod; use Moose; use HTTP::Engine; use HTTP::Headers; use LWP::UserAgent; use JSON 2.11; use DateTime; use DateTime::Format::MySQL; use XML::Simple; use Encode; use WWW::Mixi::Scraper; use MooseX::Types -declare => [qw/Mixi/]; our $VERSION = '0.0.1'; use Data::Dumper; with 'MooseX::Getopt'; class_type Mixi, { class => "WWW::Mixi::Scraper" }; has port => is => 'ro', isa => 'Int', default => 3941; # 3941 = mixi has timeline_cache => is => 'ro', isa => 'Int', default => 100; has mixi => is => 'ro', isa => Mixi; my $timelines = []; sub timelines { $timelines }; my $timeline_map = {}; sub timeline_map { $timeline_map }; my $timeline_queue = []; sub timeline_queue { $timeline_queue }; my $channels = {}; sub channels { $channels }; sub run { my($self, ) = @_; my @list = $self->mixi->new_friend_diary->parse; die 'login error' unless @list; my $engine = HTTP::Engine->new( interface => { module => 'ServerSimple', args => { host => '127.0.0.1', port => $self->port, }, request_handler => sub { $self->handler(@_) }, }, ); $engine->run; } sub handler { my($self, $c) = @_; my $req = $c->req; my $body = ''; if ($req->path eq 'http://twitter.com/statuses/friends_timeline.xml') { $body = $self->friends_timeline($req); $c->res->header('Content-Type' => 'application/xml'); } elsif ($req->path eq 'http://twitter.com/statuses/update.xml') { $body = $self->update($req); $c->res->header('Content-Type' => 'application/xml'); } elsif ($req->path =~ '^http://mixi\.jp/.*$') { my $ua = ua($req, 'mixi.jp'); my $res = $ua->get($req->path); $c->res->header('Content-Type' => $res->header('Content-Type')); $body = $res->content; } elsif ($req->path =~ '^http://profile\.img\.mixi\.jp/.*$') { my $ua = ua($req, 'profile.img.mixi.jp'); my $res = $ua->get($req->path); $c->res->header('Content-Type' => $res->header('Content-Type')); $body = $res->content; } else { warn $req->path; } $c->res->body($body); }; sub ua { my $req = shift; my $host = shift || 'mixi.jp'; my $headers = $req->headers->clone; $headers->remove_header('host'); $headers->header( Host => $host ); $headers->remove_header('user-agent'); $headers->header( 'User-Agent' => sprintf('%s/%s', __PACKAGE__, $VERSION) ); my $ua = LWP::UserAgent->new( default_headers => $headers, ); $ua->default_headers($headers); $ua; } sub _gen_timeline_id { $_[0]->{type}.':'.$_[0]->{rid} }; sub add_timeline { my $self = shift; return unless @_; for my $data (@_) { my $id = _gen_timeline_id $data; next if $self->timeline_map->{$id}; unshift @{ $self->timelines }, $data; my $pop; $pop = pop @{ $self->timelines } if scalar(@{ $self->timelines }) > $self->timeline_cache; next unless $pop; my $delete_id = _gen_timeline_id $pop; delete $self->timeline_map->{$delete_id} if exists $self->timeline_map->{$delete_id}; } } my $add_timeline_queue_id = 0; sub add_timeline_queue { my($self, $text) = @_; my $dt = DateTime->now; my $tmp = { created_at => $dt->strftime('%a %b %d %T %z %Y'), id => sprintf('W:%s:%s', time, $add_timeline_queue_id++), text => $text, source => 'mixiechopod.pl', truncated => 'false', in_reply_to_status_id => undef, in_reply_to_user_id => undef, favorited => undef, user => { id => '_MixiEchoPod', name => '_MixiEchoPod', screen_name => '_MixiEchoPod', location => undef, description => undef, profile_image_url => 'http://wassr.jp/user/staff/profile_img.png.128', url => undef, protected => 'false', followers_count => 1, }, }; push @{ $self->timeline_queue }, $tmp; } sub friends_timeline { my($self, $req) = @_; my $recent = $self->mixi->recent_echo->parse(@_); return '' unless $recent; my $data = { statuses => { status => [ ] } }; for my $item (@{ $recent->[0]->{recents} }) { my $dt = DateTime::Format::MySQL->parse_timestamp($item->{time}); $dt->set_time_zone( 'Asia/Tokyo' ); my $text = $item->{comment}; my $item_id = $item->{id}.':'.$item->{time}; my $tmp = { created_at => $dt->strftime('%a %b %d %T %z %Y'), id => $item_id, text => $text, source => 'web', truncated => 'false', in_reply_to_status_id => undef, in_reply_to_user_id => undef, favorited => undef, user => { id => $item->{name}, name => $item->{name}, screen_name => $item->{name},, location => undef, description => undef, profile_image_url => $item->{icon}||'', url => undef, protected => 0, followers_count => 1, }, }; push @{ $data->{statuses}->{status} }, $tmp; $self->add_timeline({ type => 'friend', user => $item->{name}, rid => $item_id, text => $text, }); } while (my $row = shift @{ $self->timeline_queue }) { push @{ $data->{statuses}->{status} }, $row; } my $xml = XMLout($data, NoAttr => 1, KeepRoot => 1, NumericEscape => 3 ); $xml =~ s/(?<=)/ type="array"/; $xml = qq{\n$xml}; $xml; } sub _fecth_replyid { my($self, $user, $prefix) = @_; for my $data (@{ $self->timelines }) { next if $user && $data->{user} ne $user; my $text = $data->{text}; $text =~ s/^@[-\w]+\s*//; return $data->{rid} if $text =~ /$prefix/; } return; } sub update { my($self, $req) = @_; my $status = $req->param('status'); my $mech = $self->mixi->{mech}->{mech}; $mech->get('http://mixi.jp/recent_echo.pl'); $mech->submit_form( form_number => 2, fields => { body => encode('euc-jp', decode('utf8', $status)), }, ); } package main; use ExtUtils::MakeMaker qw(prompt); my $mixi = WWW::Mixi::Scraper->new( email => prompt("Your email: ", ''), password => prompt("Your password: ", ''), mode => 'TEXT' ); MixiEchoPod->new_with_options( mixi => $mixi )->run;