misuzu/src/ClientInfo.php
2025-01-29 23:13:17 +00:00

154 lines
4.6 KiB
PHP

<?php
namespace Misuzu;
use stdClass;
use JsonSerializable;
use RuntimeException;
use Stringable;
use DeviceDetector\{ClientHints,DeviceDetector};
use Index\Json\{JsonProperty,JsonSerializableCommon};
class ClientInfo implements Stringable, JsonSerializable {
use JsonSerializableCommon;
private const SERIALIZE_VERSION = 1;
/**
* @param bool|null|array{name: string} $botInfo
* @param ?array{name: string, version?: string} $clientInfo
* @param ?array{name: string, version?: string, platform?: string} $osInfo
*/
public function __construct(
private array|bool|null $botInfo,
private ?array $clientInfo,
private ?array $osInfo,
private string $brandName,
private string $modelName
) {}
#[JsonProperty('version')]
public function getVersion(): int {
return self::SERIALIZE_VERSION;
}
/** @return bool|null|array{name: string} */
#[JsonProperty('bot')]
public function getBotInfo(): bool|array|null {
if($this->botInfo === true || is_array($this->botInfo))
return $this->botInfo;
return null;
}
/** @return ?array{name: string, version?: string} */
#[JsonProperty('client')]
public function getClientInfo(): ?array {
return $this->clientInfo;
}
/** @return ?array{name: string, version?: string, platform?: string} */
#[JsonProperty('os')]
public function getOsInfo(): ?array {
return $this->osInfo;
}
#[JsonProperty('vendor', omitIfValue: '')]
public function getVendorName(): string {
return $this->brandName;
}
#[JsonProperty('model', omitIfValue: '')]
public function getModelName(): string {
return $this->modelName;
}
public function __toString(): string {
if($this->botInfo === true || is_array($this->botInfo)) {
if($this->botInfo === true)
return 'a bot';
return $this->botInfo['name'] ?? 'an unknown bot';
}
if(empty($this->clientInfo['name']))
return 'an unknown browser';
$string = $this->clientInfo['name'];
if(!empty($this->clientInfo['version']))
$string .= ' ' . $this->clientInfo['version'];
$hasOsInfo = !empty($this->osInfo['name']);
$hasModelName = !empty($this->modelName);
if($hasOsInfo || $hasModelName)
$string .= ' on ';
if($hasModelName) {
$deviceName = trim($this->brandName . ' ' . $this->modelName);
// most naive check in the world but it works well enough for this lol
$firstCharIsVowel = in_array(strtolower($deviceName[0]), ['a', 'i', 'u', 'e', 'o']);
$string .= ($firstCharIsVowel ? 'an' : 'a') . ' ' . $deviceName;
}
if($hasOsInfo) {
if($hasModelName)
$string .= ' running ';
$string .= $this->osInfo['name'];
if(!empty($this->osInfo['version']))
$string .= ' ' . $this->osInfo['version'];
if(!empty($this->osInfo['platform']))
$string .= ' (' . $this->osInfo['platform'] . ')';
}
return $string;
}
public function encode(): string {
return json_encode($this);
}
public static function decode(string $encoded): self {
$data = json_decode($encoded, true);
$version = $data['version'] ?? 0;
if($version < 0 || $version > self::SERIALIZE_VERSION)
throw new RuntimeException('$data does not contain a valid version argument');
return new ClientInfo(
$data['bot'] ?? null,
$data['client'] ?? null,
$data['os'] ?? null,
$data['vendor'] ?? '',
$data['model'] ?? ''
);
}
/** @param mixed[]|string $serverVarsOrUserAgent */
public static function parse(array|string $serverVarsOrUserAgent): self {
static $dd = null;
$dd ??= new DeviceDetector();
if(is_string($serverVarsOrUserAgent)) {
$dd->setUserAgent($serverVarsOrUserAgent);
} else {
$dd->setUserAgent(
array_key_exists('HTTP_USER_AGENT', $serverVarsOrUserAgent)
? $serverVarsOrUserAgent['HTTP_USER_AGENT'] : ''
);
$dd->setClientHints(ClientHints::factory($serverVarsOrUserAgent));
}
$dd->parse();
return new ClientInfo(
$dd->getBot(),
$dd->getClient(),
$dd->getOs(),
$dd->getBrandName(),
$dd->getModel()
);
}
public static function fromRequest(): self {
return self::parse($_SERVER);
}
}