Teamspeak3::processPlayers()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 17
ccs 7
cts 7
cp 1
rs 10
cc 3
nc 3
nop 2
crap 3
1
<?php
2
/**
3
 * This file is part of GameQ.
4
 *
5
 * GameQ is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU Lesser General Public License as published by
7
 * the Free Software Foundation; either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * GameQ is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU Lesser General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Lesser General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
namespace GameQ\Protocols;
20
21
use GameQ\Buffer;
22
use GameQ\Exception\Protocol as Exception;
23
use GameQ\Helpers\Str;
24
use GameQ\Protocol;
25
use GameQ\Result;
26
use GameQ\Server;
27
28
/**
29
 * Teamspeak 3 Protocol Class
30
 *
31
 * All values are utf8 encoded upon processing
32
 *
33
 * This code ported from GameQ v1/v2. Credit to original author(s) as I just updated it to
34
 * work within this new system.
35
 *
36
 * @author Austin Bischoff <[email protected]>
37
 */
38
class Teamspeak3 extends Protocol
39
{
40
    /**
41
     * Array of packets we want to look up.
42
     * Each key should correspond to a defined method in this or a parent class
43
     *
44
     * @var array
45
     */
46
    protected $packets = [
47
        self::PACKET_DETAILS  => "use port=%d\x0Aserverinfo\x0A",
48
        self::PACKET_PLAYERS  => "use port=%d\x0Aclientlist\x0A",
49
        self::PACKET_CHANNELS => "use port=%d\x0Achannellist -topic\x0A",
50
    ];
51
52
    /**
53
     * The transport mode for this protocol is TCP
54
     *
55
     * @var string
56
     */
57
    protected $transport = self::TRANSPORT_TCP;
58
59
    /**
60
     * The query protocol used to make the call
61
     *
62
     * @var string
63
     */
64
    protected $protocol = 'teamspeak3';
65
66
    /**
67
     * String name of this protocol class
68
     *
69
     * @var string
70
     */
71
    protected $name = 'teamspeak3';
72
73
    /**
74
     * Longer string name of this protocol class
75
     *
76
     * @var string
77
     */
78
    protected $name_long = "Teamspeak 3";
79
80
    /**
81
     * The client join link
82
     *
83
     * @var string
84
     */
85
    protected $join_link = "ts3server://%s?port=%d";
86
87
    /**
88
     * Normalize settings for this protocol
89
     *
90
     * @var array
91
     */
92
    protected $normalize = [
93
        // General
94
        'general' => [
95
            'dedicated'  => 'dedicated',
96
            'hostname'   => 'virtualserver_name',
97
            'password'   => 'virtualserver_flag_password',
98
            'numplayers' => 'numplayers',
99
            'maxplayers' => 'virtualserver_maxclients',
100
        ],
101
        // Player
102
        'player'  => [
103
            'id'   => 'clid',
104
            'team' => 'cid',
105
            'name' => 'client_nickname',
106
        ],
107
        // Team
108
        'team'    => [
109
            'id'   => 'cid',
110
            'name' => 'channel_name',
111
        ],
112
    ];
113
114
    /**
115
     * Before we send off the queries we need to update the packets
116
     *
117
     * @param \GameQ\Server $server
118
     * @return void
119
     * @throws \GameQ\Exception\Protocol
120
     */
121 30
    public function beforeSend(Server $server)
122
    {
123
        // Check to make sure we have a query_port because it is required
124 30
        if (!isset($this->options[Server::SERVER_OPTIONS_QUERY_PORT])
125 30
            || empty($this->options[Server::SERVER_OPTIONS_QUERY_PORT])
126
        ) {
127 6
            throw new Exception(__METHOD__ . " Missing required setting '" . Server::SERVER_OPTIONS_QUERY_PORT . "'.");
128
        }
129
130
        // Let's loop the packets and set the proper pieces
131 24
        foreach ($this->packets as $packet_type => $packet) {
132
            // Update with the client port for the server
133 24
            $this->packets[$packet_type] = sprintf($packet, $server->portClient());
134
        }
135
    }
136
137
    /**
138
     * Process the response
139
     *
140
     * @return array
141
     * @throws \GameQ\Exception\Protocol
142
     */
143 18
    public function processResponse()
144
    {
145
        // Make a new buffer out of all of the packets
146 18
        $buffer = new Buffer(implode('', $this->packets_response));
147
148
        // Check the header TS3
149 18
        if (($header = trim($buffer->readString("\n"))) !== 'TS3') {
150 6
            throw new Exception(__METHOD__ . " Expected header '{$header}' does not match expected 'TS3'.");
151
        }
152
153
        // Convert all the escaped characters
154 12
        $raw = str_replace(
155 12
            [
156 12
                '\\\\', // Translate escaped \
157 12
                '\\/', // Translate escaped /
158 12
            ],
159 12
            [
160 12
                '\\',
161 12
                '/',
162 12
            ],
163 12
            $buffer->getBuffer()
164 12
        );
165
166
        // Explode the sections and filter to remove empty, junk ones
167 12
        $sections = array_filter(explode("\n", $raw), function ($value) {
168 12
            $value = trim($value);
169
170
            // Not empty string or a message response for "error id=\d"
171 12
            return !empty($value) && substr($value, 0, 5) !== 'error';
172 12
        });
173
174
        // Trim up the values to remove extra whitespace
175 12
        $sections = array_map('trim', $sections);
176
177
        // Set the result to a new result instance
178 12
        $result = new Result();
179
180
        // Iterate over the sections and offload the parsing
181 12
        foreach ($sections as $section) {
182
            // Grab a snip of the data so we can figure out what it is
183 12
            $check = substr(trim($section), 0, 4);
184
185
            // Use the first part of the response to figure out where we need to go
186 12
            if ($check == 'virt') {
187
                // Server info
188 12
                $this->processDetails($section, $result);
189 12
            } elseif ($check == 'cid=') {
190
                // Channels
191 12
                $this->processChannels($section, $result);
192 12
            } elseif ($check == 'clid') {
193
                // Clients (players)
194 12
                $this->processPlayers($section, $result);
195
            }
196
        }
197
198 12
        unset($buffer, $sections, $section, $check);
199
200 12
        return $result->fetch();
201
    }
202
203
    // Internal methods
204
205
    /**
206
     * Process the properties of the data.
207
     *
208
     * Takes data in "key1=value1 key2=value2 ..." and processes it into a usable format
209
     *
210
     * @param $data
211
     * @return array
212
     */
213 12
    protected function processProperties($data)
214
    {
215
        // Will hold the properties we are sending back
216 12
        $properties = [];
217
218
        // All of these are split on space
219 12
        $items = explode(' ', $data);
220
221
        // Iterate over the items
222 12
        foreach ($items as $item) {
223
            // Explode and make sure we always have 2 items in the array
224 12
            list($key, $value) = array_pad(explode('=', $item, 2), 2, '');
225
226
            // Convert spaces and other character changes
227 12
            $properties[$key] = Str::isoToUtf8(str_replace(
228 12
                [
229 12
                    '\\s', // Translate spaces
230 12
                ],
231 12
                [
232 12
                    ' ',
233 12
                ],
234 12
                $value
235 12
            ));
236
        }
237
238 12
        return $properties;
239
    }
240
241
    /**
242
     * Handles processing the details data into a usable format
243
     *
244
     * @param string        $data
245
     * @param \GameQ\Result $result
246
     * @return void
247
     */
248 12
    protected function processDetails($data, Result &$result)
249
    {
250
        // Offload the parsing for these values
251 12
        $properties = $this->processProperties($data);
252
253
        // Always dedicated
254 12
        $result->add('dedicated', 1);
255
256
        // Iterate over the properties
257 12
        foreach ($properties as $key => $value) {
258 12
            $result->add($key, $value);
259
        }
260
261
        // We need to manually figure out the number of players
262 12
        $result->add(
263 12
            'numplayers',
264 12
            ($properties['virtualserver_clientsonline'] - $properties['virtualserver_queryclientsonline'])
265 12
        );
266
267 12
        unset($data, $properties, $key, $value);
268
    }
269
270
    /**
271
     * Process the channel listing
272
     *
273
     * @param string        $data
274
     * @param \GameQ\Result $result
275
     * @return void
276
     */
277 12
    protected function processChannels($data, Result &$result)
278
    {
279
        // We need to split the data at the pipe
280 12
        $channels = explode('|', $data);
281
282
        // Iterate over the channels
283 12
        foreach ($channels as $channel) {
284
            // Offload the parsing for these values
285 12
            $properties = $this->processProperties($channel);
286
287
            // Iterate over the properties
288 12
            foreach ($properties as $key => $value) {
289 12
                $result->addTeam($key, $value);
290
            }
291
        }
292
293 12
        unset($data, $channel, $channels, $properties, $key, $value);
294
    }
295
296
    /**
297
     * Process the user listing
298
     *
299
     * @param string        $data
300
     * @param \GameQ\Result $result
301
     * @return void
302
     */
303 12
    protected function processPlayers($data, Result &$result)
304
    {
305
        // We need to split the data at the pipe
306 12
        $players = explode('|', $data);
307
308
        // Iterate over the channels
309 12
        foreach ($players as $player) {
310
            // Offload the parsing for these values
311 12
            $properties = $this->processProperties($player);
312
313
            // Iterate over the properties
314 12
            foreach ($properties as $key => $value) {
315 12
                $result->addPlayer($key, $value);
316
            }
317
        }
318
319 12
        unset($data, $player, $players, $properties, $key, $value);
320
    }
321
}
322