File Coverage

lib/Acme/MobileTradeFun.pm
Criterion Covered Total %
statement 59 115 51.3
branch 7 26 26.9
condition 2 3 66.6
subroutine 16 21 76.1
pod 5 5 100.0
total 89 170 52.3


line stmt bran cond sub pod time code
1             package Acme::MobileTradeFun;
2              
3 1     1   29156 use 5.006;
  1         5  
  1         40  
4 1     1   7 use strict;
  1         2  
  1         34  
5 1     1   5 use warnings;
  1         7  
  1         32  
6 1     1   14262 use Data::Dumper;
  1         12478  
  1         86  
7 1     1   11 use Carp;
  1         2  
  1         59  
8 1     1   1684 use LWP::Simple;
  1         105149  
  1         13  
9 1     1   768 use File::Path qw/make_path/;
  1         3  
  1         76  
10 1     1   2304 use Log::Log4perl qw/:easy/;
  1         65135  
  1         7  
11 1     1   2223 use AnyEvent;
  1         5255  
  1         28  
12 1     1   1059 use AnyEvent::HTTP;
  1         33322  
  1         92  
13 1     1   11 use Encode;
  1         2  
  1         73  
14 1     1   584 use Acme::MobileTradeFun::NewParser;
  1         4  
  1         27  
15 1     1   437 use Acme::MobileTradeFun::OldParser;
  1         2  
  1         996  
16              
17             =head1 NAME
18              
19             Acme::MobileTradeFun - Needlessly OO module to scrape card images off of the
20             MobileTradeFun site
21              
22             =head1 VERSION
23              
24             Version 0.12
25              
26             =cut
27              
28             our $VERSION = '0.12';
29              
30              
31             =head1 SYNOPSIS
32              
33             I recently found a site called MobileTradeFun (or MobaTreFun for short), where
34             people can search the value of cards from mobile games, such as idolmaster.
35              
36             http://mobile-trade.jp
37              
38             This site seems to contain the "official" card images as they show up on the
39             mobile devices in Japan. This module basically lets you specify a game, and
40             scrapes all the high quality cards off of the site.
41              
42             Note that card names are stored in Japanese, as each card contains certain
43             attributes such as category, card name and rarity. Without these attributes,
44             cards cannot be identified uniquely.
45              
46             The interface of the module is pretty simple. You construct a hashref of args,
47             and pass it onto the class method run() which will take care of the rest.
48              
49             use Acme::MobileTradeFun;
50              
51             my $args = {
52             game => 'idolmaster',
53             };
54              
55             my $foo = Acme::MobileTradeFun->run( $args );
56            
57             Here are the overwritable defaults in the hashref. Currently 4 games are
58             supported: bahamut, idolmaster, saintseiya and gangroad.
59              
60             game => '',
61             base_url => 'http://mobile-trade.jp',
62             php_script => 'card.php',
63             row => 100, # how many cards per page
64             page => 1, # which page to start from
65             output_dir => '/tmp', # where to save the images
66             debug => 0,
67              
68              
69             =head1 SUBROUTINES/METHODS
70              
71             =head2 new
72              
73             The constructor
74              
75             =cut
76              
77             sub new {
78 2     2 1 1059 my ( $class, $args ) = @_;
79 2         4 my $self = {};
80 2         6 bless $self, $class;
81 2         6 $self->init( $args );
82 0         0 return $self;
83             }
84              
85             =head2 init
86              
87             Sets up default args, overrides, validates args etc
88              
89             =cut
90              
91             sub init {
92 2     2 1 4 my ( $self, $args ) = @_;
93            
94 2 50 66     11 croak "not a hashref" if ( $args && ref( $args ) ne "HASH" );
95              
96             $self->{ opts } = {
97 2         18 game => '',
98             base_url => 'http://mobile-trade.jp',
99             php_script => 'card.php',
100             row => 100,
101             page => 1,
102             output_dir => '/tmp',
103             debug => 0,
104             };
105              
106 2 100       6 %{ $self->{ opts } } = ( %{ $self->{ opts } }, %{ $args } ) if ( $args );
  1         8  
  1         4  
  1         2  
107            
108 2         5 my $game = $self->{ opts }->{ game };
109 2 100       207 croak "game not specified" unless( $game );
110            
111 1         4 $self->{ parser } = $self->_parser_strategy( $game );
112            
113 0 0       0 Log::Log4perl->easy_init($DEBUG) if ( $self->{ opts }->{ debug } );
114 0         0 my $out_dir = $self->{ opts }->{ output_dir } . "/" . $self->{ opts }->{ game };
115 0 0       0 make_path( $out_dir ) unless( -d $out_dir );
116 0         0 $self->{ opts }->{ output_dir } = $out_dir; # appending game name as subdir
117             }
118              
119             sub _parser_strategy {
120 1     1   2 my ( $self, $game ) = @_;
121            
122 1         2 my @newparser_games = qw/
123             mobamasu
124             /;
125            
126 1         2 my @oldparser_games = qw/
127             saintseiya
128             /;
129            
130 1 50       16 return Acme::MobileTradeFun::NewParser->new() if ( grep( /$game/, @newparser_games ) );
131            
132 1 50       10 if ( grep( /$game/, @oldparser_games ) ) {
133 0         0 $self->{ opts }->{ base_url } .= "/fun";
134 0         0 return Acme::MobileTradeFun::OldParser->new();
135             }
136 1         251 croak "could not find parser for $game";
137             }
138              
139             =head2 run
140              
141             The driver method. Calls new then other methods to drive.
142              
143             =cut
144              
145             sub run {
146 0     0 1   my ( $class, $args ) = @_;
147 0           my $self = $class->new( $args );
148 0           $self->load_existing_cards();
149 0           $self->fetch_cards();
150 0           return $self;
151             }
152              
153             =head2 load_existing_cards
154              
155             Loads existing cards into $self->{ cards } array.
156              
157             =cut
158              
159             sub load_existing_cards {
160 0     0 1   my $self = shift;
161              
162 0           my $dir = $self->{ opts }->{ output_dir };
163 0           opendir( my $dh, $dir );
164            
165 0           while( my $file = readdir( $dh ) ) {
166             # need to decode UTF-8 to internal format
167 0           $file = decode( 'UTF-8', $file );
168 0 0         push @{ $self->{ cards } }, $file if ( $file =~ /\.jpg$/ );
  0            
169             }
170             }
171              
172             =head2 fetch_cards
173              
174             Wrapper method to initiate the fetching of cards
175              
176             =cut
177              
178             sub fetch_cards {
179 0     0 1   my $self = shift;
180            
181 0           my $page = $self->{ opts }->{ page };
182 0           my $base_url = $self->{ opts }->{ base_url };
183 0           my $script = $self->{ opts }->{ php_script };
184 0           my $row = $self->{ opts }->{ row };
185 0           my $game = $self->{ opts }->{ game };
186 0           my $dir = $self->{ opts }->{ output_dir };
187 0           my $parser = $self->{ parser };
188              
189 0           my @newcards;
190              
191 0           while( 1 ) {
192 0           my $url = "$base_url/$game/$script?row=$row&page=$page";
193 0           my $data = get( $url );
194 0           my ( $cards, @new ) = $parser->parse_data( $data, $self->{ cards } );
195 0           push @newcards, @new;
196 0           DEBUG "scraping $url. $cards found.";
197 0 0         last unless ( $cards );
198 0           $page++;
199             }
200              
201 0 0         unless( @newcards ) {
202 0           DEBUG "no card to fetch, exiting";
203 0           return;
204             }
205            
206 0           my $numcards = @newcards;
207 0           DEBUG "we have $numcards new cards. fetch starting...";
208              
209             my $cv = AE::cv {
210 0     0     DEBUG "fetched all cards!";
211 0           };
212              
213 0           for my $key ( @newcards ) {
214 0           $cv->begin;
215 0           my $url = $key->{ url };
216             http_get $url, sub {
217 0     0     my ( $data, $hdr ) = @_;
218 0 0         if ( $hdr->{ Status } =~ /^2/ ) {
219 0           my $file = "$dir/$key->{ name }";
220 0 0         open( my $fh, ">", $file ) or croak "couldn't open $file: $!";
221 0           print $fh $data;
222 0 0         close $fh or croak "couldn't close $file: $!";
223             }
224             else {
225 0           DEBUG "something went wrong with $url";
226             }
227 0           $cv->end;
228             }
229 0           }
230 0           $cv->recv;
231             }
232              
233             =head1 AUTHOR
234              
235             Satoshi Yagi, C<< >>
236              
237             =head1 BUGS
238              
239             Please report any bugs or feature requests to C, or through
240             the web interface at L. I will be notified, and then you'll
241             automatically be notified of progress on your bug as I make changes.
242              
243              
244              
245              
246             =head1 SUPPORT
247              
248             You can find documentation for this module with the perldoc command.
249              
250             perldoc Acme::MobileTradeFun
251              
252              
253             You can also look for information at:
254              
255             =over 4
256              
257             =item * RT: CPAN's request tracker (report bugs here)
258              
259             L
260              
261             =item * AnnoCPAN: Annotated CPAN documentation
262              
263             L
264              
265             =item * CPAN Ratings
266              
267             L
268              
269             =item * Search CPAN
270              
271             L
272              
273             =back
274              
275              
276             =head1 ACKNOWLEDGEMENTS
277              
278              
279             =head1 LICENSE AND COPYRIGHT
280              
281             Copyright 2012-2013 Satoshi Yagi.
282              
283             This program is free software; you can redistribute it and/or modify it
284             under the terms of the the Artistic License (2.0). You may obtain a
285             copy of the full license at:
286              
287             L
288              
289             Any use, modification, and distribution of the Standard or Modified
290             Versions is governed by this Artistic License. By using, modifying or
291             distributing the Package, you accept this license. Do not use, modify,
292             or distribute the Package, if you do not accept this license.
293              
294             If your Modified Version has been derived from a Modified Version made
295             by someone other than you, you are nevertheless required to ensure that
296             your Modified Version complies with the requirements of this license.
297              
298             This license does not grant you the right to use any trademark, service
299             mark, tradename, or logo of the Copyright Holder.
300              
301             This license includes the non-exclusive, worldwide, free-of-charge
302             patent license to make, have made, use, offer to sell, sell, import and
303             otherwise transfer the Package with respect to any patent claims
304             licensable by the Copyright Holder that are necessarily infringed by the
305             Package. If you institute patent litigation (including a cross-claim or
306             counterclaim) against any party alleging that the Package constitutes
307             direct or contributory patent infringement, then this Artistic License
308             to you shall terminate on the date that such litigation is filed.
309              
310             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
311             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
312             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
313             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
314             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
315             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
316             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
317             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
318              
319              
320             =cut
321              
322             1; # End of Acme::MobileTradeFun