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

Revision 989 (checked in by otsune, 14 years ago)

remove icon_re. RecentComment? can't get it

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