File Coverage

blib/lib/WWW/phpBB.pm
Criterion Covered Total %
statement 36 1013 3.5
branch 0 438 0.0
condition 0 273 0.0
subroutine 12 51 23.5
pod 8 37 21.6
total 56 1812 3.0


tags
line stmt bran cond sub pod time code
1             package WWW::phpBB;
2              
3 1     1   26504 use strict;
  1         2  
  1         35  
4 1     1   4 use warnings;
  1         2  
  1         28  
5 1     1   5 no warnings qw(uninitialized);
  1         5  
  1         29  
6 1     1   1489 use WWW::Mechanize;
  1         253554  
  1         44  
7 1     1   13 use Compress::Zlib;
  1         3  
  1         389  
8 1     1   1145 use HTML::TokeParser::Simple;
  1         13633  
  1         33  
9 1     1   10 use Time::Local;
  1         2  
  1         135  
10 1     1   3734 use DBI();
  1         21850  
  1         39  
11 1     1   11 use Carp;
  1         2  
  1         76  
12 1     1   1030 use POSIX ":sys_wait_h";
  1         7499  
  1         8  
13 1     1   1371 use Encode;
  1         2  
  1         126  
14 1     1   6 use HTML::Entities;
  1         2  
  1         21853  
15              
16             require Exporter;
17              
18             our @ISA = qw(Exporter);
19             our %EXPORT_TAGS = ();
20             our @EXPORT_OK = ();
21             our @EXPORT = qw();
22             our $VERSION = '0.09';
23             my $children; # number of spawned processes
24              
25             # defaults
26             my %default = (
27             db_compression => 0,
28             max_rows => 60,
29             months => [qw(jan feb mar apr may jun jul aug sep oct nov dec)],
30             post_date_format => qr/(\w+)\s+(\d+),\s+(\d+)\s+(\d+):(\d+)\s+(\w\w)/i,
31             post_date_pos => [qw(month_name day_of_month year hour minutes am_pm)],
32             reg_date_format => qr/(\d+)\s+(\w+)\s+(\d+)/i,
33             reg_date_pos => [qw(day_of_month month_name year)],
34             forum_link_regex => qr/f=(\d+)/,
35             topic_link_regex_p => qr/viewtopic.*p=(\d+)/,
36             topic_link_regex_t => qr/viewtopic.*t=(\d+)/,
37             topic_link1 => "viewtopic.php",
38             topic_link2 => "t=%d&postorder=asc",
39             alternative_page_number_regex_forum => qr//,
40             alternative_page_number_regex_topic => qr//,
41             quote_string => "wrote",
42             max_tries => 50,
43             db_empty => [qw(users categories forums topics posts posts_text vote_desc vote_results groups user_group)],
44             bbcode_uid => '48d712e388',
45             db_insert => 1,
46             update_overwrite => 0,
47             verbose => 0,
48             profile_info => 1,
49             profile_string_occupation => "occupation",
50             profile_string_msn => "msn messenger",
51             max_children => 1,
52             smiles => {
53             icon_biggrin => ':D',
54             icon_smile => ':)',
55             icon_sad => ':(',
56             icon_surprised => ':o',
57             icon_eek => ':shock:',
58             icon_confused => ':?',
59             icon_cool => '8)',
60             icon_lol => ':lol:',
61             icon_mad => ':x',
62             icon_razz => ':P',
63             icon_redface => ':oops:',
64             icon_cry => ':cry:',
65             icon_evil => ':evil:',
66             icon_twisted => ':twisted:',
67             icon_rolleyes => ':roll:',
68             icon_wink => ':wink:',
69             icon_exclaim => ':!:',
70             icon_question => ':?:',
71             icon_idea => ':idea:',
72             icon_arrow => ':arrow:',
73             icon_neutral => ':|',
74             icon_mrgreen => ':mrgreen:',
75             },
76             );
77             # mysql tables as arrays of hashes
78             for (qw(categories forums topics users posts posts_text vote_desc vote_results groups user_group)) {
79             $default{$_} = [];
80             }
81              
82              
83             package WWW::phpBB;
84              
85             ##################
86             # main functions #
87             ##################
88              
89             our %ok_field; # for accessors
90              
91             for (qw(
92             base_url
93             db_host
94             db_user
95             db_passwd
96             db_database
97             db_prefix
98             forum_user
99             forum_passwd
100             db_compression
101             max_rows
102             months
103             post_date_format
104             post_date_pos
105             reg_date_format
106             reg_date_pos
107             forum_link_regex
108             topic_link_regex_p
109             topic_link_regex_t
110             topic_link1
111             topic_link2
112             alternative_page_number_regex_forum
113             alternative_page_number_regex_topic
114             quote_string
115             max_tries
116             db_empty
117             db_insert
118             max_children
119             verbose
120             profile_info
121             )) { $ok_field{$_}++ };
122              
123             sub AUTOLOAD {
124 0     0     my $self = shift;
125 0           my $attr = our $AUTOLOAD;
126 0           $attr =~ s/.*:://;
127 0 0         return unless $attr =~ /[^A-Z]/;
128 0 0         croak "invalid attribute method: ->$attr( )" unless $ok_field{$attr};
129 0 0         $self->$attr = shift if @_;
130 0           $self->$attr;
131             }
132              
133             sub new {
134 0     0 1   my $self = {};
135 0           bless($self, shift);
136 0 0         if (@_) {
137 0           my %args = @_;
138 0           @$self{keys %args} = values %args;
139             }
140              
141             # defaults
142 0           while (my ($key, $value) = each %default) {
143 0 0         $self->{$key} = $value unless exists $self->{$key};
144             }
145              
146             # croak if the mandatory arguments are missing
147 0           for (qw(base_url db_host db_user db_passwd db_database db_prefix)) {
148 0 0         croak "you must specify a $_" unless exists $self->{$_};
149             }
150              
151             # init
152 0           $self->{mmech} = WWW::Mechanize->new(stack_depth => 1);
153 0           $self->{mmech}->agent_alias('Linux Mozilla');
154             #$self->{mmech}->add_header('Accept-Encoding' => 'gzip; deflate');
155 0           for (1..$self->{max_tries}) {
156 0           $self->{mmech}->get($self->{base_url});
157 0 0 0       last if $self->{mmech}->success && $self->{mmech}->status == 200;
158 0           print "Error fetching the start page (try $_ out of $self->{max_tries})\n";
159             }
160 0 0 0       croak "gave up...\n" unless $self->{mmech}->success && $self->{mmech}->status == 200;
161              
162 0           $self->{dbh} = DBI->connect("DBI:mysql:database=$self->{db_database};host=$self->{db_host};mysql_compression=$self->{db_compression}",
163             $self->{db_user}, $self->{db_passwd}, {AutoCommit => 1, RaiseError => 1});
164 0 0         unless ($self->{update_overwrite}) {
165 0           $self->{dbh}->do("CREATE TABLE IF NOT EXISTS $self->{db_prefix}" . "topics_trans "
166             . "(new mediumint(8) unsigned NOT NULL,"
167             . "orig mediumint(8) unsigned NOT NULL,"
168             . "PRIMARY KEY (new) )");
169 0           $self->{dbh}->do("CREATE TABLE IF NOT EXISTS $self->{db_prefix}" . "posts_trans "
170             . "LIKE $self->{db_prefix}" . "topics_trans");
171             }
172              
173 0           $self;
174             }
175              
176             sub forum_login_raw {
177 0     0 0   my $self = shift;
178 0 0 0       if (!exists $self->{forum_user} || !exists $self->{forum_passwd}) {
179 0           print "can't login without a forum_user and forum_passwd\n";
180 0           return;
181             }
182 0 0         if ($self->{verbose}) {
183 0           print "logging in...";
184             }
185 0           for (1..$self->{max_tries}) {
186             #$self->{mmech}->form_number(1);
187 0           $self->{mmech}->field('username', $self->{forum_user});
188 0           $self->{mmech}->field('password', $self->{forum_passwd});
189 0           $self->{mmech}->click();
190 0 0 0       if ($self->{mmech}->success && $self->{mmech}->status == 200) {
191 0 0         if ($self->{verbose}) {
192 0           print "\n";
193             }
194 0           last;
195             }
196 0           print "Error logging in (try $_ out of $self->{max_tries})\n";
197             }
198 0 0 0       croak "gave up...\n" unless $self->{mmech}->success && $self->{mmech}->status == 200;
199             }
200              
201             # wrapper for forum_login_raw() that retries in case of errors
202             sub forum_login {
203 0     0 1   my $self = shift;
204 0           for (1..$self->{max_tries}) {
205 0 0         eval {
206 0           $self->forum_login_raw(@_);
207 0           1;
208             } and last;
209 0           print "failed (try $_ out of $self->{max_tries})\n";
210 0           sleep(1)
211             }
212             }
213              
214             sub forum_logout_raw {
215 0     0 0   my $self = shift;
216 0 0 0       return unless exists $self->{forum_user} && exists $self->{forum_passwd};
217 0 0         if ($self->{verbose}) {
218 0           print "logging out...";
219             }
220 0           for (1..$self->{max_tries}) {
221 0           $self->{mmech}->follow_link(url_regex => qr/logout/);
222 0 0 0       if ($self->{mmech}->success && $self->{mmech}->status == 200) {
223 0 0         if ($self->{verbose}) {
224 0           print "\n";
225             }
226             last
227 0           }
228 0           print "Error logging out (try $_ out of $self->{max_tries})\n";
229             }
230 0 0 0       croak "gave up...\n" unless $self->{mmech}->success && $self->{mmech}->status == 200;
231             # reset the base_uri
232 0           $self->{mmech}->get($self->{base_url})
233             }
234              
235             # wrapper for forum_logout_raw() that retries in case of errors
236             sub forum_logout {
237 0     0 1   my $self = shift;
238 0           for (1..$self->{max_tries}) {
239 0 0         eval {
240 0           $self->forum_logout_raw(@_);
241 0           1;
242             } and last;
243 0           print "failed (try $_ out of $self->{max_tries})\n";
244 0           sleep(1)
245             }
246             }
247              
248             sub get_categories_and_forums {
249 0     0 0   my $self = shift;
250 0           my $parse;
251 0           $parse = HTML::TokeParser::Simple->new( \$self->{mmech}->content );
252 0           $parse->unbroken_text( 1 );
253 0           my $token;
254             my $cat_order;
255 0           my $rows;
256 0           my $frows;
257              
258 0           while ($token = $parse->get_token) {
259 0 0         next unless $token->get_attr('class') eq 'catLeft';
260 0           my %row;
261             my $cat_id;
262             # cat_id
263 0   0       $token = $parse->get_token until $token->is_start_tag('a') || $token == 0;
264 0           $token->get_attr('href') =~ /c=(\d+)/;
265 0           $row{cat_id} = $cat_id = $1;
266             # cat_title
267 0   0       $token = $parse->get_token until $token->is_text && $token->as_is =~ /\S/;
268 0           $row{cat_title} = $token->as_is;
269             # cat_order
270 0           $cat_order += 10;
271 0           $row{cat_order} = $cat_order;
272             # store
273 0           push @{$self->{categories}}, \%row;
  0            
274              
275             # get the forums
276 0           my $forum_order;
277 0           while ($token = $parse->get_token) {
278 0 0         last if $parse->peek =~ /catLeft/;
279 0 0 0       next unless $token->is_start_tag('a') && $token->get_attr('class') eq 'forumlink';
280 0           my %row;
281             # auth
282 0           $row{auth_post} = 1;
283 0           $row{auth_reply} = 1;
284 0           $row{auth_edit} = 1;
285 0           $row{auth_delete} = 1;
286 0           $row{auth_vote} = 1;
287 0           $row{auth_pollcreate} = 1;
288 0           $row{auth_sticky} = $row{auth_announce} = $row{auth_attachments} = 3;
289             # forum_id
290 0           $token->get_attr('href') =~ $self->{forum_link_regex};
291 0           $row{forum_id} = $1;
292             # cat_id
293 0           $row{cat_id} = $cat_id;
294             # forum_name
295 0   0       $token = $parse->get_token until $token->is_text && $token->as_is =~ /\S/;
296 0           $row{forum_name} = $token->as_is;
297             # forum_desc
298 0           $token = $parse->get_token;
299 0   0       $token = $parse->get_token until $token->is_text && $token->as_is =~ /\S/
      0        
300             || $token->is_end_tag('td');
301 0 0         $row{forum_desc} = $token->as_is if $token->is_text;
302 0           $row{forum_desc} =~ s/ //g;
303             # forum_topics
304 0           $token = $parse->get_token until $token->is_start_tag('td');
305 0   0       $token = $parse->get_token until $token->is_text && $token->as_is =~ /\S/;
306 0           $row{forum_topics} = $token->as_is;
307             # forum_posts
308 0           $token = $parse->get_token until $token->is_start_tag('td');
309 0   0       $token = $parse->get_token until $token->is_text && $token->as_is =~ /\S/;
310 0           $row{forum_posts} = $token->as_is;
311             # forum_last_post_id
312 0   0       $token = $parse->get_token until $token->is_start_tag('a')
      0        
313             && $token->get_attr('href') =~ $self->{topic_link_regex_p}
314             || $token->is_end_tag('tr');
315 0 0         $row{forum_last_post_id} = $1 if $token->is_start_tag('a');
316             # forum_order
317 0           $forum_order += 10;
318 0           $row{forum_order} = $forum_order;
319             # store
320 0           push @{$self->{forums}}, \%row;
  0            
321             }
322             }
323 0 0         if ($self->{db_insert}) {
324 0           $self->insert_array($self->{categories}, "categories");
325 0           $self->insert_array($self->{forums}, "forums");
326             }
327             }
328              
329             # $_[0]=$forum_id, $_[1]=$page_number
330             sub get_topics {
331 0     0 0   my $self = shift;
332 0           my ($forum_id, $page_number) = @_;
333 0           my $success;
334             my $rows;
335             # make a copy of the $mech object
336 0           my $mech = {%{$self->{mmech}}};
  0            
337 0           bless $mech, "WWW::Mechanize";
338 0           my $url = $self->compute_url("viewforum.php", "f=$forum_id");
339 0           for (1..$self->{max_tries}) {
340 0 0         eval {
341 0           $mech->get($url);
342 0           1;
343             } or next;
344 0 0 0       last if $mech->success && $mech->status == 200;
345 0           print "Failed to enter forum_id $forum_id (try $_ out of $self->{max_tries})\n";
346             }
347 0 0 0       return unless $mech->success && $mech->status == 200;
348             # cycle through pages
349 0           my $pages = $self->number_of_pages($mech, 'forum');
350             # get just one page ?
351 0 0         if (defined $page_number) {
352 0 0 0       if (-@$pages <= $page_number && $page_number < @$pages) {
353 0           $pages = [ $$pages[$page_number] ];
354 0           } else { return }
355             }
356             #print " pages: ", join(", ", @$pages);
357 0           for ( @$pages ) {
358 0           my $url = $mech->uri;
359 0           $url = $self->forum_url_for_page($url, $forum_id, $_);
360             #print $url, "\n";
361 0           for (1..$self->{max_tries}) {
362 0 0         eval {
363 0           $mech->get($url);
364 0           1;
365             } or next;
366 0 0 0       last if $mech->success && $mech->status == 200;
367 0           print "Failed to enter a page of forum_id $forum_id (try $_ out of $self->{max_tries})\n";
368             }
369 0 0 0       next unless $mech->success && $mech->status == 200;
370             # extract topic info
371 0           my $parse;
372 0           $parse = HTML::TokeParser::Simple->new( \$mech->content );
373 0           $parse->unbroken_text( 1 );
374 0           my $token;
375 0           $token = $parse->get_token;
376 0   0       $token = $parse->get_token until (!defined $token || $token->get_attr('class') eq 'forumline');
377 0           $token = $parse->get_token until $token->is_end_tag('tr');
378 0           while ($token = $parse->get_token) {
379 0 0         last if $token->is_end_tag('table');
380 0 0         next unless $token->is_start_tag('tr');
381 0           $token = $parse->get_token until $token->is_start_tag('td');
382 0 0         last unless $token->get_attr('class') =~ /row/;
383 0           my %row;
384             # forum_id
385 0           $row{forum_id} = $forum_id;
386             # topic_type
387 0   0       $token = $parse->get_token until $token->is_start_tag('img')
388             || $token->is_end_tag('td');
389 0 0         last if $token->is_end_tag('td');
390 0 0         $row{topic_type} = 1 if $token->get_attr('src') =~ /sticky/;
391 0 0         $row{topic_type} = 2 if $token->get_attr('src') =~ /announce/;
392             # topic_id
393 0   0       $token = $parse->get_token until $token->is_start_tag('a')
394             && $token->get_attr('href') =~ $self->{topic_link_regex_t};
395 0           $row{topic_id} = $1;
396             # topic_title
397 0   0       $token = $parse->get_token until $token->is_text
398             && $token->as_is =~ /\S/;
399 0           $row{topic_title} = $token->as_is;
400             # topic_replies
401 0           $token = $parse->get_token until $token->is_start_tag('td');
402 0   0       $token = $parse->get_token until $token->is_text
403             && $token->as_is =~ /^(\d+)$/;
404 0           $row{topic_replies} = $1;
405             # topic_poster
406 0           $token = $parse->get_token until $token->is_start_tag('td');
407 0           while ($token = $parse->get_token) {
408 0 0         last if $token->is_end_tag('td');
409 0 0         if ($token->is_start_tag('a')) {
410 0 0 0       $row{topic_poster} = $1
411             if $token->is_start_tag('a')
412             && $token->get_attr('href') =~ /viewprofile.*u=(\d+)/;
413 0           last;
414             }
415 0 0 0       if ( ! exists $row{topic_poster}
      0        
416             && $token->is_text
417             && $token->as_is =~ /\S/ ) {
418 0           my $username = $token->as_is;
419 0           for (@{$self->{users}}) {
  0            
420 0 0         if ($_->{username} eq $username) {
421 0           $row{topic_poster} = $_->{user_id};
422 0           last;
423             }
424             }
425             }
426             }
427 0 0         $row{topic_poster} = -1 unless exists $row{topic_poster};
428             # topic_views
429 0           $token = $parse->get_token until $token->is_start_tag('td');
430 0   0       $token = $parse->get_token until (!defined $token || ($token->is_text && $token->as_is =~ /^(\d+)$/));
      0        
431 0           $row{topic_views} = $1;
432             # topic_last_post_id
433 0   0       $token = $parse->get_token until $token->is_start_tag('a')
434             && $token->get_attr('href') =~ $self->{topic_link_regex_p};
435 0           $row{topic_last_post_id} = $1;
436              
437             # manage the same topic appearing on more pages (like announcements)
438 0           my $unique = 1;
439 0           for (@{$self->{topics}}) {
  0            
440 0 0         if ( $_->{topic_id} == $row{topic_id} ) {
441 0           $unique = 0;
442 0           last;
443             }
444             }
445             # check also the db to see if it's the shadow of a moved topic
446 0 0 0       if ($unique && ! defined $page_number) {
447 0           my $sth = $self->{dbh}->prepare("SELECT topic_id FROM $self->{db_prefix}"
448             . "topics WHERE topic_id=$row{topic_id}" );
449 0           $sth->execute;
450 0 0         if ($sth->fetch) {
451 0           $unique = 0;
452             }
453             }
454              
455 0 0         push @{$self->{topics}}, \%row if $unique;
  0            
456             }
457             }
458             }
459              
460             # $_[0]=$page_number
461             sub get_users_raw {
462 0     0 0   my $self = shift;
463 0 0         if ($self->{verbose}) {
464 0           print "getting users...\n";
465             }
466 0           my ($page_number) = @_;
467 0           my $success;
468             my $rows;
469              
470 0           $self->get_new_admin();
471             # make a copy of the $mech object
472 0           my $mech = {%{$self->{mmech}}};
  0            
473 0           bless $mech, "WWW::Mechanize";
474 0           my $url = $self->compute_url("memberlist.php", "");
475 0           for (1..$self->{max_tries}) {
476 0           $mech->get($url);
477 0 0 0       last if $mech->success && $mech->status == 200;
478 0           print "Failed to enter memberlist (try $_ out of $self->{max_tries})\n";
479             }
480 0 0 0       return unless $mech->success && $mech->status == 200;
481             # cycle through pages
482 0           my $pages = $self->number_of_pages($mech);
483             # get just one page ?
484 0 0         if (defined $page_number) {
485 0 0 0       if (-@$pages <= $page_number && $page_number < @$pages) {
486 0           $pages = [ $$pages[$page_number] ];
487 0           } else { return }
488             }
489 0           for ( @$pages ) {
490 0           my $url = $mech->uri;
491 0           $url =~ s/&$//;
492 0           $url = $self->memberlist_url_for_page($url, $_);
493              
494 0           for (1..$self->{max_tries}) {
495 0           $mech->get($url);
496 0 0 0       last if $mech->success && $mech->status == 200;
497 0           print "Failed to enter a page of the memberlist (try $_ out of $self->{max_tries})\n";
498             }
499 0 0 0       next unless $mech->success && $mech->status == 200;
500             # extract memberlist info
501 0           my $parse;
502 0           $parse = HTML::TokeParser::Simple->new( \$mech->content );
503 0           $parse->unbroken_text( 1 );
504 0           my $token;
505 0           $token = $parse->get_token;
506 0   0       $token = $parse->get_token until $token->is_start_tag('table')
507             && $token->get_attr('class') eq 'forumline';
508 0           $token = $parse->get_token until $token->is_end_tag('tr');
509 0           while ($token = $parse->get_token) {
510             #print "|>", $parse->peek(7), "<|\n";
511 0 0         next unless $token->is_start_tag('tr');
512 0           $token = $parse->get_token until $token->is_start_tag('td');
513 0           my %row;
514             # various default fields
515 0           $row{user_sig_bbcode_uid} = $self->{bbcode_uid};
516 0           $row{user_style} = 1;
517 0           $row{user_lang} = 'english';
518 0           $row{user_viewemail} = 0;
519 0           $row{user_attachsig} = 1;
520 0           $row{user_allowhtml} = 0;
521 0           $row{user_notify} = 0;
522 0           $row{user_notify_pm} = 1;
523 0           $row{user_popup_pm} = 1;
524             # user_id
525 0   0       $token = $parse->get_token until !defined $token || ($token->is_start_tag('a') && $token->get_attr('href') =~ /viewprofile.*u=(\d+)(\D|$)/);
      0        
526 0 0         last if !defined $token;
527 0           $row{user_id} = $1;
528             # username
529 0   0       $token = $parse->get_token until $token->is_text
530             && $token->as_is =~ /\S/;
531 0           $row{username} = $token->as_is;
532 0 0         die "the destination forum's admin has the same username as one user from the scraped forum ($row{username})! aborting...\n" if $row{username} eq $self->{new_admin_username};
533             #print $row{username}, "\n";
534             # user_email
535 0           $token = $parse->get_token until $token->is_start_tag('td');
536 0           while ($token = $parse->get_token) {
537 0 0         last if $token->is_end_tag('td');
538 0 0 0       if ($token->is_start_tag('a') && $token->as_is =~ /mailto:([^"]+)\"/) {
539 0           $row{user_email} = $1;
540             }
541             }
542             # user_from
543 0           $token = $parse->get_token until $token->is_start_tag('td');
544 0           my $td_count = 0;
545 0           while ($token = $parse->get_token) {
546 0 0         $td_count++ if $token->is_start_tag('td');
547 0 0         $td_count-- if $token->is_end_tag('td');
548 0 0         last if $td_count < 0;
549 0 0 0       if ($token->is_text
      0        
550             && $token->as_is !~ /^( )+$/
551             && $token->as_is =~ /\S/) {
552 0           $row{user_from} = $token->as_is;
553             }
554             }
555             # user_regdate
556 0   0       $token = $parse->get_token until $token->is_text
557             && $token->as_is =~ /\S/;
558             #print $token->as_is(), "\n";
559 0           $row{user_regdate} = $self->parse_date($token->as_is, $self->{reg_date_format},
560             $self->{reg_date_pos});
561             # user_posts
562 0   0       $token = $parse->get_token until $token->is_text
563             && $token->as_is =~ /^(\d+)$/;
564 0           $row{user_posts} = $1;
565             # user_website
566 0           while ($token = $parse->get_token) {
567 0 0         last if $token->is_end_tag('tr');
568 0 0 0       if ($token->is_start_tag('a')
569             && $token->get_attr('target') eq "_userwww") {
570 0           $row{user_website} = $token->get_attr('href');
571             }
572             }
573             #while( my ($k, $v) = each %row ) {
574             #print "$k : $v\n";
575             #}
576             #print "\n";
577              
578 0 0         if($self->{profile_info}) {
579             # get profile info
580 0 0         if ($self->{verbose}) {
581 0           print "getting the profile info for '$row{username}'\n";
582             }
583             # make a copy of the $mech object in order to get the profile page
584 0           my $p_mech = {%{$mech}};
  0            
585 0           bless $p_mech, "WWW::Mechanize";
586 0           my $urlp = $self->compute_url("profile.php", "mode=viewprofile&u=$row{user_id}");
587 0           for (1..$self->{max_tries}) {
588 0           $p_mech->get($urlp);
589 0 0 0       last if $p_mech->success && $p_mech->status == 200;
590 0           print "Failed to enter profile (try $_ out of $self->{max_tries})\n";
591             }
592 0 0 0       next unless $p_mech->success && $p_mech->status == 200;
593 0           my $p_parse;
594 0           $p_parse = HTML::TokeParser::Simple->new( \$p_mech->content );
595 0           $p_parse->unbroken_text( 1 );
596 0           my $p_token;
597 0           $p_token = $p_parse->get_token;
598 0           $p_token = $p_parse->get_token until $p_token->get_attr('class')
599             eq 'forumline';
600 0   0       $p_token = $p_parse->get_token until $p_token->get_attr('class')
601             =~ /row/ && $p_token->is_start_tag('td');
602             # user_avatar
603 0           while ($p_token = $p_parse->get_token) {
604 0 0         last if $p_token->is_end_tag('td');
605 0 0         if ($p_token->is_tag('img')) {
606 0           $row{user_avatar} = $p_token->get_attr('src');
607 0           (my $b_url = $p_mech->uri) =~ s%^(.*/).*$%$1%;
608 0 0         $row{user_avatar} = $b_url . $row{user_avatar} if $row{user_avatar} !~ m%^http://%;
609 0           $row{user_avatar_type} = 2;
610 0           last;
611             }
612             }
613             #print "$row{username} - $row{user_avatar}\n";
614             # user_occ
615             #for (1..5) {
616             #$p_token = $p_parse->get_token;
617             #$p_token = $p_parse->get_token until $p_token->is_start_tag('tr');
618             #}
619             #$p_token = $p_parse->get_token until $p_token->is_end_tag('td');
620             #$p_token = $p_parse->get_token until $p_token->is_text
621             #&& $p_token->as_is !~ /^\s+$|^( )+$/
622             #|| $p_token->is_end_tag('tr');
623 0   0       $p_token = $p_parse->get_token until $p_token->is_text && $p_token->as_is =~ /^$self->{profile_string_occupation}:/i;
624 0           $p_token = $p_parse->get_token until $p_token->is_start_tag('span');
625 0           $p_token = $p_parse->get_token until $p_token->is_text;
626 0 0         $row{user_occ} = $p_token->as_is if $p_token->as_is !~ /^\s+$|^( )+$/;
627             # user_interests
628 0           $p_token = $p_parse->get_token until $p_token->is_end_tag('tr');
629 0           $p_token = $p_parse->get_token until $p_token->is_end_tag('td');
630 0   0       $p_token = $p_parse->get_token until $p_token->is_text
      0        
631             && $p_token->as_is !~ /^\s+$|^( )+$/
632             || $p_token->is_end_tag('tr');
633 0 0         $row{user_interests} = $p_token->as_is if $p_token->is_text;
634             # user_msnm
635             #$p_token = $p_parse->get_token until $p_token->is_start_tag('table');
636             #for (1..3) {
637             #$p_token = $p_parse->get_token;
638             #$p_token = $p_parse->get_token until $p_token->is_start_tag('tr');
639             #}
640             #$p_token = $p_parse->get_token until $p_token->is_end_tag('td');
641             #$p_token = $p_parse->get_token until $p_token->is_text
642             #&& $p_token->as_is !~ /^\s+$|^( )+$/
643             #|| $p_token->is_end_tag('tr');
644 0   0       $p_token = $p_parse->get_token until $p_token->is_text && $p_token->as_is =~ /^$self->{profile_string_msn}:/i;
645 0           $p_token = $p_parse->get_token until $p_token->is_start_tag('span');
646 0           $p_token = $p_parse->get_token until $p_token->is_text;
647 0 0         $row{user_msnm} = $p_token->as_is if $p_token->as_is !~ /^\s+$|^( )+$/;
648             # user_yim
649 0           $p_token = $p_parse->get_token until $p_token->is_end_tag('tr');
650 0           $p_token = $p_parse->get_token until $p_token->is_end_tag('td');
651 0   0       $p_token = $p_parse->get_token until $p_token->is_start_tag('a')
      0        
652             && $p_token->get_attr('href') =~ /target=(.+?)(&|$)/
653             || $p_token->is_end_tag('tr');
654 0 0         $row{user_yim} = $1 if $p_token->is_start_tag('a');
655             # user_aim
656 0           $p_token = $p_parse->get_token until $p_token->is_end_tag('tr');
657 0           $p_token = $p_parse->get_token until $p_token->is_end_tag('td');
658 0   0       $p_token = $p_parse->get_token until $p_token->is_start_tag('a')
      0        
659             && $p_token->get_attr('href') =~ /screenname=(.+?)(&|$)/
660             || $p_token->is_end_tag('tr');
661 0 0         $row{user_aim} = $1 if $p_token->is_start_tag('a');
662             # user_icq
663 0           $p_token = $p_parse->get_token until $p_token->is_end_tag('tr');
664 0           $p_token = $p_parse->get_token until $p_token->is_end_tag('td');
665 0   0       $p_token = $p_parse->get_token until $p_token->is_start_tag('a')
      0        
666             && $p_token->get_attr('href') =~ /icq\.com.*=(\d+?)(&|$)/
667             || $p_token->is_end_tag('tr');
668 0 0         $row{user_icq} = $1 if $p_token->is_start_tag('a');
669             }
670             #while( my ($k, $v) = each %row ) {
671             #print "$k : $v\n";
672             #}
673             #print "\n";
674              
675 0           push @{$self->{users}}, \%row;
  0            
676 0 0 0       if ($self->{db_insert} && ++$rows >= $self->{max_rows}) {
677 0           $self->insert_array($self->{users}, "users");
678 0           @{$self->{users}} = ();
  0            
679             }
680              
681             }
682             }
683 0 0         if ($self->{db_insert}) {
684 0           $self->insert_array($self->{users}, "users");
685 0           @{$self->{users}} = ();
  0            
686             }
687 0           $self->create_groups;
688             }
689              
690             # wrapper for get_users_raw() that retries in case of errors
691             sub get_users {
692 0     0 1   my $self = shift;
693 0           for (1..$self->{max_tries}) {
694 0 0         eval {
695 0           $self->get_users_raw(@_);
696 0           1;
697             } and last;
698 0           print "failed (try $_ out of $self->{max_tries})\n";
699 0           sleep(1)
700             }
701             }
702              
703             # $_[0]=$topic_id, $_[1]=$page_number
704             sub get_posts {
705 0     0 0   my $self = shift;
706 0           my ($topic_id, $page_number) = @_;
707 0           my $success;
708 0           my ($url, $url1, $url2);
709 0 0         if ($self->{verbose}) {
710 0           print "getting the posts from topic #$topic_id\n";
711             }
712             # make a copy of the $mech object
713 0           my $mech = {%{$self->{mmech}}};
  0            
714 0           bless $mech, "WWW::Mechanize";
715 0           $url1 = $self->{topic_link1};
716 0 0         $url1 = sprintf($url1, $topic_id) if $url1 =~ m/%d/;
717 0           $url2 = $self->{topic_link2};
718 0 0         $url2 = sprintf($url2, $topic_id) if $url2 =~ m/%d/;
719 0           $url = $self->compute_url($url1, $url2);
720 0           for (1..$self->{max_tries}) {
721 0 0         eval {
722 0           $mech->get($url);
723 0           1;
724             } or next;
725 0 0 0       last if $mech->success && $mech->status == 200;
726 0           print "Failed to enter topic_id $topic_id (try $_ out of $self->{max_tries})\n";
727             }
728 0 0 0       return unless $mech->success && $mech->status == 200;
729             # cycle through pages
730 0           my $pages = $self->number_of_pages($mech, 'topic');
731             # get just one page ?
732 0 0         if (defined $page_number) {
733 0 0 0       if (-@$pages <= $page_number && $page_number < @$pages) {
734 0           $pages = [ $$pages[$page_number] ];
735 0           } else { return }
736             }
737 0           for ( @$pages ) {
738 0           my $url = $mech->uri;
739 0           $url = $self->topic_url_for_page($url, $topic_id, $_);
740 0           for (1..$self->{max_tries}) {
741 0 0         eval {
742 0           $mech->get($url);
743 0           1;
744             } or next;
745 0 0 0       last if $mech->success && $mech->status == 200;
746 0           print "Failed to enter a page of topic_id $topic_id (try $_ out of $self->{max_tries})\n";
747             }
748 0 0 0       next unless $mech->success && $mech->status == 200;
749 0           $self->get_posts_from_page($mech, $topic_id, $_);
750             }
751             # get last_post_id only if scraping the full topic or just the last page
752 0 0 0       unless (defined $page_number && $page_number != -1) {
753 0           for (@{$self->{topics}}) {
  0            
754 0 0         if ($_->{topic_id} == $topic_id) {
755 0           $_->{topic_last_post_id} = $self->{posts}[-1]{post_id};
756 0           last;
757             }
758             }
759             }
760 0 0         if ($self->{db_insert}) {
761 0           $self->insert_array($self->{posts}, "posts");
762 0           $self->insert_array($self->{posts_text}, "posts_text");
763 0           $self->insert_array($self->{vote_desc}, "vote_desc");
764 0           $self->insert_array($self->{vote_results}, "vote_results");
765 0           @{$self->{posts}} = ();
  0            
766 0           @{$self->{posts_text}} = ();
  0            
767 0           @{$self->{vote_desc}} = ();
  0            
768 0           @{$self->{vote_results}} = ();
  0            
769             }
770             }
771              
772             sub get_posts_from_page {
773 0     0 0   my $self = shift;
774 0           my ($mech, $topic_id, $start_from) = @_;
775 0           my $parse;
776 0           $parse = HTML::TokeParser::Simple->new( \$mech->content );
777 0           $parse->unbroken_text( 1 );
778 0           my $token;
779             my $rows;
780 0           my %v_row;
781              
782 0           $token = $parse->get_token;
783 0           $token = $parse->get_token until $token->get_attr('class') eq 'forumline';
784 0           for (1..2) {
785 0           $token = $parse->get_token;
786 0           $token = $parse->get_token until $token->is_start_tag('tr');
787             }
788 0           my $counter;
789 0           while (1) {
790 0           $token = $parse->get_token;
791 0 0         $counter++ if $token->is_start_tag('tr');
792 0 0         $counter-- if $token->is_end_tag('tr');
793 0 0         last if $counter < 0;
794             # get the poll only on the first page
795 0 0 0       if ($token->is_start_tag('table') && $start_from == 0) {
796             #################
797             ## it's a poll ##
798             #################
799 0           for (@{$self->{topics}}) {
  0            
800 0 0         if ($_->{topic_id} == $topic_id) {
801             # topic_vote
802 0           $_->{topic_vote} = 1;
803 0           last;
804             }
805             }
806             # topic_id
807 0           $v_row{topic_id} = $topic_id;
808             # vote_id
809 0           $v_row{vote_id} = ++$self->{vote_id};
810             # vote_text
811 0   0       $token = $parse->get_token until $token->is_text
812             && $token->as_is =~ /\S/;
813 0           $v_row{vote_text} = $token->as_is;
814             # @{$self->{vote_results}}
815 0           my $vote_option_id;
816 0           $token = $parse->get_token until $token->is_start_tag('table');
817 0           my $counter2;
818 0           while (1) {
819 0           my %vr_row;
820 0           $token = $parse->get_token;
821 0 0         $counter2++ if $token->is_start_tag('table');
822 0 0         $counter2-- if $token->is_end_tag('table');
823 0 0         last if $counter2 < 0;
824 0 0         next unless $token->is_start_tag('td');
825 0   0       $token = $parse->get_token until $token->is_text
      0        
      0        
826             && $token->as_is =~ /\S/
827             && $token->as_is !~ / /
828             && $token->as_is !~ /^\s+$/;
829 0           $vr_row{vote_id} = $self->{vote_id};
830 0           $vr_row{vote_option_text} = $token->as_is;
831 0           $vr_row{vote_option_id} = ++$vote_option_id;
832 0           push @{$self->{vote_results}}, \%vr_row;
  0            
833 0           my $counter3;
834 0           while (1) {
835 0           $token = $parse->get_token;
836 0 0         if ($token->is_start_tag('tr')) {
837 0           $counter++;
838 0           $counter3++;
839             }
840 0 0         if ($token->is_end_tag('tr')) {
841 0           $counter--;
842 0           $counter3--;
843             }
844 0 0         last if $counter3 < 0;
845             }
846             # print "|>", $parse->peek(7), "<|\n";
847             }
848 0           push @{$self->{vote_desc}}, \%v_row;
  0            
849             }
850             }
851              
852 0           while ( $token = $parse->get_token ) {
853 0 0         last if $token->get_attr('class') eq 'catBottom';
854             # post_username
855 0 0         next unless $token->get_attr('class') eq 'name';
856 0           $token = $parse->get_token until $token->is_text;
857 0           my $post_username = $token->as_is;
858             # post_id
859 0   0       $token = $parse->get_token until $token->is_start_tag('a')
860             && $token->get_attr('href') =~ $self->{topic_link_regex_p};
861 0           my %row;
862 0           $row{post_id} = $1;
863             # topic_id
864 0           $row{topic_id} = $topic_id;
865             # forum_id
866 0           for (@{$self->{topics}}) {
  0            
867 0 0         if ($_->{topic_id} == $topic_id) {
868 0           $row{forum_id} = $_->{forum_id};
869 0           last;
870             }
871             }
872             # post_time
873 0   0       $token = $parse->get_token until $token->is_text
874             && $token->as_is =~ /\S/;
875 0           $row{post_time} = $self->parse_date($token->as_is, $self->{post_date_format},
876             $self->{post_date_pos});
877 0 0 0       if (@{$self->{posts}} && $row{post_time} <= $self->{posts}[-1]->{post_time}) {
  0            
878 0           $row{post_time} = $self->{posts}[-1]->{post_time} + 1
879             }
880              
881             ## fill some @{$self->{topics}} and @{$self->{vote_desc}} fields ##
882             # just for the first page
883 0 0         if ($start_from == 0) {
884 0           for (@{$self->{topics}}) {
  0            
885 0 0         if ($_->{topic_id} == $topic_id) {
886             # topic_first_post_id
887 0 0         $_->{topic_first_post_id} = $row{post_id}
888             unless $_->{topic_first_post_id};
889             # vote_start and topic_time
890 0 0         $v_row{vote_start} = $_->{topic_time} = $row{post_time}
891             unless $_->{topic_time};
892 0           last;
893             }
894             }
895             }
896              
897             # @{$self->{posts_text}}
898 0           my %t_row;
899             # post_subject
900 0           $token = $parse->get_token;
901 0   0       $token = $parse->get_token until $token->is_text
902             && $token->as_is =~ /\S: (.*)$/;
903 0           $t_row{post_subject} = $1;
904             # post_id
905 0           $t_row{post_id} = $row{post_id};
906             # bbcode_uid
907 0           $t_row{bbcode_uid} = $self->{bbcode_uid};
908             # post_text
909 0           $token = $parse->get_token until $token->is_start_tag('tr');
910 0           $token = $parse->get_token;
911 0           $token = $parse->get_token until $token->is_start_tag('tr');
912 0           my $text;
913             my $tr_count; # keep a track of
914 0           while ($token = $parse->get_token) {
915 0 0         $tr_count++ if $token->is_start_tag('tr');
916 0 0         $tr_count-- if $token->is_end_tag('tr');
917 0 0         last if $tr_count < 0;
918 0           $text .= $token->as_is;
919             }
920             ## bbcode
921 0           $text = ${ $self->html_to_bbcode(\$text, 1) };
  0            
922             # take care of HTML entities
923 0           $text = decode_entities($text);
924 0           $text = encode_entities($text);
925              
926 0           $t_row{post_text} = $text;
927             # poster_id
928 0           $token = $parse->get_token until $token->is_start_tag('table');
929 0           while ($token = $parse->get_token) {
930 0 0         last if $token->is_end_tag('table');
931 0 0 0       if ($token->is_start_tag('a') && $token->get_attr('href')
932             =~ /viewprofile.*u=(\d+)/) {
933 0           $row{poster_id} = $1;
934 0           last;
935             }
936             }
937             # anonymous?
938 0 0         unless ( exists $row{poster_id}) {
939 0           $row{poster_id} = -1;
940 0           $row{post_username} = $post_username;
941             }
942              
943 0           push @{$self->{posts}}, \%row;
  0            
944 0           push @{$self->{posts_text}}, \%t_row;
  0            
945 0 0 0       if ($self->{db_insert} && ++$rows == $self->{max_rows}) {
946 0           $rows = 0;
947             # keep the last post
948 0           my $lastpost = pop @{$self->{posts}};
  0            
949 0           $self->insert_array($self->{posts}, "posts");
950 0           @{$self->{posts}} = ();
  0            
951             # put it back
952 0           push @{$self->{posts}}, $lastpost;
  0            
953              
954 0           $self->insert_array($self->{posts_text}, "posts_text");
955 0           @{$self->{posts_text}} = ();
  0            
956             }
957             }
958             }
959              
960             sub update_users_raw {
961 0     0 0   my $self = shift;
962 0           $self->{db_insert} = 0;
963 0           my $page = -1;
964 0           my @new;
965             # max user_id
966 0           my $sth = $self->{dbh}->prepare("SELECT MAX(user_regdate) AS regdate FROM $self->{db_prefix}" . "users");
967 0           $sth->execute;
968 0           $sth->bind_columns(\my ($max_regdate));
969 0           $sth->fetch;
970 0           MLOOP: while (1) {
971 0           @{$self->{users}} = ();
  0            
972 0           $self->get_users($page);
973 0 0         last unless @{$self->{users}};
  0            
974 0           for (reverse @{$self->{users}}) {
  0            
975 0 0         if ($_->{user_regdate} < $max_regdate) { # get out
    0          
976 0           last MLOOP;
977             } elsif ($_->{user_regdate} == $max_regdate) {
978 0           $sth = $self->{dbh}->prepare("SELECT user_id, username FROM $self->{db_prefix}"
979             . "users WHERE user_regdate=$max_regdate");
980 0           $sth->execute;
981 0           $sth->bind_columns(\my ($user_id, $username));
982 0           while ($sth->fetch) {
983 0 0 0       if ($_->{user_id} == $user_id && $user_id != 1) {
984 0 0         if ($_->{username} ne $username) {
985             # this user was deleted
986 0           $self->{dbh}->do("DELETE FROM $self->{db_prefix}" . "users WHERE user_id=$user_id");
987 0           $self->{dbh}->do("UPDATE $self->{db_prefix}"
988             . "posts SET poster_id=-1, post_username="
989             . $self->{dbh}->quote($_->{username})
990             . " WHERE poster_id=$user_id");
991 0           push @new, $_;
992             }
993 0           last;
994             }
995             }
996             } else { # insert
997 0           push @new, $_;
998             }
999             }
1000 0           $page--;
1001             }
1002 0           $self->insert_array(\@new, 'users');
1003 0           $self->create_groups;
1004             }
1005              
1006             # wrapper for update_users_raw() that retries in case of errors
1007             sub update_users {
1008 0     0 1   my $self = shift;
1009 0           for (1..$self->{max_tries}) {
1010 0 0         eval {
1011 0           $self->update_users_raw(@_);
1012 0           1;
1013             } and last;
1014 0           print "failed (try $_ out of $self->{max_tries})\n";
1015 0           sleep(1)
1016             }
1017             }
1018              
1019             sub update_topics {
1020 0     0 0   my $self = shift;
1021 0           my ($forum_id) = @_;
1022 0           my @modified_topics;
1023 0           my $page = 0;
1024 0           PAGE: while (1) {
1025 0           @{$self->{topics}} = ();
  0            
1026 0           $self->get_topics($forum_id, $page);
1027 0 0         last unless @{$self->{topics}};
  0            
1028 0           for (@{$self->{topics}}) {
  0            
1029 0           my $sth = $self->{dbh}->prepare("SELECT post_time FROM $self->{db_prefix}"
1030             . "posts WHERE post_id=" . $_->{topic_last_post_id});
1031 0           $sth->execute;
1032 0           $sth->bind_columns(\my $post_time);
1033 0 0         if ($sth->fetch) {
1034             # we have the last_post_id, but it can be a new post with the id of a deleted one
1035 0 0         if ($post_time >= $self->{last_timestamp}) {
    0          
1036 0           push @modified_topics, $_;
1037             } elsif ($_->{topic_type} == 0) {
1038             # normal topic - it won't appear on more pages
1039 0           last PAGE;
1040             }
1041             } else {
1042             # new topic
1043 0           push @modified_topics, $_;
1044             }
1045             }
1046 0           $page++;
1047             }
1048 0           @{$self->{topics}} = @modified_topics;
  0            
1049             }
1050              
1051             sub update_topics_insert {
1052 0     0 0   my $self = shift;
1053 0           my ($t) = @_;
1054 0 0         unless ($self->{update_overwrite}) {
1055 0           my $sth = $self->{dbh}->prepare("SELECT topic_title FROM $self->{db_prefix}"
1056             . "topics WHERE topic_id=" . $t->{topic_id});
1057 0           $sth->execute;
1058 0           $sth->bind_columns(\my $topic_title);
1059 0 0 0       if ($sth->fetch && $topic_title ne $t->{topic_title}) {
1060             # it was probably deleted, create a new one
1061 0           my $topic_id;
1062 0           my $sth = $self->{dbh}->prepare("SELECT MAX(topic_id) FROM $self->{db_prefix}" . "topics");
1063 0           $sth->execute;
1064 0           $sth->bind_columns(\$topic_id);
1065 0           $sth->fetch;
1066 0           $topic_id++;
1067 0           $self->{dbh}->do("REPLACE $self->{db_prefix}" . "topics_trans "
1068             . "SET new=$topic_id, orig=" . $t->{topic_id});
1069 0           $t->{topic_id} = $topic_id;
1070             }
1071             }
1072 0           $self->insert_array([$t], 'topics');
1073             }
1074              
1075             sub update_posts {
1076 0     0 0   my $self = shift;
1077 0           my ($topic_id) = @_;
1078 0           my @new_posts;
1079             my @new_posts_text;
1080 0           my $page = -1;
1081 0           my $last_post_id; # that was used for a post
1082 0           my $sth = $self->{dbh}->prepare("SELECT MAX(post_id) FROM $self->{db_prefix}" . "posts");
1083 0           $sth->execute;
1084 0           $sth->bind_columns(\$last_post_id);
1085 0           $sth->fetch;
1086              
1087 0           PAGE: while (1) {
1088 0           @{$self->{posts}} = ();
  0            
1089 0           @{$self->{posts_text}} = ();
  0            
1090 0           $self->get_posts($topic_id, $page);
1091 0 0         last unless @{$self->{posts}};
  0            
1092             ##$self->print_AoH($self->{posts});
1093 0           POST: for (reverse 0..$#{$self->{posts}}) {
  0            
1094 0           my $sth;
1095 0 0         last PAGE if $self->{posts}[$_]{post_time} < $self->{last_timestamp};
1096             # don't get the posts that appeared betwen scraping the topic
1097             # and scraping the posts, because it will mess up
1098             # our last_timestamp at the next update
1099 0           for my $t (@{$self->{topics}}) {
  0            
1100 0 0         if ($t->{topic_id} == $topic_id) {
1101 0 0         next POST if $self->{posts}[$_]{post_id} > $t->{topic_last_post_id};
1102 0           last;
1103             }
1104             }
1105              
1106 0           my ($in_table, $in_orig, $in_new);
1107 0           my $new_id;
1108              
1109 0           $sth = $self->{dbh}->prepare("SELECT post_id FROM $self->{db_prefix}"
1110             . "posts WHERE post_id=" . $self->{posts}[$_]{post_id});
1111 0           $sth->execute;
1112 0 0         if ($sth->fetch) {
1113             # we have that id
1114 0           $in_table = 1;
1115             }
1116 0 0         unless ($self->{update_overwrite}) {
1117 0           $sth = $self->{dbh}->prepare("SELECT new FROM $self->{db_prefix}"
1118             . "posts_trans WHERE new=" . $self->{posts}[$_]{post_id});
1119 0           $sth->execute;
1120 0 0         if ($sth->fetch) {
1121             # it's in the translation table, as a new id
1122 0           $in_new = 1;
1123             }
1124 0           $sth = $self->{dbh}->prepare("SELECT new FROM $self->{db_prefix}"
1125             . "posts_trans WHERE orig=" . $self->{posts}[$_]{post_id});
1126 0           $sth->execute;
1127 0           $sth->bind_columns(\$new_id);
1128 0 0         if ($sth->fetch) {
1129 0           $in_orig = 1;
1130             }
1131             }
1132              
1133 0 0         if ($in_table) {
1134             # could be already scraped, check post_text
1135 0           my $against_id = $self->{posts}[$_]{post_id};
1136 0 0         if ($in_orig) {
1137             # could be deleted, check against new_id
1138 0           $against_id = $new_id;
1139             }
1140 0 0 0       unless ($in_new && ! $in_orig) {
1141             # compare text
1142 0           $sth = $self->{dbh}->prepare("SELECT post_text FROM $self->{db_prefix}"
1143             . "posts_text WHERE post_id=$against_id");
1144 0           $sth->execute;
1145 0           $sth->bind_columns(\my $post_text);
1146 0 0         if ($sth->fetch) {
1147             # already scraped
1148 0 0         last PAGE if $post_text eq $self->{posts_text}[$_]{post_text};
1149             }
1150             }
1151 0 0         unless ($self->{update_overwrite}) {
1152             # set and record a new post_id
1153 0           $last_post_id++;
1154 0           $self->{dbh}->do("REPLACE $self->{db_prefix}" . "posts_trans "
1155             . "SET new=$last_post_id, orig=" . $self->{posts}[$_]{post_id});
1156 0           $self->{posts_text}[$_]{post_id} = $self->{posts}[$_]{post_id} = $last_post_id;
1157             }
1158             }
1159              
1160 0           push @new_posts, $self->{posts}[$_];
1161 0           push @new_posts_text, $self->{posts_text}[$_];
1162             }
1163 0           $page--;
1164             }
1165             ##$self->print_AoH(\@new_posts);
1166 0           @{$self->{posts}} = @new_posts;
  0            
1167 0           @{$self->{posts_text}} = @new_posts_text;
  0            
1168             }
1169              
1170             sub update_posts_insert {
1171 0     0 0   my $self = shift;
1172 0           my $sth;
1173              
1174 0           for (@{$self->{posts}}) {
  0            
1175 0 0         unless ($self->{update_overwrite}) {
1176             # coordinate topic_id
1177 0           my $new_topic_id;
1178 0           $sth = $self->{dbh}->prepare("SELECT new FROM $self->{db_prefix}"
1179             . "topics_trans WHERE orig=" . $_->{topic_id});
1180 0           $sth->execute;
1181 0           $sth->bind_columns(\$new_topic_id);
1182 0 0         if ($sth->fetch) {
1183 0           $_->{topic_id} = $new_topic_id;
1184             }
1185             }
1186              
1187             }
1188              
1189 0           $self->insert_array($self->{posts}, 'posts');
1190 0           $self->insert_array($self->{posts_text}, 'posts_text');
1191             }
1192              
1193             ####################
1194             # helper functions #
1195             ####################
1196              
1197             sub empty_tables {
1198 0     0 1   my $self = shift;
1199 0           for (@{$self->{db_empty}}) {
  0            
1200 0 0         if ($_ eq 'users') {
    0          
    0          
1201 0           my $sth = $self->{dbh}->prepare("SELECT * FROM $self->{db_prefix}" . "users WHERE user_id=1");
1202 0           $sth->execute;
1203 0 0         $self->{dbh}->do("UPDATE $self->{db_prefix}" . "users SET user_id=1 WHERE user_id=2")
1204             unless $sth->fetch;
1205 0           $self->{dbh}->do("DELETE FROM $self->{db_prefix}" . "users WHERE user_id!=1 AND user_id!=-1");
1206             } elsif($_ eq 'groups') {
1207 0           $self->{dbh}->do("DELETE FROM $self->{db_prefix}" . "groups WHERE group_id>2");
1208             } elsif($_ eq 'user_group') {
1209 0           $self->{dbh}->do("DELETE FROM $self->{db_prefix}" . "user_group WHERE group_id>2");
1210             } else {
1211 0           $self->{dbh}->do("DELETE FROM " . $self->{db_prefix} . $_);
1212             }
1213             }
1214             }
1215              
1216             # $_[0]=$array_ref, $_[1]=$table
1217             sub insert_array {
1218 0     0 0   my $self = shift;
1219 0           my ($aref, $table) = @_;
1220 0           for my $row (@$aref) {
1221 0           my $query;
1222 0           my @cols = keys %$row;
1223 0           for (my $i = 0; $i < @cols; $i++) {
1224 0 0         $query .= ',' if $i;
1225 0           $query .= "$cols[$i]=" . $self->{dbh}->quote($$row{$cols[$i]});
1226             }
1227 0           $query = "INSERT $self->{db_prefix}$table SET " . $query . " ON DUPLICATE KEY UPDATE " . $query;
1228 0           eval {
1229 0           $self->{dbh}->do($query);
1230             };
1231 0 0         croak ("$query\n$@\n") if $@;
1232             ##print "$query\n";
1233             }
1234             }
1235              
1236             # $_[0]=$mech
1237             # returns an array ref with page numbers
1238             sub number_of_pages {
1239 0     0 0   my $self = shift;
1240 0           my ($mech, $type) = @_;
1241 0           my %page;
1242 0           $page{0} = 1;
1243 0           my $success;
1244 0           (my $url_ident = $mech->uri) =~ s%.*/(.*?)\?.*%$1%;
1245 0           $success = $mech->find_all_links(url_regex => qr/^${url_ident}.*start=\d+/);
1246 0 0 0       if (@$success) {
    0 0        
    0          
1247 0           for(@$success) {
1248 0           $_->url =~ /start=(\d+)(\D|$)/;
1249 0           $page{$1} = 1;
1250             #print $_->url . " : '$1'\n"
1251             }
1252             } elsif($self->{alternative_page_number_regex_forum} ne qr// && $type eq 'forum') {
1253 0           $success = $mech->find_all_links(url_regex => $self->{alternative_page_number_regex_forum});
1254 0 0         if (@$success) {
1255 0           for(@$success) {
1256 0           $_->url =~ $self->{alternative_page_number_regex_forum};
1257 0           $page{$1} = 1;
1258             }
1259             }
1260             } elsif($self->{alternative_page_number_regex_topic} ne qr// && $type eq 'topic') {
1261 0           $success = $mech->find_all_links(url_regex => $self->{alternative_page_number_regex_topic});
1262 0 0         if (@$success) {
1263 0           for(@$success) {
1264 0           $_->url =~ $self->{alternative_page_number_regex_topic};
1265 0           $page{$1} = 1;
1266             }
1267             }
1268             }
1269             # fill in missing pages
1270 0           my @page_keys = sort {$a <=> $b} keys %page;
  0            
1271 0 0         if (scalar(@page_keys) > 1) {
1272 0           my $per_page = $page_keys[1] - $page_keys[0];
1273 0           for(my $p=$page_keys[0]; $p<=$page_keys[-1]; $p+=$per_page) {
1274 0           $page{$p} = 1;
1275             }
1276             }
1277             # return array ref
1278 0           [sort {$a <=> $b} keys %page];
  0            
1279             }
1280              
1281             sub forum_url_for_page {
1282 0     0 0   my $self = shift;
1283 0           my ($url, $forum_id, $page) = @_;
1284              
1285 0           $url =~ s/&start=\d+//;
1286 0           $url .= "&start=$page";
1287 0           return $url;
1288             }
1289              
1290             sub topic_url_for_page {
1291 0     0 0   my $self = shift;
1292 0           my ($url, $topic_id, $page) = @_;
1293              
1294 0           $url =~ s/&start=\d+//;
1295 0           $url .= "&start=$page";
1296 0           return $url;
1297             }
1298              
1299             sub memberlist_url_for_page {
1300 0     0 0   my $self = shift;
1301 0           my ($url, $page) = @_;
1302              
1303 0           $url =~ s/&start=\d+//;
1304 0           $url .= "&start=$page";
1305 0           return $url;
1306             }
1307              
1308             # $_[0]=string_ref, $_[1]=$prepare_html
1309             sub html_to_bbcode {
1310 0     0 0   my $self = shift;
1311 0           my ($html, $prepare_html) = @_;
1312 0 0         if ($prepare_html) {
1313             ## discard excess whitespace
1314 0           $$html =~ s/\s{2,}/ /g; # trim spaces
1315 0           $$html =~ s/(>|^)\s+(<|$)/$1$2/smg; # delete whitespace between tags
1316 0           $$html =~ s/\n//g; # no need for that
1317             ## replace
with \n
1318 0           $$html =~ s(
)(\n)isg;
1319             }
1320 0           my $text;
1321             my @close_tag; # push and pop them as they come along (LIFO)
1322 0           my $parse;
1323 0           $parse = HTML::TokeParser::Simple->new( $_[0] );
1324 0           $parse->unbroken_text( 1 );
1325 0           my $token;
1326 0           while ($token = $parse->get_token) {
1327 0 0 0       if ($token->is_start_tag('span')) {
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1328 0 0         if ($token->get_attr('style') eq 'font-weight: bold') {
    0          
    0          
    0          
    0          
1329 0           $text .= "[b:$self->{bbcode_uid}]";
1330 0           push @close_tag, "[/b:$self->{bbcode_uid}]";
1331             } elsif ($token->get_attr('style') eq 'font-style: italic') {
1332 0           $text .= "[i:$self->{bbcode_uid}]";
1333 0           push @close_tag, "[/i:$self->{bbcode_uid}]";
1334             } elsif ($token->get_attr('style') eq 'text-decoration: underline') {
1335 0           $text .= "[u:$self->{bbcode_uid}]";
1336 0           push @close_tag, "[/u:$self->{bbcode_uid}]";
1337             } elsif ($token->get_attr('style') =~ 'color: (.*)$') {
1338 0           $text .= "[color=$1:$self->{bbcode_uid}]";
1339 0           push @close_tag, "[/color:$self->{bbcode_uid}]";
1340             } elsif ($token->get_attr('style') =~ 'font-size: (\d*)px') {
1341 0           $text .= "[size=$1:$self->{bbcode_uid}]";
1342 0           push @close_tag, "[/size:$self->{bbcode_uid}]";
1343             } else { # some other we don't care about
1344 0           push @close_tag, "";
1345             }
1346             } elsif ($token->is_end_tag('span')) {
1347 0           $text .= pop @close_tag;
1348             } elsif ($token->is_start_tag('table')) { # quote or code
1349             #print "|>", $parse->peek(7), "<|\n";
1350 0           $token = $parse->get_token until $token->is_start_tag('td');
1351             #$token = $parse->get_token until $token->is_text && $token->as_is =~ /^(.*?) ?$self->{quote_string}:$/s;
1352 0           my $author = '';
1353 0           while($token = $parse->get_token) {
1354 0 0 0       if($token->is_text && $token->as_is =~ /^(.*?) ?$self->{quote_string}:$/s) {
    0          
1355             #print $token->as_is, "\n";
1356 0           $author = $1;
1357 0           last;
1358             } elsif($token->is_end_tag('td')) {
1359 0           last;
1360             }
1361             }
1362             #print $author, "\n";
1363 0           $token = $parse->get_token until $token->is_start_tag('td');
1364             #print "test\n";
1365 0 0         if ($token->get_attr('class') eq 'quote') {
    0          
1366 0 0         if ($author eq '') {
1367 0           $text .= "[quote:$self->{bbcode_uid}]";
1368             } else {
1369 0           $text .= "[quote:$self->{bbcode_uid}=\"$author\"]";
1370             }
1371 0           push @close_tag, "[/quote:$self->{bbcode_uid}]";
1372             } elsif ($token->get_attr('class') eq 'code') {
1373 0           $text .= "[code:$self->{bbcode_uid}]";
1374 0           push @close_tag, "[/code:$self->{bbcode_uid}]";
1375             }
1376 0           my $counter;
1377             my $in_table;
1378 0           while ($token = $parse->get_token) {
1379 0 0         $counter++ if $token->is_start_tag('td');
1380 0 0         $counter-- if $token->is_end_tag('td');
1381 0 0         last if $counter < 0;
1382 0           $in_table .= $token->as_is;
1383             }
1384             # catch the bbcode inside
1385 0           $text .= ${ $self->html_to_bbcode(\$in_table) };
  0            
1386 0           $text .= pop @close_tag;
1387             } elsif ($token->is_start_tag('ul')) {
1388 0           $text .= "[list:$self->{bbcode_uid}]";
1389 0           push @close_tag, "[/list:u:$self->{bbcode_uid}]";
1390             } elsif ($token->is_end_tag('ul')) {
1391 0           $text .= pop @close_tag;
1392             } elsif ($token->is_start_tag('ol')
1393             && $token->get_attr('type') eq '1') {
1394 0           $text .= "[list=1:$self->{bbcode_uid}]";
1395 0           push @close_tag, "[/list:o:$self->{bbcode_uid}]";
1396             } elsif ($token->is_start_tag('ol')
1397             && $token->get_attr('type') eq 'a') {
1398 0           $text .= "[list=a:$self->{bbcode_uid}]";
1399 0           push @close_tag, "[/list:o:$self->{bbcode_uid}]";
1400             } elsif ($token->is_end_tag('ol')) {
1401 0           $text .= pop @close_tag;
1402             } elsif ($token->is_start_tag('li')) {
1403 0           $text .= "[*:$self->{bbcode_uid}]";
1404             } elsif ($token->is_tag('img')) {
1405             # check for smiles
1406 0           my $is_smile;
1407 0           my $src = $token->get_attr('src');
1408 0           for (keys %{$self->{smiles}}) {
  0            
1409 0 0         if ( $src =~ /\/$_\.gif/ ) {
1410 0           $text .= $self->{smiles}{$_};
1411 0           $is_smile = 1;
1412 0           last;
1413             }
1414             }
1415 0 0         unless ($is_smile) {
1416             # a simple image
1417 0           $text .= "[img:$self->{bbcode_uid}]"
1418             . $token->get_attr('src')
1419             . "[/img:$self->{bbcode_uid}]";
1420             }
1421             } elsif ($token->is_start_tag('a')) {
1422 0 0         if ($token->get_attr('href') =~ /mailto:/) {
1423 0           push @close_tag, "";
1424 0           next;
1425             }
1426 0           $text .= "[url="
1427             . $token->get_attr('href')
1428             . "]";
1429 0           push @close_tag, "[/url]";
1430             } elsif ($token->is_end_tag('a')) {
1431 0           $text .= pop @close_tag;
1432             } elsif ($token->is_text) {
1433 0           $text .= $token->as_is;
1434             }
1435             }
1436 0           \$text;
1437             }
1438              
1439             # $_[0]=$string, $_[1]=$date_format_regex, $_[2]=$date_pos_arrray-ref
1440             sub parse_date {
1441 0     0 0   my $self = shift;
1442 0           my ($str, $date_format, $date_pos) = @_;
1443 0           my %date_vars;
1444             my %month_number;
1445 0           for( my $i=0; $i<@{$self->{months}}; $i++ ) {
  0            
1446 0           $month_number{$self->{months}[$i]} = $i;
1447             }
1448              
1449 0           $_ = $str;
1450 0 0         return 0 unless /$date_format/i;
1451 0           my @res = /$date_format/i;
1452 0           for (my $i = 0; $i < @res; $i++) {
1453 0           $date_vars{$$date_pos[$i]} = $res[$i];
1454             }
1455              
1456             # strip leading zero
1457 0           for (qw(seconds minutes hour day_of_month)) {
1458 0 0         $date_vars{$_} =~ s/^0(\d)/$1/ if exists $date_vars{$_};
1459             }
1460              
1461             # AM/PM
1462 0 0         if (exists $date_vars{am_pm}) {
1463 0 0 0       $date_vars{hour} += 12 if $date_vars{am_pm} =~ /pm/i
1464             && $date_vars{hour} != 12;
1465 0 0 0       $date_vars{hour} -= 12 if $date_vars{am_pm} =~ /am/i
1466             && $date_vars{hour} == 12;
1467             }
1468             # month name
1469 0 0         $date_vars{mon} = $month_number{lc substr($date_vars{month_name}, 0, 3)}
1470             if exists $date_vars{month_name};
1471             # month
1472 0 0         $date_vars{mon} = $date_vars{month} - 1 if exists $date_vars{month};
1473             # get rid of warnings
1474 0           for (qw(seconds minutes day_of_month mon year)) {
1475 0 0         $date_vars{$_} = 0 unless exists $date_vars{$_};
1476             }
1477 0 0         $date_vars{hour} = 12 unless exists $date_vars{hour};
1478             # return timestamp
1479 0           timelocal( $date_vars{seconds}, $date_vars{minutes}, $date_vars{hour},
1480             $date_vars{day_of_month}, $date_vars{mon}, $date_vars{year} );
1481             }
1482              
1483             # $_[0]=$array_ref
1484             sub print_AoH {
1485 0     0 0   my $self = shift;
1486 0           my $AoHr = $_[0];
1487 0           print scalar @$AoHr, " elements:\n";
1488 0           for my $row (@$AoHr) {
1489 0           print "==>";
1490 0           for (sort keys %$row) {
1491 0           print "\t$_ => \"$row->{$_}\"\n";
1492             }
1493 0           print "\n";
1494             }
1495             }
1496              
1497             sub update_forum_first_last_post {
1498 0     0 0   my $self = shift;
1499 0           my ($forum_id) = @_;
1500 0           for (qw(ASC DESC)) {
1501 0           $self->{dbh}->do( "UPDATE $self->{db_prefix}" . "forums SET forum_last_post_id="
1502             . "(SELECT p.post_id FROM $self->{db_prefix}" . "posts p, "
1503             . "$self->{db_prefix}" . "topics t "
1504             . "WHERE p.topic_id = t.topic_id "
1505             . "AND t.forum_id =$forum_id "
1506             . "ORDER BY p.post_time $_, p.post_id $_ "
1507             . "LIMIT 1) WHERE forum_id=$forum_id");
1508             }
1509             }
1510              
1511             sub get_last_timestamp {
1512 0     0 0   my $self = shift;
1513 0           my $sth = $self->{dbh}->prepare("SELECT MAX(post_time) FROM $self->{db_prefix}" . "posts");
1514 0           $sth->execute;
1515 0           $sth->bind_columns(\$self->{last_timestamp});
1516 0           $sth->fetch;
1517             }
1518              
1519             sub get_new_admin {
1520 0     0 0   my $self = shift;
1521 0           my $sth = $self->{dbh}->prepare("SELECT user_id, username FROM $self->{db_prefix}" . "users ORDER BY user_id DESC LIMIT 1");
1522 0           $sth->execute;
1523 0           $sth->bind_columns(\$self->{new_admin_id}, \$self->{new_admin_username});
1524 0           $sth->fetch;
1525             }
1526              
1527             sub create_groups {
1528 0     0 0   my $self = shift;
1529 0           my $sth = $self->{dbh}->prepare("SELECT user_id FROM $self->{db_prefix}users WHERE user_id NOT IN ( SELECT user_id FROM $self->{db_prefix}user_group )");
1530 0           $sth->execute;
1531 0           my @row;
1532 0           while(@row = $sth->fetchrow_array) {
1533             #print "$row[0]\n";
1534 0           my $gsth = $self->{dbh}->prepare("INSERT INTO $self->{db_prefix}groups SET group_description='Personal User'");
1535 0           $gsth->execute;
1536 0           my $group_id = $gsth->{mysql_insertid};
1537 0           my $ugsth = $self->{dbh}->prepare("INSERT INTO $self->{db_prefix}user_group SET group_id=?, user_id=?, user_pending=0");
1538 0           $ugsth->execute($group_id, $row[0]);
1539             }
1540             }
1541              
1542             sub compute_url {
1543 0     0 0   my $self = shift;
1544 0           my ($url1, $url2) = @_;
1545 0           my $url;
1546 0           ($url = $self->{mmech}->uri) =~ s%^(.*)/.*?(\?)|$%$1/$url1$2%;
1547 0 0         if ($url =~ /\?/) {
1548 0           $url .= "&";
1549             } else {
1550 0           $url .= "?";
1551             }
1552 0           $url .= $url2;
1553             }
1554              
1555             sub reaper {
1556 0     0 0   while (waitpid(-1, WNOHANG) > 0) {
1557 0           $children--;
1558             }
1559 0           $SIG{CHLD} = \&reaper;
1560             }
1561             $SIG{CHLD} = \&reaper;
1562              
1563             # takes a function reference as argument
1564             # remember to "wait for 1..$children;" after the loop
1565             sub parallelize {
1566 0     0 0   my $self = shift;
1567 0           my ($func_ref) = @_;
1568 0 0         if($self->{max_children} < 2) {
1569             # avoid forking when parallelism is not requested (workaround for a windoze bug)
1570 0           &$func_ref;
1571             } else {
1572 0 0         if ($children < $self->{max_children}) { # fork a subprocess
1573 0 0         if (my $pid = fork) {
1574             # parent
1575 0           $children++;
1576 0 0         if ($children == $self->{max_children}) {
1577 0           wait;
1578 0           $children--;
1579             }
1580             } else {
1581             # child
1582 0 0         croak "can't fork" if undef $pid;
1583             # the db link was destroyed by forking. create it again
1584 0           $self->{dbh}{InactiveDestroy} = 1;
1585 0           $self->{dbh} = DBI->connect("DBI:mysql:database=$self->{db_database};host=$self->{db_host};mysql_compression=$self->{db_compression}",
1586             $self->{db_user}, $self->{db_passwd}, {AutoCommit => 1, RaiseError => 1});
1587             # run function
1588 0           &$func_ref;
1589 0           exit;
1590             }
1591             }
1592             }
1593             }
1594              
1595             #########################
1596             # integrating functions #
1597             #########################
1598              
1599             sub scrape_forum_common_raw {
1600 0     0 0   my $self = shift;
1601 0 0         if ($self->{verbose}) {
1602 0           print "getting categories and forums...";
1603             }
1604 0           $self->get_categories_and_forums();
1605 0 0         if ($self->{verbose}) {
1606 0           print "\n";
1607             }
1608 0           for (@{$self->{forums}}) {
  0            
1609 0 0         if ($self->{verbose}) {
1610 0           print "getting the topics from forum #", $_->{forum_id}, "\n";
1611             }
1612 0           $self->get_topics( $_->{forum_id} );
1613 0           for (@{$self->{topics}}) {
  0            
1614             $self->parallelize(
1615             sub {
1616 0     0     $self->get_posts( $_->{topic_id} );
1617 0           $self->insert_array([$_], "topics");
1618             }
1619 0           );
1620             }
1621 0           @{$self->{topics}} = ();
  0            
1622             }
1623             #wait for 1..$children;
1624 0           1 while waitpid(-1, WNOHANG)>0; # reaps childs
1625 0           $self->update_forum_first_last_post($_->{forum_id}) for @{$self->{forums}};
  0            
1626             }
1627              
1628             # wrapper for scrape_forum_common_raw() that retries in case of errors
1629             sub scrape_forum_common {
1630 0     0 1   my $self = shift;
1631 0           for (1..$self->{max_tries}) {
1632 0 0         eval {
1633 0           $self->scrape_forum_common_raw(@_);
1634 0           1;
1635             } and last;
1636 0           print "failed (try $_ out of $self->{max_tries})\n";
1637 0           sleep(1)
1638             }
1639             }
1640              
1641             sub update_forum_common_raw {
1642 0     0 0   my $self = shift;
1643 0           $self->{db_insert} = 0;
1644 0 0         if ($self->{verbose}) {
1645 0           print "getting categories and forums...";
1646             }
1647 0           $self->get_categories_and_forums();
1648 0 0         if ($self->{verbose}) {
1649 0           print "\n";
1650             }
1651 0           $self->insert_array($self->{categories}, 'categories');
1652 0           $self->insert_array($self->{forums}, 'forums');
1653 0           $self->get_last_timestamp();
1654 0           for (@{$self->{forums}}) {
  0            
1655 0 0         if ($self->{verbose}) {
1656 0           print "updating topics from forum #", $_->{forum_id}, "\n";
1657             }
1658 0           $self->update_topics($_->{forum_id});
1659 0           for (@{$self->{topics}}) {
  0            
1660 0           $self->update_posts($_->{topic_id});
1661 0           $self->update_topics_insert($_);
1662 0           $self->update_posts_insert();
1663             }
1664 0           @{$self->{topics}} = ();
  0            
1665 0           $self->update_forum_first_last_post($_->{forum_id});
1666             }
1667             }
1668              
1669             # wrapper for update_forum_common_raw() that retries in case of errors
1670             sub update_forum_common {
1671 0     0 1   my $self = shift;
1672 0           for (1..$self->{max_tries}) {
1673 0 0         eval {
1674 0           $self->update_forum_common_raw(@_);
1675 0           1;
1676             } and last;
1677 0           print "failed (try $_ out of $self->{max_tries})\n";
1678 0           sleep(1)
1679             }
1680             }
1681              
1682              
1683             1;
1684             __END__