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

List 1: rss2htmlmail.pl
#!/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();
}
*1) Movable Type で content:encoded を出力するには NDO::Weblog: RSSリーダーで段落整形させて表示させる方法を参照してください。blosxom で content:encoded を出力するには rss10 plugin を使用します。
*2) HTML のエンコーディングを事前に設定する必要があり、ちょっと使いにくいのが難点です。

Copyright©2002-2003 Tatsuhiko Miyagawa