comparison Ikariam.pm @ 79:9d92e8c12f58

rewrited the code in XPath.
author "Rex Tsai <chihchun@kalug.linux.org.tw>"
date Fri, 24 Oct 2008 21:46:33 +0800
parents 3d1784140009
children 7ab5fc8c847c
comparison
equal deleted inserted replaced
78:4120f560f214 79:9d92e8c12f58
10 options => { RaiseError => 1 }, 10 options => { RaiseError => 1 },
11 tables => ['cities', 'island', 'user'], 11 tables => ['cities', 'island', 'user'],
12 use_base => 'Class::DBI::SQLite', 12 use_base => 'Class::DBI::SQLite',
13 namespace => 'Ikariam', 13 namespace => 'Ikariam',
14 ); 14 );
15
16 package Ikariam::Extractor;
17 use strict;
18 use Data::Dumper;
19 use XML::LibXML;
20 use IO::Uncompress::Gunzip qw(gunzip $GunzipError) ;
21 use Carp;
22 use utf8;
23
24 sub new {
25 Carp::croak("Options should be key/value pairs, not hash reference") if ref($_[1]) eq 'HASH';
26
27 my($class, %conf) = @_;
28
29 my $self = bless {
30 doc => undef,
31 gzip => 1,
32 }, $class;
33
34 $self->{gzip} = $conf{'gzip'} if(defined($conf{'gzip'}));
35 $self->parse($conf{'content'}) if(defined($conf{'content'}));
36
37 return $self;
38 }
39
40 # parse($Content);
41 sub parse {
42 my ($self, $content) = @_;
43 my $string;
44 my $parser = XML::LibXML->new('1.0','utf-8');
45
46 if($self->{gzip} == 1) {
47 gunzip \$content => \$string
48 or die "gunzip failed: $GunzipError\n";
49 } else {
50 $string = $content;
51 }
52
53 $self->{doc} = $parser->parse_html_string ($string, { suppress_errors => 1 });
54 return;
55 }
56
57 # find($XPathQuery);
58 sub find {
59 my $self = shift;
60 my $query = shift;
61 my $out = [];
62
63 my $result = $self->{doc}->find($query);
64
65 return undef unless defined($result);
66
67 if ( $result->isa( 'XML::LibXML::NodeList' ) ) {
68 return undef if($result->size() == 0);
69 foreach ( @$result ) {
70 # $_ is XML::LibXML::Element, XML::LibXML::Node
71 my $literal = $_->to_literal() , "\n";
72 utf8::encode($literal);
73 # warn $_->toString(1) , "\n";
74 return $literal unless wantarray;
75 push( @$out, $literal);
76 }
77 } else {
78 Carp::croak("Unsupported data type");
79 }
80 # TODO
81 # XML::LibXML::Literal
82 # XML::LibXML::Boolean
83
84 return @$out;
85 }
86
87 1;
15 88
16 package Ikariam; 89 package Ikariam;
17 use strict; 90 use strict;
18 use Data::Dumper; 91 use Data::Dumper;
19 use LWP; 92 use LWP;
20 # use LWP::Debug qw(+ -conns -trace -debug); 93 # use LWP::Debug qw(+ -conns -trace -debug);
21 # use LWP::Debug qw(+trace); 94 # use LWP::Debug qw(+trace);
22 use HTTP::Cookies; 95 use HTTP::Cookies;
23 use WWW::Mechanize; 96 use WWW::Mechanize;
24 use HTML::TagParser; 97 use HTML::TagParser;
98 use XML::LibXML qw(:encoding);
25 use IO::Uncompress::Gunzip qw(gunzip $GunzipError) ; 99 use IO::Uncompress::Gunzip qw(gunzip $GunzipError) ;
100 use utf8;
26 101
27 sub new 102 sub new
28 { 103 {
29 my ($class, $server, $user, $pass) = @_; 104 my ($class, $server, $user, $pass) = @_;
30 105
345 sub checkResearch { 420 sub checkResearch {
346 my $self = shift; 421 my $self = shift;
347 my $cityId = shift; 422 my $cityId = shift;
348 423
349 my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=researchOverview&id=%s', $self->{server}, $cityId)); 424 my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=researchOverview&id=%s', $self->{server}, $cityId));
350 425
426 my @urls = Ikariam::Extractor->new(content => $res->content)->find('//ul[@class="explored"]//a/@href');
427 my $out = {};
428 foreach(@urls) {
429 if(/view=researchDetail&id=\d+&position=\d+&researchId=(\d+)$/) {
430 # we ignore the chinese name of technology researched.
431 @$out{$1} = 1;
432 }
433 }
434 return $out;
435 }
436
437 sub checkCity {
438 my $self = shift;
439 my $cityId = shift;
440
441 # search for goods
442 my $res = $self->{mech}->get(sprintf("http://%s/index.php?view=city&id=%d", $self->{server}, $cityId));
443 my $extractor = new Ikariam::Extractor(content => $res->content);
444
445 $self->{'cities'}->{$cityId}->{resources}->{gold} = $extractor->find('//span[@id="value_gold"]/text()');
446 $self->{'cities'}->{$cityId}->{resources}->{gold} =~ s/,//g;
447
448 $self->{'cities'}->{$cityId}->{name} = $extractor->find('//span[@class="city"]/text()');
449
450 $self->{'cities'}->{$cityId}->{construction} = defined($extractor->find('//*[@class="constructionSite"]')) ? 1 : 0;
451
452 # maxCapacity
453 my $page = $extractor->{doc}->toString(1);
454 if($page =~ /maxCapacity : {\s+wood: (\d+),\s+wine: (\d+),\s+marble: (\d+),\s+crystal: (\d+),\s+sulfur: (\d+)\s+}/s) {
455 $self->{'cities'}->{$cityId}->{maxCapacity}->{wood} = $1;
456 $self->{'cities'}->{$cityId}->{maxCapacity}->{wine} = $2;
457 $self->{'cities'}->{$cityId}->{maxCapacity}->{marble} = $3;
458 $self->{'cities'}->{$cityId}->{maxCapacity}->{crystal} = $4;
459 $self->{'cities'}->{$cityId}->{maxCapacity}->{sulfur} = $5;
460 }
461
462 foreach my $good (qw/wood wine marble crystal sulfur/) {
463 $self->{'cities'}->{$cityId}->{resources}->{$good} = $extractor->find(sprintf('//span[@id="value_%s"]', $good));
464 $self->{'cities'}->{$cityId}->{resources}->{$good} =~ s/,//g;
465 }
466
467 foreach my $i (0..14) {
468 my @buildings = $extractor->find(sprintf('//*[@id="position%s"]/@class', $i));
469 foreach my $building (@buildings) {
470 if (!($building =~ /buildingGround/) && !($building =~ /townhall/)) {
471 $self->{'cities'}->{$cityId}->{locations}[$i] = $building;
472
473 my $span;
474 if($building eq 'townHall') {
475 # this is stupid, the new vession give two cityTown information.
476 $span = ($extractor->find(sprintf('//*[@id="position%s"]//span[@class="textLabel"]/text()', $i)))[1];
477 } else {
478 $span = $extractor->find(sprintf('//*[@id="position%s"]//span[@class="textLabel"]/text()', $i));
479 }
480 my (undef, undef, $level) = split(/ /, $span);
481 $self->{'cities'}->{$cityId}->{buildings}->{$building} = $level;
482 }
483 }
484 }
485
486 $self->{'cities'}->{$cityId}->{transporters}->{avail} = $extractor->find('//span[@id="value_transAvail"]/text()');
487 $self->{'cities'}->{$cityId}->{transporters}->{sum} = $extractor->find('//span[@id="value_transSum"]/text()');
488 $self->{'cities'}->{$cityId}->{transporters}->{sum} =~ s/[\(|\)]//g;
489 $self->{'cities'}->{$cityId}->{maxActionPoints} = $extractor->find('//span[@id="value_maxActionPoints"]');;
490 }
491
492
493 sub checkMilitaryAdvisorMilitaryMovements {
494 my $self = shift;
495 # TODO
496 # http://s2.ikariam.tw/index.php?view=militaryAdvisorMilitaryMovements
497 my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=militaryAdvisorMilitaryMovements', $self->{server}));
498 }
499
500 sub checkMilitaryAdvisorCombatReports {
501 my $self = shift;
351 my $content; 502 my $content;
503 my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=militaryAdvisorCombatReports', $self->{server}));
352 gunzip \$res->content => \$content 504 gunzip \$res->content => \$content
353 or die "gunzip failed: $GunzipError\n"; 505 or die "gunzip failed: $GunzipError\n";
354 my $html = HTML::TagParser->new($content); 506
355 507 open(OUT,">foo.html");
356 my @elems = $html->getElementsByAttribute('class', 'explored'); 508 print OUT $content;
357 509 close(OUT);
358 my $out = {}; 510
359 foreach my $elem (@elems) { 511 # TODO
360 my @items = getElementsByTagName($elem, "a"); 512 # $self->{'cities'}->{$cityId}->{force}->{attacks} = $1 if($content =~ /敵人攻擊: (\d+)/);
361 foreach my $item (@items) { 513 # $self->{'cities'}->{$cityId}->{force}->{wars} = $1 if($content =~ /我方軍隊行程: (\d+)/);
362 if($item->getAttribute('href') =~ /view=researchDetail&id=\d+&position=\d+&researchId=(\d+)$/) { 514
363 @$out{$1} = $item->innerText(); 515 # list down reports.
516 # /index.php?view=militaryAdvisorReportView&amp;combatId=1887662
517 # //form[@id='finishedReports']//a
518 }
519
520 sub checkTownHall {
521 my $self = shift;
522 my $cityId = shift;
523
524 my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=townHall&id=%d', $self->{server}, $cityId));
525 my $extractor = new Ikariam::Extractor(content => $res->content);
526
527 # //div[@id='SatisfactionOverview']//div[@class='value']
528 # //div[@id='SatisfactionOverview']//div[@class='text']
529 $self->{"cities"}->{$cityId}->{happiness} = $extractor->find('//div[@id="SatisfactionOverview"]//div[@class="value"]');
530
531 # 取 SatisfactionOverview 中的其他值
532 # 一個城鎮的市民滿意度結合了多方面的因素
533 # check happiness
534 # Happiness = Basic bonuses (196 + Capital Bonus + Holiday Bonus(25)) +
535 # Wine (Tavern Base(12*level) +
536 # Tavern Bonus(80*step)) +
537 # Culture (Museum Base(20*level) +
538 # Cultural Goods Bonus(50*Cultural Goods)) -
539 # Population (population) -
540 # Corruption (Corruption rate * population)
541 #
542 # Growth Rate = Happiness * 0.02
543
544 # Space, 房屋數
545 $self->{'cities'}->{$cityId}->{"space"} = {};
546 $self->{'cities'}->{$cityId}->{"space"}->{'total'} = $extractor->find('//span[@class="value total"]/text()');
547 $self->{'cities'}->{$cityId}->{"space"}->{'occupied'} = $extractor->find('//span[@class="value occupied"]/text()');
548
549 my @values = $extractor->find('//div[@id="CityOverview"]//span[@class="value"]');
550 $self->{'cities'}->{$cityId}->{"growth"} = $values[0];
551 $self->{'cities'}->{$cityId}->{"incomegold"} = $values[1];
552
553 $self->{'cities'}->{$cityId}->{corruption} = $extractor->find('//li[@class="corruption"]//span[@title="目前腐敗程度"]');
554 $self->{'cities'}->{$cityId}->{corruption} =~ s/%//g;
555
556 my @citizens_type = qw/citizens woodworkers specialworkers scientists/;
557 @values = $extractor->find('//div[@id="PopulationGraph"]//span[@class="count"]');
558 foreach my $i (0..$#citizens_type)
559 {
560 $self->{'cities'}->{$cityId}->{'citizens'}->{$citizens_type[$i]} = $values[$i];
561 $self->{'cities'}->{$cityId}->{'citizens'}->{total} += $values[$i];
562 }
563 }
564
565
566 sub checkArmies
567 {
568 my $self = shift;
569 my $cityId = shift;
570 my %force_types;
571
572 $force_types{'army'} = [ qw/undef undef Slinger Swordsman Phalanx Ram Archer Catapult Gunsman Mortar SteamGiant Gyrocopter Bombardier Doctor Cook/ ];
573 $force_types{'fleet'} = [ qw/undef undef Ram-Ship BallistaShip Flamethrower CatapultShip MortarShip PaddleWheelRam DivingBoat/ ];
574
575 foreach my $x (qw/army fleet/) {
576 $self->{'cities'}->{$cityId}->{$x} = {};
577 my $res = $self->{mech}->get(sprintf('http://%s/index.php?view=cityMilitary-%s&id=%d', $self->{server}, $x, $cityId));
578 my @numbers = Ikariam::Extractor->new(content => $res->content)->find('//table//tr/td');
579 foreach my $j (0..$#{$force_types{$x}}) {
580 if ($numbers[$j] == '-') {
581 $self->{'cities'}->{$cityId}->{$x}->{$force_types{$x}[$j]} = 0;
582 } else {
583 $self->{'cities'}->{$cityId}->{$x}->{$force_types{$x}[$j]} = $numbers[$j];
364 } 584 }
365 } 585 }
366 } 586 }
367 return $out;
368 } 587 }
369 588
370 sub check 589 sub check
371 { 590 {
372 my $self = shift; 591 my $self = shift;
592
593 # MilitaryAdvisor
594 $self->checkMilitaryAdvisorMilitaryMovements();
595 $self->checkMilitaryAdvisorCombatReports();
373 596
374 # looking for cities 597 # looking for cities
375 foreach my $cityId (keys(%{$self->{'cities'}})) 598 foreach my $cityId (keys(%{$self->{'cities'}}))
376 { 599 {
377 # search for goods 600 $self->checkCity($cityId);
378 my $res = $self->{mech}->post(sprintf('http://%s/index.php', $self->{server}), [ 601 $self->checkTownHall($cityId);
379 action => 'header', 602 $self->checkArmies($cityId);
380 cityId => $cityId,
381 function => 'changeCurrentCity',
382 id => $cityId,
383 oldView => 'city',
384 ]);
385
386 my $content;
387 gunzip \$res->content => \$content
388 or die "gunzip failed: $GunzipError\n";
389
390 my $html = HTML::TagParser->new($content);
391 my @elems;
392
393 my ($elem) = $html->getElementsByAttribute("id", "value_gold");
394 $self->{'cities'}->{$cityId}->{resources}->{gold} = $elem->innerText();
395 $self->{'cities'}->{$cityId}->{resources}->{gold} =~ s/,//g;
396
397 # XXX
398 my ($elem) = $html->getElementsByAttribute("class", "city");
399 $self->{'cities'}->{$cityId}->{name} = $elem->innerText();
400
401 my ($elem) = $html->getElementsByAttribute("class", 'constructionSite');
402 $self->{'cities'}->{$cityId}->{construction} = 0;
403 $self->{'cities'}->{$cityId}->{construction} = 1 if(defined($elem));
404
405 # check goods
406 foreach my $good (qw/wood wine marble crystal sulfur/) {
407 my ($elem) = $html->getElementsByAttribute("id", "value_" . $good);
408 $self->{'cities'}->{$cityId}->{resources}->{$good} = $elem->innerText();
409 $self->{'cities'}->{$cityId}->{resources}->{$good} =~ s/,//g;
410 }
411
412 # search locations
413 foreach my $i (0..14) {
414 my ($elem) = $html->getElementsByAttribute("id", "position" . $i);
415 my $building = $elem->getAttribute('class');
416 if (!($building =~ /buildingGround/)) {
417 $self->{'cities'}->{$cityId}->{locations}[$i] = $building;
418 my $span = getElementsByAttribute($elem, "class", "textLabel");
419 my (undef, undef, $level) = split(/ /, $span->innerText());
420 $self->{'cities'}->{$cityId}->{buildings}->{$building} = $level;
421 }
422 }
423
424 # transporters
425 my ($elem) = $html->getElementsByAttribute("class", 'transAvail');
426 $self->{'cities'}->{$cityId}->{transporters}->{avail} = $elem->innerText();
427 my ($elem) = $html->getElementsByAttribute("class", 'transSum');
428 $self->{'cities'}->{$cityId}->{transporters}->{sum} = $elem->innerText();
429 $self->{'cities'}->{$cityId}->{transporters}->{sum} =~ s/\(//;
430 $self->{'cities'}->{$cityId}->{transporters}->{sum} =~ s/\)//;
431
432 $res = $self->{mech}->get(sprintf('http://%s/index.php?view=militaryAdvisorMilitaryMovements', $self->{server}, $cityId));
433 gunzip \$res->content => \$content
434 or die "gunzip failed: $GunzipError\n";
435
436 $self->{'cities'}->{$cityId}->{force}->{attacks} = $1 if($content =~ /敵人攻擊: (\d+)/);
437 $self->{'cities'}->{$cityId}->{force}->{wars} = $1 if($content =~ /我方軍隊行程: (\d+)/);
438 # if($content =~ /更新戰鬥報告: (\d+)/);
439 # if($content =~ /新的戰鬥報告: (\d+)/);
440
441 # sub checkTownHall {
442 $res = $self->{mech}->get(sprintf('http://%s/index.php?view=townHall&id=%d', $self->{server}, $cityId));
443 gunzip \$res->content => \$content
444 or die "gunzip failed: $GunzipError\n";
445
446 # check happiness
447 # Happiness = Basic bonuses (196 + Capital Bonus + Holiday Bonus(25)) +
448 # Wine (Tavern Base(12*level) +
449 # Tavern Bonus(80*step)) +
450 # Culture (Museum Base(20*level) +
451 # Cultural Goods Bonus(50*Cultural Goods)) -
452 # Population (population) -
453 # Corruption (Corruption rate * population)
454 #
455 # Growth Rate = Happiness * 0.02
456 $html = HTML::TagParser->new($content);
457
458 my @happiness = ("ecstatic", "happy", "neutral", "sad", "outraged");
459 foreach my $j (0..$#happiness) {
460 my ($elem) = $html->getElementsByAttribute("class", sprintf("happiness happiness_%s", $happiness[$j]));
461 if(defined($elem)) {
462 $self->{'cities'}->{$cityId}->{happiness} = $j;
463 $self->{'cities'}->{$cityId}->{happiness_text} = $happiness[$j];
464 }
465 }
466
467 # Space, 房屋數
468 $self->{'cities'}->{$cityId}->{"space"} = {};
469 foreach my $j (qw/occupied total/) {
470 my ($elem) = $html->getElementsByAttribute("class", sprintf("value %s", $j));
471 if(defined($elem)) {
472 $self->{'cities'}->{$cityId}->{"space"}->{$j} = $elem->innerText();
473 }
474 }
475
476 # Actions
477 # <span id="value_maxActionPoints">1</span>
478
479 # <li class="incomegold incomegold_negative">
480 # <li class="incomegold incomegold_positive">
481 # <span class="value">-178</span>
482
483 my ($elem) = $html->getElementsByAttribute("title", "目前腐敗程度");
484 if(defined($elem)) {
485 $self->{'cities'}->{$cityId}->{corruption} = $elem->innerText();
486 $self->{'cities'}->{$cityId}->{corruption} =~ s/%//g;
487 }
488
489 # countCiizens
490 my @citizens_type = qw/citizens woodworkers specialworkers scientists/;
491 @elems = $html->getElementsByAttribute('class', 'count');
492 $self->{'cities'}->{$cityId}->{'citizens'} = {};
493 $self->{'cities'}->{$cityId}->{'citizens'}->{total} = 0;
494
495 foreach my $i (0..$#citizens_type)
496 {
497 $self->{'cities'}->{$cityId}->{'citizens'}->{$citizens_type[$i]} = $elems[$i]->innerText();
498 $self->{'cities'}->{$cityId}->{'citizens'}->{total} += $elems[$i]->innerText();;
499 }
500
501 # }
502
503 $self->{'cities'}->{$cityId}->{'research'} = $self->checkResearch($cityId); 603 $self->{'cities'}->{$cityId}->{'research'} = $self->checkResearch($cityId);
504 604 }
505 # sub checkArmies { 605 print Dumper($self->{'cities'});
506 my %force_types;
507 $force_types{'army'} = [ qw/undef undef Slinger Swordsman Phalanx Ram Archer Catapult Gunsman Mortar SteamGiant Gyrocopter Bombardier Doctor Cook/ ];
508 $force_types{'fleet'} = [ qw/undef undef Ram-Ship BallistaShip Flamethrower CatapultShip MortarShip PaddleWheelRam DivingBoat/ ];
509 foreach my $x (qw/army fleet/)
510 {
511 $self->{'cities'}->{$cityId}->{$x} = {};
512 # search army
513 $res = $self->{mech}->get(sprintf('http://%s/index.php?view=cityMilitary-%s&id=%d', $self->{server}, $x, $cityId));
514 gunzip \$res->content => \$content
515 or die "gunzip failed: $GunzipError\n";
516
517 $html = HTML::TagParser->new($content);
518 @elems = $html->getElementsByTagName('td');
519 foreach my $j (0..$#{$force_types{$x}}) {
520 next if($force_types{$x}[$j] eq 'undef');
521 if($elems[$j]->innerText() == '-') {
522 $self->{'cities'}->{$cityId}->{$x}->{$force_types{$x}[$j]} = 0;
523 } else {
524 $self->{'cities'}->{$cityId}->{$x}->{$force_types{$x}[$j]} = $elems[$j]->innerText();
525 }
526 }
527 }
528 }
529 # print Dumper($self->{'cities'});
530 return $self->{'cities'}; 606 return $self->{'cities'};
531 } 607 }
532 608
533 sub logout 609 sub logout
534 { 610 {
542 618
543 my $res = $self->{mech}->post(sprintf("http://%s/index.php?action=loginAvatar&function=login", $self->{server}), [ 619 my $res = $self->{mech}->post(sprintf("http://%s/index.php?action=loginAvatar&function=login", $self->{server}), [
544 name => $self->{user}, 620 name => $self->{user},
545 password => $self->{pass}, 621 password => $self->{pass},
546 ]); 622 ]);
623
547 my $c; 624 my $c;
548 my $status = gunzip \$res->content => \$c 625 my $status = gunzip \$res->content => \$c
549 or die "gunzip failed: $GunzipError\n"; 626 or die "gunzip failed: $GunzipError\n";
550 627
551 if($c =~ /錯誤!/) 628 if($c =~ /錯誤!/)
552 { 629 {
553 die ("password error\n"); 630 die ("password error\n");
554 } else { 631 } else {
555 my $html = HTML::TagParser->new($c); 632 my $result = XML::LibXML->new('1.0','utf-8')
556 my @elems; 633 ->parse_html_string ($c, { suppress_errors => 1 })
557 634 ->find( '//option[@class="avatarCities coords"]' );
558 # XXX 635
559 @elems = $html->getElementsByAttribute("class", "avatarCities coords"); 636 if ( $result->isa( 'XML::LibXML::NodeList' ) ) {
560 foreach my $elem (@elems) { 637 foreach ( @$result ) {
561 # my cities 638 $self->{'cities'}->{$_->getAttribute('value')} = {};
562 $self->{'cities'}->{$elem->getAttribute('value')} = {}; 639 }
563 } 640 }
564 } 641 }
565 } 642 }
566 643
567 sub getElementsByTagName { 644 sub getElementsByTagName {