📄 Source: clientid.php
<?php
/**
* ClientID mod integration.
*
* RCON responses arrive as a single line, e.g.:
* "ezyalex700_'s mod list:appleskin, architectury, balm, ..."
* "ezyalex700_'s pack list:Default, Fabric Mod \"AppleSkin\", ..."
*
* There is no newline between the header and the list. We split on the
* first ':' that appears AFTER the word "list".
*/
require_once __DIR__ . '/rcon.php';
function clientid_rcon(string $serverDir, array $cfg, string $command): string {
$rcon = rcon_from_properties($serverDir, $cfg['rcon']);
if (empty($rcon['password'])) {
throw new RuntimeException('RCON not configured in server.properties');
}
$r = new Rcon($rcon['host'], (int)$rcon['port'], $rcon['password'], (int)$rcon['timeout']);
try {
return $r->command($command);
} finally {
$r->close();
}
}
/**
* Extract the comma-separated list part of a ClientID response.
*
* Examples:
* "ezyalex700_'s mod list:appleskin, architectury" → "appleskin, architectury"
* "ezyalex700_'s pack list:Default, Fabric Mod \"X\"" → "Default, Fabric Mod \"X\""
* "ezyalex700_'s mod list:" → ""
* "<player> not found" → null
*/
function clientid_extract_list(string $raw, string $kind): ?string {
$raw = trim($raw);
if ($raw === '') return null;
// Find "mod list" or "pack list" (case-insensitive), then the first ':'
// after it. Everything after that colon is the list.
$needle = $kind . ' list';
$pos = stripos($raw, $needle);
if ($pos === false) return null;
$colon = strpos($raw, ':', $pos);
if ($colon === false) return null;
return trim(substr($raw, $colon + 1));
}
/**
* Parse the "viewmods" response into a lowercase list of mod IDs.
*/
function clientid_parse_mods(string $raw): array {
$listPart = clientid_extract_list($raw, 'mod');
if ($listPart === null || $listPart === '') return [];
$mods = [];
foreach (explode(',', $listPart) as $piece) {
$piece = trim($piece);
if ($piece !== '') $mods[] = strtolower($piece);
}
return array_values(array_unique($mods));
}
/**
* Parse the "viewpacks" response.
*
* Entries look like:
* Default
* Fabric Mod "AppleSkin"
* Fullbright-UB-26.3 fub-7.0.zip
*
* Some pack names contain commas? Unlikely, but the split on ', ' with
* a lookahead protects against it: we split on commas that are followed
* by "Fabric Mod " or by a capitalized word that isn't inside quotes.
* Simplest safe approach: split on ', ' and re-join any piece that
* starts with a lowercase continuation of a quoted string.
*/
function clientid_parse_packs(string $raw): array {
$listPart = clientid_extract_list($raw, 'pack');
if ($listPart === null || $listPart === '') return [];
// Split on ", " — the ClientID format uses a comma-space separator
// between entries. Quoted names don't contain ", " inside them
// (Fabric Mod names are simple).
$parts = preg_split('/,\s+/', $listPart);
$packs = [];
foreach ($parts as $piece) {
$piece = trim($piece);
if ($piece !== '') $packs[] = $piece;
}
return array_values(array_unique($packs));
}
/**
* Filter a mod list against a baseline (case-insensitive).
*/
function clientid_filter_mods(array $playerMods, array $baseline): array {
$baseSet = [];
foreach ($baseline as $b) $baseSet[strtolower(trim($b))] = true;
$extra = [];
foreach ($playerMods as $m) {
$m = strtolower(trim($m));
if ($m === '') continue;
if (!isset($baseSet[$m])) $extra[] = $m;
}
sort($extra);
return $extra;
}
/**
* Filter a pack list against a baseline (case-insensitive).
*/
function clientid_filter_packs(array $playerPacks, array $baseline): array {
$baseSet = [];
foreach ($baseline as $b) $baseSet[strtolower(trim($b))] = true;
$extra = [];
foreach ($playerPacks as $p) {
$p = trim($p);
if ($p === '') continue;
if (!isset($baseSet[strtolower($p)])) $extra[] = $p;
}
sort($extra);
return $extra;
}
← Back