#!/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;