File Coverage

blib/lib/Games/AssaultCube/ServerQuery.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             # Declare our package
2             package Games::AssaultCube::ServerQuery;
3              
4             # import the Moose stuff
5 2     2   768362 use Moose;
  0            
  0            
6             use MooseX::StrictConstructor;
7             use Moose::Util::TypeConstraints;
8              
9             # Initialize our version
10             use vars qw( $VERSION );
11             $VERSION = '0.04';
12              
13             # get some utility stuff
14             use Games::AssaultCube::Utils qw( default_port tostr get_ac_pingport );
15             use Games::AssaultCube::ServerQuery::Response;
16             use IO::Socket::INET;
17              
18             # TODO make validation so we accept either hostname or IPv4/6...
19             has 'server' => (
20             isa => 'Str',
21             is => 'ro',
22             required => 1,
23             );
24              
25             # <mst> Apocalypse: { my $port_spec = subtype as Int => where { ... }; has 'attr' => (isa => $port_spec, ...); }
26             {
27             my $port_type = subtype as 'Int' => where {
28             if ( $_ <= 0 or $_ > 65535 ) {
29             return 0;
30             } else {
31             return 1;
32             }
33             };
34              
35             has 'port' => (
36             isa => $port_type,
37             # isa => 'Int',
38             is => 'rw',
39             default => default_port(),
40             );
41             }
42              
43             has 'timeout' => (
44             isa => 'Int',
45             is => 'rw',
46             default => 30,
47             );
48              
49             has 'get_players' => (
50             isa => 'Bool',
51             is => 'rw',
52             default => 0,
53             );
54              
55             sub BUILDARGS {
56             my $class = shift;
57              
58             if ( @_ == 1 && ! ref $_[0] ) {
59             # set the server as the first argument
60             return { server => $_[0] };
61             } elsif ( @_ == 2 && ! ref $_[0] ) {
62             # server/port argument
63             return { server => $_[0], port => $_[1] };
64             } else {
65             # normal hash/hashref way
66             return $class->SUPER::BUILDARGS(@_);
67             }
68             }
69              
70             sub run {
71             my $self = shift;
72              
73             # Ok, get our socket and send off the PING!
74             my $sock = IO::Socket::INET->new(
75             Proto => 'udp',
76             PeerPort => get_ac_pingport( $self->port ),
77             PeerAddr => $self->server,
78             ) or die "Could not create socket: $!";
79             binmode $sock, ":utf8" or die "Unable to set binmode: $!";
80              
81             # generate the PING packet
82             # TODO support the EXT_XYZ options
83             my $pingpacket;
84             if ( $self->get_players ) {
85             $pingpacket = tostr('1') . tostr('1');
86             } else {
87             $pingpacket = tostr('1') . tostr('0');
88             }
89              
90             # send it!
91             $sock->send( $pingpacket ) or die "Unable to send: $!";
92              
93             # set the alarm, and wait for the response
94             my( $datagram, $result );
95             eval {
96             # perldoc -f alarm says I need to put \n in the die... weird!
97             local $SIG{ALRM} = sub { die "alarm\n" };
98             alarm $self->timeout;
99             $result = $sock->recv( $datagram, 1024 ) or die "Unable to recv: $!";
100             alarm 0;
101             };
102             if ( $@ ) {
103             if ( $@ =~ /^alarm/ ) {
104             die "Unable to query server: Timed out";
105             } else {
106             die "Unable to query server: $@";
107             }
108             } else {
109             if ( defined $result and defined $datagram and length( $datagram ) > 0 ) {
110             return Games::AssaultCube::ServerQuery::Response->new( $self, $datagram );
111             } else {
112             return;
113             }
114             }
115             }
116              
117             # from Moose::Manual::BestPractices
118             no Moose;
119             __PACKAGE__->meta->make_immutable;
120              
121             1;
122             __END__
123              
124             =for stopwords PxL playerlist PHP hostname ip
125              
126             =head1 NAME
127              
128             Games::AssaultCube::ServerQuery - Queries a running AssaultCube server for information
129              
130             =head1 SYNOPSIS
131              
132             use Games::AssaultCube::ServerQuery;
133             my $query = Games::AssaultCube::ServerQuery->new( 'my.server.com' );
134             #my $query = Games::AssaultCube::ServerQuery->new( 'my.server.com', 12345 );
135             #my $query = Games::AssaultCube::ServerQuery->new({ server => 'foo.com', port => 12345, timeout => 5 });
136             my $response = $query->run;
137             if ( defined $response ) {
138             print "Server is running with " . $response->players . " players\n";
139             } else {
140             print "Server is not responding!\n";
141             }
142              
143             =head1 ABSTRACT
144              
145             This module queries a running AssaultCube server for information.
146              
147             =head1 DESCRIPTION
148              
149             This module queries a running AssaultCube server for information. This has been tested extensively on
150             AssaultCube-1.0.2 servers, so beware if you try older/newer ones! Also, not all servers return all data, so
151             be sure to check for it in your code...
152              
153             =head2 Constructor
154              
155             This module uses Moose, so you can pass either a hash, hashref, or a server/port to the constructor. Passing
156             a string means we're passing in a server hostname/ip. If you want to specify more options, please use the
157             hash/hashref method.
158              
159             The attributes are:
160              
161             =head3 server
162              
163             The server hostname or ip.
164              
165             =head3 port
166              
167             The server port. Defaults to 28763.
168              
169             WARNING: AssaultCube uses $port+1 for the query port. Please do not do pass $port+1 to the constructor,
170             we do it internally. Maybe in the future AC will use $port+2 or another system, so let us deal with it :)
171              
172             =head3 get_players
173              
174             Should we also retrieve the playerlist? This is a boolean which defaults to false.
175              
176             =head3 timeout
177              
178             The timeout waiting for the server response in seconds. Defaults to 30.
179              
180             WARNING: We use alarm() internally to do the timeout. If you used it somewhere else, it will cause conflicts
181             and potentially render it useless. Please inform me if there's conflicts in your script and we can try to
182             work around it.
183              
184             =head2 Methods
185              
186             Currently, there is only one method: run(). You call this and get the response object back. For more
187             information please look at the L<Games::AssaultCube::ServerQuery::Response> class. You can call run() as
188             many times as you want, no need to re-instantiate the object for each query.
189              
190             WARNING: run() will die() if errors happen. For sanity, you should wrap it in an eval.
191              
192             =head2 Attributes
193              
194             You can modify some attributes before calling run() on the object. They are:
195              
196             =head3 port
197              
198             Same as the constructor
199              
200             =head3 timeout
201              
202             Same as the constructor
203              
204             =head3 get_players
205              
206             Same as the constructor
207              
208             =head1 AUTHOR
209              
210             Apocalypse E<lt>apocal@cpan.orgE<gt>
211              
212             Props goes to Getty and the BS clan for the support!
213              
214             This project is sponsored by L<http://cubestats.net>
215              
216             Also, thanks goes out to PxL for the initial PHP implementation which helped in unraveling the AssaultCube
217             mess.
218              
219             We also couldn't have done it without staring at the AssaultCube C++ code for hours, ha!
220              
221             =head1 COPYRIGHT AND LICENSE
222              
223             Copyright 2009 by Apocalypse
224              
225             This library is free software; you can redistribute it and/or modify
226             it under the same terms as Perl itself.
227              
228             =cut