File Coverage

blib/lib/WWW/MetaForge/GameMapData.pm
Criterion Covered Total %
statement 72 82 87.8
branch 14 28 50.0
condition 1 7 14.2
subroutine 17 17 100.0
pod 3 3 100.0
total 107 137 78.1


line stmt bran cond sub pod time code
1             package WWW::MetaForge::GameMapData;
2             our $AUTHORITY = 'cpan:GETTY';
3             # ABSTRACT: Perl client for the MetaForge Game Map Data API
4             our $VERSION = '0.002';
5              
6 6     6   463050 use Moo;
  6         10316  
  6         47  
7 6     6   6373 use LWP::UserAgent;
  6         144353  
  6         258  
8 6     6   661 use JSON::MaybeXS;
  6         11225  
  6         619  
9 6     6   47 use Carp qw(croak);
  6         10  
  6         430  
10 6     6   600 use namespace::clean;
  6         18612  
  6         59  
11              
12 6     6   3179 use WWW::MetaForge::Cache;
  6         23  
  6         287  
13 6     6   4159 use WWW::MetaForge::GameMapData::Request;
  6         28  
  6         275  
14 6     6   4289 use WWW::MetaForge::GameMapData::Result::MapMarker;
  6         39  
  6         9287  
15              
16             our $DEBUG = $ENV{WWW_METAFORGE_GAMEMAPDATA_DEBUG};
17              
18              
19             has ua => (
20             is => 'ro',
21             lazy => 1,
22             builder => '_build_ua',
23             );
24              
25              
26             has request => (
27             is => 'ro',
28             lazy => 1,
29             default => sub { WWW::MetaForge::GameMapData::Request->new },
30             );
31              
32              
33             has cache => (
34             is => 'ro',
35             lazy => 1,
36             builder => '_build_cache',
37             predicate => 'has_cache',
38             );
39              
40              
41             has use_cache => (
42             is => 'ro',
43             default => 1,
44             );
45              
46              
47             has cache_dir => (
48             is => 'ro',
49             );
50              
51              
52             has json => (
53             is => 'ro',
54             lazy => 1,
55             default => sub { JSON::MaybeXS->new(utf8 => 1) },
56             );
57              
58              
59             has debug => (
60             is => 'ro',
61             default => sub { $DEBUG },
62             );
63              
64              
65             has marker_class => (
66             is => 'ro',
67             default => 'WWW::MetaForge::GameMapData::Result::MapMarker',
68             );
69              
70              
71             sub _debug {
72 11     11   136 my ($self, $msg) = @_;
73 11 50       59 return unless $self->debug;
74 0         0 my $ts = localtime;
75 0         0 warn "[WWW::MetaForge::GameMapData $ts] $msg\n";
76             }
77              
78             sub _build_ua {
79 1     1   9 my ($self) = @_;
80 1   50     10 my $ua = LWP::UserAgent->new(
81             agent => 'WWW-MetaForge-GameMapData/' . ($WWW::MetaForge::GameMapData::VERSION // 'dev'),
82             timeout => 30,
83             );
84 1         2477 return $ua;
85             }
86              
87             sub _build_cache {
88 1     1   15 my ($self) = @_;
89 1         3 my %args;
90 1 50       10 $args{cache_dir} = $self->cache_dir if defined $self->cache_dir;
91 1         15 return WWW::MetaForge::Cache->new(%args);
92             }
93              
94             sub _fetch {
95 5     5   21 my ($self, $endpoint, $http_request, %params) = @_;
96              
97 5 100       31 if ($self->use_cache) {
98 2         95 my $cached = $self->cache->get($endpoint, \%params);
99 2 100       59 if (defined $cached) {
100 1         6 $self->_debug("CACHE HIT: $endpoint");
101 1         3 return $cached;
102             }
103 1         8 $self->_debug("CACHE MISS: $endpoint");
104             }
105              
106 4         15 my $url = $http_request->uri;
107 4         50 $self->_debug("REQUEST: GET $url");
108              
109 4         135 my $response = $self->ua->request($http_request);
110              
111 4         1211178 $self->_debug("RESPONSE: " . $response->code . " " . $response->message);
112              
113 4 50       21 unless ($response->is_success) {
114 0         0 croak sprintf("API request failed: %s %s",
115             $response->code, $response->message);
116             }
117              
118 4         44 my $data = eval { $self->json->decode($response->decoded_content) };
  4         208  
119 4 50       15919 croak "Failed to parse JSON response: $@" if $@;
120              
121 4 100       28 if ($self->use_cache) {
122 1         64 $self->cache->set($endpoint, \%params, $data);
123 1         8 $self->_debug("CACHE SET: $endpoint");
124             }
125              
126 4         146 return $data;
127             }
128              
129             sub _extract_markers {
130 4     4   10 my ($self, $response) = @_;
131              
132 4 50       18 return $response unless ref $response eq 'HASH';
133              
134             # API returns {"allData": [...]} for arc_map_data tableID
135 4 50       53 if (exists $response->{allData}) {
136 4         12 return $response->{allData};
137             }
138             # Fallback: {"markers": [...]} or {"data": {"markers": [...]}}
139 0 0       0 if (exists $response->{markers}) {
140 0         0 return $response->{markers};
141             }
142 0 0 0     0 if (exists $response->{data} && ref $response->{data} eq 'HASH') {
143 0   0     0 return $response->{data}{markers} // [];
144             }
145              
146 0         0 return $response;
147             }
148              
149             sub _to_objects {
150 4     4   10 my ($self, $data) = @_;
151              
152 4 50       13 return [] unless defined $data;
153              
154 4         18 my $class = $self->marker_class;
155              
156 4 50       33 if (ref $data eq 'ARRAY') {
    0          
157 4         24 return [ map { $class->from_hashref($_) } @$data ];
  1676         342595  
158             } elsif (ref $data eq 'HASH') {
159             # Single item - wrap in array for consistency
160 0         0 return [ $class->from_hashref($data) ];
161             }
162              
163 0         0 return $data;
164             }
165              
166             sub map_data {
167 4     4 1 10817 my ($self, %params) = @_;
168 4         155 my $req = $self->request->map_data(%params);
169 4         372 my $response = $self->_fetch('map_data', $req, %params);
170 4         21 my $markers = $self->_extract_markers($response);
171 4         40 return $self->_to_objects($markers);
172             }
173              
174              
175             sub map_data_raw {
176 1     1 1 5893 my ($self, %params) = @_;
177 1         73 my $req = $self->request->map_data(%params);
178 1         121 return $self->_fetch('map_data', $req, %params);
179             }
180              
181              
182             sub clear_cache {
183 1     1 1 20 my ($self, $endpoint) = @_;
184 1         29 $self->cache->clear($endpoint);
185             }
186              
187              
188             1;
189              
190             __END__
191              
192             =pod
193              
194             =encoding UTF-8
195              
196             =head1 NAME
197              
198             WWW::MetaForge::GameMapData - Perl client for the MetaForge Game Map Data API
199              
200             =head1 VERSION
201              
202             version 0.002
203              
204             =head1 SYNOPSIS
205              
206             use WWW::MetaForge::GameMapData;
207              
208             my $api = WWW::MetaForge::GameMapData->new;
209              
210             # Get map markers for a specific map
211             my $markers = $api->map_data(map => 'dam');
212             for my $marker (@$markers) {
213             say $marker->type . " at " . $marker->x . "," . $marker->y;
214             }
215              
216             # Filter by marker type
217             my $loot = $api->map_data(map => 'dam', type => 'loot');
218              
219             =head1 DESCRIPTION
220              
221             Perl interface to the MetaForge Game Map Data API. This API provides
222             map marker data (POIs, loot locations, quest markers, etc.) for games
223             supported by MetaForge.
224              
225             This is a generic base module. Game-specific distributions (like
226             L<WWW::MetaForge::ArcRaiders>) can use this module and extend the
227             result classes with game-specific attributes.
228              
229             =head2 ua
230              
231             L<LWP::UserAgent> instance. Built lazily with sensible defaults.
232              
233             =head2 request
234              
235             L<WWW::MetaForge::GameMapData::Request> instance for creating
236             L<HTTP::Request> objects.
237              
238             =head2 cache
239              
240             L<WWW::MetaForge::Cache> instance for response caching.
241              
242             =head2 use_cache
243              
244             Boolean, default true. Set to false to disable caching.
245              
246             =head2 cache_dir
247              
248             Optional L<Path::Tiny> path for cache directory. Defaults to
249             XDG cache dir on Unix, LOCALAPPDATA on Windows.
250              
251             =head2 json
252              
253             L<JSON::MaybeXS> instance for encoding/decoding JSON.
254              
255             =head2 debug
256              
257             Boolean. Enable debug output. Also settable via
258             C<$ENV{WWW_METAFORGE_GAMEMAPDATA_DEBUG}>.
259              
260             =head2 marker_class
261              
262             Class to use for map marker objects. Defaults to
263             L<WWW::MetaForge::GameMapData::Result::MapMarker>. Override this
264             to use a subclass with game-specific attributes.
265              
266             =head2 map_data
267              
268             my $markers = $api->map_data(map => 'dam');
269             my $markers = $api->map_data(map => 'dam', type => 'loot');
270              
271             Returns ArrayRef of L<WWW::MetaForge::GameMapData::Result::MapMarker>
272             (or subclass specified by C<marker_class>).
273              
274             Required parameter: C<map> - name of the map to fetch markers for.
275             Optional parameter: C<type> - filter by marker type.
276              
277             =head2 map_data_raw
278              
279             Same as C<map_data> but returns raw HashRef/ArrayRef instead of objects.
280              
281             =head2 clear_cache
282              
283             $api->clear_cache('map_data'); # Clear specific endpoint
284             $api->clear_cache; # Clear all
285              
286             Clear cached responses.
287              
288             =head1 ATTRIBUTION
289              
290             This module uses the MetaForge API: L<https://metaforge.app>
291              
292             =head1 SUPPORT
293              
294             =head2 Issues
295              
296             Please report bugs and feature requests on GitHub at
297             L<https://github.com/Getty/p5-www-metaforge/issues>.
298              
299             =head2 IRC
300              
301             You can reach Getty on C<irc.perl.org> for questions and support.
302              
303             =head1 CONTRIBUTING
304              
305             Contributions are welcome! Please fork the repository and submit a pull request.
306              
307             =head1 AUTHOR
308              
309             Torsten Raudssus <torsten@raudssus.de>
310              
311             =head1 COPYRIGHT AND LICENSE
312              
313             This software is copyright (c) 2026 by Torsten Raudssus.
314              
315             This is free software; you can redistribute it and/or modify it under
316             the same terms as the Perl 5 programming language system itself.
317              
318             =cut