新着情報をgrowlに送る、Windows用の簡単なフィード(RSS、ATOMフィード)クローラ。
Windows用の簡単なフィード(RSS、ATOMフィード)クローラです。新着情報があればGrowlに送ります。
以下のような特徴をもっています。
feed2growlは「Windows上でフィードの更新をポップアップ通知させたい」という個人的なニーズで書かれました。このために、Windowsのタスクに登録でき、GUIやコマンドプロンプトなしで動作し、新着情報はGrowl(やそのクローンであるGrowl for WindowsやWhineに通知させたいと思いました。コマンドプロンプトを出させないためには、PAR::PackerでGUIを持たないGUIアプリケーションとしてexeファイル化する方法が簡単でした。ただしexeファイル化する以上、設定情報はすべて外出しにする必要がありました。タスクマネージャに登録して以降は動作を意識したくない、と思ったら、プロキシ情報はInternet Explorerから取得して来るのが最も便利でした。
こうした理由で、一般的ではない特徴を持ったfeed2growl.pl(100行ちょっとのスクリプト)を書きました。
zipファイルには以下が入っています。
| ファイル名 | 内容 |
| feed2growl.pl | Perlスクリプト本体です。 |
| config.yml.sample | 設定ファイルのサンプルです。config.ymlなどにコピーして使います。 |
| feed2growl.exe | exe化されたfeed2growlです。 |
exeファイルは、以下のようにすれば実行できます。
feed2growl
Perlスクリプトは、以下のようにすれば実行できます。Perlスクリプトの実行時には、モジュールを追加インストールする必要があるかもしれません。
perl feed2growl.pl
上記のように実行すると、カレントディレクトリのconfig.ymlを設定ファイルとして読み込みます。他のファイルを読み込む時は、-cオプションで指定します。
feed2growl -c myconfig.yml
Windowsのタスクに登録する時は、コマンドプロンプトのような画面が出ないexeファイルを使うと良いと思います。タスクスケジューラを使い、実行ディレクトリにfeed2growl.exeがあるディレクトリ、実行コマンドを上記のようにすればよいでしょう。
スクリプトを変更し、exeファイルを作り直すには、以下のようにします。ただし、Perlスクリプトとして実行できる環境、PARでPerlスクリプトをexe化できる環境が整っていることが必要です。
pp --gui feed2growl.pl -o feed2growl.exe
use strict;
use warnings;
use Encode;
use LWP::UserAgent::ProxyAny;
use Net::GrowlClient;
use Win32::TieRegistry;
use Win32::Registry;
use XML::FeedPP;
use YAML;
# initialize
my %args = @ARGV;
my $yaml = exists($args{'-c'}) ? $args{'-c'} : 'config.yml';
my $config = load_config($yaml);
my $ua = LWP::UserAgent::ProxyAny->new;
$ua->env_proxy;
if ($ua->proxy('http') && $config->{'proxy_auth'}) {
foreach my $auth (@{$config->{'proxy_auth'}}) {
next if ($ua->proxy('http') ne $auth->{'proxy'});
my $header = $ua->default_headers;
$header->proxy_authorization_basic($auth->{'username'}, $auth->{'password'});
$ua->default_headers($header);
last;
}
}
$XML::FeedPP::TREEPP_OPTIONS->{'lwp_useragent'} = $ua;
my $growl_opt = $config->{'growl'} || {};
my $growl = Net::GrowlClient->init(%$growl_opt);
# process
foreach my $group (@{$config->{'group'}}) {
next unless ($group->{'feed'});
my $title = $group->{'title'};
my $limit = $group->{'limit'};
my @items = ();
my @feeds = @{$group->{'feed'}};
foreach my $feed (@feeds) {
my %recent = map { $_ => 1 } @{$feed->{'recent'}};
my $url = $feed->{'url'} || next;
my $fpp = eval { XML::FeedPP->new($url) };
next if ($@);
my @feed_items = $fpp->get_item();
@feed_items = sort {$b->pubDate() gt $a->pubDate()} @feed_items;
@feed_items = splice(@feed_items, 0, $limit) if ($limit);
my @recents = map { $_->link() } @feed_items;
$feed->{'recent'} = [ splice(@recents, 0, 5) ];
$feed->{'title'} = $fpp->title();
$title = $feed->{'title'} unless ($feed->{'title'});
foreach my $item (@feed_items) {
last if ($recent{$item->link()});
$item->{'feed'} = $feed;
push(@items, $item);
}
}
@items = sort {$b->pubDate() gt $a->pubDate()} @items;
@items = splice(@items, 0, $limit) if ($limit);
foreach my $item (@items) {
my $message = sprintf("%s\n(%s at %s)", $item->description, $item->pubDate, $item->{'feed'}->{'title'});
$growl->notify(
application => $title,
title => decode_utf8($item->title),
message => decode_utf8($message)
);
sleep(1);
}
}
# finish
YAML::DumpFile($yaml, $config);
exit();
sub load_config {
my $yaml = shift;
die sprintf("Config file is not specified.") unless (length($yaml));
die sprintf("File '%s' is not exists.", $yaml) unless (-f $yaml);
my $config = YAML::LoadFile($yaml);
die sprintf("File '%s' is not valid.") unless ($config);
my @groups = ();
push(@groups, @{delete($config->{'group'})}) if (ref($config->{'group'}) eq 'ARRAY');
push(@groups, delete($config->{'group'})) if ($config->{'group'});
push(@groups, delete($config->{'feed'})) if ($config->{'feed'});
foreach my $group (@groups) {
$group = { 'feed' => $group } if (ref($group) ne 'HASH');
$group->{'title'} = '' unless (defined($group->{'title'}));
$group->{'limit'} = 0 unless (defined($group->{'limit'}));
$group->{'feed'} = [$group->{'feed'}] if (ref($group->{'feed'}) ne 'ARRAY');
foreach my $feed (@{$group->{'feed'}}) {
$feed = {'url' => $feed} if (not ref($feed));
$feed->{'title'} = '' unless (defined($feed->{'title'}));
$feed->{'recent'} = [$feed->{'recent'}] if ($feed->{'recent'} && not ref($feed->{'recent'}));
$feed->{'recent'} = [] unless (UNIVERSAL::isa($feed->{'recent'}, 'ARRAY'));
}
@{$group->{'feed'}} = grep { $_->{'url'} } @{$group->{'feed'}};
}
$config->{'group'} = \@groups;
my @proxy_auths = ();
push(@proxy_auths, @{delete($config->{'proxy_auth'})}) if (ref($config->{'proxy_auth'}) eq 'ARRAY');
push(@proxy_auths, delete($config->{'proxy_auth'})) if ($config->{'proxy_auth'});
if (@proxy_auths) {
foreach my $proxy_auth (@proxy_auths) {
$proxy_auth = {'proxy' => $proxy_auth} if (not ref($proxy_auth));
$proxy_auth->{'username'} = '' unless (defined($proxy_auth->{'username'}));
$proxy_auth->{'password'} = '' unless (defined($proxy_auth->{'password'}));
}
$config->{'proxy_auth'} = \@proxy_auths;
}
return $config;
}
---
group:
- feed:
- http://tsukamoto.tumblr.com/rss
- http://walrus.vox.com/library/posts/atom.xml
limit: 5
title: Makio Tsukamot Blog & Reblogs
growl:
CLIENT_TYPE_NOTIFICATION: 1
CLIENT_PASSWORD: ''
CLIENT_SKIP_REGISTER: 1
proxy_auth:
- proxy: http://myproxy.example.com:8080/
username: alibaba
password: opensesami