root/trunk/plagger/lib/Plagger/Plugin/Subscription/Bloglines.pm

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

try Bloglines with mark_read=0 first, to avoid data loss due to bad XML.
Fixes #215

  • Property svn:keywords set to Id Revision
Line 
1 pakage Plagger::Plugin::Subscription::Bloglines;
2 use strict;
3 use base qw( Plagger::Plugin );
4
5 our $VERSION = '0.10';
6 use WebService::Bloglines;
7
8 sub plugin_id {
9     my $self = shift;
10     $self->class_id . '-' . $self->conf->{username};
11 }
12
13 sub register {
14     my($self, $context) = @_;
15
16     $self->init_bloglines();
17
18     if ($self->conf->{no_sync_api}) {
19         $context->register_hook(
20             $self,
21             'subscription.load' => \&getsubs,
22         );
23     } else {
24         $context->register_hook(
25             $self,
26             'subscription.load' => \&notifier,
27         );
28     }
29 }
30
31 sub getsubs {
32     my($self, $context) = @_;
33     my $subscription = $self->{bloglines}->listsubs();
34
35     for my $folder ($subscription->folders, 0) {
36         my $subid = $folder ? $folder->{BloglinesSubId} : 0;
37         my $title = $folder ? $folder->{title} : undef;
38         $self->add_subscription($subscription, $subid, $title);
39     }
40 }
41
42 sub add_subscription {
43     my($self, $subscription, $subid, $title) = @_;
44
45     my @feeds = $subscription->feeds_in_folder($subid);
46     for my $source (@feeds) {
47         my $feed = Plagger::Feed->new;
48         $feed->title($source->{title});
49         $feed->link($source->{htmlUrl});
50         $feed->url($source->{xmlUrl} );
51         $feed->tags([ $title ]) if $title;
52         Plagger->context->subscription->add($feed);
53     }
54 }
55
56 sub init_bloglines {
57     my $self = shift;
58     $self->{bloglines} = WebService::Bloglines->new(
59         username => $self->conf->{username},
60         password => $self->conf->{password},
61         use_liberal => 1,
62     );
63 }
64
65 sub notifier {
66     my($self, $context) = @_;
67
68     my $count = $self->{bloglines}->notify();
69     $context->log(info => "You have $count unread item(s) on Bloglines.");
70     if ($count) {
71         my $feed = Plagger::Feed->new;
72         $feed->aggregator(sub { $self->sync(@_) });
73         $context->subscription->add($feed);
74
75         if ($self->conf->{fetch_meta}) {
76             $self->{bloglines_meta} = $self->cache->get_callback(
77                 'listsubs_meta',
78                 sub { $self->fetch_meta($context) },
79                 '1 day',
80             );
81         }
82     }
83 }
84
85 sub fetch_meta {
86     my($self, $context) = @_;
87
88     $self->{folders} = {};
89     $context->log(info => "call Bloglines listsubs API to get folder structure");
90
91     my $subscription = $self->{bloglines}->listsubs();
92
93     my $meta;
94     for my $folder ($subscription->folders, 0) {
95         my $subid = ref $folder ? $folder->{BloglinesSubId} : 0;
96         my @feeds = $subscription->feeds_in_folder($subid);
97         for my $feed (@feeds) {
98             $meta->{$feed->{htmlUrl}} = {
99                 folder => $folder ? $folder->{title} : undef,
100                 xmlUrl => $feed->{xmlUrl},
101                 subid  => $feed->{BloglinesSubId},
102             };
103         }
104     }
105
106     $meta;
107 }
108
109 sub sync {
110     my($self, $context, $args) = @_;
111
112     my $mark_read = $self->conf->{mark_read};
113        $mark_read = 1 unless defined $mark_read;
114
115     my @updates;
116
117     # catch bad XML feed by Bloglines
118     eval {
119         @updates = $self->{bloglines}->getitems(0, 0);
120     };
121
122     if ($@) {
123         $context->log(warn => "Bloglines Sync API returned bad XML. fallbacks to loop mode");
124         my @feeds = $self->{bloglines}->listsubs()->feeds;
125         for my $feed (@feeds) {
126             if ($feed->{BloglinesUnread}) {
127                 $context->log(debug => "Fetch $feed->{BloglinesSubId}");
128                 push @updates, eval { $self->{bloglines}->getitems($feed->{BloglinesSubId}, $mark_read) };
129             }
130         }
131     } elsif ($mark_read) {
132         # no error found with XML ... call the API again to mark read
133         eval {
134             @updates = $self->{bloglines}->getitems(0, $mark_read);
135         };
136     }
137
138     $context->log(info => scalar(@updates) . " feed(s) updated.");
139
140     for my $update (@updates) {
141         my $source = $update->feed;
142
143         my $feed = Plagger::Feed->new;
144         $feed->type('bloglines');
145         $feed->title($source->{title});
146         $feed->link($source->{link});
147         $feed->image($source->{image});
148         $feed->description($source->{description});
149         $feed->language($source->{language});
150         $feed->author($source->{webmaster});
151         $feed->meta->{bloglines_id} = $source->{bloglines}->{siteid};
152
153         # under fetch_pfolders option, set folder as tags to feeds
154         if (my $meta = $self->{bloglines_meta}->{$feed->link}) {
155             $feed->tags([ $meta->{folder} ]) if $meta->{folder};
156             $feed->url($meta->{xmlUrl});
157             $feed->meta->{bloglines_subid} = $meta->{subid};
158         }
159
160         $feed->source_xml($update->{_xml});
161
162         for my $item ( $update->items ) {
163             my $entry = Plagger::Entry->new;
164
165             $entry->title($item->{title});
166             $entry->author($item->{dc}->{creator});
167             $entry->tags([ $item->{dc}->{subject} ])
168                 if $item->{dc}->{subject};
169             $entry->date( Plagger::Date->parse('Mail', $item->{pubDate}) );
170             $entry->link($item->{link});
171
172             if ($item->{guid}) {
173                 my $is_permalink = eval { $item->{guid}->{isPermaLink}  } ||
174                     'false';
175                 my $guid_url     = "$item->{guid}"; # stringify MagicElement
176                 $entry->permalink($guid_url)
177                     if $guid_url =~ m!^https?://! && $is_permalink eq 'true';
178             }
179
180             $entry->feed_link($feed->link);
181             $entry->id($item->{guid});
182             $entry->body($item->{description});
183
184             if ($item->{enclosure}) {
185                 my $enclosure = Plagger::Enclosure->new;
186                 $enclosure->url( URI->new($item->{enclosure}->{url}) );
187                 $enclosure->length($item->{enclosure}->{length});
188                 $enclosure->auto_set_type($item->{enclosure}->{type});
189                 $entry->add_enclosure($enclosure);
190             }
191
192             $feed->add_entry($entry);
193         }
194
195         $context->update->add($feed);
196     }
197 }
198
199 1;
200
201 __END__
202
203 =head1 NAME
204
205 Plagger::Plugin::Subscription::Bloglines - Bloglines Subscription
206
207 =head1 SYNOPSIS
208
209   - module: Subscription::Bloglines
210     config:
211       username: your-email@account
212       password: your-password
213       mark_read: 1
214
215 =head1 DESCRIPTION
216
217 This plugin allows you to synchronize your subscription using
218 Bloglines Web Services sync API.
219
220 =head1 CONFIGURATION
221
222 =over 4
223
224 =item username, password
225
226 Your username & password to use with Bloglines API.
227
228 =item mark_read
229
230 C<mark_read> specifies whether this plugin I<marks as read> the items
231 you synchronize. With this option set to 0, you will get the
232 duplicated updates everytime you run Plagger, until you mark them
233 unread using Bloglines browser interface. Defaults to 1.
234
235 For people who uses Bloglines browser interface regularly, and use
236 Plagger as a tool to synchronize feed updates to mobile devices (like
237 PSP or iPod), I'd recommend set this option to 0.
238
239 Otherwise, especially for Publish::Gmail plugin users, I recommend set
240 to 1, the default.
241
242 =item fetch_meta
243
244 C<fetch_meta> specifies whether this plugin fetches I<folder>
245 strucuture using listsubs API. With this option on, all feeds under
246 I<Plagger> folder will have I<Plagger> as its tag.
247
248 You can use this tags information using Rules in later phase.
249
250 =back
251
252 =head1 AUTHOR
253
254 Tatsuhiko Miyagawa
255
256 =head1 SEE ALSO
257
258 L<Plagger>, L<WebService::Bloglines>, L<http://www.bloglines.com/>
259
260 =cut
261
Note: See TracBrowser for help on using the browser.