root/branches/feature-server/plagger/lib/Plagger/Plugin/Publish/Feed.pm

Revision 856 (checked in by miyagawa, 3 years ago)

merge from trunk to plagger-server for Enclosures support and such. Sorry for the big commit

Line 
1 package Plagger::Plugin::Publish::Feed;
2
3 use strict;
4 use base qw( Plagger::Plugin );
5
6 our $VERSION = 0.01;
7
8 use XML::Feed;
9 use XML::Feed::Entry;
10 use XML::RSS::LibXML;
11 use File::Spec;
12
13 # xxx ugh
14 $XML::Feed::RSS::PREFERRED_PARSER =
15     $XML::RSS::LibXML::VERSION >= 0.16 ? "XML::RSS::LibXML" : "XML::RSS";
16
17 sub register {
18     my($self, $context) = @_;
19     $context->register_hook(
20         $self,
21         'publish.feed' => \&publish_feed,
22     );
23     $self->init_feed($context);
24 }
25
26 sub init_feed {
27     my($self, $context) = @_;
28
29     # check dir
30     my $dir = $self->conf->{dir};
31     unless (-e $dir && -d _) {
32         mkdir $dir, 0755 or $context->error("mkdir $dir: $!");
33     }
34 }
35
36 sub publish_feed {
37     my($self, $context, $args) = @_;
38
39     my $conf = $self->conf;
40     my $f = $args->{feed};
41     my $feed_format = $conf->{format} || 'Atom';
42
43     # generate feed
44     my $feed = XML::Feed->new($feed_format);
45     $feed->title($f->title);
46     $feed->link($f->link);
47     $feed->modified(Plagger::Date->now);
48     $feed->generator("Plagger/$Plagger::VERSION");
49
50     # add entry
51     for my $e ($f->entries) {
52         my $entry = XML::Feed::Entry->new($feed_format);
53         $entry->title($e->title);
54         $entry->link($e->link);
55         $entry->summary($e->body_text);
56         $entry->content($e->body);
57         $entry->category(join(' ', @{$e->tags}));
58         $entry->issued($e->date) if $e->date;
59         $entry->author($e->author);
60
61         if ($e->has_enclosure) {
62             # RSS 2.0 by spec doesn't allow multiple enclosures
63             my @enclosures = $feed_format eq 'RSS' ? ($e->enclosures->[0]) : $e->enclosures;
64             for my $enclosure (grep { defined $_->url && !$_->is_inline } @enclosures) {
65                 $entry->add_enclosure({
66                     url    => $enclosure->url,
67                     length => $enclosure->length,
68                     type   => $enclosure->type,
69                 });
70             }
71         }
72
73         $feed->add_entry($entry);
74     }
75
76     # generate file path
77     my $filepath = File::Spec->catfile($self->conf->{dir}, $self->gen_filename($f));
78
79     $context->log(info => "save feed for " . $f->link . " to $filepath");
80
81     my $xml = $feed->as_xml;
82     utf8::decode($xml) unless utf8::is_utf8($xml);
83     open my $output, ">:utf8", $filepath or $context->error("$filepath: $!");
84     print $output $xml;
85     close $output;
86 }
87
88 my %formats = (
89     'u' => sub { my $s = $_[0]->url;  $s =~ s!^https?://!!; $s },
90     'l' => sub { my $s = $_[0]->link; $s =~ s!^https?://!!; $s },
91     't' => sub { $_[0]->title },
92     'i' => sub { $_[0]->id },
93 );
94
95 my $format_re = qr/%(u|l|t|i)/;
96
97 sub gen_filename {
98     my($self, $feed) = @_;
99
100     my $file = $self->conf->{filename} ||
101         '%i.' . ($self->conf->{format} eq 'RSS' ? 'rss' : 'atom');
102     $file =~ s{$format_re}{
103         $self->safe_filename($formats{$1}->($feed))
104     }egx;
105     $file;
106 }
107
108 sub safe_filename {
109     my($self, $path) = @_;
110     $path =~ s![^\w\s]+!_!g;
111     $path =~ s!\s+!_!g;
112     $path;
113 }
114
115 # XXX okay, this is a hack until XML::Feed is updated
116 *XML::Feed::Entry::Atom::add_enclosure = sub {
117     my($entry, $enclosure) = @_;
118     my $link = XML::Atom::Link->new;
119     $link->rel('enclosure');
120     $link->type($enclosure->{type});
121     $link->href($enclosure->{url});
122     $link->length($enclosure->{length});
123     $entry->{entry}->add_link($link);
124 };
125
126 *XML::Feed::Entry::RSS::add_enclosure = sub {
127     my($entry, $enclosure) = @_;
128     $entry->{entry}->{enclosure} = {
129         url    => $enclosure->{url},
130         type   => $enclosure->{type},
131         length => $enclosure->{length},
132     };
133 };
134
135
136 1;
137
138 __END__
139
140 =head1
141
142 Plagger::Plugin::Publish::Feed - republish RSS/Atom feeds
143
144 =head1 SYNOPSYS
145
146   - module: Publish::Feed
147     config:
148       format: RSS
149       dir: /home/yoshiki/plagger/feed
150       filename: my_%t.rss
151
152 =head1 CONFIG
153
154 =head2 format
155
156 Specify the format of feed. C<Plagger::Plugin::Publish::Feed> supports
157 the following syndication feed formats:
158
159 =over 4
160
161 =item * Atom (default)
162
163 =item * RSS
164
165 =back
166
167 =head2 dir
168
169 Directory to save feed files in.
170
171 =head2 filename
172
173 Filename to be used to create feed files. It defaults to C<%i.rss> for
174 RSS and C<%i.atom> for Atom feed. It supports the following format
175 like printf():
176
177 =over 4
178
179 =item * %u url
180
181 =item * %l link
182
183 =item * %t title
184
185 =item * %i id
186
187 =back
188
189 =head1 AUTHOR
190
191 Yoshiki KURIHARA
192
193 Tatsuhiko Miyagawa
194
195 Gosuke Miyashita
196
197 =head1 SEE ALSO
198
199 L<Plagger>, L<XML::Feed>
200
201 =cut
Note: See TracBrowser for help on using the browser.