File Coverage

blib/lib/WWW/GameMonitor.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package WWW::GameMonitor;
2              
3             our $VERSION = '0.03';
4              
5 1     1   38964 use XML::Simple;
  0            
  0            
6             use Data::Dumper;
7             use LWP::Simple;
8             use Hey::Common;
9             use Hey::Cache;
10              
11             =cut
12              
13             =head1 NAME
14              
15             WWW::GameMonitor - Fetch information about game servers from Game-Monitor.com
16              
17             =head1 SYNOPSIS
18              
19             # example 1
20             use WWW::GameMonitor;
21             my $gm1 = WWW::GameMonitor->new;
22             my $serv1 = $gm1->getServerInfo( Host => '216.237.126.132', Port => '16567' ); # ACE Battlefield2 Server
23             print qq(On $serv1->{name}, $serv1->{count}->{current} players ($serv1->{count}->{max} limit) are playing $serv1->{game}->{longname}, map $serv1->{map}.\n);
24            
25             # example 2
26             use WWW::GameMonitor;
27             my $gm2 = WWW::GameMonitor->new( Host => '216.237.126.132', Port => '16567' ); # default to a certain server
28             my $serv2 = $gm2->getServerInfo; # uses the defaults specified in the constructor
29              
30             =head1 DESCRIPTION
31              
32             This module will help you get information about various official and clan game servers (Battlefield 2, Quake 4, and many more). The server
33             that is being queried must be listed as a "premium" server. This means someone (you, the server owner, or someone else) must have an active
34             subscription with Game-Monitor.com for that server to be accessible in this way. You, yourself, do not have to have an account with them, but
35             someone out there on the Internet must have listed that specific server in their paid account. For example, at the time of writing, the ACE
36             Battlefield 2 server Ehttp://www.armchairextremist.com/E is listed under such an account. This means that you could, without needing
37             to contact or pay anyone, use this module to ask for information about the ACE Battlefield 2 server. If you run your own clan game server or
38             you want to monitor someone else's game server (and Game-Monitor.com supports your game), it might be worth it to you to pay them the
39             ~USD$3-7/month for this ability. They take PayPal.
40              
41             =head2 new
42              
43             my $gm = WWW::GameMonitor->new; # no options or defaults specified
44            
45             my $gm = WWW::GameMonitor->new( Host => '216.237.126.132', Port => '16567' ); # default to a certain server
46              
47             You can specify several options in the constructor.
48              
49             my $gm = WWW::GameMonitor->new(
50             Expires => 300,
51             Host => '216.237.126.132',
52             Port => '16567',
53             CacheFile => 'my_gm_cache.xml',
54             DebugLog => 'my_debug_log.txt',
55             DebugLevel => 3,
56             UID => 12345,
57             List => 0,
58             );
59              
60             =head3 Expires [optional]
61              
62             Sets the data cache freshness in seconds. If the cache has data older than this number of seconds, it is no longer valid. It's best that
63             you set this value to something higher than 1 minute and would be even better if you were satisfied with setting it around 5 minutes. If
64             the cache is fresh enough, it won't even ask the Game-Monitor.com server for any information. Keep in mind that Game-Monitor doesn't update
65             their information more than once every several minutes. It won't be useful for you to set the Expires value too low.
66              
67             =head3 Host [optional]
68              
69             Sets the default host to ask about. If you don't specify a host when asking for data, it will use this value instead.
70              
71             =head3 Port [optional]
72              
73             Sets the default port to ask about. If you don't specify a port when asking for data, it will use this value instead.
74              
75             =head3 CacheFile [optional]
76              
77             Sets the path and filename for the data cache. This is "gameServerInfoCache.xml" by default.
78              
79             =head3 DebugLog [optional]
80              
81             Sets the path and filename for the debug log. This is "gmDebug.log" by default. To enable logging, you'll have to choose a DebugLevel
82             greater than zero (zero is default).
83              
84             =head3 DebugLevel [optional]
85              
86             Sets the level of debugging. The larger the number, the more verbose the logging. This is zero by default, which means no logging at all.
87              
88             =head3 UID [optional]
89              
90             Sets the default UID used for fetching buddy lists.
91              
92             =head3 List [optional]
93              
94             Sets the default buddy list used for fetching buddy lists.
95              
96             =cut
97              
98             sub new {
99             my $class = shift;
100             my %options = @_;
101             my $self = {};
102             bless($self, $class); # class-ify it.
103              
104             $self->{fxn} = Hey::Common->new;
105              
106             $self->{debugLog} = $options{DebugLog} || 'gmDebug.log';
107             $self->{debugLevel} = $options{DebugLevel} || 0;
108              
109             $self->{cache} = Hey::Cache->new(
110             Namespace => $options{Namespace} || $options{NameSpace} || 'WWW::GameMonitor',
111             CacheFile => $options{CacheFile} || $options{StoreFile} || 'gameServerInfoCache.xml',
112             Expires => $options{Expires} || $options{Fresh} || 600,
113             );
114              
115             $self->{host} = $options{Host} || undef;
116             $self->{port} = $options{Port} || undef;
117              
118             $self->{uid} = (defined($options{UID}) ? $options{UID} : 0);
119             $self->{buddyList} = (defined($options{List}) ? $options{List} : 0);
120              
121             $self->__debug(7, 'Object Attributes:', Dumper($self));
122              
123             return $self;
124             }
125              
126             sub __debug {
127             my $self = shift || return undef;
128             return undef unless $self->{debugLog}; # skip unless log file is defined
129             my $level = int(shift);
130             return undef unless $self->{debugLevel} >= $level; # skip unless log level is as high as this item
131             if (open(GAMEMONDEBUG, ">>$self->{debugLog}")) {
132             my $time = localtime();
133             foreach my $group (@_) { # roll through many items if they are passed in as an array
134             foreach my $line (split(/\r?\n/, $group)) { # roll through items that are multiline, converting to multiple separate lines
135             print GAMEMONDEBUG "[$time] $line\n";
136             }
137             }
138             close(GAMEMONDEBUG);
139             }
140             return undef;
141             }
142              
143             sub __fetchServerInfo {
144             my $self = shift || return undef;
145             my %options = @_;
146             my $host = $options{Host} || return undef; # if the host isn't defined, fail
147             my $port = $options{Port} || return undef; # if the port isn't defined, fail
148              
149             my $name = $host.':'.$port;
150              
151             my $cache = $self->{cache}->get( Name => $name ); # get data from cache
152             if ($cache) { # cache data exists for this host/port
153             $self->__debug(3, 'Cache data is fresh.');
154             return $cache if ($VERSION eq $cache->{client_version}); ## check the client version against the cache, in case the client (this code) has been upgraded, which might break the cache
155             }
156             else {
157             $self->__debug(2, 'Cache is not fresh or no data. Fetching from source.');
158             }
159              
160             my $url = qq(http://www.game-monitor.com/client/server-xml.php?rules=1&ip=$host:$port); # format the url for the source
161             my $response = get($url); # fetch the info from the source
162             unless ($response) { # it failed (rejected, bad connection, etc)
163             $self->__debug(2, 'Could not fetch data from source.');
164             if ($store) {
165             $self->__debug(2, 'Going to provide stale store data instead of failing.');
166             return $self->{cache}->get( Name => $name, Expires => 99999999 ); # get data from cache with no expiration
167             }
168             else { # there is nothing to send back, fail
169             $self->__debug(3, 'There is no store data to return.');
170             return undef;
171             }
172             }
173             my $data = XMLin($response, KeyAttr => undef); # parse the xml into hashref
174             $data->{count} = $data->{players}; # move the player counts
175             $data->{players} = $self->{fxn}->forceArray($data->{players}->{player}); # make sure players is an arrayref
176             delete($data->{count}->{player}); # cleanup unnecessary stuff
177             my $variables = $self->{fxn}->forceArray($data->{variables}->{variable}); # make sure variables is an arrayref
178             delete($data->{variables}); # remove the messy looking and difficult to use variables structure
179              
180             foreach my $variable (@{$variables}) { # loop through the messy variables
181             $data->{variables}->{$variable->{name}} = $variable->{value}; # make them pretty and easy to use
182             }
183              
184             $data->{client_version} = $VERSION;
185              
186             $self->{cache}->set( Name => $name, Value => $data ); # store it, baby!
187              
188             return $data;
189             }
190              
191             =cut
192              
193             =head2 getServerInfo
194              
195             my $serv = $gm->getServerInfo; # uses the defaults specified in the constructor
196             print qq(On $serv1->{name}, $serv1->{count}->{current} players ($serv1->{count}->{max} limit) are playing $serv1->{game}->{longname}, map $serv1->{map}.\n);
197            
198             my $serv = $gm->getServerInfo( Host => '216.237.126.132', Port => '16567' ); # ask about a certain server
199             print qq(On $serv1->{name}, $serv1->{count}->{current} players ($serv1->{count}->{max} limit) are playing $serv1->{game}->{longname}, map $serv1->{map}.\n);
200              
201             =head3 Host [required]
202              
203             Asks about the specified host. If this was specified in the constructor, this value is optional.
204              
205             =head3 Port [required]
206              
207             Asks about the specified port. If this was specified in the constructor, this value is optional.
208              
209             =cut
210              
211             sub getServerInfo {
212             my $self = shift || return undef;
213             my %options = @_;
214             my $host = $options{Host} || $self->{host} || return undef; # if the host isn't defined, get the default or fail
215             my $port = $options{Port} || $self->{port} || return undef; # if the port isn't defined, get the default or fail
216             my $data = $self->__fetchServerInfo( Host => $host, Port => $port ); # fetch it!
217             return $data; # return the post-processed server info
218             }
219              
220             =cut
221              
222             =head2 getBuddyList
223              
224             $list = $gm->getBuddyList; # uses defaults set in the constructor
225             $list = $gm->getBuddyList( List => 1 ); # sets a different list than the default
226             $list = $gm->getBuddyList( UID => 12345, List => 2 ); # also sets a different UID along with a different list
227              
228             =head3 UID [required]
229              
230             Sets the UID used for fetching buddy lists. If this was specified in the constructor, this value is optional.
231              
232             =head3 List [required]
233              
234             Sets the buddy list used for fetching buddy lists. If this was specified in the constructor, this value is optional.
235              
236             =cut
237              
238             sub getBuddyList {
239             my $self = shift || return undef;
240             my %options = @_;
241              
242             $self->__debug(4, 'getBuddyList');
243              
244             my $uid = (defined($options{UID}) ? $options{UID} : (defined($self->{uid}) ? $self->{uid} : return undef));
245             my $list = (defined($options{List}) ? $options{List} : (defined($self->{buddyList}) ? $self->{buddyList} : return undef));
246              
247             my $name = "BuddyList:${uid}:${list}"; # make a pretty name
248              
249             $self->__debug(4, 'getBuddyList('.$name.')');
250              
251             my $cache = $self->{cache}->get( Name => $name ); # get data from cache
252             if ($cache) { # cache is still fresh
253             $self->__debug(3, 'Cache data is fresh.');
254             return $cache; # return the still fresh cache
255             }
256             else { # cache is stale
257             $self->__debug(2, 'Cache is not fresh or no data. Fetching from source.');
258             my $url = qq(http://www.game-monitor.com/client/buddyList.php?uid=$uid&listid=$list&xml=1); # format the url for the source
259             my $response = get($url); # fetch the info from the source
260             if ($response) { # fetching from source succeeded
261             my $data = XMLin($response, KeyAttr => undef); # parse the xml into hashref
262              
263             my $buddies = $self->{fxn}->forceArray($data->{buddy}); # make sure buddies is an arrayref
264             delete($data->{buddy});
265             foreach my $buddy (@{$buddies}) { # loop through the returned players
266             if ($buddy->{server}->{fullip} eq '0.0.0.0:') { # no valid server, remove it
267             $buddy->{server} = {}; # wipe it out
268             }
269             $data->{player}->{$buddy->{name}} = $buddy; # add this player to the list of players
270             }
271              
272             $self->{cache}->set( Name => $name, Value => $data ); # store it away into the cache
273             return $data; # return the new, fresh data
274             }
275             else { # fetching from source failed (rejected, bad connection, etc)
276             $self->__debug(2, 'Could not fetch data from source.');
277             $cache = $self->{cache}->get( Name => $name, Expires => 99999999 ); # get data from cache, ignoring expiration
278             if ($cache) {
279             $self->__debug(2, 'Going to provide stale cache data instead of failing.');
280             return $cache; # return the old, stale cache
281             }
282             else {
283             $self->__debug(3, 'There is no cache data to return.');
284             return undef; # nothing to return
285             }
286             }
287             }
288            
289             }
290              
291             =cut
292              
293             =head1 AUTHOR
294              
295             Dusty Wilson, Ewww-gamemonitor-module@dusty.hey.nuE
296              
297             =head1 COPYRIGHT AND LICENSE
298              
299             Copyright (C) 2006 by Dusty Wilson Ehttp://dusty.hey.nu/E
300              
301             This library is free software; you can redistribute it and/or modify
302             it under the same terms as Perl itself, either Perl version 5.8.8 or,
303             at your option, any later version of Perl 5 you may have available.
304              
305             =cut
306              
307             1;