root/trunk/plagger/lib/Plagger/Plugin/CustomFeed/Mixi.pm

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

CustomFeed?::Mixi: mixi updated their HTML for the login system

  • Property svn:keywords set to Id Revision
Line 
1 package Plagger::Plugin::CustomFeed::Mixi;
2 use strict;
3 use base qw( Plagger::Plugin );
4
5 use DateTime::Format::Strptime;
6 use Encode;
7 use WWW::Mixi;
8 use Time::HiRes;
9 use URI;
10
11 our $MAP = {
12     FriendDiary => {
13         start_url  => 'http://mixi.jp/new_friend_diary.pl',
14         title      => 'マイミク最新日記',
15         get_list   => 'parse_new_friend_diary',
16         get_detail => 'get_view_diary',
17         icon_re    => qr/owner_id=(\d+)/,
18     },
19     # can't get icon
20     Message => {
21         start_url  => 'http://mixi.jp/list_message.pl',
22         title      => 'ミクシィメッセージ受信箱',
23         get_list   => 'parse_list_message',
24         get_detail => 'get_view_message',
25     },
26     # can't get icon & body
27     RecentComment => {
28         start_url  => 'http://mixi.jp/list_comment.pl',
29         title      => 'ミクシィ最近のコメント一覧',
30         get_list   => 'parse_list_comment',
31     },
32     Log => {
33         start_url  => 'http://mixi.jp/show_log.pl',
34         title      => 'ミクシィ足跡',
35         get_list   => 'parse_show_log',
36         icon_re    => qr/[^_]id=(\d+)/,
37     },
38     MyDiary => {
39         start_url  => 'http://mixi.jp/list_diary.pl',
40         title      => 'ミクシィ日記',
41         get_list   => 'parse_list_diary',
42         get_detail => 'get_view_diary',
43         icon_re    => qr/owner_id=(\d+)/,
44     },
45     Calendar => {
46         start_url  => 'http://mixi.jp/show_calendar.pl',
47         title      => 'ミクシィカレンダー',
48         get_list   => 'parse_show_calendar',
49         get_detail => 'get_view_event',
50     },
51 };
52
53 sub plugin_id {
54     my $self = shift;
55     $self->class_id . '-' . $self->conf->{email};
56 }
57
58 sub register {
59     my($self, $context) = @_;
60     $context->register_hook(
61         $self,
62         'subscription.load' => \&load,
63     );
64 }
65
66 sub load {
67     my($self, $context) = @_;
68
69     my $cookie_jar = $self->cookie_jar;
70     if (ref($cookie_jar) ne 'HTTP::Cookies') {
71         # using foreign cookies = don't have to set email/password. Fake them
72         $self->conf->{email}    ||= 'plagger@localhost';
73         $self->conf->{password} ||= 'pl4gg5r';
74     }
75
76     $self->{mixi} = WWW::Mixi->new($self->conf->{email}, $self->conf->{password});
77     $self->{mixi}->cookie_jar($cookie_jar);
78
79     my $feed = Plagger::Feed->new;
80        $feed->aggregator(sub { $self->aggregate(@_) });
81     $context->subscription->add($feed);
82 }
83
84 sub aggregate {
85     my($self, $context, $args) = @_;
86     for my $type (@{$self->conf->{feed_type} || ['FriendDiary']}) {
87         $context->error("$type not found") unless $MAP->{$type};
88         $self->aggregate_feed($context, $type, $args);
89     }
90 }
91 sub aggregate_feed {
92     my($self, $context, $type, $args) = @_;
93
94     my $start_url = $MAP->{$type}->{start_url};
95     my $response  = $self->{mixi}->get($start_url);
96
97     my $next_url = URI->new($start_url)->path;
98
99     if ($response->content =~ /action="login\.pl"/) {
100         $context->log(debug => "Cookie not found. Logging in");
101
102         if ($self->conf->{email} eq 'plagger@localhost') {
103             $context->log(error => 'email/password should be set to login');
104         }
105
106         $response = $self->{mixi}->post("http://mixi.jp/login.pl", {
107             next_url => $next_url,
108             email    => $self->conf->{email},
109             password => $self->conf->{password},
110             sticky   => 'on',
111         });
112         if (!$response->is_success || $response->content =~ /action=login\.pl/) {
113             $context->log(error => "Login failed.");
114             return;
115         }
116
117         # meta refresh, ugh!
118         if ($response->content =~ m!"0;url=(.*?)"!) {
119             $response = $self->{mixi}->get($1);
120         }
121     }
122
123     my $feed = Plagger::Feed->new;
124     $feed->type('mixi');
125     $feed->title($MAP->{$type}->{title});
126     $feed->link($MAP->{$type}->{start_url});
127
128     my $format = DateTime::Format::Strptime->new(pattern => '%Y/%m/%d %H:%M');
129
130     my $meth = $MAP->{$type}->{get_list};
131     my @msgs = $self->{mixi}->$meth($response);
132     my $items = $self->conf->{fetch_items} || 20;
133     $self->log(info => 'fetch ' . scalar(@msgs) . ' entries');
134
135     my $i = 0;
136     my $blocked = 0;
137     for my $msg (@msgs) {
138         next if $type eq 'FriendDiary' and not $msg->{image}; # external blog
139         last if $i++ >= $items;
140
141         my $entry = Plagger::Entry->new;
142         $entry->title( decode('euc-jp', $msg->{subject}) );
143         $entry->link($msg->{link});
144         $entry->author( decode('euc-jp', $msg->{name}) );
145         $entry->date( Plagger::Date->parse($format, $msg->{time}) );
146
147         if ($self->conf->{show_icon} && !$blocked && defined $MAP->{$type}->{icon_re}) {
148             my $owner_id = ($msg->{link} =~ $MAP->{$type}->{icon_re})[0];
149             my $link = "http://mixi.jp/show_friend.pl?id=$owner_id";
150             $context->log(info => "Fetch icon from $link");
151
152             my $item = $self->cache->get_callback(
153                 "outline-$owner_id",
154                 sub {
155                     Time::HiRes::sleep( $self->conf->{fetch_body_interval} || 1.5 );
156                     my($item) = $self->{mixi}->get_show_friend_outline($link);
157                     $item;
158                 },
159                 '12 hours',
160             );
161             if ($item && $item->{image} !~ /no_photo/) {
162                 # prefer smaller image
163                 my $image = $item->{image};
164                    $image =~ s/\.jpg$/s.jpg/;
165                 $entry->icon({
166                     title => decode('euc-jp', $item->{name}),
167                     url   => $image,
168                     link  => $link,
169                 });
170             }
171         }
172
173         if ($self->conf->{fetch_body} && !$blocked && $msg->{link} =~ /view_/ && defined $MAP->{$type}->{get_detail}) {
174             $context->log(info => "Fetch body from $msg->{link}");
175             my $item = $self->cache->get_callback(
176                 "item-$msg->{link}",
177                 sub {
178                     Time::HiRes::sleep( $self->conf->{fetch_body_interval} || 1.5 );
179                     my $meth = $MAP->{$type}->{get_detail};
180                     my($item) = $self->{mixi}->$meth($msg->{link});
181                     $item;
182                 },
183                 '12 hours',
184             );
185             if ($item) {
186                 my $body = decode('euc-jp', $item->{description});
187                    $body =~ s!(\r\n?|\n)!<br />!g;
188                 for my $image (@{ $item->{images} }) {
189                     # xxx this should be $entry->enclosures
190                     $body .= qq(<div><a href="$image->{link}"><img src="$image->{thumb_link}" style="border:0" /></a></div>);
191                 }
192                 $entry->body($body);
193
194                 $entry->date( Plagger::Date->parse($format, $item->{time}) );
195             } else {
196                 $context->log(warn => "Fetch body failed. You might be blocked?");
197                 $blocked++;
198             }
199         }
200
201         $feed->add_entry($entry);
202     }
203
204     $context->update->add($feed);
205 }
206
207 1;
208
209 __END__
210
211 =head1 NAME
212
213 Plagger::Plugin::CustomFeed::Mixi -  Custom feed for mixi.jp
214
215 =head1 SYNOPSIS
216
217     - module: CustomFeed::Mixi
218       config:
219         email: email@example.com
220         password: password
221         fetch_body: 1
222         show_icon: 1
223         feed_type:
224           - RecentComment
225           - FriendDiary
226           - Message
227
228 =head1 DESCRIPTION
229
230 This plugin fetches your friends diary updates from mixi
231 (L<http://mixi.jp/>) and creates a custom feed.
232
233 =head1 CONFIGURATION
234
235 =over 4
236
237 =item email, password
238
239 Credential you need to login to mixi.jp.
240
241 Note that you don't have to supply email and password if you set
242 global cookie_jar in your configuration file and the cookie_jar
243 contains a valid login session there, such as:
244
245   global:
246     user_agent:
247       cookies: /path/to/cookies.txt
248
249 See L<Plagger::Cookies> for details.
250
251 =item fetch_body
252
253 With this option set, this plugin fetches entry body HTML, not just a
254 link to the entry. Defaults to 0.
255
256 =item fetch_body_interval
257
258 With C<fetch_body> option set, your Plagger script is recommended to
259 wait for a little, to avoid mixi.jp throttling. Defaults to 1.5.
260
261 =item show_icon: 1
262
263 With this option set, this plugin fetches users buddy icon from
264 mixi.jp site, which makes the output HTML very user-friendly.
265
266 =item feed_type
267
268 With this option set, you can set the feed types.
269
270 Now supports: RecentComment, FriendDiary, Message, Log, MyDiary, and Calendar.
271
272 Default: FriendDiary.
273
274 =back
275
276 =head1 SCREENSHOT
277
278 L<http://blog.bulknews.net/mt/archives/plagger-mixi-icon.gif>
279
280 =head1 AUTHOR
281
282 Tatsuhiko Miyagawa
283
284 =head1 SEE ALSO
285
286 L<Plagger>, L<WWW::Mixi>
287
288 =cut
Note: See TracBrowser for help on using the browser.