128 lines
3.9 KiB
PHP
128 lines
3.9 KiB
PHP
<?php
|
|
namespace Misuzu;
|
|
|
|
use stdClass;
|
|
use JsonSerializable;
|
|
use RuntimeException;
|
|
use Stringable;
|
|
use DeviceDetector\ClientHints;
|
|
use DeviceDetector\DeviceDetector;
|
|
|
|
class ClientInfo implements Stringable, JsonSerializable {
|
|
private const SERIALIZE_VERSION = 1;
|
|
|
|
public function __construct(
|
|
private array|bool|null $botInfo,
|
|
private ?array $clientInfo,
|
|
private ?array $osInfo,
|
|
private string $brandName,
|
|
private string $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 function jsonSerialize(): mixed {
|
|
$data = new stdClass;
|
|
$data->version = self::SERIALIZE_VERSION;
|
|
|
|
if($this->botInfo === true || is_array($this->botInfo))
|
|
$data->bot = $this->botInfo;
|
|
if($this->clientInfo !== null)
|
|
$data->client = $this->clientInfo;
|
|
if($this->osInfo !== null)
|
|
$data->os = $this->osInfo;
|
|
if($this->brandName !== '')
|
|
$data->vendor = $this->brandName;
|
|
if($this->modelName !== '')
|
|
$data->model = $this->modelName;
|
|
|
|
return $data;
|
|
}
|
|
|
|
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'] ?? ''
|
|
);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|