Created
May 11, 2011 09:13
-
-
Save mtsukamoto/966157 to your computer and use it in GitHub Desktop.
Showbelx Update to Local Growl for Windows
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use utf8; | |
use strict; | |
use warnings; | |
use Encode; | |
use Growl::GNTP; | |
use HTTP::Request::Common; | |
use URI; | |
use WWW::Mechanize; | |
use YAML; | |
use Web::Scraper; | |
our $VERSION = '0.0.2'; | |
my %args = @ARGV; | |
my $yaml = exists($args{'-c'}) ? $args{'-c'} : './shovelx2growl.yml'; | |
my $config = load_config($yaml); | |
my $ua = WWW::Mechanize->new; | |
$ua->env_proxy; | |
my $res = &login($config, $ua); | |
die unless ($res); | |
my @entries = &fetch($config, $ua); | |
¬ify(@entries); | |
YAML::DumpFile($yaml, $config); | |
exit(); | |
sub load_config { | |
my $yaml = shift; | |
die sprintf("Config file is not specified.") unless (length($yaml)); | |
unless (-f $yaml) { | |
my $default = { | |
'pages' => { | |
'1' => {'enabled' => '1', 'url' => '/direct_messages', 'title' => 'しゃべる - 受信箱'}, | |
'2' => {'enabled' => '0', 'url' => '/direct_messages/sent', 'title' => 'しゃべる - 送信箱'}, | |
'3' => {'enabled' => '0', 'url' => '/replies', 'title' => 'しゃべる - あなた宛のつぶやき'}, | |
'4' => {'enabled' => '1', 'url' => '/home', 'title' => 'しゃべる - 最近のつぶやき'}, | |
'5' => {'enabled' => '0', 'url' => '/account/archive', 'title' => 'しゃべる - あなたのつぶやき'}, | |
'6' => {'enabled' => '1', 'url' => '/public_timeline', 'title' => 'しゃべる - 公開つぶやき'} | |
}, | |
'site' => {'account' => 'alibaba', 'password' => 'jUgeMJugEm', 'url' => 'http://shovelx.example.com:80/'} | |
}; | |
$_->{'title'} = encode_utf8(decode_utf8($_->{'title'})) for (values(%{$default->{'pages'}})); | |
print "'$yaml' is not exists, creating.\n"; | |
YAML::DumpFile($yaml, $default); | |
die "Edit '$yaml', then run shovelx2growl again."; | |
} | |
$yaml = decode_utf8($yaml); | |
my $config = YAML::LoadFile($yaml); | |
die sprintf("File '%s' is not valid.") unless ($config); | |
return $config; | |
} | |
sub login { | |
my ($config, $ua) = @_; | |
my $base = URI->new($config->{'site'}->{'url'}); | |
my $account = $config->{'site'}->{'account'}; | |
my $password = $config->{'site'}->{'password'}; | |
# login | |
my $uri = URI->new_abs('/login', $base); | |
my $req = HTTP::Request::Common::POST( | |
$uri, | |
['account' => $config->{'site'}->{'account'}, 'password' => $config->{'site'}->{'password'}] | |
); | |
my $res = $ua->request($req); | |
die(decode_utf8($res->status_line). "\n") unless ($res->is_success); | |
&detect_error($ua); | |
return 1; | |
} | |
sub fetch { | |
my ($config, $ua) = @_; | |
my @keys = UNIVERSAL::isa($config->{'pages'}, 'HASH') ? keys(%{$config->{'pages'}}) : (); | |
my @pages = map { $config->{'pages'}->{$_} } sort { $b <=> $a } @keys; | |
my $statuses = {}; | |
my $messages = {}; | |
foreach my $page (@pages) { | |
next unless ($page->{'url'} && $page->{'enabled'}); | |
my $title = decode_utf8($page->{'title'}); | |
my $res = &scrape($config, $ua, $page->{'url'}); | |
my ($type) = grep { exists($res->{$_}) } qw(status message); | |
my @entries = (UNIVERSAL::isa($res->{$type}, 'ARRAY')) ? @{$res->{$type}} : (); | |
next unless (@entries); | |
# additional field | |
foreach my $entry (@entries) { | |
$entry->{'id'} = ($entry->{$type} =~ /(\d+)$/) ? $1 : 0; | |
$entry->{'title'} = $title; | |
$entry->{'page'} = $page->{'url'}; | |
} | |
# unread filtering | |
my $min = $page->{'recent'} || 0; | |
@entries = sort { $a->{'id'} <=> $b->{'id'} } grep { $_->{'id'} > $min } @entries; | |
$page->{'recent'} = $entries[-1]->{'id'} if (@entries); | |
# map | |
if ($type eq 'status') { $statuses->{$_->{'id'}} = $_ for (@entries); } | |
elsif ($type eq 'message') { $messages->{$_->{'id'}} = $_ for (@entries); } | |
} | |
my @entries = ( | |
(map { $messages->{$_ } } sort { $a <=> $b } (keys(%$messages))), | |
(map { $statuses->{$_ } } sort { $a <=> $b } (keys(%$statuses))), | |
); | |
return @entries; | |
} | |
sub scrape { | |
my ($config, $ua, $url) = @_; | |
# NOTE: url shoud be /home, /replies, /account/archive, /public_timeline, /direct_messages, /direct_messages/sent | |
my $base = URI->new($config->{'site'}->{'url'}); | |
my $uri = URI->new_abs($url, $base); | |
my $scraper = scraper { | |
process '#directMessages_timeline table.timeline tbody tr', 'message[]' => scraper { | |
process 'img.profile-image', 'image' => '@src'; | |
process 'span.enter span', 'name' => '@title'; | |
process 'span.enter span', 'user' => 'TEXT'; | |
process 'span.entry-body', 'body' => 'TEXT'; | |
process 'span.entry-body', 'message' => '@id'; | |
process 'span.dateTime', 'time' => 'TEXT'; | |
}; | |
process '#home_timeline tr.status', 'status[]' => scraper { | |
process 'img.profile-image', 'image' => '@src'; | |
process 'strong.enter a', 'home' => '@href'; | |
process 'strong.enter span', 'name' => '@title'; | |
process 'strong.enter span', 'user' => 'TEXT'; | |
process 'span.entry-body', 'body' => 'TEXT'; | |
process 'span.entry-body', 'status' => '@id'; | |
process 'span.time', 'time' => 'TEXT'; | |
}; | |
}; | |
$scraper->user_agent($ua); | |
my $res = $scraper->scrape($uri); | |
&detect_error($ua); | |
return $res; | |
} | |
sub notify { | |
my @entries = @_; | |
return unless (@entries); | |
my $app = 'ShovelX2Growl'; | |
my $growl = Growl::GNTP->new(AppName => $app); | |
my %pages = map { $_->{'page'} => 1 } (@entries); | |
my @events = map { { Name => $_ } } keys(%pages); | |
$growl->register(\@events); | |
foreach my $entry (@entries) { | |
my $notify = { | |
Event => $entry->{'page'} || '', | |
Title => sprintf('%s / %s', $entry->{'name'}, $entry->{'user'}), | |
Message => sprintf("%s\n(%s via %s)", $entry->{'body'}, $entry->{'time'}, $entry->{'title'}), | |
Icon => $entry->{'image'}->as_string | |
}; | |
$notify->{$_} = encode_utf8($notify->{$_}) for (keys(%$notify)); | |
$growl->notify(%$notify); | |
} | |
} | |
sub detect_error { | |
my ($ua) = @_; | |
if ($ua->content =~ /(<input [^<>]*name="error"[^<>]*>)/) { | |
my $input = $1; | |
my $error = ($input =~ /id="(.*?)"/) ? $1 : 'unknown_error'; | |
die(decode_utf8("[ERROR] $error". "\n")); | |
} | |
} | |
1; | |
__END__ | |
=head1 history | |
=over | |
=item ver.0.0.1 | |
Initial release. Status update notification available. | |
=item ver.0.0.2 | |
Direct message notification available. | |
=back |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment