<?php
/**
* Copyright (c) 2023 Roman Grinko <[email protected]>
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace Core\ExternalServices\Request;
use Core\CoreException;
use Exception;
/**
* Класс для генерации фейковых Useragent
*/
class FakeUserAgent
{
/** @var array WINDOWS_OS */
private const WINDOWS_OS = [
'[Windows; |Windows; U; |]Windows NT 6.:number0-3:;[ Win64; x64| WOW64| x64|]',
'[Windows; |Windows; U; |]Windows NT 10.:number0-5:;[ Win64; x64| WOW64| x64|]',
];
/** @var array LINUX_OS */
private const LINUX_OS = [
'[Linux; |][U; |]Linux x86_64',
'[Linux; |][U; |]Linux i:number5-6::number4-8::number0-6: [x86_64|]',
];
/** @var array MAC_OS */
private const MAC_OS = [
'Macintosh; [U; |]Intel Mac OS X :number7-9:_:number0-9:_:number0-9:',
'Macintosh; [U; |]Intel Mac OS X 10_:number0-12:_:number0-9:',
];
/** @var array ANDROID_VERSIONS Версии Android */
private const ANDROID_VERSIONS = [
'4.3.1',
'4.4',
'4.4.1',
'4.4.4',
'5.0',
'5.0.1',
'5.0.2',
'5.1',
'5.1.1',
'6.0',
'6.0.1',
'7.0',
'7.1',
'7.1.1',
];
/** @property string $androidVersion Сгенерированная версия Android */
private string $androidVersion;
/** @var array ANDROID_DEVICES Устройства Android */
private const ANDROID_DEVICES = [
'4.3' => [
'GT-I9:number2-5:00 Build/JDQ39',
'Nokia 3:number1-3:[10|15] Build/IMM76D',
'[SAMSUNG |]SM-G3:number1-5:0[R5|I|V|A|T|S] Build/JLS36C',
'Ascend G3:number0-3:0 Build/JLS36I',
'[SAMSUNG |]SM-G3:number3-6::number1-8::number0-9:[V|A|T|S|I|R5] Build/JLS36C',
'HUAWEI G6-L:number10-11: Build/HuaweiG6-L:number10-11:',
'[SAMSUNG |]SM-[G|N]:number7-9:1:number0-8:[S|A|V|T] Build/[JLS36C|JSS15J]',
'[SAMSUNG |]SGH-N0:number6-9:5[T|V|A|S] Build/JSS15J',
'Samsung Galaxy S[4|IV] Mega GT-I:number89-95:00 Build/JDQ39',
'SAMSUNG SM-T:number24-28:5[s|a|t|v] Build/[JLS36C|JSS15J]',
'HP :number63-73:5 Notebook PC Build/[JLS36C|JSS15J]',
'HP Compaq 2:number1-3:10b Build/[JLS36C|JSS15J]',
'HTC One 801[s|e] Build/[JLS36C|JSS15J]',
'HTC One max Build/[JLS36C|JSS15J]',
'HTC Xplorer A:number28-34:0[e|s] Build/GRJ90',
],
'4.4' => [
'XT10:number5-8:0 Build/SU6-7.3',
'XT10:number12-52: Build/[KXB20.9|KXC21.5]',
'Nokia :number30-34:10 Build/IMM76D',
'E:number:20-23::number0-3::number0-4: Build/24.0.[A|B].1.34',
'[SAMSUNG |]SM-E500[F|L] Build/KTU84P',
'LG Optimus G Build/KRT16M',
'LG-E98:number7-9: Build/KOT49I',
'Elephone P:number2-6:000 Build/KTU84P',
'IQ450:number0-4: Quad Build/KOT49H',
'LG-F:number2-5:00[K|S|L] Build/KOT49[I|H]',
'LG-V:number3-7::number0-1:0 Build/KOT49I',
'[SAMSUNG |]SM-J:number1-2::number0-1:0[G|F] Build/KTU84P',
'[SAMSUNG |]SM-N80:number0-1:0 Build/[KVT49L|JZO54K]',
'[SAMSUNG |]SM-N900:number5-8: Build/KOT49H',
'[SAMSUNG-|]SGH-I337[|M] Build/[JSS15J|KOT49H]',
'[SAMSUNG |]SM-G900[W8|9D|FD|H|V|FG|A|T] Build/KOT49H',
'[SAMSUNG |]SM-T5:number30-35: Build/[KOT49H|KTU84P]',
'[Google |]Nexus :number5-7: Build/KOT49H',
'LG-H2:number0-2:0 Build/KOT49[I|H]',
'HTC One[_M8|_M9|0P6B|801e|809d|0P8B2|mini 2|S][ dual sim|] Build/[KOT49H|KTU84L]',
'[SAMSUNG |]GT-I9:number3-5:0:number0-6:[V|I|T|N] Build/KOT49H',
'Lenovo P7:number7-8::number1-6: Build/[Lenovo|JRO03C]',
'LG-D95:number1-8: Build/KOT49[I|H]',
'LG-D:number1-8::number0-8:0 Build/KOT49[I|H]',
'Nexus5 V:number6-7:.1 Build/KOT49H',
'Nexus[_|] :number4-10: Build/[KOT49H|KTU84P]',
'Nexus[_S_| S ][4G |]Build/GRJ22',
'[HM NOTE|NOTE-III|NOTE2 1LTE[TD|W|T]',
'ALCATEL ONE[| ]TOUCH 70:number2-4::number0-9:[X|D|E|A] Build/KOT49H',
'MOTOROLA [MOTOG|MSM8960|RAZR] Build/KVT49L',
],
'5.0' => [
'Nokia :number10-11:00 [wifi|4G|LTE] Build/GRK39F',
'HTC 80:number1-2[s|w|e|t] Build/[LRX22G|JSS15J]',
'Lenovo A7000-a Build/LRX21M;',
'HTC Butterfly S [901|919][s|d|] Build/LRX22G',
'HTC [M8|M9|M8 Pro Build/LRX22G',
'LG-D3:number25-37: Build/LRX22G',
'LG-D72:number0-9: Build/LRX22G',
'[SAMSUNG |]SM-G4:number0-9:0 Build/LRX22[G|C]',
'[|SAMSUNG ]SM-G9[00|25|20][FD|8|F|F-ORANGE|FG|FQ|H|I|L|M|S|T] Build/[LRX21T|KTU84F|KOT49H]',
'[SAMSUNG |]SM-A:number7-8:00[F|I|T|H|] Build/[LRX22G|LMY47X]',
'[SAMSUNG-|]SM-N91[0|5][A|V|F|G|FY] Build/LRX22C',
'[SAMSUNG |]SM-[T|P][350|550|555|355|805|800|710|810|815] Build/LRX22G',
'LG-D7:number0-2::number0-9: Build/LRX22G',
'[LG|SM]-[D|G]:number8-9::number0-5::number0-9:[|P|K|T|I|F|T1] Build/[LRX22G|KOT49I|KVT49L|LMY47X]',
],
'5.1' => [
'Nexus :number5-9: Build/[LMY48B|LRX22C]',
'[|SAMSUNG ]SM-G9[28|25|20][X|FD|8|F|F-ORANGE|FG|FQ|H|I|L|M|S|T] Build/[LRX22G|LMY47X]',
'[|SAMSUNG ]SM-G9[35|350][X|FD|8|F|F-ORANGE|FG|FQ|H|I|L|M|S|T] Build/[MMB29M|LMY47X]',
'[MOTOROLA |][MOTO G|MOTO G XT1068|XT1021|MOTO E XT1021|MOTO XT1580|MOTO X FORCE XT1580|MOTO X PLAY XT1562|MOTO XT1562|MOTO XT1575|MOTO X PURE XT1575|MOTO XT1570 MOTO X STYLE] Build/[LXB22|LMY47Z|LPC23|LPK23|LPD23|LPH223]',
],
'6.0' => [
'[SAMSUNG |]SM-[G|D][920|925|928|9350][V|F|I|L|M|S|8|I] Build/[MMB29K|MMB29V|MDB08I|MDB08L]',
'Nexus :number5-7:[P|X|] Build/[MMB29K|MMB29V|MDB08I|MDB08L]',
'HTC One[_| ][M9|M8|M8 Pro] Build/MRA58K',
'HTC One[_M8|_M9|0P6B|801e|809d|0P8B2|mini 2|S][ dual sim|] Build/MRA58K',
],
'7.0' => [
'Pixel [XL|C] Build/[NRD90M|NME91E]',
'Nexus :number5-9:[X|P|] Build/[NPD90G|NME91E]',
'[SAMSUNG |]GT-I:number91-98:00 Build/KTU84P',
'Xperia [V |]Build/NDE63X',
'LG-H:number90-93:0 Build/NRD90[C|M]',
],
'7.1' => [
'Pixel [XL|C] Build/[NRD90M|NME91E]',
'Nexus :number5-9:[X|P|] Build/[NPD90G|NME91E]',
'[SAMSUNG |]GT-I:number91-98:00 Build/KTU84P',
'Xperia [V |]Build/NDE63X',
'LG-H:number90-93:0 Build/NRD90[C|M]',
],
];
/** @var array ANDROID_OS Строки названия ОС, используемые на Android */
private const ANDROID_OS = [
'Linux; Android :androidVersion:; :androidDevice:',
'Linux; U; Android :androidVersion:; :androidDevice:',
'Android; Android :androidVersion:; :androidDevice:',
];
/** @var array MOBILE_IOS Строки названия ОС, используемые на IOS */
private const MOBILE_IOS = [
'iphone' => 'iPhone; CPU iPhone OS :number7-11:_:number0-9:_:number0-9:; like Mac OS X;',
'ipad' => 'iPad; CPU iPad OS :number7-11:_:number0-9:_:number0-9: like Mac OS X;',
'ipod' => 'iPod; CPU iPod OS :number7-11:_:number0-9:_:number0-9:; like Mac OS X;',
];
/**
* Генерация случайной операционной системы
*
* @param null|string $os Операционная система
*
* @return string Строка ОС
* @throws Exception
*/
private function getOS(?string $os = null): string
{
$_os = [];
if ($os === null || in_array($os, ['chrome', 'firefox', 'explorer'])) {
$_os = $os === 'explorer' ? self::WINDOWS_OS : array_merge(self::WINDOWS_OS, self::LINUX_OS, self::MAC_OS);
} else {
$_os += constant('self::' . strtoupper($os) . '_OS');
}
// Случайный выбор ОС
$selectedOS = rtrim($_os[random_int(0, count($_os) - 1)], ';');
if (strpos($selectedOS, '[') !== false) {
$selectedOS = self::processSpinSyntax($selectedOS);
}
if (strpos($selectedOS, ':number') !== false) {
$selectedOS = self::processRandomNumbers($selectedOS);
}
if (random_int(1, 100) > 50) {
$selectedOS .= '; en-US';
}
return $selectedOS;
}
/**
* Генерация случайной мобильной ОС
* ОС можно указать android, iphone, ipad, ipod, или null для случайной генерации
*
* @param null|string $os ОС
*
* @return string Строка ОС
* @throws Exception
*/
private function getMobileOS(string $os = null): string
{
$os = strtolower($os);
$_os = [];
switch ($os) {
case'android':
$_os += self::ANDROID_OS;
break;
case 'iphone':
case 'ipad':
case 'ipod':
$_os[] = self::MOBILE_IOS[$os];
break;
default:
$_os = array_merge(self::ANDROID_OS, array_values(self::MOBILE_IOS));
}
// Выбираем случайную мобильную ОС
$selectedOS = rtrim($_os[random_int(0, count($_os) - 1)], ';');
if (strpos($selectedOS, ':androidVersion:') !== false) {
$selectedOS = $this->processAndroidVersion($selectedOS);
}
if (strpos($selectedOS, ':androidDevice:') !== false) {
$selectedOS = $this->addAndroidDevice($selectedOS);
}
if (strpos($selectedOS, ':number') !== false) {
$selectedOS = self::processRandomNumbers($selectedOS);
}
return $selectedOS;
}
/**
* @param $selectedOS
*
* @return null|string|string[]
* @throws Exception
*/
private static function processRandomNumbers($selectedOS)
{
return preg_replace_callback(
'/:number(\d+)-(\d+):/i',
function ($matches) {
return random_int($matches[1], $matches[2]);
},
$selectedOS
);
}
/**
* @param $selectedOS
*
* @return null|string|string[]
*/
private static function processSpinSyntax($selectedOS)
{
return preg_replace_callback(
'/\[([\w\-\s|;]*?)\]/i',
function ($matches) {
$shuffle = explode('|', $matches[1]);
return $shuffle[array_rand($shuffle)];
},
$selectedOS
);
}
/**
* @param $selectedOS
*
* @return null|string|string[]
*/
private function processAndroidVersion($selectedOS)
{
$this->androidVersion = $version = self::ANDROID_VERSIONS[array_rand(self::ANDROID_VERSIONS)];
return preg_replace_callback(
'/:androidVersion:/i',
function ($matches) use ($version) {
return $version;
},
$selectedOS
);
}
/**
* @param $selectedOS
*
* @return null|string|string[]
*/
private function addAndroidDevice($selectedOS)
{
$devices = self::ANDROID_DEVICES[substr($this->androidVersion, 0, 3)];
$device = $devices[array_rand($devices)];
$device = self::processSpinSyntax($device);
return preg_replace_callback('/:androidDevice:/i', function ($matches) use ($device) {
return $device;
}, $selectedOS);
}
/**
* Генерация версии Chrome
*
* @param array $version Параметры версии
*
* @return string Результат
* @throws Exception
*/
private static function chromeVersion(array $version): string
{
return random_int($version['min'], $version['max']) . '.0.' . random_int(1000, 4000) . '.' . random_int(100, 400);
}
/**
* Генерация версии Firefox
*
* @param array $version Параметры версии
*
* @return string Результат
* @throws Exception
*/
private static function firefoxVersion(array $version): string
{
return random_int($version['min'], $version['max']) . '.' . random_int(0, 9);
}
/**
* Генерация версии Windows
*
* @param array $version Параметры версии
*
* @return string Результат
* @throws Exception
*/
private static function windows(array $version): string
{
return random_int($version['min'], $version['max']) . '.' . random_int(0, 9);
}
/**
* @param string|null $userAgent
*
* @return string
* @throws Exception
*/
public function generate(?string $userAgent = null): string
{
if ($userAgent === null) {
$r = random_int(0, 100);
if ($r >= 44) {
$userAgent = array_rand(['firefox' => 1, 'chrome' => 1, 'explorer' => 1]);
} else {
$userAgent = array_rand(['iphone' => 1, 'android' => 1, 'mobile' => 1]);
}
} elseif ($userAgent == 'windows' || $userAgent == 'mac' || $userAgent == 'linux') {
$agents = ['firefox' => 1, 'chrome' => 1];
if ($userAgent == 'windows') {
$agents['explorer'] = 1;
}
$userAgent = array_rand($agents);
}
$_SESSION['agent'] = $userAgent;
if ($userAgent == 'chrome') {
return 'Mozilla/5.0 (' . $this->getOS($userAgent) . ') AppleWebKit/'
. (random_int(1, 100) > 50 ? random_int(533, 537) : random_int(600, 603))
. '.' . random_int(1, 50) . ' (KHTML, like Gecko) Chrome/'
. self::chromeVersion(['min' => 47, 'max' => 55,]) . ' Safari/'
. (random_int(1, 100) > 50 ? random_int(533, 537 ) : random_int(600, 603));
} elseif ($userAgent == 'firefox') {
return 'Mozilla/5.0 (' . $this->getOS($userAgent) . ') Gecko/'
. (random_int(1, 100) > 30 ? '20100101' : '20130401') . ' Firefox/'
. self::firefoxVersion(['min' => 45, 'max' => 74,]);
} elseif ($userAgent == 'explorer') {
return 'Mozilla / 5.0 (compatible; MSIE ' . ($int = random_int(7, 11)) . '.0; '
. $this->getOS('windows') . ' Trident / ' . ($int == 7 || $int == 8 ? '4' : ($int == 9 ? '5' : ($int== 10 ? '6' : '7'))) . '.0)';
} elseif ($userAgent == 'mobile' || $userAgent == 'android' || $userAgent == 'iphone' || $userAgent == 'ipad' || $userAgent == 'ipod') {
return 'Mozilla/5.0 (' . $this->getMobileOS($userAgent) . ') AppleWebKit/'
. (random_int(1, 100) > 50 ? random_int(533, 537) : random_int(600, 603)) . '.' . random_int(1, 50)
. ' (KHTML, like Gecko) Chrome/' . self::chromeVersion(['min' => 47, 'max' => 55,]) . ' Mobile Safari/'
. (random_int(1, 100) > 50 ? random_int(533, 537) : random_int(600, 603)) . '.' . random_int(0, 9);
}
return 'undefined';
}
}