Skip to content

Commit

Permalink
Merge branch 'main' of github.com:hexydec/agentzero into main
Browse files Browse the repository at this point in the history
Fixed tests.
  • Loading branch information
hexydec committed Feb 5, 2025
2 parents 995620d + 30660a4 commit b74a3db
Show file tree
Hide file tree
Showing 18 changed files with 792 additions and 77 deletions.
61 changes: 58 additions & 3 deletions src/agentzero.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,19 +194,74 @@ protected static function getTokens(string $ua, array $single, array $ignore) :
* @return agentzero|false An agentzero object containing the parsed values of the input UA, or false if it could not be parsed
*/
public static function parse(string $ua) : agentzero|false {
$ua = \trim(\preg_replace('/\s{2,}/', ' ', $ua), ' "\'');
if (($config = config::get()) === null) {
if (($ua = \preg_replace('/\s{2,}/', ' ', $ua)) === null) {

} elseif (($tokens = self::getTokens($ua, $config['single'], $config['ignore'])) !== false) {
} elseif (($config = config::get()) === null) {

} elseif (($tokens = self::getTokens(\trim($ua, ' "\''), $config['single'], $config['ignore'])) !== false) {

// extract UA info
$browser = new \stdClass();
$tokenslower = \array_map('mb_strtolower', $tokens);
foreach ($config['match'] AS $key => $item) {
$item->match($browser, $key, $tokens, $tokenslower);
}

// default information
$arr = (array) $browser;
if (empty($arr) && !empty($tokens)) {
self::parseDefault($browser, $tokens);
}

// create agentzero object and return
return new agentzero($ua, $browser);
}
return false;
}

/**
* Parse the UA string when no other extractions were able to be made
*
* @param \stdClass $obj A standard class object to populate
* @param array<string> $tokens An array of tokens
* @return void
*/
protected static function parseDefault(\stdClass $obj, array $tokens) : void {
$obj->type = 'robot';
$obj->category = 'scraper';

// find app names
foreach ($tokens AS $item) {
if (\str_contains($item, '/')) {
$parts = \explode('/', $item);
$obj->app = crawlers::normaliseAppname($parts[0]);
$obj->appname = $parts[0];
if (!empty($parts[1])) {
$obj->appversion = \ltrim($parts[1], 'v');
}
return;
}
}

// parse the string
foreach ($tokens AS $token) {
$name = [];
foreach (\explode(' ', $token) AS $item) {
$ver = \ltrim($item, 'v'); // strip 'v' off the front of version number
if (\strpbrk($ver, '0123456789.') === $ver) {
$app = \implode(' ', $name);
$obj->app = crawlers::normaliseAppname($app);
$obj->appname = $app;
$obj->appversion = $ver;
return;
} else {
$name[] = $item;
}
}
}

// just use the string
$obj->app = crawlers::normaliseAppname($tokens[0]);
$obj->appname = $tokens[0];
}
}
109 changes: 102 additions & 7 deletions src/mappings/apps.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ public static function get() : array {
'appslash' => function (string $value, int $i, array $tokens, string $match) : array {
if (\mb_stripos($value, 'AppleWebKit') === false && !\str_contains($value, '://')) {
$parts = \explode('/', $value, 4);
$offset = isset($parts[2]) ? 1 : 0;
$app = $parts[0 + $offset];
$offset = isset($parts[2]) && !\is_numeric($parts[1]) ? 1 : 0;
$app = \str_replace('GooglePlayStore ', '', $parts[0 + $offset]);
if (\mb_stripos($app, \rtrim($match, '/')) !== false) { // check the match is before the slash
return [
'app' => self::getApp($app),
Expand Down Expand Up @@ -114,10 +114,31 @@ public static function get() : array {
'appname' => 'Zoom',
'appversion' => \mb_substr($value, 5)
]),
'Reddit/' => new props('start', function (string $value, int $i, array $tokens) : array {
$os = !empty($tokens[$i+2]) ? \explode('/', $tokens[$i+2], 2) : null;
$parts = !empty($os[1]) ? \explode(' ', $os[1], 3) : null;
return [
'type' => 'human',
'app' => 'Reddit',
'appname' => 'Reddit',
'appversion' => $value === 'Reddit/Version' ? (\mb_strstr($tokens[$i+1], '/', true) ?: null) : \mb_substr($value, 7),
'platform' => $parts[0] ?? null,
'platformversion' => $tokens[$i+3] ?? null
];
}),
'Pinterest for ' => new props('start', fn (string $value) : array => [
'type' => 'human',
'app' => 'Pinterest',
'appname' => 'Pinterest',
'appversion' => \explode('/', $value, 3)[1],
'platform' => \mb_substr($value, 14, \mb_strpos($value, '/') - 14)
]),
'OculusBrowser/' => new props('start', $fn['appslash']),
'BaiduBrowser/' => new props('start', $fn['appslash']),
'bdhonorbrowser/' => new props('start', $fn['appslash']),
'YaBrowser/' => new props('start', $fn['appslash']),
'YaApp_iOS/' => new props('start', $fn['appslash']),
'YaApp_Android/' => new props('start', $fn['appslash']),
'choqok/' => new props('start', $fn['appslash']),
'Quora ' => new props('start', fn (string $value) : array => [
'type' => 'human',
Expand All @@ -129,6 +150,68 @@ public static function get() : array {
'whisper/' => new props('start', $fn['appslash']),
'Teams/' => new props('start', $fn['appslash']),
'Viber/' => new props('start', $fn['appslash']),
'MXPlayer/' => new props('start', fn (string $value) : array => [
'app' => 'MXPlayer',
'appname' => 'MXPlayer',
'appversion' => \explode('/', $value, 3)[1] ?: null
]),
'Todoist-Android/' => new props('start', fn (string $value) : array => [
'type' => 'human',
'platform' => 'Android',
'app' => 'Todoist',
'appname' => 'Todoist-Android',
'appversion' => \explode('/', $value, 3)[1] ?: null
]),
'Trello for Android/' => new props('start', fn (string $value) : array => [
'type' => 'human',
'platform' => 'Android',
'app' => 'Trello',
'appname' => 'Trello for Android',
'appversion' => \explode('/', $value, 3)[1] ?: null
]),
'iPad PurpleMashApp' => new props('start', fn (string $value) : array => [
'type' => 'human',
'platform' => 'iOS',
'vendor' => 'Apple',
'device' => 'iPad',
'app' => 'Purple Mash',
'appname' => 'PurpleMashApp'
]),
'Instapaper for Android ' => new props('start', fn (string $value) : array => [
'type' => 'human',
'platform' => 'Android',
'app' => 'Instapaper',
'appname' => 'Instapaper',
'appversion' => \mb_substr($value, \mb_strrpos($value, ' ') + 1)
]),
'Instapaper' => new props('start', fn (string $value, int $i, array $tokens, string $match) => \array_merge([
'type' => 'human'
], $fn['appslash']($value, $i, $tokens, $match))),
'Player/LG' => new props('start', function (string $value, int $i, array $tokens) : ?array {
if (\str_starts_with($tokens[$i+1], 'Player ')) {
$parts = \explode(' ', $tokens[$i+1]);
$device = $i === 1 ? \explode('/', $tokens[0]) : null;
return [
'type' => 'human',
'vendor' => 'LG',
'device' => $device[0] ?? null,
'model' => $device[1] ?? null,
'app' => 'LG Player',
'appname' => 'LG Player',
'appversion' => $parts[1] ?? null,
'platform' => $parts[3] ?? null,
'platformversion' => $parts[4] ?? null
];
}
return null;
}),
'RobloxApp/' => new props('any', $fn['appslash']),
'Nexo/' => new props('start', $fn['appslash']),
'nu.nl/' => new props('any', fn (string $value) : array => [
'app' => 'nu.nl',
'appname' => 'nu.nl',
'appversion' => \mb_substr($value, 6)
]),
'Google Web Preview' => new props('start', $fn['appslash']),
'MicroMessenger/' => new props('start', $fn['appslash']),
'MicroMessenger Weixin QQ' => new props('start', fn () : array => [
Expand Down Expand Up @@ -281,10 +364,16 @@ public static function get() : array {
}),

// TikTok
'AppName/' => new props('start', fn(string $value) : array => [
'app' => $value === 'AppName/musical_ly' ? 'TikTok' : \mb_substr($value, 8),
'appname' => \mb_substr($value, 8)
]),
'AppName/' => new props('start', function (string $value) : array {
$map = [
'AppName/musical_ly' => 'TikTok',
'AppName/aweme' => 'Douyin'
];
return [
'app' => $map[$value] ?? \mb_substr($value, 8),
'appname' => \mb_substr($value, 8)
];
}),
'app_version/' => new props('start', fn(string $value) : array => [
'appversion' => \mb_substr($value, 12)
]),
Expand All @@ -296,6 +385,10 @@ public static function get() : array {
'appname' => 'musical_ly',
'appversion' => \str_replace('_', '.', \mb_substr(\explode(' ', $value, 2)[0], 11))
]),
'Android App' => new props('any', [
'type' => 'human',
'platform' => 'Android'
]),

// generic
'App' => new props('any', $fn['appslash'])
Expand All @@ -308,6 +401,7 @@ public static function getApp(string $value) : string {
'YaApp_Android' => 'Yandex',
'YaSearchApp' => 'Yandex',
'YaBrowser' => 'Yandex',
'YaSearchBrowser' => 'Yandex',
'LinkedInApp' => 'LinkedIn',
'[LinkedInApp]' => 'LinkedIn',
'GoogleApp' => 'Google',
Expand All @@ -329,7 +423,8 @@ public static function getApp(string $value) : string {
'baiduboxapp' => 'Baidu',
'lite baiduboxapp' => 'Baidu',
'baidubrowser' => 'Baidu',
'bdhonorbrowser' => 'Baidu'
'bdhonorbrowser' => 'Baidu',
'RobloxApp' => 'Roblox'
];
if (isset($map[$value])) {
return $map[$value];
Expand Down
14 changes: 7 additions & 7 deletions src/mappings/browsers.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ public static function get(array $versions) : array {
'k-meleon' => 'K-Meleon',
'samsungbrowser' => 'Samsung Internet',
'huaweibrowser' => 'Huawei Browser',
'qqbrowser' => 'QQ Browser'
'qqbrowser' => 'QQ Browser',
'miuibrowser' => 'MIUI Browser'
];
$data = ['type' => 'human'];
$browser = \mb_strtolower(\array_shift($parts));
Expand Down Expand Up @@ -90,13 +91,8 @@ public static function get(array $versions) : array {
},
];
return [
'HeadlessChrome/' => new props('start', fn (string $value) : array => [
'type' => 'robot',
'category' => 'crawler',
'browser' => 'HeadlessChrome',
'browserversion' => \mb_substr($value, 15)
]),
'Opera Mini/' => new props('start', $fn['presto']),
'Native Opera Mini/' => new props('start', $fn['presto']),
'Opera/' => new props('start', $fn['presto']),
'OPR/' => new props('start', $fn['browserslash']),
'CriOS/' => new props('start', $fn['browserslash']),
Expand Down Expand Up @@ -133,7 +129,11 @@ public static function get(array $versions) : array {
'Falkon/' => new props('start', $fn['browserslash']),
'Namoroka/' => new props('start', $fn['browserslash']),
'CocCoc/' => new props('start', $fn['browserslash']),
'Obigo/' => new props('start', fn (string $value) : array => [
'browser' => 'Obigo'
]),
'QQBrowser/' => new props('any', fn (string $value) : array => $fn['browserslash'](\mb_substr($value, \mb_stripos($value, 'QQBrowser/') ?: 0))), // sometimes missing a space from previous declaration, and MQQBrowser for mobile.
'MiuiBrowser/' => new props('any', fn (string $value) : array => $fn['browserslash'](\mb_substr($value, \mb_stripos($value, 'MiuiBrowser/') ?: 0))),
'Lynx/' => new props('start', fn (string $value) : array => [
'browser' => 'Lynx',
'browserversion' => \explode('/', $value, 2)[1] ?? null,
Expand Down
10 changes: 9 additions & 1 deletion src/mappings/categories.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static function get() : array {
'type' => 'human',
'category' => 'mobile'
]),
'Phone' => new props('exact', [
'Phone' => new props('any', [
'type' => 'human',
'category' => 'mobile'
]),
Expand All @@ -31,6 +31,14 @@ public static function get() : array {
'type' => 'human',
'category' => 'tablet'
]),
'TAB ' => new props('start', [
'type' => 'human',
'category' => 'tablet'
]),
'TAB_' => new props('start', [
'type' => 'human',
'category' => 'tablet'
]),
'Large Screen' => new props('exact', [
'type' => 'human',
'category' => 'tv'
Expand Down
Loading

0 comments on commit b74a3db

Please sign in to comment.