SweetPotato::Plagger このページをアンテナに追加 RSSフィード

2007-10-29

[] tumblrのAPIを叩いてCustomFeed::Script用にフィード化するPerlスクリプト 05:24  tumblrのAPIを叩いてCustomFeed::Script用にフィード化するPerlスクリプト - SweetPotato::Plagger を含むブックマーク はてなブックマーク -  tumblrのAPIを叩いてCustomFeed::Script用にフィード化するPerlスクリプト - SweetPotato::Plagger  tumblrのAPIを叩いてCustomFeed::Script用にフィード化するPerlスクリプト - SweetPotato::Plagger のブックマークコメント

への反応:

API用意されてるのになんでRSSたたくんだろう。

それplaggerで、やることじゃないんじゃいかな。 - sasezakiの日記 - たんぶら部 - Tumblove -

が至極ごもっともだと思ったので,APIを叩く版を作った。

assets/plugins/CustomFeed-Script/tumblr.pl

引数は3つ。

  1. ユーザ名 or URL
    • 例:sweetpotato,http://sweetpotato.tumblr.com/,http://tumblelog.marco.org/
  2. エントリ数(省略可能)
    • 正の整数を指定する。省略時は20が使用される。10単位での指定が望ましい。エントリは10件ずつAPIを通じて取得し,取得した分は無駄なく結果として返すので,指定した数以上のエントリが返される場合もある。
  3. タイプ(省略可能)
    • regular,quote,photo,link,conversation,videoのいずれかを指定することで,そのタイプの投稿のみを取得する。これらはそれぞれtumblrのDashboardの「Add a new...」の左から順に対応している。省略時は全てのタイプの投稿を取得する。
#!/usr/bin/perl
# author: SweetPotato
use strict;
use warnings;

use DateTime::Format::HTTP;
use FindBin;
use File::Spec::Functions qw/catfile/;
use HTML::Template;
use LWP::UserAgent;
use List::MoreUtils qw/any/;
use URI;
use Switch;
use XML::TreePP;
use YAML;

my $user = shift or die "specify user or url";
$user = "http://$user.tumblr.com" unless $user =~ m!^http://!;
die "bad url: $user" unless $user =~ m!^http://[^/]+/?$!;
$user =~ s!/$!!;

my $num = @ARGV ? shift : 20;
die "specify positive number: $num" unless $num > 0;

my $type = @ARGV ? shift : '';
if ($type ne '') {
    any { $type eq $_ } qw/regular quote photo link conversation video/
    or die "type must be one of regular, quote, photo, link, conversation, or video: $type";
}

my $ua = LWP::UserAgent->new;

my ($title, $timezone, @entry);

for (my $start = 0; $start < $num; $start += 10) {
    my $url = URI->new("$user/api/read");
       $url->query_form(start => $start, num => 10, type => $type);
    my $res = $ua->get($url);
    unless ($res->is_success) {
        warn "GET $url failed: " . $res->status_line;
        last;
    }

    my $parser = XML::TreePP->new;
       $parser->set(utf8_flag => 1);
    my $tree = $parser->parse($res->content);

    $title = $tree->{tumblr}->{tumblelog}->{'-title'}
        unless defined $title;
    $timezone = $tree->{tumblr}->{tumblelog}->{'-timezone'}
        unless defined $timezone;

    for (@{ $tree->{tumblr}->{posts}->{post} || [] }) {
        # pre-process for template
        if ($_->{'-type'} eq 'photo') {
            $_->{'photo-url'} = $_->{'photo-url'}->[0]->{'#text'};
        }
        if ($_->{'-type'} eq 'conversation') {
            $_->{'conversation-text'} =~ s!\r\n!<br />!g;
        }

        push @entry, +{
            link  => $_->{'-url'},
            date  => &mk_date($_->{'-date'}, $timezone),
            title => &mk_title($_),
            body  => &mk_body($_),
        };
    }

    # fetch next ?
    last if $tree->{tumblr}->{posts}->{'-start'} < $start;
}

binmode STDOUT, ":utf8";
print YAML::Dump +{
    title => $title,
    link  => "$user/",
    entry => \@entry,
};

sub mk_title {
    my $post = shift;

    switch ($post->{'-type'}) {
        case 'regular' { return $post->{'regular-title'} || 'regular post' }
        case 'quote' { return 'quote post' }
        case 'photo' { return 'photo post' }
        case 'link' { return $post->{'link-text'} || 'link post' }
        case 'conversation' { return $post->{'conversation-title'} || 'conversation post' }
        case 'video' { return 'video post' }
        else { return }
    }
    return;
}

sub mk_date {
    my ($date, $timezone) = @_;

    DateTime::Format::HTTP->format_datetime(
        DateTime::Format::HTTP->parse_datetime($date, $timezone)
    );
}

sub mk_body {
    my $post = shift;

    my $t = HTML::Template->new(
        filename => catfile($FindBin::Bin, 'tumblr.t'),
        die_on_bad_params => 0,
    );
    $t->param($post);
    return $t->output;
}

assets/plugins/CustomFeed-Script/tumblr.t

HTML::Template用テンプレート。

<TMPL_IF NAME="regular-body">
    <TMPL_VAR NAME="regular-body">
</TMPL_IF>

<TMPL_IF NAME="photo-url">
    <img src="<TMPL_VAR ESCAPE=HTML NAME="photo-url">" />
    <TMPL_IF NAME="photo-caption">
        <br /><br /><TMPL_VAR NAME="photo-caption">
    </TMPL_IF>
</TMPL_IF>

<TMPL_IF NAME="quote-text">
    &#8220;<TMPL_VAR NAME="quote-text">&#8221;
    <TMPL_IF NAME="quote-source">
        <br /><br />&mdash; <TMPL_VAR NAME="quote-source">
    </TMPL_IF>
</TMPL_IF>

<TMPL_IF NAME="link-url">
    <a href="<TMPL_VAR ESCAPE=HTML NAME="link-url">">
        <TMPL_IF NAME="link-text">
            <TMPL_VAR ESCAPE=HTML NAME="link-text">
        <TMPL_ELSE>
            <TMPL_VAR ESCAPE=HTML NAME="link-url">
        </TMPL_IF>
    </a>
    <TMPL_IF NAME="link-description">
        <br /><br /><TMPL_VAR NAME="link-description">
    </TMPL_IF>
</TMPL_IF>

<TMPL_IF NAME="conversation-text">
    <TMPL_VAR NAME="conversation-text">
</TMPL_IF>

<TMPL_IF NAME="video-player">
    <TMPL_VAR NAME="video-player">
    <TMPL_IF NAME="video-caption">
        <br /><br /><TMPL_VAR NAME="video-caption">
    </TMPL_IF>
</TMPL_IF>

config.yaml

エロい例。

plugins:
  - module: Subscription::Config
    config:
      feed:
        - url: 'script:/path/to/tumblr.pl moeura 100 photo'
        - url: 'script:/path/to/tumblr.pl detailura 100 photo'
        - url: 'script:/path/to/tumblr.pl detailurac 100 photo'

  - module: CustomFeed::Script

  - module: Filter::FindEnclosures
  - module: Filter::FetchEnclosure
    config:
      dir: /path/to/enclosure
      fake_referer: 1

ぱふぱふ2008/05/19 14:23ntifier って何ですか?

SweetPotatoSweetPotato2008/05/19 20:06ぱふさん,ご指摘ありがとうございます。Perlコードの貼り付けがうまくいっていませんでした。該当箇所を修正しました。
「ntifier」,私も確認しましたが,何でしょうね……。コード中にはそのような部分文字列すら存在しないミステリー……。

ぱふぱふ2008/05/19 22:05早速の対応ありがとうございます。
コードを見て勉強させていただきます。

トラックバック - http://plagger.g.hatena.ne.jp/SweetPotato/20071029