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

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

support Ashiato in Mixi: via http://subtech.g.hatena.ne.jp/otsune/20060504/mixilog

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