File Coverage

blib/lib/Games/EveOnline/API.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             package Games::EveOnline::API;
2             $Games::EveOnline::API::VERSION = '0.07';
3 1     1   1866 use Moo;
  1         2  
  1         9  
4              
5             =head1 NAME
6              
7             Games::EveOnline::API - A simple Perl wrapper around the EveOnline XML API.
8              
9             =head1 SYNOPSIS
10              
11             use Games::EveOnline::API;
12             my $eapi = Games::EveOnline::API->new();
13            
14             my $skill_groups = $eapi->skill_tree();
15             my $ref_types = $eapi->ref_types();
16             my $systems = $eapi->sovereignty();
17            
18             # The rest of the methods require authentication.
19             my $eapi = Games::EveOnline::API->new( user_id => '..', api_key => '..' );
20            
21             my $characters = $eapi->characters();
22             my $sheet = $eapi->character_sheet( character_id => $character_id );
23             my $in_training = $eapi->skill_in_training( character_id => $character_id );
24              
25             =head1 DESCRIPTION
26              
27             This module provides a Perl wrapper around the Eve-Online API, version 2.
28             The need for the wrapper arrises for two reasons. First, the XML that
29             is provided by the API is overly complex, at least for my taste. So, other
30             than just returning you a perl data representation of the XML, it also
31             simplifies the results.
32              
33             Only a couple of the methods provided by this module can be used straight
34             away. The rest require that you get a user_id (keyID) and api_key (vCode).
35              
36             =head1 A NOTE ON CACHING
37              
38             Most of these methods return a 'cached_until' value. I've no clue if this
39             is CCP telling you how long you should cache the information before you
40             should request it again, or if this is the point at which CCP will refresh
41             their cache of this information.
42              
43             Either way, it is good etiquet to follow the cacheing guidelines of a
44             provider. If you over-use the API I'm sure you'll eventually get blocked.
45              
46             =cut
47              
48 1     1   1834 use Types::Standard qw( Int Str );
  1         74482  
  1         12  
49 1     1   2013 use Type::Utils qw( class_type );
  1         4653  
  1         8  
50              
51 1     1   4491 use URI;
  1         4560  
  1         32  
52 1     1   990 use LWP::UserAgent qw();
  1         55388  
  1         26  
53 1     1   398 use XML::Simple qw();
  0            
  0            
54             use Carp qw( croak );
55              
56             =head1 ARGUMENTS
57              
58             =head2 user_id
59              
60             An Eve Online API user ID (also known as a keyID).
61              
62             =head2 api_key
63              
64             The key, as provided Eve Online, to access the API (also known
65             as a vCode).
66              
67             =head2 character_id
68              
69             Set the default C. Any methods that require
70             a characte ID, and are not given one, will use this one.
71              
72             =head2 api_url
73              
74             The URL that will be used to access the Eve Online API.
75             Defaults to L. Normally you
76             won't want to change this.
77              
78             =head2 ua
79              
80             The underlying L object. Default to a new one
81             with no special arguments. Override this if you want to, for
82             example, enable keepalive or an HTTP proxy.
83              
84             =cut
85              
86             has user_id => (is=>'ro', isa=>Int );
87             has api_key => (is=>'ro', isa=>Str );
88             has character_id => (is=>'ro', isa=>Int );
89             has api_url => (is=>'ro', isa=>Str, default=>'https://api.eveonline.com');
90             has ua => (is=>'lazy', isa=>class_type('LWP::UserAgent'));
91              
92             sub _build_ua {
93             return LWP::UserAgent->new;
94             }
95              
96             =head1 ANONYMOUS METHODS
97              
98             These methods may be called anonymously, without authentication.
99              
100             =head2 skill_tree
101              
102             my $skill_groups = $eapi->skill_tree();
103              
104             Returns a complex data structure containing the entire skill tree.
105             The data structure is:
106              
107             {
108             cached_until => $date_time,
109             $group_id => {
110             name => $group_name,
111             skills => {
112             $skill_id => {
113             name => $skill_name,
114             description => $skill_description,
115             rank => $skill_rank,
116             primary_attribute => $skill_primary_attribute,
117             secondary_attribute => $skill_secondary_attribute,
118             bonuses => {
119             $bonus_name => $bonus_value,
120             },
121             required_skills => {
122             $skill_id => $skill_level,
123             },
124             }
125             }
126             }
127             }
128              
129             =cut
130              
131             sub skill_tree {
132             my ($self) = @_;
133              
134             my $data = $self->_load_xml(
135             path => 'eve/SkillTree.xml.aspx',
136             );
137              
138             my $result = {};
139              
140             return $self->_get_error( $data ) if defined $data->{error};
141              
142             my $group_rows = $data->{result}->{rowset}->{row};
143             foreach my $group_id (keys %$group_rows) {
144             my $group_result = $result->{$group_id} ||= {};
145             $group_result->{name} = $group_rows->{$group_id}->{groupName};
146              
147             $group_result->{skills} = {};
148             my $skill_rows = $group_rows->{$group_id}->{rowset}->{row};
149             foreach my $skill_id (keys %$skill_rows) {
150             my $skill_result = $group_result->{skills}->{$skill_id} ||= {};
151             $skill_result->{name} = $skill_rows->{$skill_id}->{typeName};
152             $skill_result->{description} = $skill_rows->{$skill_id}->{description};
153             $skill_result->{rank} = $skill_rows->{$skill_id}->{rank};
154             $skill_result->{primary_attribute} = $skill_rows->{$skill_id}->{requiredAttributes}->{primaryAttribute};
155             $skill_result->{secondary_attribute} = $skill_rows->{$skill_id}->{requiredAttributes}->{secondaryAttribute};
156              
157             $skill_result->{bonuses} = {};
158             my $bonus_rows = $skill_rows->{$skill_id}->{rowset}->{skillBonusCollection}->{row};
159             foreach my $bonus_name (keys %$bonus_rows) {
160             $skill_result->{bonuses}->{$bonus_name} = $bonus_rows->{$bonus_name}->{bonusValue};
161             }
162              
163             $skill_result->{required_skills} = {};
164             my $required_skill_rows = $skill_rows->{$skill_id}->{rowset}->{requiredSkills}->{row};
165             foreach my $required_skill_id (keys %$required_skill_rows) {
166             $skill_result->{required_skills}->{$required_skill_id} = $required_skill_rows->{$required_skill_id}->{skillLevel};
167             }
168             }
169             }
170              
171             $result->{cached_until} = $data->{cachedUntil};
172              
173             return $result;
174             }
175              
176             =head2 ref_types
177              
178             my $ref_types = $eapi->ref_types();
179              
180             Returns a simple hash structure containing definitions of the
181             various financial transaction types. This is useful when pulling
182             wallet information. The key of the hash is the ref type's ID, and
183             the value of the title of the ref type.
184              
185             =cut
186              
187             sub ref_types {
188             my ($self) = @_;
189              
190             my $data = $self->_load_xml(
191             path => 'eve/RefTypes.xml.aspx',
192             );
193              
194             return $self->_get_error( $data ) if defined $data->{error};
195              
196             my $ref_types = {};
197              
198             my $rows = $data->{result}->{rowset}->{row};
199             foreach my $ref_type_id (keys %$rows) {
200             $ref_types->{$ref_type_id} = $rows->{$ref_type_id}->{refTypeName};
201             }
202              
203             $ref_types->{cached_until} = $data->{cachedUntil};
204              
205             return $ref_types;
206             }
207              
208             =head2 sovereignty
209              
210             my $systems = $eapi->sovereignty();
211              
212             Returns a hashref where each key is the system ID, and the
213             value is a hashref with the keys:
214              
215             name
216             faction_id
217             sovereignty_level
218             constellation_sovereignty
219             alliance_id
220              
221             =cut
222              
223             sub sovereignty {
224             my ($self) = @_;
225              
226             my $data = $self->_load_xml(
227             path => 'map/Sovereignty.xml.aspx',
228             );
229              
230             return $self->_get_error( $data ) if defined $data->{error};
231              
232             my $systems = {};
233              
234             my $rows = $data->{result}->{rowset}->{row};
235             foreach my $system_id (keys %$rows) {
236             my $system = $systems->{$system_id} = {};
237             $system->{name} = $rows->{$system_id}->{solarSystemName};
238             $system->{faction_id} = $rows->{$system_id}->{factionID};
239             $system->{sovereignty_level} = $rows->{$system_id}->{sovereigntyLevel};
240             $system->{constellation_sovereignty} = $rows->{$system_id}->{constellationSovereignty};
241             $system->{alliance_id} = $rows->{$system_id}->{allianceID};
242             }
243              
244             $systems->{cached_until} = $data->{cachedUntil};
245             $systems->{data_time} = $data->{result}->{dataTime};
246              
247             return $systems;
248             }
249              
250             =head1 RESTRICTED METHODS
251              
252             These methods require authentication to use, so you must have set
253             the L and L arguments to use them.
254              
255             =head2 characters
256              
257             my $characters = $eapi->characters();
258              
259             Returns a hashref where key is the character ID and the
260             value is a hashref with a couple bits about the character.
261             Here's a sample:
262              
263             {
264             '1972081734' => {
265             'corporation_name' => 'Bellator Apparatus',
266             'corporation_id' => '1044143901',
267             'name' => 'Ardent Dawn'
268             }
269             }
270              
271             =cut
272              
273             sub characters {
274             my ($self) = @_;
275              
276             my $data = $self->_load_xml(
277             path => 'account/Characters.xml.aspx',
278             requires_auth => 1,
279             );
280              
281             return $self->_get_error( $data ) if defined $data->{error};
282              
283             my $characters = {};
284             my $rows = $data->{result}->{rowset}->{row};
285              
286             foreach my $character_id (keys %$rows) {
287             $characters->{$character_id} = {
288             name => $rows->{$character_id}->{name},
289             corporation_name => $rows->{$character_id}->{corporationName},
290             corporation_id => $rows->{$character_id}->{corporationID},
291             };
292             }
293              
294             $characters->{cached_until} = $data->{cachedUntil};
295              
296             return $characters;
297             }
298              
299             =head2 character_sheet
300              
301             my $sheet = $eapi->character_sheet( character_id => $character_id );
302              
303             For the given character ID a hashref is returned with
304             the all the information about the character. Here's
305             a sample:
306              
307             {
308             'name' => 'Ardent Dawn',
309             'balance' => '99010910.10',
310             'race' => 'Amarr',
311             'blood_line' => 'Amarr',
312             'corporation_name' => 'Bellator Apparatus',
313             'corporation_id' => '1044143901',
314            
315             'skills' => {
316             '3455' => {
317             'level' => '2',
318             'skill_points' => '1415'
319             },
320            
321             # Removed the rest of the skills for readability.
322             },
323            
324             'attribute_enhancers' => {
325             'memory' => {
326             'value' => '3',
327             'name' => 'Memory Augmentation - Basic'
328             },
329            
330             # Removed the rest of the enhancers for readability.
331             },
332            
333             'attributes' => {
334             'memory' => '7',
335             'intelligence' => '7',
336             'perception' => '4',
337             'charisma' => '4',
338             'willpower' => '17'
339             }
340             }
341              
342             =cut
343              
344             sub character_sheet {
345             my ($self, %args) = @_;
346              
347             my $character_id = $args{character_id} || $self->character_id();
348             croak('No character_id specified') unless $character_id;
349              
350             my $data = $self->_load_xml(
351             path => 'char/CharacterSheet.xml.aspx',
352             requires_auth => 1,
353             character_id => $character_id,
354             );
355              
356             return $self->_get_error( $data ) if defined $data->{error};
357              
358             my $result = $data->{result};
359              
360             my $sheet = {};
361             my $enhancers = $sheet->{attribute_enhancers} = {};
362             my $enhancer_rows = $result->{attributeEnhancers};
363             foreach my $attribute (keys %$enhancer_rows) {
364             my ($real_attribute) = ($attribute =~ /^([a-z]+)/xm);
365             my $enhancer = $enhancers->{$real_attribute} = {};
366              
367             $enhancer->{name} = $enhancer_rows->{$attribute}->{augmentatorName};
368             $enhancer->{value} = $enhancer_rows->{$attribute}->{augmentatorValue};
369             }
370            
371             $sheet = {
372             character_id => $result->{characterID},
373             date_of_birth => $result->{DoB},
374             ancestry => $result->{ancestry},
375             gender => $result->{gender},
376             clone_name => $result->{cloneName},
377             blood_line => $result->{bloodLine},
378             name => $result->{name},
379             corporation_id => $result->{corporationID},
380             corporation_name => $result->{corporationName},
381             balance => $result->{balance},
382             race => $result->{race},
383             attributes => $result->{attributes},
384             clone_skill_points => $result->{cloneSkillPoints},
385             attribute_enhancers => $enhancers,
386             cached_until => $data->{cachedUntil},
387             };
388              
389             my $skills = $sheet->{skills} = {};
390             my $skill_rows = $result->{rowset}->{skills}->{row};
391             foreach my $skill_id (keys %$skill_rows) {
392             my $skill = $skills->{$skill_id} = {};
393              
394             $skill->{level} = $skill_rows->{$skill_id}->{level};
395             $skill->{skill_points} = $skill_rows->{$skill_id}->{skillpoints};
396             }
397              
398             # TODO: Add logic to parse next rowsets:
399             # certificates, corporationRoles, corporationRolesAtHQ,
400             # corporationRolesAtBase, corporationRolesAtOther, corporationTitles
401              
402             return $sheet;
403             }
404              
405             =head2 skill_in_training
406              
407             my $in_training = $eapi->skill_in_training( character_id => $character_id );
408              
409             Returns a hashref with the following structure:
410              
411             {
412             'current_tq_time' => {
413             'content' => '2008-05-10 04:06:35',
414             'offset' => '0'
415             },
416             'end_time' => '2008-05-10 19:23:18',
417             'start_sp' => '139147',
418             'to_level' => '5',
419             'start_time' => '2008-05-07 16:15:05',
420             'skill_id' => '3436',
421             'end_sp' => '256000'
422             }
423              
424             =cut
425              
426             sub skill_in_training {
427             my ($self, %args) = @_;
428              
429             my $character_id = $args{character_id} || $self->character_id();
430             croak('No character_id specified') unless $character_id;
431              
432             my $data = $self->_load_xml(
433             path => 'char/SkillInTraining.xml.aspx',
434             requires_auth => 1,
435             character_id => $character_id,
436             );
437             my $result = $data->{result};
438              
439             return $self->_get_error( $data ) if defined $data->{error};
440              
441             my $training = {
442             current_tq_time => $result->{currentTQTime},
443             skill_id => $result->{trainingTypeID},
444             to_level => $result->{trainingToLevel},
445             start_time => $result->{trainingStartTime},
446             end_time => $result->{trainingEndTime},
447             start_sp => $result->{trainingStartSP},
448             end_sp => $result->{trainingDestinationSP},
449             };
450              
451             $training->{cached_until} = $data->{cachedUntil};
452              
453             return $training;
454             }
455              
456             =head2 api_key_info
457              
458             my $api_info = $eapi->api_key_info();
459              
460             Returns a hashref with the following structure:
461              
462             {
463             'cached_until' => '2014-06-26 16:57:40',
464             'type' => 'Account',
465             'access_mask' => '268435455',
466             'characters' => {
467             '12345678' => {
468             'faction_id' => '0',
469             'character_name' => 'Char Name',
470             'corporation_name' => 'School of Applied Knowledge',
471             'faction_name' => '',
472             'alliance_id' => '0',
473             'corporation_id' => '1000044',
474             'alliance_name' => ''
475             },
476             '87654321' => {
477             'faction_id' => '0',
478             'character_name' => 'Char Name2',
479             'corporation_name' => 'Corp Name',
480             'faction_name' => '',
481             'alliance_id' => '1234567890',
482             'corporation_id' => '987654321',
483             'alliance_name' => 'Alliance Name'
484             }
485             },
486             'expires' => ''
487             }
488              
489             =cut
490              
491             sub api_key_info {
492             my ($self) = @_;
493              
494             my $data = $self->_load_xml(
495             path => 'account/ApiKeyInfo.xml.aspx',
496             requires_auth => 1,
497             );
498              
499             my $result = $data->{result}->{key};
500              
501             return $self->_get_error( $data ) if defined $data->{error};
502              
503             my $type = $result->{type};
504              
505             my $key_info = {
506             type => $type,
507             expires => $result->{expires},
508             access_mask => $result->{accessMask},
509             };
510              
511             # TODO: add structure for corporation and alliance API
512             if ( defined $result->{rowset}->{row} and $type and ($type eq 'Account' or $type eq 'Character') ) {
513             $key_info->{characters} = {};
514             foreach my $char_id ( keys %{ $result->{rowset}->{row} } ) {
515             $key_info->{characters}->{$char_id} = {
516             'character_name' => $result->{rowset}->{row}->{$char_id}->{characterName},
517             'faction_name' => $result->{rowset}->{row}->{$char_id}->{factionName} || '',
518             'corporation_id' => $result->{rowset}->{row}->{$char_id}->{corporationID},
519             'alliance_name' => $result->{rowset}->{row}->{$char_id}->{allianceName} || '',
520             'faction_id' => $result->{rowset}->{row}->{$char_id}->{factionID} || '0',
521             'corporation_name' => $result->{rowset}->{row}->{$char_id}->{corporationName},
522             'alliance_id' => $result->{rowset}->{row}->{$char_id}->{allianceID} || '0',
523             };
524             }
525             }
526              
527             $key_info->{cached_until} = $data->{cachedUntil};
528              
529             return $key_info;
530             }
531              
532             =head2 account_status
533              
534             my $account_status = $eapi->account_status();
535              
536             Returns a hashref with the following structure:
537              
538             {
539             'cachedUntil' => '2014-06-26 17:17:12',
540             'logon_minutes' => '79114',
541             'logon_count' => '940',
542             'create_date' => '2011-06-22 11:44:37',
543             'paid_until' => '2014-08-26 16:37:43'
544             }
545              
546             =cut
547              
548             sub account_status {
549             my ($self) = @_;
550              
551             my $data = $self->_load_xml(
552             path => 'account/AccountStatus.xml.aspx',
553             requires_auth => 1,
554             );
555              
556             my $result = $data->{result};
557              
558             return $self->_get_error( $data ) if defined $data->{error};
559              
560             return {
561             paid_until => $result->{paidUntil},
562             create_date => $result->{createDate},
563             logon_count => $result->{logonCount},
564             logon_minutes => $result->{logonMinutes},
565             cached_until => $data->{cachedUntil},
566             }
567             }
568              
569             =head2 character_info
570              
571             my $character_info = $eapi->character_info( character_id => $character_id );
572              
573             Returns a hashref with the following structure:
574              
575             {
576             'character_name' => 'Char Name',
577             'alliance_id' => '1234567890',
578             'corporation_id' => '987654321',
579             'corporation' => 'Corp Name',
580             'alliance' => 'Alliance Name',
581             'race' => 'Caldari',
582             'bloodline' => 'Achura',
583             'skill_points' => '40955856',
584             'employment_history' => {
585             '23046655' => {
586             'corporation_id' => '123456789',
587             'start_date' => '2013-02-03 13:39:00',
588             'record_id' => '23046655'
589             },
590             '29131760' => {
591             'corporation_id' => '987654321',
592             'start_date' => '2013-11-04 16:40:00',
593             'record_id' => '29131760'
594             },
595             },
596             'ship_type_id' => '670',
597             'account_balance' => '38131.68',
598             'cached_until' => '2014-06-26 17:18:29',
599             'last_known_location' => 'Jita',
600             'character_id' => '12345678',
601             'alliance_date' => '2012-08-05 00:12:00',
602             'corporation_date' => '2012-09-11 20:32:00',
603             'ship_type_name' => 'Capsule',
604             'security_status' => '1.3534973114985',
605             'ship_name' => 'Char Name Capsule'
606             }
607              
608             =cut
609              
610             sub character_info {
611             my ($self, %args) = @_;
612              
613             my $character_id = $args{character_id} || $self->character_id();
614             croak('No character_id specified') unless $character_id;
615              
616             my $data = $self->_load_xml(
617             path => 'eve/CharacterInfo.xml.aspx',
618             requires_auth => 1,
619             character_id => $character_id,
620             );
621              
622             my $result = $data->{result};
623              
624             return $self->_get_error( $data ) if defined $data->{error};
625              
626             my $info = {
627             character_id => $result->{characterID},
628             character_name => $result->{characterName},
629             race => $result->{race},
630             bloodline => $result->{bloodline},
631             account_balance => $result->{accountBalance},
632             skill_points => $result->{skillPoints},
633             ship_name => $result->{shipName},
634             ship_type_id => $result->{shipTypeID},
635             ship_type_name => $result->{shipTypeName},
636             corporation_id => $result->{corporationID},
637             corporation => $result->{corporation},
638             corporation_date => $result->{corporationDate},
639             alliance_id => $result->{allianceID},
640             alliance => $result->{alliance},
641             alliance_date => $result->{allianceDate},
642             last_known_location => $result->{lastKnownLocation},
643             security_status => $result->{securityStatus},
644             cached_until => $data->{cachedUntil},
645             };
646              
647             if ( defined $result->{rowset}->{row} ) {
648             foreach my $history_row ( @{$result->{rowset}->{row}} ) {
649             $info->{employment_history}->{$history_row->{recordID}}->{record_id} = $history_row->{recordID};
650             $info->{employment_history}->{$history_row->{recordID}}->{corporation_id} = $history_row->{corporationID};
651             $info->{employment_history}->{$history_row->{recordID}}->{start_date} = $history_row->{startDate};
652             }
653             }
654              
655             return $info;
656             }
657              
658             =head2 asset_list
659              
660             my $asset_list = $eapi->asset_list( character_id => $character_id );
661              
662             Returns a hashref with the following structure:
663              
664             {
665             '1014951232473' => {
666             'contents' => {
667             '1014957890964' => {
668             'type_id' => '2454',
669             'quantity' => '1',
670             'flag' => '87',
671             'raw_quantity' => '-1',
672             'singleton' => '1',
673             'item_id' => '1014957890964'
674             }
675             },
676             'quantity' => '1',
677             'flag' => '4',
678             'location_id' => '60014680',
679             'singleton' => '1',
680             'item_id' => '1014951232473',
681             'type_id' => '32880',
682             'raw_quantity' => '-1'
683             },
684             '1014951385057' => {
685             'type_id' => '1178',
686             'quantity' => '1',
687             'flag' => '4',
688             'raw_quantity' => '-2',
689             'location_id' => '60015001',
690             'singleton' => '1',
691             'item_id' => '1014951385057'
692             }
693             }
694              
695             =cut
696              
697             sub asset_list {
698             my ($self, %args) = @_;
699              
700             my $character_id = $args{character_id} || $self->character_id();
701             croak('No character_id specified') unless $character_id;
702              
703             my $data = $self->_load_xml(
704             path => 'char/AssetList.xml.aspx',
705             requires_auth => 1,
706             character_id => $character_id,
707             );
708              
709             my $result = $data->{result};
710              
711             return $self->_get_error( $data ) if defined $data->{error};
712              
713             return $self->_parse_assets( $result );
714             }
715              
716             =head2 contact_list
717              
718             my $contact_list = $eapi->contact_list( character_id => $character_id );
719              
720             Returns a hashref with the following structure:
721              
722             {
723             'contact_list' => {
724             '962693552' => {
725             'standing' => '10',
726             'contact_name' => 'Char Name',
727             'contact_id' => '962693552',
728             'in_watchlist' => undef,
729             'contact_type_id' => '1384'
730             },
731             '3019494' => {
732             'standing' => '0',
733             'contact_name' => 'Char Name 3',
734             'contact_id' => '3019494',
735             'in_watchlist' => undef,
736             'contact_type_id' => '1375'
737             },
738             '1879838281' => {
739             'standing' => '10',
740             'contact_name' => 'Char Name 2',
741             'contact_id' => '1879838281',
742             'in_watchlist' => undef,
743             'contact_type_id' => '1378'
744             }
745             }
746             }
747              
748             =cut
749              
750             sub contact_list {
751             my ($self, %args) = @_;
752              
753             my $character_id = $args{character_id} || $self->character_id();
754             croak('No character_id specified') if ! $character_id && $args{type} && $args{type} ne 'corp';
755              
756             my $type = $args{type} && $args{type} eq 'corp' ? 'corp' : 'char';
757              
758             my $data = $self->_load_xml(
759             path => "$type/ContactList.xml.aspx",
760             requires_auth => 1,
761             character_id => $type eq 'char' ? $character_id : undef,
762             );
763              
764             my $result = $data->{result};
765              
766             return $self->_get_error( $data ) if defined $data->{error};
767              
768             my $contacts;
769             foreach my $rows ( keys %{$result->{rowset}} ) {
770             next unless defined $result->{rowset}->{$rows}->{row};
771             my $key = $rows;
772             $key =~ s/L/_l/;
773             $key =~ s/C/_c/; # TODO: more correctly regexp
774             foreach my $contact_id ( keys %{ $result->{rowset}->{$rows}->{row} } ) {
775             $contacts->{$key}->{$contact_id}->{contact_id} = $contact_id;
776             $contacts->{$key}->{$contact_id}->{standing} = $result->{rowset}->{$rows}->{row}->{$contact_id}->{standing};
777             $contacts->{$key}->{$contact_id}->{contact_name} = $result->{rowset}->{$rows}->{row}->{$contact_id}->{contactName};
778             $contacts->{$key}->{$contact_id}->{contact_type_id} = $result->{rowset}->{$rows}->{row}->{$contact_id}->{contactTypeID};
779             if ( $rows eq 'contactList' ) {
780             $contacts->{$key}->{$contact_id}->{in_watchlist} = $result->{rowset}->{$rows}->{row}->{$contact_id}->{inWatchlist};
781             }
782             }
783             }
784              
785             $contacts->{cached_until} = $data->{cachedUntil};
786              
787             return $contacts;
788             }
789              
790             =head2 wallet_transactions
791              
792             my $wallet_transactions = $eapi->wallet_transactions(
793             character_id => $character_id,
794             row_count => $row_count, # optional, default is 2560
795             account_key => $account_key, # optional, default is 1000
796             from_id => $args{from_id}, # optional, need for offset
797             );
798              
799             Returns a hashref with the following structure:
800              
801             {
802             '3499165305' => {
803             'type_name' => 'Mining Frigate',
804             'quantity' => '1',
805             'client_id' => '90646537',
806             'transaction_date_time' => '2014-06-28 12:23:41',
807             'station_id' => '60015001',
808             'transaction_id' => '3499165305',
809             'transaction_for' => 'personal',
810             'type_id' => '32918',
811             'station_name' => 'Akiainavas III - School of Applied Knowledge',
812             'client_name' => 'Zeta Zhang',
813             'price' => '1201.02',
814             'transaction_type' => 'sell'
815             },
816             '3482136396' => {
817             'type_name' => 'Mining Barge',
818             'quantity' => '1',
819             'client_id' => '1000167',
820             'transaction_date_time' => '2014-06-15 20:15:26',
821             'station_id' => '60014680',
822             'transaction_id' => '3482136396',
823             'transaction_for' => 'personal',
824             'type_id' => '17940',
825             'station_name' => 'Autama V - Moon 9 - State War Academy',
826             'client_name' => 'State War Academy',
827             'price' => '500000.00',
828             'transaction_type' => 'buy'
829             }
830             }
831              
832             =cut
833              
834             sub wallet_transactions {
835             my ($self, %args) = @_;
836              
837             my $character_id = $args{character_id} || $self->character_id();
838             croak('No character_id specified') unless $character_id;
839              
840             my $row_count = $args{row_count} || 2560;
841             my $account_key = $args{account_key} || 1000;
842              
843              
844             my $data = $self->_load_xml(
845             path => 'char/WalletTransactions.xml.aspx',
846             requires_auth => 1,
847             character_id => $character_id,
848             row_count => $row_count,
849             account_key => $account_key,
850             from_id => $args{from_id},
851             );
852              
853             my $result = $data->{result}->{rowset}->{row};
854              
855             return $self->_get_error( $data ) if defined $data->{error};
856              
857             my $trans;
858             foreach my $t_id ( keys %$result ) {
859             $trans->{$t_id} = {
860             transaction_for => $result->{$t_id}->{transactionFor},
861             transaction_type => $result->{$t_id}->{transactionType},
862             station_name => $result->{$t_id}->{stationName},
863             station_id => $result->{$t_id}->{stationID},
864             client_name => $result->{$t_id}->{clientName},
865             client_id => $result->{$t_id}->{clientID},
866             price => $result->{$t_id}->{price},
867             type_id => $result->{$t_id}->{typeID},
868             type_name => $result->{$t_id}->{typeName},
869             quantity => $result->{$t_id}->{quantity},
870             transaction_id => $t_id,
871             transaction_date_time => $result->{$t_id}->{transactionDateTime},
872             };
873             }
874              
875             $trans->{cached_until} = $data->{cachedUntil};
876              
877             return $trans;
878             }
879              
880             =head2 wallet_journal
881              
882             my $wallet_journal = $eapi->wallet_journal(
883             character_id => $character_id,
884             row_count => $row_count, # optional, default is 2560
885             account_key => $account_key, # optional, default is 1000
886             from_id => $args{from_id}, # optional, need for offset
887             );
888              
889             Returns a hashref with the following structure:
890              
891             {
892             '9729070529' => {
893             'owner_name2' => 'Milolika Muvila',
894             'arg_id1' => '0',
895             'date' => '2014-07-08 19:02:53',
896             'reason' => '',
897             'tax_receiver_id' => '',
898             'owner_name1' => 'Cyno Chain',
899             'amount' => '814900000.00',
900             'owner_id1' => '93496706',
901             'tax_amount' => '',
902             'balance' => '826371087.94',
903             'arg_name1' => '3513456219',
904             'ref_id' => '9729070529',
905             'ref_type_id' => '2',
906             'owner_id2' => '94701913'
907             },
908             '9729071394' => {
909             'owner_name2' => '',
910             'arg_id1' => '0',
911             'date' => '2014-07-08 19:03:04',
912             'reason' => '',
913             'tax_receiver_id' => '',
914             'owner_name1' => 'Milolika Muvila',
915             'amount' => '-28369982.50',
916             'owner_id1' => '94701913',
917             'tax_amount' => '',
918             'balance' => '785777605.44',
919             'arg_name1' => '',
920             'ref_id' => '9729071394',
921             'ref_type_id' => '42',
922             'owner_id2' => '0'
923             }
924             }
925              
926             =cut
927              
928             sub wallet_journal {
929             my ($self, %args) = @_;
930              
931             my $character_id = $args{character_id} || $self->character_id();
932             croak('No character_id specified') unless $character_id;
933              
934             my $row_count = $args{row_count} || 2560;
935             my $account_key = $args{account_key} || 1000;
936              
937              
938             my $data = $self->_load_xml(
939             path => 'char/WalletJournal.xml.aspx',
940             requires_auth => 1,
941             character_id => $character_id,
942             row_count => $row_count,
943             account_key => $account_key,
944             from_id => $args{from_id},
945             );
946              
947             my $result = $data->{result}->{rowset}->{row};
948              
949             return $self->_get_error( $data ) if defined $data->{error};
950              
951             my $journal;
952             foreach my $r_id ( keys %$result ) {
953             $journal->{$r_id} = {
954             ref_id => $r_id,
955             date => $result->{$r_id}->{date},
956             ref_type_id => $result->{$r_id}->{refTypeID},
957             owner_name1 => $result->{$r_id}->{ownerName1},
958             owner_id1 => $result->{$r_id}->{ownerID1},
959             owner_name2 => $result->{$r_id}->{ownerName2},
960             owner_id2 => $result->{$r_id}->{ownerID2},
961             arg_name1 => $result->{$r_id}->{argName1},
962             arg_id1 => $result->{$r_id}->{argID1},
963             amount => $result->{$r_id}->{amount},
964             balance => $result->{$r_id}->{balance},
965             reason => $result->{$r_id}->{reason},
966             tax_amount => $result->{$r_id}->{taxAmount},
967             tax_receiver_id => $result->{$r_id}->{taxReceiverID},
968             };
969             }
970              
971             $journal->{cached_until} = $data->{cachedUntil};
972              
973             return $journal;
974             }
975              
976             =head2 mail_messages
977              
978             my $mail_messages = $eapi->mail_messages( character_id => $character_id );
979              
980             Returns a hashref with the following structure:
981              
982             {
983             '331477595' => {
984             'to_list_id' => '145156607',
985             'message_id' => '331477595',
986             'to_character_ids' => '',
987             'sender_id' => '91669871',
988             'sent_date' => '2013-10-08 06:30:00',
989             'to_corp_or_alliance_id' => '',
990             'title' =>
991             "\x{420}\x{430}\x{441}\x{43f}\x{440}\x{43e}\x{434}\x{430}\x{436}\x{430}",
992             'sender_name' => 'Valerii Ostudnev'
993             },
994             '336393982' => {
995             'to_list_id' => '',
996             'message_id' => '336393982',
997             'to_character_ids' => '1203082547',
998             'sender_id' => '90922771',
999             'sent_date' => '2014-03-02 13:30:00',
1000             'to_corp_or_alliance_id' => '',
1001             'title' => 'TSG -> Z-H',
1002             'sender_name' => 'Chips Merkaba'
1003             },
1004             'cached_until' => '2014-07-10 18:33:59'
1005             }
1006              
1007             =cut
1008              
1009             sub mail_messages {
1010             my ($self, %args) = @_;
1011              
1012             my $character_id = $args{character_id} || $self->character_id();
1013             croak('No character_id specified') unless $character_id;
1014              
1015             my $data = $self->_load_xml(
1016             path => 'char/MailMessages.xml.aspx',
1017             requires_auth => 1,
1018             character_id => $character_id,
1019             );
1020              
1021             my $result = $data->{result}->{rowset}->{row};
1022              
1023             return $self->_get_error( $data ) if defined $data->{error};
1024              
1025             my $messages;
1026            
1027             foreach my $mes_id ( keys %$result ) {
1028             $messages->{$mes_id} = {
1029             message_id => $mes_id,
1030             sender_id => $result->{$mes_id}->{senderID},
1031             sender_name => $result->{$mes_id}->{senderName},
1032             sent_date => $result->{$mes_id}->{sentDate},
1033             title => $result->{$mes_id}->{title},
1034             to_corp_or_alliance_id => $result->{$mes_id}->{toCorpOrAllianceID},
1035             to_character_ids => $result->{$mes_id}->{toCharacterIDs},
1036             to_list_id => $result->{$mes_id}->{toListID},
1037             };
1038             }
1039             $messages->{cached_until} = $data->{cachedUntil};
1040              
1041             return $messages;
1042             }
1043              
1044             =head2 mail_bodies
1045              
1046             my $mail_bodies = $eapi->mail_bodies( character_id => $character_id, ids => $ids );
1047              
1048             Returns a hashref with the following structure:
1049              
1050             {
1051             'cached_until' => '2024-07-07 18:13:16',
1052             'missing_message_ids' => '331477591',
1053             '331477595' =>
1054             "[Multiple Items]"
1055             }
1056              
1057             =cut
1058              
1059             sub mail_bodies {
1060             my ($self, %args) = @_;
1061              
1062             my $character_id = $args{character_id} || $self->character_id();
1063             croak('No character_id specified') unless $character_id;
1064             croak('No comma separated messages ids specified') unless $args{ids};
1065              
1066             my $data = $self->_load_xml(
1067             path => 'char/MailBodies.xml.aspx',
1068             requires_auth => 1,
1069             character_id => $character_id,
1070             ids => $args{ids},
1071             );
1072              
1073             my $result = $data->{result}->{rowset}->{row};
1074              
1075             return $self->_get_error( $data ) if defined $data->{error};
1076              
1077             my $bodies;
1078            
1079             foreach my $mes_id ( keys %$result ) {
1080             $bodies->{$mes_id} = $result->{$mes_id}->{content};
1081             }
1082              
1083             $bodies->{cached_until} = $data->{cachedUntil};
1084             $bodies->{missing_message_ids} = $data->{result}->{missingMessageIDs};
1085              
1086             return $bodies;
1087             }
1088              
1089             =head2 mail_lists
1090              
1091             my $mail_lists = $eapi->mail_lists( character_id => $character_id );
1092              
1093             Returns a hashref with the following structure:
1094              
1095             {
1096             'cached_until' => '2014-07-11 00:06:57',
1097             '145156367' => 'RAISA Shield Fits'
1098             }
1099              
1100             =cut
1101              
1102             sub mail_lists {
1103             my ($self, %args) = @_;
1104              
1105             my $character_id = $args{character_id} || $self->character_id();
1106             croak('No character_id specified') unless $character_id;
1107              
1108             my $data = $self->_load_xml(
1109             path => 'char/mailinglists.xml.aspx',
1110             requires_auth => 1,
1111             character_id => $character_id,
1112             );
1113              
1114             my $result = $data->{result}->{rowset}->{row};
1115              
1116             return $self->_get_error( $data ) if defined $data->{error};
1117              
1118             my $lists;
1119             foreach my $list_id ( keys %$result ) {
1120             $lists->{$list_id} = $result->{$list_id}->{displayName};
1121             }
1122              
1123             $lists->{cached_until} = $data->{cachedUntil};
1124            
1125             return $lists;
1126             }
1127              
1128             =head2 character_name
1129              
1130             my $character_name = $eapi->character_name( ids => '90922771,94701913' );
1131              
1132             Returns a hashref with the following structure:
1133              
1134             {
1135             '94701913' => 'Milolika Muvila',
1136             'cached_until' => '2014-08-10 20:59:55',
1137             '90922771' => 'Chips Merkaba'
1138             }
1139              
1140             =cut
1141              
1142             sub character_name {
1143             my ($self, %args) = @_;
1144              
1145             croak('No comma separated character ids specified') unless $args{ids};
1146              
1147             my $data = $self->_load_xml(
1148             path => 'eve/CharacterName.xml.aspx',
1149             ids => $args{ids},
1150             );
1151              
1152             my $result = $data->{result}->{rowset}->{row};
1153              
1154             return $self->_get_error( $data ) if defined $data->{error};
1155              
1156             my $names;
1157             foreach my $char_id ( keys %$result ) {
1158             $names->{$char_id} = $result->{$char_id}->{name};
1159             }
1160              
1161             $names->{cached_until} = $data->{cachedUntil};
1162            
1163             return $names;
1164             }
1165              
1166             =head2 character_ids
1167              
1168             my $character_ids = $eapi->character_ids( names => 'Milolika Muvila,Chips Merkaba' );
1169              
1170             Returns a hashref with the following structure:
1171              
1172             {
1173             '94701913' => 'Milolika Muvila',
1174             'cached_until' => '2014-08-10 20:59:55',
1175             '90922771' => 'Chips Merkaba'
1176             }
1177              
1178             =cut
1179              
1180             sub character_ids {
1181             my ($self, %args) = @_;
1182              
1183             croak('No comma separated character names specified') unless $args{names};
1184              
1185             my $data = $self->_load_xml(
1186             path => 'eve/CharacterID.xml.aspx',
1187             names => $args{names},
1188             );
1189              
1190             my $result = $data->{result}->{rowset}->{row};
1191              
1192             return $self->_get_error( $data ) if defined $data->{error};
1193              
1194             my $ids;
1195             foreach my $char_id ( keys %$result ) {
1196             $ids->{$char_id} = $result->{$char_id}->{name};
1197             }
1198              
1199             $ids->{cached_until} = $data->{cachedUntil};
1200            
1201             return $ids;
1202             }
1203              
1204             =head2 station_list
1205              
1206             my $station_list = $eapi->station_list();
1207              
1208             Returns a hashref with the following structure:
1209              
1210             {
1211             '61000051' => {
1212             'station_type_id' => '21644',
1213             'corporation_name' => 'Nulli Secunda Holding',
1214             'corporation_id' => '1463841432',
1215             'station_name' => 'DB1R-4 VIII - We brought the Trash Out',
1216             'solar_system_id' => '30004470',
1217             'station_id' => '61000051'
1218             },
1219             '61000438' => {
1220             'station_type_id' => '21646',
1221             'corporation_name' => 'Greater Western Co-Prosperity Sphere Exec',
1222             'corporation_id' => '98237912',
1223             'station_name' => 'F-D49D III - Error - Clever name not found',
1224             'solar_system_id' => '30000279',
1225             'station_id' => '61000438'
1226             }
1227             }
1228              
1229             =cut
1230              
1231             sub station_list {
1232             my ($self) = @_;
1233              
1234             my $data = $self->_load_xml(
1235             path => 'eve/ConquerableStationList.xml.aspx',
1236             );
1237              
1238             return $self->_get_error( $data ) if defined $data->{error};
1239              
1240             my $stations = {};
1241              
1242             my $rows = $data->{result}->{rowset}->{row};
1243             foreach my $station_id (keys %$rows) {
1244             $stations->{$station_id} = {
1245             station_id => $station_id,
1246             station_name => $rows->{$station_id}->{stationName},
1247             station_type_id => $rows->{$station_id}->{stationTypeID},
1248             solar_system_id => $rows->{$station_id}->{solarSystemID},
1249             corporation_id => $rows->{$station_id}->{corporationID},
1250             corporation_name => $rows->{$station_id}->{corporationName},
1251             };
1252             }
1253              
1254             $stations->{cached_until} = $data->{cachedUntil};
1255              
1256             return $stations;
1257              
1258             }
1259              
1260             =head2 corporation_sheet
1261              
1262             my $station_list = $eapi->corporation_sheet();
1263              
1264             Returns a hashref with the following structure:
1265              
1266             {
1267             'shares' => '1000',
1268             'faction_id' => '0',
1269             'cached_until' => '2014-08-24 22:18:02',
1270             'member_count' => '43',
1271             'alliance_id' => '0',
1272             'corporation_id' => '1043735888',
1273             'description' =>
1274             "\x{418}\x{441}\x{441}\x{43b}\x{435}\x{434}\x{43e}\x{432}\x{430}\x{43d}\x{438}\x{44f} \x{438} \x{440}\x{430}\x{437}\x{440}\x{430}\x{431}\x{43e}\x{442}\x{43a}\x{438}",
1275             'station_id' => '60004861',
1276             'ceo_name' => 'Krasotulya',
1277             'logo' => {
1278             'color3' => '674',
1279             'color1' => '677',
1280             'shape3' => '415',
1281             'shape2' => '480',
1282             'graphic_id' => '0',
1283             'shape1' => '437',
1284             'color2' => '676'
1285             },
1286             'tax_rate' => '5',
1287             'corporation_name' => 'Zaporozhye Sich',
1288             'ceo_id' => '423270919',
1289             'url' => 'http://',
1290             'station_name' => 'Lasleinur V - Moon 11 - Republic Fleet Assembly Plant'
1291             }
1292              
1293             =cut
1294              
1295             sub corporation_sheet {
1296             my ($self, %args) = @_;
1297              
1298             croak('No corporation_id specified') unless $args{corporation_id};
1299              
1300             my $data = $self->_load_xml(
1301             path => 'corp/CorporationSheet.xml.aspx',
1302             requires_auth => 1,
1303             corporation_id => $args{corporation_id},
1304             );
1305              
1306             return $self->_get_error( $data ) if defined $data->{error};
1307              
1308             my $corp_info = {};
1309              
1310             my $result = $data->{result};
1311            
1312             $corp_info->{cached_until} = $data->{cachedUntil};
1313             $corp_info->{corporation_id} = $result->{corporationID};
1314             $corp_info->{corporation_name} = $result->{corporationName};
1315             $corp_info->{ticker} = $result->{ticker};
1316             $corp_info->{ceo_id} = $result->{ceoID};
1317             $corp_info->{ceo_name} = $result->{ceoName};
1318             $corp_info->{station_id} = $result->{stationID};
1319             $corp_info->{station_name} = $result->{stationName};
1320             $corp_info->{description} = $result->{description};
1321             $corp_info->{url} = $result->{url};
1322             $corp_info->{alliance_id} = $result->{allianceID};
1323             $corp_info->{faction_id} = $result->{factionID};
1324             $corp_info->{tax_rate} = $result->{taxRate};
1325             $corp_info->{member_count} = $result->{memberCount};
1326             $corp_info->{shares} = $result->{shares};
1327             $corp_info->{logo}->{graphic_id} = $result->{logo}->{graphicID};
1328             $corp_info->{logo}->{shape1} = $result->{logo}->{shape1};
1329             $corp_info->{logo}->{shape2} = $result->{logo}->{shape2};
1330             $corp_info->{logo}->{shape3} = $result->{logo}->{shape3};
1331             $corp_info->{logo}->{color1} = $result->{logo}->{color1};
1332             $corp_info->{logo}->{color2} = $result->{logo}->{color2};
1333             $corp_info->{logo}->{color3} = $result->{logo}->{color3};
1334              
1335             return $corp_info;
1336             }
1337              
1338             # Generate error answer
1339             sub _get_error {
1340             my ($self, $data) = @_;
1341              
1342             return {
1343             error => $data->{error} || { code => 500, content => 'Unknown error' },
1344             };
1345             }
1346              
1347             # convert keys
1348             sub _parse_assets {
1349             my ($self, $xml) = @_;
1350              
1351             return () unless $xml;
1352              
1353             my $parsed;
1354             my $rows = $xml->{rowset}->{row};
1355              
1356             foreach my $id ( keys %$rows ) {
1357             $parsed->{$id}->{item_id} = $id;
1358             $parsed->{$id}->{location_id} = $rows->{$id}->{locationID} if $rows->{$id}->{locationID};
1359             $parsed->{$id}->{raw_quantity} = $rows->{$id}->{rawQuantity};
1360             $parsed->{$id}->{quantity} = $rows->{$id}->{quantity};
1361             $parsed->{$id}->{flag} = $rows->{$id}->{flag};
1362             $parsed->{$id}->{singleton} = $rows->{$id}->{singleton};
1363             $parsed->{$id}->{type_id} = $rows->{$id}->{typeID};
1364              
1365             if ( $rows->{$id}->{rowset} && $rows->{$id}->{rowset}->{name} eq 'contents' ) {
1366             $parsed->{$id}->{contents} = $self->_parse_assets( $rows->{$id} );
1367             }
1368             }
1369              
1370             return $parsed;
1371             }
1372              
1373             sub _load_xml {
1374             my $self = shift;
1375              
1376             my $xml = $self->_retrieve_xml( @_ );
1377              
1378             my $data = $self->_parse_xml( $xml );
1379             die('Unsupported EveOnline API XML version (requires version 2)') if ($data->{version} != 2);
1380              
1381             return $data;
1382             }
1383              
1384             sub _retrieve_xml {
1385             my ($self, %args) = @_;
1386              
1387             croak('No feed path provided') if !$args{path};
1388              
1389             my $params = {};
1390              
1391             if ($args{requires_auth}) {
1392             $params->{keyID} = $self->user_id();
1393             $params->{vCode} = $self->api_key();
1394             }
1395              
1396             if ($args{character_id}) {
1397             $params->{characterID} = $args{character_id};
1398             }
1399             if ($args{row_count}) {
1400             $params->{rowCount} = $args{row_count};
1401             }
1402             if ($args{account_key}) {
1403             $params->{accountKey} = $args{account_key};
1404             }
1405             if ($args{from_id}) {
1406             $params->{fromID} = $args{from_id};
1407             }
1408             if ($args{ids}) {
1409             $params->{ids} = $args{ids};
1410             }
1411             if ($args{names}) {
1412             $params->{names} = $args{names};
1413             }
1414             if ($args{corporation_id}) {
1415             $params->{corporationID} = $args{corporation_id};
1416             }
1417              
1418             my $uri = URI->new( $self->api_url() . '/' . $args{path} );
1419             $uri->query_form( $params );
1420              
1421             my $xml = $self->ua->get( $uri->as_string() );
1422              
1423             return $xml->content;
1424             }
1425              
1426             sub _parse_xml {
1427             my ($self, $xml) = @_;
1428              
1429             my $data = XML::Simple::XMLin(
1430             $xml,
1431             ForceArray => ['row'],
1432             KeyAttr => ['characterID', 'listID', 'messageID', 'transactionID', 'refID', 'itemID', 'typeID', 'stationID', 'bonusType', 'groupID', 'refTypeID', 'solarSystemID', 'name', 'contactID'],
1433             );
1434              
1435             return $data;
1436             }
1437              
1438             1;
1439             __END__