Published on Blog Developer's Cookbook.
Printer friendly version of http://blog.bulknews.net/cookbook/blosxom/rss/rss2htmlmail.html.
RSS をメールで送信する (HTML メール編)
by miyagawa at Tue, 28 Oct 2003 00:07
RSS をメールで送信する では、RSS のエントリを普段使いなれた MUA (メーラ) で閲覧する方法を紹介しました。しかし RSS の description だけというのもちょっと味気ないものです。今回はさらに進んで、HTML メールを受信することによって、コンテンツをより見易い形で送信するように改良してみます。
HTML メールの送信
ネット歴が長い人ほど、HTML メールというと嫌な顔をするものですが、最近ではブロードバンドの普及が原因かどうかはわかりませんが、Amazon.com や Sony Style といったメールマガジンに見られるように、HTML メールによるリッチコンテンツの供給といった手法が割と一般的になってきたようです。
今回は、RSS によって受信したコンテンツを、HTML メール化するスクリプトを作成してみます。rss2email では、description 要素をテキストで出していただけですので、ちょっと読みづらいものでしたが、HTML にして出すことにより、メーラ内である程度エントリが完結して読めるようになり、より MUA を RSS Aggregator に近い使用感で扱うことができるようになります。
content モジュール
RSS 内から HTML の断片を抽出するには、RSS 1.0/2.0 の content モジュールを使用します。content モジュールの namespace は http://purl.org/rss/1.0/modules/content/ で、Blog のエントリの HTML コードをそのまま、content:encoded
要素に CDATA で挿入することができます。
<item rdf:about="http://example.org/item/">
<title>The Example Item</title>
<link>http://example.org/item/</link>
<content:encoded>
<![CDATA[<p>What a <em>beautiful</em> day!</p>]]>
</content:encoded>
</item>
これを利用することによって、RSS Aggregator がエントリの Permalink を取得することなく、個別 item のリッチ表示を行うことが可能になります(*1)。
サンプルコード
rss2email を改良して、content モジュールを使用している場合には HTML メールを送信するスクリプトは List 1 のようになります。rss2email との差分についてのみ、解説します。
$rss->add_module(
prefix => 'content',
uri => 'http://purl.org/rss/1.0/modules/content/',
);
XML::RSS の add_module
メソッドで、content モジュールの名前空間と prefix を指定します。これによって、各 item の content:encoded
要素が、$item->{content}->{encoded}
で取得できるようになります。
my $textpart = <<BODY;
New entry arrived for $rss->{channel}->{title}
$item->{link}
>> $item->{description}
BODY
;
my $htmlpart = $item->{content}->{encoded};
HTML メールは通常テキスト部と HTML 部が multipart/alternative な Content-Type で送信されます。ここでは item の description
からテキストパートを作成し、content:encoded
から HTML パートを作成しています。
my $mime = MIME::Lite->new(
From => $MailTo,
To => $MailTo,
Subject => encode('MIME-Header' => $item->{title}),
Type => 'multipart/alternative',
Datestamp => 0,
Date => HTTP::Date::time2str($epoch),
);
MIME::Lite オブジェクトを Content-Type: multipart/alternative で作成します。
$mime->attach(
Type => 'text/plain; charset=UTF-8',
Data => encode('UTF-8' => $textpart),
);
$mime->attach(
Type => 'text/html; charset=UTF-8',
Data => encode('UTF-8' => $htmlpart),
) if $htmlpart;
テキストパートと HTML パートを順に attach
します。どちらも charset=UTF-8 とし、UTF-8 に encode して添付しています。
実行例
% ./rss2htmlmail.pl http://blog.bulknews.net/cookbook/blosxom/index.rss10
この Blog の RSS/1.0 Feed を引数にして実行します。各エントリの content:encoded
要素が HTML メールになって送信されてくることが確認できます。
MIME::Lite::HTML
このスクリプトでは、content:encoded
要素内に img タグで画像が埋め込まれている場合などは対応できません。CPAN モジュールの MIME::Lite::HTML を使用すると、これらの画像などをすべて multipart/alternative 内に埋めこんだ HTML メールを送信することができます(*2)。
See Also
Listings
#!/usr/local/bin/perl -w
# rss2htmlmail - send HTML email from RSS feed
use strict;
use Digest::MD5 qw(md5_hex);
use Encode;
use File::stat;
use HTTP::Date;
use LWP::Simple;
use MIME::Lite;
use XML::RSS;
our $CacheDir = "rsscache";
our $MailTo = 'you@example.com';
mkdir $CacheDir, 0755 unless -e $CacheDir;
my $rss = shift or die "Usage: rss2htmlmail URL\n";
my $digest = md5_hex($rss);
my $cache = "$CacheDir/$digest.xml";
my $lastmod = -e $cache ? stat($cache)->mtime : 0;
my $status = LWP::Simple::mirror($rss, $cache);
if ($status == RC_NOT_MODIFIED) {
warn "$rss not modified\n";
} elsif (is_error($status)) {
die "$rss not found!\n";
} elsif (is_success($status)) {
proc_rss($cache, $lastmod);
}
sub proc_rss {
my($xml, $lastmod) = @_;
my $rss = XML::RSS->new();
$rss->add_module(
prefix => 'content',
uri => 'http://purl.org/rss/1.0/modules/content/',
);
$rss->parsefile($xml);
for my $item (@{$rss->{items}}) {
my $dc_date = $item->{dc}->{date}
or die "RSS should have dc:date element";
my $epoch = HTTP::Date::str2time($dc_date);
if ($epoch > $lastmod) {
do_sendmail($rss, $item, $epoch);
} else {
last;
}
}
}
sub do_sendmail {
my($rss, $item, $epoch) = @_;
my $textpart = <<BODY;
New entry arrived for $rss->{channel}->{title}
$item->{link}
>> $item->{description}
BODY
;
my $htmlpart = $item->{content}->{encoded};
my $mime = MIME::Lite->new(
From => $MailTo,
To => $MailTo,
Subject => encode('MIME-Header' => $item->{title}),
Type => 'multipart/alternative',
Datestamp => 0,
Date => HTTP::Date::time2str($epoch),
);
$mime->attach(
Type => 'text/plain; charset=UTF-8',
Data => encode('UTF-8' => $textpart),
);
$mime->attach(
Type => 'text/html; charset=UTF-8',
Data => encode('UTF-8' => $htmlpart),
) if $htmlpart;
$mime->send();
}
Copyright©2002-2003 Tatsuhiko Miyagawa