feat(player-counter): add TeamSpeak 3 ServerQuery support#120
feat(player-counter): add TeamSpeak 3 ServerQuery support#120IceCupe123 wants to merge 2 commits intopelican-dev:mainfrom
Conversation
- Add TeamSpeakQueryTypeSchema with raw TCP ServerQuery protocol implementation - Reads QUERY_PORT directly from server variables via new resolvePort() hook - Filters out ServerQuery clients (client_type=1) from player list - Unescapes TeamSpeak protocol encoding in server/player names - Extend GameQuery model with resolvePort() hook: schemas can optionally implement resolvePort(Allocation) to override the default port-offset logic with custom resolution (e.g. from server variables) - Fix canRunQuery() and runQuery() to support hostname aliases (ip_alias), not just raw IPs, enabling use behind reverse proxies with 0.0.0.0 bindings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds TeamSpeak 3 ServerQuery support via a new QueryTypeSchema, updates GameQuery to resolve IPs and allow schema-specific port resolution, and registers the new schema in the plugin provider. Changes
Sequence DiagramsequenceDiagram
participant GameQuery
participant TeamSpeakSchema
participant TS3Server as TeamSpeak Server
GameQuery->>GameQuery: resolveIp(allocation)
GameQuery->>TeamSpeakSchema: resolvePort(allocation)
TeamSpeakSchema-->>GameQuery: port or null
GameQuery->>TeamSpeakSchema: process(ip, port)
TeamSpeakSchema->>TS3Server: TCP connect (ip:port)
TS3Server-->>TeamSpeakSchema: ServerQuery greeting
TeamSpeakSchema->>TS3Server: use sid=1
TS3Server-->>TeamSpeakSchema: ack
TeamSpeakSchema->>TS3Server: serverinfo
TS3Server-->>TeamSpeakSchema: server metadata
TeamSpeakSchema->>TS3Server: clientlist
TS3Server-->>TeamSpeakSchema: clients + error marker
TeamSpeakSchema->>TeamSpeakSchema: parse/unescape/filter clients
TeamSpeakSchema->>TS3Server: quit
TeamSpeakSchema->>TeamSpeakSchema: close socket
TeamSpeakSchema-->>GameQuery: normalized result (hostname,map,current,max,players)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php`:
- Around line 77-84: The returned array in TeamSpeakQueryTypeSchema uses manual
alignment spacing around the => which causes Pint formatting failures; update
the array (the block that constructs 'hostname', 'map', 'current_players',
'max_players', 'players') to remove the extra spaces and use a single space on
each side of => (do not align keys), and leave the existing value expressions
and array_map callback unchanged so only spacing/formatting is modified.
- Around line 52-55: The greeting read uses fgets($socket) and then
trim($greeting) without validating the return value and the exception handler
only catches Exception, so change the greeting guard to check for falsy/false
return from fgets before calling trim and throw a descriptive Exception if
reading failed; also broaden the catch to catch \Throwable where the greeting is
validated (reference fgets and trim usage and the surrounding try/catch block)
so TypeError/\Error are handled. Additionally, add the missing parameter type
hint to the readUntilError($socket) function signature (use the same
socket/resource type used elsewhere) to satisfy PHPStan level 6 and ensure
consistent typing.
- Around line 97-114: The readUntilError function currently treats any "error
..." line as a termination but still returns accumulated data (and overwrites
rather than accumulates), hiding failures and lacks socket typing; update
readUntilError to declare the socket parameter as a resource in the PHPDoc
(e.g., `@param` resource $socket) and change the loop so it accumulates non-empty
trimmed lines into $data (append instead of overwrite) and when encountering a
line that starts with "error " parse the id (e.g., regex for id=(\d+)) and if
the id is not "0" throw/return a proper exception or error object containing the
id and message instead of returning $data, otherwise (id==0) break and return
the accumulated success data; reference the readUntilError function and the
local variables $socket, $line, $trimmed, and $data to locate where to apply
these changes.
In `@player-counter/src/Models/GameQuery.php`:
- Line 42: Calls to the private static method resolveIp in GameQuery are using
late static binding (static::resolveIp), which trips PHPStan level 6 because
private methods cannot be overridden; change those call sites to self::resolveIp
(both occurrences currently at the resolveIp invocations) so the private static
dispatch is safe and consistent with visibility.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 52bc20cb-c03e-4bc9-ba5b-38cb15203fd2
📒 Files selected for processing (3)
player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.phpplayer-counter/src/Models/GameQuery.phpplayer-counter/src/Providers/PlayerCounterPluginProvider.php
📜 Review details
🧰 Additional context used
🪛 GitHub Actions: Lint
player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php
[error] 1-1: Laravel Pint reported 1 style issue (FAIL). The pint test run failed with exit code 1.
🪛 GitHub Check: PHPStan (8.2)
player-counter/src/Models/GameQuery.php
[failure] 42-42:
Unsafe call to private method Boy132\PlayerCounter\Models\GameQuery::resolveIp() through static::.
[failure] 70-70:
Unsafe call to private method Boy132\PlayerCounter\Models\GameQuery::resolveIp() through static::.
player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php
[failure] 97-97:
Method Boy132\PlayerCounter\Extensions\Query\Schemas\TeamSpeakQueryTypeSchema::readUntilError() has parameter $socket with no type specified.
🪛 GitHub Check: PHPStan (8.3)
player-counter/src/Models/GameQuery.php
[failure] 42-42:
Unsafe call to private method Boy132\PlayerCounter\Models\GameQuery::resolveIp() through static::.
[failure] 70-70:
Unsafe call to private method Boy132\PlayerCounter\Models\GameQuery::resolveIp() through static::.
player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php
[failure] 97-97:
Method Boy132\PlayerCounter\Extensions\Query\Schemas\TeamSpeakQueryTypeSchema::readUntilError() has parameter $socket with no type specified.
🪛 GitHub Check: PHPStan (8.4)
player-counter/src/Models/GameQuery.php
[failure] 42-42:
Unsafe call to private method Boy132\PlayerCounter\Models\GameQuery::resolveIp() through static::.
[failure] 70-70:
Unsafe call to private method Boy132\PlayerCounter\Models\GameQuery::resolveIp() through static::.
player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php
[failure] 97-97:
Method Boy132\PlayerCounter\Extensions\Query\Schemas\TeamSpeakQueryTypeSchema::readUntilError() has parameter $socket with no type specified.
🪛 GitHub Check: PHPStan (8.5)
player-counter/src/Models/GameQuery.php
[failure] 42-42:
Unsafe call to private method Boy132\PlayerCounter\Models\GameQuery::resolveIp() through static::.
[failure] 70-70:
Unsafe call to private method Boy132\PlayerCounter\Models\GameQuery::resolveIp() through static::.
player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php
[failure] 97-97:
Method Boy132\PlayerCounter\Extensions\Query\Schemas\TeamSpeakQueryTypeSchema::readUntilError() has parameter $socket with no type specified.
🔇 Additional comments (1)
player-counter/src/Providers/PlayerCounterPluginProvider.php (1)
15-15: TeamSpeak schema registration is correctly wired.Import + registration align with
QueryTypeServiceusage and keeps the default schema bootstrap path consistent.Also applies to: 39-39
| $greeting = fgets($socket); | ||
| if (!str_starts_with(trim($greeting), 'TS3')) { | ||
| throw new Exception('Not a TeamSpeak 3 ServerQuery interface'); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find and read the file
find . -name "TeamSpeakQueryTypeSchema.php" -type fRepository: pelican-dev/plugins
Length of output: 137
🏁 Script executed:
# Also check for composer.json to understand PHP version requirements
find . -name "composer.json" -type fRepository: pelican-dev/plugins
Length of output: 45
🏁 Script executed:
# Read the file to verify the concerns
cat -n ./player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.phpRepository: pelican-dev/plugins
Length of output: 5772
🏁 Script executed:
# Check for composer.json in player-counter directory
cat -n ./player-counter/composer.json 2>/dev/null || echo "No composer.json in player-counter"Repository: pelican-dev/plugins
Length of output: 97
🏁 Script executed:
# Check PHP version requirements at repository root
find . -maxdepth 2 -name "composer.json" -type f | head -5Repository: pelican-dev/plugins
Length of output: 45
🏁 Script executed:
# Check if there are any other files that might indicate PHP version or linting rules
find . -maxdepth 2 \( -name ".php-version" -o -name "phpstan.neon" -o -name "pint.json" \) -type fRepository: pelican-dev/plugins
Length of output: 89
🏁 Script executed:
# Check PHPStan and Pint configurations
cat -n ./phpstan.neonRepository: pelican-dev/plugins
Length of output: 385
🏁 Script executed:
# Check Pint configuration
cat -n ./pint.jsonRepository: pelican-dev/plugins
Length of output: 367
Guard greeting reads and catch \Throwable to prevent uncaught runtime failures.
Line 53: fgets() can return false on timeout or stream closure. Calling trim($greeting) with a false value throws TypeError in PHP 8+, which is not caught by catch (Exception) since TypeError extends \Error, not \Exception.
Additionally, line 97 has a missing parameter type hint on readUntilError($socket), which violates PHPStan level 6 checking.
Proposed fix
use Exception;
+use Throwable;
@@
- $greeting = fgets($socket);
- if (!str_starts_with(trim($greeting), 'TS3')) {
+ $greeting = fgets($socket);
+ if (!is_string($greeting) || !str_starts_with(trim($greeting), 'TS3')) {
throw new Exception('Not a TeamSpeak 3 ServerQuery interface');
}
@@
- } catch (Exception $exception) {
+ } catch (Throwable $exception) {
report($exception);
} finally {
@@
- private function readUntilError($socket): string
+ private function readUntilError(\Socket|false $socket): string🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php`
around lines 52 - 55, The greeting read uses fgets($socket) and then
trim($greeting) without validating the return value and the exception handler
only catches Exception, so change the greeting guard to check for falsy/false
return from fgets before calling trim and throw a descriptive Exception if
reading failed; also broaden the catch to catch \Throwable where the greeting is
validated (reference fgets and trim usage and the surrounding try/catch block)
so TypeError/\Error are handled. Additionally, add the missing parameter type
hint to the readUntilError($socket) function signature (use the same
socket/resource type used elsewhere) to satisfy PHPStan level 6 and ensure
consistent typing.
player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php
Outdated
Show resolved
Hide resolved
| private function readUntilError($socket): string | ||
| { | ||
| $data = ''; | ||
| while (!feof($socket)) { | ||
| $line = fgets($socket); | ||
| if ($line === false) { | ||
| break; | ||
| } | ||
| $trimmed = trim($line); | ||
| if (str_starts_with($trimmed, 'error ')) { | ||
| break; | ||
| } | ||
| if ($trimmed !== '') { | ||
| $data = $trimmed; | ||
| } | ||
| } | ||
| return $data; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "TeamSpeakQueryTypeSchema.php" | head -20Repository: pelican-dev/plugins
Length of output: 137
🏁 Script executed:
cat -n ./player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php | sed -n '90,130p'Repository: pelican-dev/plugins
Length of output: 1385
🏁 Script executed:
cat -n ./player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php | head -50Repository: pelican-dev/plugins
Length of output: 1787
🏁 Script executed:
cat -n ./player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php | sed -n '50,100p'Repository: pelican-dev/plugins
Length of output: 2182
🏁 Script executed:
rg "readUntilError" ./player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.phpRepository: pelican-dev/plugins
Length of output: 273
🏁 Script executed:
cat -n ./player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php | sed -n '116,127p'Repository: pelican-dev/plugins
Length of output: 514
readUntilError() silently converts command failures into successful results and lacks socket parameter typing.
The function stops on any error ... line without validating whether id=0. When a TS3 ServerQuery command fails (e.g., error id=1024 msg=...), the accumulated data is still returned and subsequently parsed as a successful response, masking the actual failure. Additionally, the untyped $socket parameter causes PHPStan failures in strict mode.
Proposed fix
- private function readUntilError($socket): string
+ /**
+ * `@param` resource $socket
+ */
+ private function readUntilError(mixed $socket): string
{
$data = '';
while (!feof($socket)) {
$line = fgets($socket);
if ($line === false) {
break;
}
$trimmed = trim($line);
if (str_starts_with($trimmed, 'error ')) {
+ $error = $this->parseLine($trimmed);
+ if (($error['id'] ?? '1') !== '0') {
+ throw new Exception('TeamSpeak ServerQuery command failed: ' . ($error['msg'] ?? 'unknown'));
+ }
break;
}
if ($trimmed !== '') {
$data = $trimmed;
}
}
return $data;
}🧰 Tools
🪛 GitHub Check: PHPStan (8.2)
[failure] 97-97:
Method Boy132\PlayerCounter\Extensions\Query\Schemas\TeamSpeakQueryTypeSchema::readUntilError() has parameter $socket with no type specified.
🪛 GitHub Check: PHPStan (8.3)
[failure] 97-97:
Method Boy132\PlayerCounter\Extensions\Query\Schemas\TeamSpeakQueryTypeSchema::readUntilError() has parameter $socket with no type specified.
🪛 GitHub Check: PHPStan (8.4)
[failure] 97-97:
Method Boy132\PlayerCounter\Extensions\Query\Schemas\TeamSpeakQueryTypeSchema::readUntilError() has parameter $socket with no type specified.
🪛 GitHub Check: PHPStan (8.5)
[failure] 97-97:
Method Boy132\PlayerCounter\Extensions\Query\Schemas\TeamSpeakQueryTypeSchema::readUntilError() has parameter $socket with no type specified.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php`
around lines 97 - 114, The readUntilError function currently treats any "error
..." line as a termination but still returns accumulated data (and overwrites
rather than accumulates), hiding failures and lacks socket typing; update
readUntilError to declare the socket parameter as a resource in the PHPDoc
(e.g., `@param` resource $socket) and change the loop so it accumulates non-empty
trimmed lines into $data (append instead of overwrite) and when encountering a
line that starts with "error " parse the id (e.g., regex for id=(\d+)) and if
the id is not "0" throw/return a proper exception or error object containing the
id and message instead of returning $data, otherwise (id==0) break and return
the accumulated success data; reference the readUntilError function and the
local variables $socket, $line, $trimmed, and $data to locate where to apply
these changes.
| } | ||
|
|
||
| $ip = config('player-counter.use_alias') && is_ip($allocation->alias) ? $allocation->alias : $allocation->ip; | ||
| $ip = static::resolveIp($allocation); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find the GameQuery.php file
find . -name "GameQuery.php" -type fRepository: pelican-dev/plugins
Length of output: 104
🏁 Script executed:
# Read the GameQuery.php file to inspect the code
cat -n player-counter/src/Models/GameQuery.phpRepository: pelican-dev/plugins
Length of output: 2940
🏁 Script executed:
# Look for PHPStan configuration
find . -name "phpstan.neon*" -o -name "phpstan.json" -o -name ".phpstan*" 2>/dev/null | head -20Repository: pelican-dev/plugins
Length of output: 77
🏁 Script executed:
# Search for any CI configuration that runs PHPStan
find . -name ".github" -o -name ".gitlab-ci.yml" -o -name ".travis.yml" -o -name "*.yml" 2>/dev/null | grep -E "(github|gitlab|github)" | head -10Repository: pelican-dev/plugins
Length of output: 146
🏁 Script executed:
# Check if there are other usages of this private method besides lines 42 and 70
rg "resolveIp" player-counter/src/Models/GameQuery.php -nRepository: pelican-dev/plugins
Length of output: 233
🏁 Script executed:
cat -n phpstan.neonRepository: pelican-dev/plugins
Length of output: 385
🏁 Script executed:
cat -n .github/workflows/lint.ymlRepository: pelican-dev/plugins
Length of output: 2537
Use self::resolveIp() to fix unsafe private static dispatch.
Lines 42 and 70 call a private static method via static::, which violates PHPStan analysis at level 6. Late static binding is semantically inconsistent with private visibility—private methods cannot be overridden, so static:: serves no purpose. Change to self::.
Proposed fix
- $ip = static::resolveIp($allocation);
+ $ip = self::resolveIp($allocation);Apply to lines 42 and 70.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| $ip = static::resolveIp($allocation); | |
| $ip = self::resolveIp($allocation); |
🧰 Tools
🪛 GitHub Check: PHPStan (8.2)
[failure] 42-42:
Unsafe call to private method Boy132\PlayerCounter\Models\GameQuery::resolveIp() through static::.
🪛 GitHub Check: PHPStan (8.3)
[failure] 42-42:
Unsafe call to private method Boy132\PlayerCounter\Models\GameQuery::resolveIp() through static::.
🪛 GitHub Check: PHPStan (8.4)
[failure] 42-42:
Unsafe call to private method Boy132\PlayerCounter\Models\GameQuery::resolveIp() through static::.
🪛 GitHub Check: PHPStan (8.5)
[failure] 42-42:
Unsafe call to private method Boy132\PlayerCounter\Models\GameQuery::resolveIp() through static::.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@player-counter/src/Models/GameQuery.php` at line 42, Calls to the private
static method resolveIp in GameQuery are using late static binding
(static::resolveIp), which trips PHPStan level 6 because private methods cannot
be overridden; change those call sites to self::resolveIp (both occurrences
currently at the resolveIp invocations) so the private static dispatch is safe
and consistent with visibility.
- Add @param resource type hint to readUntilError() socket parameter - Change resolveIp() from private to protected static to allow safe static:: calls from canRunQuery() and runQuery() - Fix code style issues flagged by Laravel Pint
There was a problem hiding this comment.
♻️ Duplicate comments (2)
player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php (2)
107-113:⚠️ Potential issue | 🟠 MajorHandle non-zero TeamSpeak
error idas failure instead of success.
readUntilError()currently breaks on anyerror ...line but does not validateid=0, so failed commands can be parsed as valid empty/partial results.Proposed fix
private function readUntilError($socket): string { - $data = ''; + $data = []; while (!feof($socket)) { $line = fgets($socket); if ($line === false) { break; } $trimmed = trim($line); if (str_starts_with($trimmed, 'error ')) { + $error = $this->parseLine($trimmed); + if (($error['id'] ?? '1') !== '0') { + throw new Exception( + 'TeamSpeak ServerQuery command failed: ' . ($error['msg'] ?? 'unknown') + ); + } break; } if ($trimmed !== '') { - $data = $trimmed; + $data[] = $trimmed; } } - return $data; + return implode("\n", $data); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php` around lines 107 - 113, The readUntilError() loop currently breaks on any line starting with 'error ' but doesn't check the error id; update the logic in readUntilError() to parse the 'error id=...' token from the $trimmed line and treat non-zero ids as a failure (e.g., return/throw or signal an error) instead of accepting the response as successful; ensure you still accept the command as success only when the error id equals '0' and continue parsing/assigning $data for other non-error lines.
52-55:⚠️ Potential issue | 🔴 CriticalGuard greeting reads and catch
\Throwableinprocess().
fgets()may returnfalse(Line 52);trim($greeting)can throwTypeErroron PHP 8+, andcatch (Exception)(Line 86) won’t catch it.Proposed fix
use Boy132\PlayerCounter\Extensions\Query\QueryTypeSchemaInterface; use Exception; +use Throwable; @@ // Read greeting: "TS3", welcome text, empty line $greeting = fgets($socket); - if (!str_starts_with(trim($greeting), 'TS3')) { + if (!is_string($greeting) || !str_starts_with(trim($greeting), 'TS3')) { throw new Exception('Not a TeamSpeak 3 ServerQuery interface'); } @@ - } catch (Exception $exception) { + } catch (Throwable $exception) { report($exception); } finally {#!/bin/bash # Verifies the risky pattern is still present. rg -n "greeting = fgets|trim\(\$greeting\)|catch \(Exception" player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.phpAlso applies to: 86-87
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php` around lines 52 - 55, The greeting read from fgets in process() can be false and trim($greeting) can raise a TypeError on PHP 8+, and the current catch (Exception) won't catch it; update the code around the $greeting = fgets($socket) check in TeamSpeakQueryTypeSchema::process() to first verify $greeting !== false before calling trim(), handle the false case by throwing or returning a clear error, and replace or supplement the existing catch (Exception) with catch (\Throwable) so TypeError and other throwables are caught; ensure the thrown/returned error message remains descriptive (e.g., "Not a TeamSpeak 3 ServerQuery interface" or a distinct message for read failures).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php`:
- Around line 107-113: The readUntilError() loop currently breaks on any line
starting with 'error ' but doesn't check the error id; update the logic in
readUntilError() to parse the 'error id=...' token from the $trimmed line and
treat non-zero ids as a failure (e.g., return/throw or signal an error) instead
of accepting the response as successful; ensure you still accept the command as
success only when the error id equals '0' and continue parsing/assigning $data
for other non-error lines.
- Around line 52-55: The greeting read from fgets in process() can be false and
trim($greeting) can raise a TypeError on PHP 8+, and the current catch
(Exception) won't catch it; update the code around the $greeting =
fgets($socket) check in TeamSpeakQueryTypeSchema::process() to first verify
$greeting !== false before calling trim(), handle the false case by throwing or
returning a clear error, and replace or supplement the existing catch
(Exception) with catch (\Throwable) so TypeError and other throwables are
caught; ensure the thrown/returned error message remains descriptive (e.g., "Not
a TeamSpeak 3 ServerQuery interface" or a distinct message for read failures).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 1a671b75-e6c3-4757-92c4-4678b59b47f4
📒 Files selected for processing (2)
player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.phpplayer-counter/src/Models/GameQuery.php
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: PHPStan (8.2)
🔇 Additional comments (3)
player-counter/src/Extensions/Query/Schemas/TeamSpeakQueryTypeSchema.php (1)
21-36:resolvePort()implementation looks clean and backward-compatible.The null-safe fallback behavior is good for schemas/environments where
QUERY_PORTis absent.player-counter/src/Models/GameQuery.php (2)
48-61: Schema lookup + optionalresolvePort()fallback flow is solid.Good defensive handling when schema is missing, while preserving legacy offset-based behavior.
70-82:resolveIp()reuse incanRunQuery()is a good fix for hostname alias support.This keeps eligibility checks consistent with the actual query target resolution.
Summary
TeamSpeakQueryTypeSchema— connects via raw TCP to the TeamSpeak 3 ServerQuery interface, reads server name, max clients and connected players, filters out ServerQuery clients (client_type=1), and unescapes TS3 protocol encodingGameQuery::runQuery()now checks for an optionalresolvePort(Allocation $allocation): ?intmethod on schemas, allowing them to resolve the query port from server variables instead of relying solely on thequery_port_offset. Schemas without this method continue to work exactly as beforeGameQuery::canRunQuery()andrunQuery()now support hostname-basedip_aliasvalues (not just raw IPs), which is required for servers bound to0.0.0.0behind a reverse proxyWhy the resolvePort() hook?
TeamSpeak exposes its ServerQuery port as a configurable server variable (
QUERY_PORT), independent of the voice port. A static offset would break whenever an admin changes the query port. The hook lets the schema read the actual configured value directly from the server.The hook is fully backwards-compatible — existing schemas and the
query_port_offsetfield are unaffected.Test plan
QUERY_PORTuses the correct port0.0.0.0with a hostnameip_aliasresolves correctly🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes / Reliability