root/trunk/plagger/lib/Plagger/Plugin/Publish/Feed.pm

Revision 1897 (checked in by miyagawa, 14 years ago)

Added 'taguri_base' config to Publish::Feed, not to use plagger.org as a default.
updated Bundle::Planet to pass the hostname to taguri_base.
Fixes #455

Line 
1 package Plagger::Plugin::Publish::Feed;
2
3 use strict;
4 use base qw( Plagger::Plugin );
5
6 use XML::Feed;
7 use XML::Feed::Entry;
8 use XML::RSS::LibXML;
9 use File::Spec;
10
11 $XML::Feed::RSS::PREFERRED_PARSER = "XML::RSS::LibXML";
12
13 sub register {
14     my($self, $context) = @_;
15     $context->autoload_plugin({ module => 'Filter::FloatingDateTime' });
16     $context->register_hook(
17         $self,
18         'publish.feed' => \&publish_feed,
19         'plugin.init'  => \&plugin_init,
20     );
21 }
22
23 sub plugin_init {
24     my($self, $context, $args) = @_;
25
26     # check dir
27     my $dir = $self->conf->{dir};
28     unless (-e $dir && -d _) {
29         mkdir $dir, 0755 or $context->error("mkdir $dir: $!");
30     }
31
32     unless (exists $self->conf->{full_content}) {
33         $self->conf->{full_content} = 1;
34     }
35 }
36
37 sub publish_feed {
38     my($self, $context, $args) = @_;
39
40     my $conf = $self->conf;
41     my $f = $args->{feed};
42     my $feed_format = $conf->{format} || 'Atom';
43
44     # generate feed
45     my $feed = XML::Feed->new($feed_format);
46     $feed->title($f->title);
47     $feed->link($f->link);
48     $feed->modified(Plagger::Date->now);
49     $feed->generator("Plagger/$Plagger::VERSION");
50     $feed->description($f->description || '');
51     $feed->author( $self->make_author($f->author, $feed_format) )
52         if $f->primary_author;
53
54     my $taguri_base = $self->conf->{taguri_base} || do {
55         require Sys::Hostname;
56         Sys::Hostname::hostname();
57     };
58
59     if ($feed_format eq 'Atom') {
60         $feed->{atom}->id("tag:$taguri_base,2006:" . $f->id); # XXX what if id is empty?
61     }
62
63     # add entry
64     for my $e ($f->entries) {
65         my $entry = XML::Feed::Entry->new($feed_format);
66         $entry->title($e->title);
67         $entry->link($e->permalink);
68         $entry->summary($e->body_text) if defined $e->body;
69
70         # hack to bypass XML::Feed Atom 0.3 crufts (type="text/html")
71         if ($self->conf->{full_content} && defined $e->body) {
72             if ($feed_format eq 'RSS') {
73                 $entry->content($e->body);
74             } else {
75                 $entry->{entry}->content($e->body->utf8);
76             }
77         }
78
79         $entry->category(join(' ', @{$e->tags})) if @{$e->tags};
80         $entry->issued($e->date)   if $e->date;
81         $entry->modified($e->date) if $e->date;
82
83         if ($feed_format eq 'RSS') {
84             my $author = 'nobody@example.com';
85             $author .= ' (' . $e->author . ')' if $e->author;
86             $entry->author($author);
87         } else {
88             unless ($feed->author) {
89                 $entry->author($e->author || 'nobody');
90             }
91         }
92
93         $entry->id("tag:$taguri_base,2006:" . $e->id);
94
95         if ($e->has_enclosure) {
96             for my $enclosure (grep { defined $_->url && !$_->is_inline } $e->enclosures) {
97                 $entry->add_enclosure({
98                     url    => $enclosure->url,
99                     length => $enclosure->length,
100                     type   => $enclosure->type,
101                 });
102
103                 # RSS 2.0 by spec doesn't allow multiple enclosures
104                 last if $feed_format eq 'RSS';
105             }
106         }
107
108         $feed->add_entry($entry);
109     }
110
111     # generate file path
112     my $tmpl = '%i.' . ($feed_format eq 'RSS' ? 'rss' : 'atom');
113     my $file = Plagger::Util::filename_for($f, $self->conf->{filename} || $tmpl);
114     my $filepath = File::Spec->catfile($self->conf->{dir}, $file);
115
116     $context->log(info => "save feed for " . $f->link . " to $filepath");
117
118     my $xml = $feed->as_xml;
119     utf8::decode($xml) unless utf8::is_utf8($xml);
120     open my $output, ">:utf8", $filepath or $context->error("$filepath: $!");
121     print $output $xml;
122     close $output;
123 }
124
125 sub make_author {
126     my($self, $author, $feed_format) = @_;
127
128     if ($feed_format eq 'RSS') {
129         my $rfc822 = 'nobody@example.com';
130         $rfc822 .= ' (' . $author . ')' if $author;
131         return $rfc822;
132     } else {
133         return defined $author ? $author : 'nobody';
134     }
135 }
136
137 # XXX okay, this is a hack until XML::Feed is updated
138 *XML::Feed::Entry::Atom::add_enclosure = sub {
139     my($entry, $enclosure) = @_;
140     my $link = XML::Atom::Link->new;
141     $link->rel('enclosure');
142     $link->type($enclosure->{type});
143     $link->href($enclosure->{url});
144     $link->length($enclosure->{length});
145     $entry->{entry}->add_link($link);
146 };
147
148 *XML::Feed::Entry::RSS::add_enclosure = sub {
149     my($entry, $enclosure) = @_;
150     $entry->{entry}->{enclosure} = {
151         url    => $enclosure->{url},
152         type   => $enclosure->{type},
153         length => $enclosure->{length},
154     };
155 };
156
157
158 1;
159
160 __END__
161
162 =head1
163
164 Plagger::Plugin::Publish::Feed - republish RSS/Atom feeds
165
166 =head1 SYNOPSIS
167
168   - module: Publish::Feed
169     config:
170       format: RSS
171       dir: /home/yoshiki/plagger/feed
172       filename: my_%t.rss
173
174 =head1 CONFIG
175
176 =over 4
177
178 =item format
179
180 Specify the format of feed. C<Plagger::Plugin::Publish::Feed> supports
181 the following syndication feed formats:
182
183 =over 8
184
185 =item Atom (default)
186
187 =item RSS
188
189 =back
190
191 =item dir
192
193 Directory to save feed files in.
194
195 =item filename
196
197 Filename to be used to create feed files. It defaults to C<%i.rss> for
198 RSS and C<%i.atom> for Atom feed. It supports the following format
199 like printf():
200
201 =over 8
202
203 =item %u url
204
205 =item %l link
206
207 =item %t title
208
209 =item %i id
210
211 =back
212
213 =item full_content
214
215 Whether to publish full content feed. Defaults to 1.
216
217 =item taguri_base
218
219 Domain name to use with Tag URI base for Atom feed IDs. If it's not
220 set, the domain is grabbed using Sys::Hostname module. Optional.
221
222 =back
223
224 =head1 AUTHOR
225
226 Tatsuhiko Miyagawa
227
228 =head1 CONTRIBUTORS
229
230 Yoshiki Kurihara
231
232 Gosuke Miyashita
233
234 =head1 SEE ALSO
235
236 L<Plagger>, L<XML::Feed>
237
238 =cut
Note: See TracBrowser for help on using the browser.