Compare commits

...

11 Commits

Author SHA1 Message Date
Faisal Salman
5a5b321347 [extensions] Add new library: Bun, Dart, Deno, hackney, Node.js, rest-client, undici 2025-11-09 15:33:15 +07:00
Faisal Salman
e9f78ceb80 Add new device vendor: Logitech
- G-Cloud https://www.logitechg.com/en-ch/shop/p/cloud-handheld-gaming
2025-11-08 10:06:04 +07:00
Faisal Salman
38301f8803 Add new device vendor: Anbernic - https://anbernic.com/ 2025-11-06 13:42:23 +07:00
Faisal Salman
e5648826b9 [cli] Update to use extensions when parsing string input 2025-11-06 12:36:26 +07:00
Faisal Salman
a558cc1a5b [extensions][bot-detection] Add new bot: Amazon Nova Act 2025-11-03 13:11:35 +07:00
Faisal Salman
cb9b50a81a Fix import path 2025-10-27 12:50:52 +07:00
Faisal Salman
042c57cc10 [bot-detection] Add new method: isAIAssistant() to check whether user-agent is an AI assistant 2025-10-25 16:42:36 +07:00
Faisal Salman
232fb321f1 [test] Add tests for the new submodules 2025-10-25 10:28:48 +07:00
Faisal Salman
a3a7a5e377 Break some methods in helpers as new submodules: bot-detection, browser-detection, device-detection
`isAIBot()` & `isBot()` => `bot-detection`
`isChromeFamily()`, `isElectron()`, `isFromEU()`, & `isStandalonePWA()` => `browser-detection`
`getDeviceVendor()` & `isAppleSilicon()` => `device-detection`
2025-10-25 09:51:42 +07:00
Faisal Salman
2d8c8fa142 Fix #809 - Detect OpenAI's Atlas browser
https://openai.com/index/introducing-chatgpt-atlas/
2025-10-24 22:59:48 +07:00
Faisal Salman
d84ba1888b Update image link in README 2025-10-13 20:38:26 +07:00
30 changed files with 938 additions and 434 deletions

View File

@@ -5,7 +5,7 @@
--- ---
[![https://uaparser.dev](https://raw.githubusercontent.com/faisalman/ua-parser-js/gh-pages/images/uap-header.png)](https://uaparser.dev) [![https://uaparser.dev](https://raw.githubusercontent.com/faisalman/ua-parser-js/gh-pages/images/uap-header.png)](https://uaparser.dev)
[![https://uaparser.dev](https://github.com/user-attachments/assets/9f30f3d4-5cfe-441c-8f86-ead7c955f940)](https://uaparser.dev) [![https://uaparser.dev](https://github.com/user-attachments/assets/a626166c-17cc-45e3-8ff6-d7e948a5ded0)](https://uaparser.dev)
[![https://uaparser.dev](https://github.com/user-attachments/assets/50da50fc-7c8a-46e3-a2bc-6a8249914372)](https://uaparser.dev) [![https://uaparser.dev](https://github.com/user-attachments/assets/50da50fc-7c8a-46e3-a2bc-6a8249914372)](https://uaparser.dev)
[![https://uaparser.dev](https://github.com/user-attachments/assets/9f2aaff0-a9b4-4ac9-bdf3-eea8081a2582)](https://uaparser.dev) [![https://uaparser.dev](https://github.com/user-attachments/assets/9f2aaff0-a9b4-4ac9-bdf3-eea8081a2582)](https://uaparser.dev)

View File

@@ -190,6 +190,21 @@
"import": "./src/main/ua-parser.mjs", "import": "./src/main/ua-parser.mjs",
"types": "./src/main/ua-parser.d.ts" "types": "./src/main/ua-parser.d.ts"
}, },
"./bot-detection": {
"require": "./src/bot-detection/bot-detection.js",
"import": "./src/bot-detection/bot-detection.mjs",
"types": "./src/bot-detection/bot-detection.d.ts"
},
"./browser-detection": {
"require": "./src/browser-detection/browser-detection.js",
"import": "./src/browser-detection/browser-detection.mjs",
"types": "./src/browser-detection/browser-detection.d.ts"
},
"./device-detection": {
"require": "./src/device-detection/device-detection.js",
"import": "./src/device-detection/device-detection.mjs",
"types": "./src/device-detection/device-detection.d.ts"
},
"./enums": { "./enums": {
"require": "./src/enums/ua-parser-enums.js", "require": "./src/enums/ua-parser-enums.js",
"import": "./src/enums/ua-parser-enums.mjs", "import": "./src/enums/ua-parser-enums.mjs",

View File

@@ -40,6 +40,24 @@ const files = [
...defaultReplacements.mjs ...defaultReplacements.mjs
] ]
}, },
{
src : 'src/bot-detection/bot-detection.js',
dest :'src/bot-detection/bot-detection.mjs',
title : 'Generated ESM version of ua-parser-js/bot-detection',
replacements : [...defaultReplacements.mjs]
},
{
src : 'src/browser-detection/browser-detection.js',
dest :'src/browser-detection/browser-detection.mjs',
title : 'Generated ESM version of ua-parser-js/browser-detection',
replacements : [...defaultReplacements.mjs]
},
{
src : 'src/device-detection/device-detection.js',
dest :'src/device-detection/device-detection.mjs',
title : 'Generated ESM version of ua-parser-js/device-detection',
replacements : [...defaultReplacements.mjs]
},
{ {
src : 'src/enums/ua-parser-enums.js', src : 'src/enums/ua-parser-enums.js',
dest :'src/enums/ua-parser-enums.mjs', dest :'src/enums/ua-parser-enums.mjs',

View File

@@ -11,7 +11,7 @@ try {
if (!process.argv[2].startsWith('-')) { if (!process.argv[2].startsWith('-')) {
const results = process.argv.slice(2).map(ua => UAParser(ua)); const results = process.argv.slice(2).map(ua => UAParser(ua, [Bots, Emails, ExtraDevices, InApps, Vehicles]));
console.log(JSON.stringify(results, null, 4)); console.log(JSON.stringify(results, null, 4));
process.exit(0); process.exit(0);

7
src/bot-detection/bot-detection.d.ts vendored Normal file
View File

@@ -0,0 +1,7 @@
// Type definitions for bot-detection submodule of UAParser.js v2.0.7
// Project: https://github.com/faisalman/ua-parser-js
// Definitions by: Faisal Salman <https://github.com/faisalman>
export function isAIAssistant(ua: string): boolean;
export function isAICrawler(ua: string): boolean;
export function isBot(ua: string): boolean;

View File

@@ -0,0 +1,188 @@
//////////////////////////////////////////////////
/* bot-detection submodule of UAParser.js v2.0.7
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
/////////////////////////////////////////////////
/*jshint esversion: 6 */
const { UAParser } = require('../main/ua-parser');
const { Bots, Crawlers, Fetchers } = require('../extensions/ua-parser-extensions');
const { BrowserType, Extension } = require('../enums/ua-parser-enums');
const { Crawler, Fetcher } = Extension.BrowserName;
class BotList {
constructor(ext, prop, list) {
this.ext = ext;
this.prop = prop;
this.list = list.map(x => x.toLowerCase());
}
includes(ua) {
return this.list.includes(
(typeof ua === 'string' ?
new UAParser(ua, this.ext).getBrowser() :
ua.browser
)[this.prop]?.toLowerCase());
}
}
const BotTypes = new BotList(Bots, 'type', [
BrowserType.CLI,
BrowserType.CRAWLER,
BrowserType.FETCHER,
BrowserType.LIBRARY
]);
const AIAssistants = new BotList(Fetchers, 'name', [
// Amazon
Fetcher.AMAZON_NOVA_ACT,
// Anthropic
Fetcher.ANTHROPIC_CLAUDE_USER,
// Cohere
Fetcher.COHERE_AI,
// DuckDuckGo
Fetcher.DUCKDUCKGO_ASSISTBOT,
// Google
Fetcher.GOOGLE_GEMINI_DEEP_RESEARCH,
// Mistral AI
Fetcher.MISTRALAI_USER,
// OpenAI
Fetcher.OPENAI_CHATGPT_USER,
// Perplexity
Fetcher.PERPLEXITY_USER
]);
const AICrawlers = new BotList(Crawlers, 'name', [
// AI2
Crawler.AI2_BOT,
// Amazon
Crawler.AMAZON_BOT,
// Anthropic
Crawler.ANTHROPIC_AI,
Crawler.ANTHROPIC_CLAUDE_BOT,
Crawler.ANTHROPIC_CLAUDE_SEARCHBOT,
Crawler.ANTHROPIC_CLAUDE_WEB,
// Apple
Crawler.APPLE_BOT,
Crawler.APPLE_BOT_EXTENDED,
// Brave
Crawler.BRAVE_BOT,
// ByteDance
Crawler.BYTEDANCE_BYTESPIDER,
Crawler.BYTEDANCE_TIKTOKSPIDER,
// Cohere
Crawler.COHERE_TRAINING_DATA_CRAWLER,
// Common Crawl
Crawler.COMMON_CRAWL_CCBOT,
// Coveo
Crawler.COVEO_BOT,
// DataForSeo
Crawler.DATAFORSEO_BOT,
// DeepSeek
Crawler.DEEPSEEK_BOT,
// Diffbot
Crawler.DIFFBOT,
// Google
Crawler.GOOGLE_EXTENDED,
Crawler.GOOGLE_OTHER,
Crawler.GOOGLE_OTHER_IMAGE,
Crawler.GOOGLE_OTHER_VIDEO,
Crawler.GOOGLE_CLOUDVERTEXBOT,
// Hive AI
Crawler.HIVE_IMAGESIFTBOT,
// Huawei
Crawler.HUAWEI_PETALBOT,
Crawler.HUAWEI_PANGUBOT,
// Hugging Face
Crawler.HUGGINGFACE_BOT,
// Kangaroo
Crawler.KANGAROO_BOT,
// Mendable.ai
Crawler.FIRECRAWL_AGENT,
// Meta
Crawler.META_FACEBOOKBOT,
Crawler.META_EXTERNALAGENT,
// OpenAI
Crawler.OPENAI_GPTBOT,
Crawler.OPENAI_SEARCH_BOT,
// Perplexity
Crawler.PERPLEXITY_BOT,
// Replicate
Crawler.REPLICATE_BOT,
// Runpod
Crawler.RUNPOD_BOT,
// SB Intuitions
Crawler.SB_INTUITIONS_BOT,
// Semrush
Crawler.SEMRUSH_BOT_CONTENTSHAKE,
// Timpi
Crawler.TIMPI_BOT,
// Together AI
Crawler.TOGETHER_BOT,
// Velen.io
Crawler.HUNTER_VELENPUBLICWEBCRAWLER,
// Vercel
Crawler.VERCEL_V0BOT,
// Webz.io
Crawler.WEBZIO_OMGILI,
Crawler.WEBZIO_OMGILI_BOT,
Crawler.WEBZIO_EXTENDED,
// X
Crawler.XAI_BOT,
// You.com
Crawler.YOU_BOT,
// Zhipu AI
Crawler.ZHIPU_CHATGLM_SPIDER
]);
const isBot = ua => BotTypes.includes(ua);
const isAIAssistant = ua => AIAssistants.includes(ua);
const isAICrawler = ua => AICrawlers.includes(ua);
module.exports = {
isAIAssistant,
isAICrawler,
isBot
}

View File

@@ -0,0 +1,189 @@
// Generated ESM version of ua-parser-js/bot-detection
// DO NOT EDIT THIS FILE!
// Source: /src/bot-detection/bot-detection.js
//////////////////////////////////////////////////
/* bot-detection submodule of UAParser.js v2.0.7
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
/////////////////////////////////////////////////
/*jshint esversion: 6 */
import { UAParser } from '../main/ua-parser.mjs';
import { Extension, BrowserType } from '../enums/ua-parser-enums.mjs';
import { Bots, Crawlers, Fetchers } from '../extensions/ua-parser-extensions.mjs';
const { Crawler, Fetcher } = Extension.BrowserName;
class BotList {
constructor(ext, prop, list) {
this.ext = ext;
this.prop = prop;
this.list = list.map(x => x.toLowerCase());
}
includes(ua) {
return this.list.includes(
(typeof ua === 'string' ?
new UAParser(ua, this.ext).getBrowser() :
ua.browser
)[this.prop]?.toLowerCase());
}
}
const BotTypes = new BotList(Bots, 'type', [
BrowserType.CLI,
BrowserType.CRAWLER,
BrowserType.FETCHER,
BrowserType.LIBRARY
]);
const AIAssistants = new BotList(Fetchers, 'name', [
// Anthropic
Fetcher.ANTHROPIC_CLAUDE_USER,
// Cohere
Fetcher.COHERE_AI,
// DuckDuckGo
Fetcher.DUCKDUCKGO_ASSISTBOT,
// Google
Fetcher.GOOGLE_GEMINI_DEEP_RESEARCH,
// Mistral AI
Fetcher.MISTRALAI_USER,
// OpenAI
Fetcher.OPENAI_CHATGPT_USER,
// Perplexity
Fetcher.PERPLEXITY_USER
]);
const AICrawlers = new BotList(Crawlers, 'name', [
// AI2
Crawler.AI2_BOT,
// Amazon
Crawler.AMAZON_BOT,
// Anthropic
Crawler.ANTHROPIC_AI,
Crawler.ANTHROPIC_CLAUDE_BOT,
Crawler.ANTHROPIC_CLAUDE_SEARCHBOT,
Crawler.ANTHROPIC_CLAUDE_WEB,
// Apple
Crawler.APPLE_BOT,
Crawler.APPLE_BOT_EXTENDED,
// Brave
Crawler.BRAVE_BOT,
// ByteDance
Crawler.BYTEDANCE_BYTESPIDER,
Crawler.BYTEDANCE_TIKTOKSPIDER,
// Cohere
Crawler.COHERE_TRAINING_DATA_CRAWLER,
// Common Crawl
Crawler.COMMON_CRAWL_CCBOT,
// Coveo
Crawler.COVEO_BOT,
// DataForSeo
Crawler.DATAFORSEO_BOT,
// DeepSeek
Crawler.DEEPSEEK_BOT,
// Diffbot
Crawler.DIFFBOT,
// Google
Crawler.GOOGLE_EXTENDED,
Crawler.GOOGLE_OTHER,
Crawler.GOOGLE_OTHER_IMAGE,
Crawler.GOOGLE_OTHER_VIDEO,
Crawler.GOOGLE_CLOUDVERTEXBOT,
// Hive AI
Crawler.HIVE_IMAGESIFTBOT,
// Huawei
Crawler.HUAWEI_PETALBOT,
Crawler.HUAWEI_PANGUBOT,
// Hugging Face
Crawler.HUGGINGFACE_BOT,
// Kangaroo
Crawler.KANGAROO_BOT,
// Mendable.ai
Crawler.FIRECRAWL_AGENT,
// Meta
Crawler.META_FACEBOOKBOT,
Crawler.META_EXTERNALAGENT,
// OpenAI
Crawler.OPENAI_GPTBOT,
Crawler.OPENAI_SEARCH_BOT,
// Perplexity
Crawler.PERPLEXITY_BOT,
// Replicate
Crawler.REPLICATE_BOT,
// Runpod
Crawler.RUNPOD_BOT,
// SB Intuitions
Crawler.SB_INTUITIONS_BOT,
// Semrush
Crawler.SEMRUSH_BOT_CONTENTSHAKE,
// Timpi
Crawler.TIMPI_BOT,
// Together AI
Crawler.TOGETHER_BOT,
// Velen.io
Crawler.HUNTER_VELENPUBLICWEBCRAWLER,
// Vercel
Crawler.VERCEL_V0BOT,
// Webz.io
Crawler.WEBZIO_OMGILI,
Crawler.WEBZIO_OMGILI_BOT,
Crawler.WEBZIO_EXTENDED,
// X
Crawler.XAI_BOT,
// You.com
Crawler.YOU_BOT,
// Zhipu AI
Crawler.ZHIPU_CHATGLM_SPIDER
]);
const isBot = ua => BotTypes.includes(ua);
const isAIAssistant = ua => AIAssistants.includes(ua);
const isAICrawler = ua => AICrawlers.includes(ua);
export {
isAIAssistant,
isAICrawler,
isBot
}

View File

@@ -0,0 +1,10 @@
// Type definitions for browser-detection submodule of UAParser.js v2.0.7
// Project: https://github.com/faisalman/ua-parser-js
// Definitions by: Faisal Salman <https://github.com/faisalman>
import type { IResult } from "../main/ua-parser";
export function isChromeFamily(resultOrUA: IResult | string): boolean;
export function isElectron(): boolean;
export function isFromEU(): boolean;
export function isStandalonePWA(): boolean;

View File

@@ -0,0 +1,30 @@
//////////////////////////////////////////////////////
/* browser-detection submodule of UAParser.js v2.0.7
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
/////////////////////////////////////////////////////
/*jshint esversion: 6 */
const { UAParser } = require('../main/ua-parser');
const { EngineName } = require('../enums/ua-parser-enums');
const { isStandalonePWA } = require('is-standalone-pwa');
const { isFromEU } = require('detect-europe-js');
const isChromeFamily = val => !!(
(typeof val === 'string' ?
new UAParser(val).getEngine() :
val.engine
)?.is(EngineName.BLINK));
const isElectron = () => !!(
process?.versions?.hasOwnProperty('electron') || // node.js
/ electron\//i.test(navigator?.userAgent)); // browser
module.exports = {
isChromeFamily,
isElectron,
isFromEU,
isStandalonePWA
}

View File

@@ -0,0 +1,30 @@
// Generated ESM version of ua-parser-js/browser-detection
// DO NOT EDIT THIS FILE!
// Source: /src/browser-detection/browser-detection.js
//////////////////////////////////////////////////////
/* browser-detection submodule of UAParser.js v2.0.7
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
/////////////////////////////////////////////////////
/*jshint esversion: 6 */
import { UAParser } from '../main/ua-parser.mjs';
import { isStandalonePWA } from 'is-standalone-pwa';
const isChromeFamily = val => !!(
(typeof val === 'string' ?
new UAParser(val).getEngine() :
val.engine
)?.is(EngineName.BLINK));
const isElectron = () => !!(process?.versions?.hasOwnProperty('electron') || // node.js
/ electron\//i.test(navigator?.userAgent)); // browser
export {
isChromeFamily,
isElectron,
isStandalonePWA
}

View File

@@ -0,0 +1,8 @@
// Type definitions for device-detection submodule of UAParser.js v2.0.7
// Project: https://github.com/faisalman/ua-parser-js
// Definitions by: Faisal Salman <https://github.com/faisalman>
import type { IResult } from "../main/ua-parser";
export function getDeviceVendor(model: string): string | undefined;
export function isAppleSilicon(resultOrUA: IResult | string): boolean;

View File

@@ -0,0 +1,43 @@
/////////////////////////////////////////////////////
/* device-detection submodule of UAParser.js v2.0.7
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
////////////////////////////////////////////////////
/*jshint esversion: 6 */
const { UAParser } = require('../main/ua-parser');
const { CPUArch, OSName } = require('../enums/ua-parser-enums');
const getDeviceVendor = (model) => new UAParser(`Mozilla/5.0 (Linux; Android 10; ${model}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36`).getDevice().vendor;
const isAppleSilicon = (val) => {
const { os, cpu } = typeof val !== 'string' ? val : {
os: new UAParser(val).getOS(),
cpu: new UAParser(val).getCPU()
};
if (os.is(OSName.MACOS)) {
if (cpu.is(CPUArch.ARM)) {
return true;
} else if (typeof window !== 'undefined') {
try {
const canvas = document.createElement('canvas');
const webgl = canvas.getContext('webgl2') ||
canvas.getContext('webgl') ||
canvas.getContext('experimental-webgl');
return webgl
.getParameter(webgl.getExtension('WEBGL_debug_renderer_info').UNMASKED_RENDERER_WEBGL)
.match(/apple m\d/i);
} catch {
return false;
}
}
}
return false;
}
module.exports = {
getDeviceVendor,
isAppleSilicon
}

View File

@@ -0,0 +1,47 @@
// Generated ESM version of ua-parser-js/device-detection
// DO NOT EDIT THIS FILE!
// Source: /src/device-detection/device-detection.js
/////////////////////////////////////////////////////
/* device-detection submodule of UAParser.js v2.0.7
https://github.com/faisalman/ua-parser-js
Author: Faisal Salman <f@faisalman.com>
AGPLv3 License */
////////////////////////////////////////////////////
/*jshint esversion: 6 */
import { UAParser } from '../main/ua-parser.mjs';
import { CPUArch, OSName } from '../enums/ua-parser-enums.mjs';
const getDeviceVendor = (model) => new UAParser(`Mozilla/5.0 (Linux; Android 10; ${model}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36`).getDevice().vendor;
const isAppleSilicon = (val) => {
const { os, cpu } = typeof val !== 'string' ? val : {
os: new UAParser(val).getOS(),
cpu: new UAParser(val).getCPU()
};
if (os.is(OSName.MACOS)) {
if (cpu.is(CPUArch.ARM)) {
return true;
} else if (typeof window !== 'undefined') {
try {
const canvas = document.createElement('canvas');
const webgl = canvas.getContext('webgl2') ||
canvas.getContext('webgl') ||
canvas.getContext('experimental-webgl');
return webgl
.getParameter(webgl.getExtension('WEBGL_debug_renderer_info').UNMASKED_RENDERER_WEBGL)
.match(/apple m\d/i);
} catch {
return false;
}
}
}
return false;
}
export {
getDeviceVendor,
isAppleSilicon
}

View File

@@ -15,6 +15,7 @@ const BrowserName = Object.freeze({
AMAYA: 'Amaya', AMAYA: 'Amaya',
ANDROID: 'Android Browser', ANDROID: 'Android Browser',
ARORA: 'Arora', ARORA: 'Arora',
ATLAS: 'Atlas',
AVANT: 'Avant', AVANT: 'Avant',
AVAST: 'Avast Secure Browser', AVAST: 'Avast Secure Browser',
AVG: 'AVG Secure Browser', AVG: 'AVG Secure Browser',
@@ -224,8 +225,9 @@ const DeviceVendor = Object.freeze({
ACER: 'Acer', ACER: 'Acer',
ADVAN: 'Advan', ADVAN: 'Advan',
ALCATEL: 'Alcatel', ALCATEL: 'Alcatel',
APPLE: 'Apple',
AMAZON: 'Amazon', AMAZON: 'Amazon',
ANBERNIC: 'Anbernic',
APPLE: 'Apple',
ARCHOS: 'Archos', ARCHOS: 'Archos',
ASUS: 'ASUS', ASUS: 'ASUS',
ATT: 'AT&T', ATT: 'AT&T',
@@ -254,6 +256,7 @@ const DeviceVendor = Object.freeze({
LAVA: 'Lava', LAVA: 'Lava',
LENOVO: 'Lenovo', LENOVO: 'Lenovo',
LG: 'LG', LG: 'LG',
LOGITECH: 'Logitech',
MEIZU: 'Meizu', MEIZU: 'Meizu',
MICROMAX: 'Micromax', MICROMAX: 'Micromax',
MICROSOFT: 'Microsoft', MICROSOFT: 'Microsoft',
@@ -627,6 +630,7 @@ const Extension = Object.freeze({
}, },
Fetcher: { Fetcher: {
AHREFS_SITEAUDIT: 'AhrefsSiteAudit', AHREFS_SITEAUDIT: 'AhrefsSiteAudit',
AMAZON_NOVA_ACT: 'NovaAct',
ANTHROPIC_CLAUDE_USER: 'Claude-User', ANTHROPIC_CLAUDE_USER: 'Claude-User',
ASANA: 'Asana', ASANA: 'Asana',
BETTER_UPTIME_BOT: 'Better Uptime Bot', BETTER_UPTIME_BOT: 'Better Uptime Bot',
@@ -704,9 +708,13 @@ const Extension = Object.freeze({
AIOHTTP: 'aiohttp', AIOHTTP: 'aiohttp',
APACHE_HTTPCLIENT: 'Apache-HttpClient', APACHE_HTTPCLIENT: 'Apache-HttpClient',
AXIOS: 'axios', AXIOS: 'axios',
BUN: 'Bun',
DART: 'Dart',
DENO: 'Deno',
GO_HTTP_CLIENT: 'go-http-client', GO_HTTP_CLIENT: 'go-http-client',
GOT: 'got', GOT: 'got',
GUZZLEHTTP: 'GuzzleHttp', GUZZLEHTTP: 'GuzzleHttp',
HACKNEY: 'hackney',
JAVA: 'Java', JAVA: 'Java',
JAVA_HTTPCLIENT: 'Java-http-client', JAVA_HTTPCLIENT: 'Java-http-client',
JSDOM: 'jsdom', JSDOM: 'jsdom',
@@ -714,16 +722,19 @@ const Extension = Object.freeze({
LUA_RESTY_HTTP: 'lua-resty-http', LUA_RESTY_HTTP: 'lua-resty-http',
NEEDLE: 'Needle', NEEDLE: 'Needle',
NUTCH: 'Nutch', NUTCH: 'Nutch',
OKHTTP: 'OkHttp',
NODE_FETCH: 'node-fetch', NODE_FETCH: 'node-fetch',
NODE_JS: 'Node.js',
NODE_SUPERAGENT: 'node-superagent', NODE_SUPERAGENT: 'node-superagent',
OKHTTP: 'OkHttp',
PHP_SOAP: 'PHP-SOAP', PHP_SOAP: 'PHP-SOAP',
POSTMAN_RUNTIME: 'PostmanRuntime', POSTMAN_RUNTIME: 'PostmanRuntime',
PYTHON_HTTPX: 'python-httpx', PYTHON_HTTPX: 'python-httpx',
PYTHON_URLLIB: 'python-urllib', PYTHON_URLLIB: 'python-urllib',
PYTHON_URLLIB3: 'python-urllib3', PYTHON_URLLIB3: 'python-urllib3',
PYTHON_REQUESTS: 'python-requests', PYTHON_REQUESTS: 'python-requests',
SCRAPY: 'Scrapy' REST_CLIENT: 'rest-client',
SCRAPY: 'Scrapy',
UNDICI: 'undici'
} }
}, },
DeviceVendor: { DeviceVendor: {

View File

@@ -258,7 +258,7 @@ const Emails = Object.freeze({
const Fetchers = Object.freeze({ const Fetchers = Object.freeze({
browser : [ browser : [
[ [
// Asana / Bitlybot / Better Uptime / BingPreview / Blueno / Cohere-AI / HubSpot Page Fetcher / kakaotalk-scrap / Mastodon / MicrosoftPreview / Pinterestbot / Redditbot / Rogerbot / SiteAuditBot / Telegrambot / Twitterbot / UptimeRobot // Asana / Bitlybot / Better Uptime / BingPreview / Blueno / Cohere-AI / HubSpot Page Fetcher / kakaotalk-scrap / Mastodon / MicrosoftPreview / Pinterestbot / Redditbot / Rogerbot / SiteAuditBot / Telegrambot / Twitterbot / UptimeRobot / WhatsApp
// AhrefsSiteAudit - https://ahrefs.com/robot/site-audit // AhrefsSiteAudit - https://ahrefs.com/robot/site-audit
// Buffer Link Preview Bot - https://scraper.buffer.com/about/bots/link-preview-bot // Buffer Link Preview Bot - https://scraper.buffer.com/about/bots/link-preview-bot
// ChatGPT-User - https://platform.openai.com/docs/plugins/bot // ChatGPT-User - https://platform.openai.com/docs/plugins/bot
@@ -268,19 +268,19 @@ const Fetchers = Object.freeze({
// Perplexity-User - https://docs.perplexity.ai/guides/bots // Perplexity-User - https://docs.perplexity.ai/guides/bots
// MistralAI-User - https://docs.mistral.ai/robots/ // MistralAI-User - https://docs.mistral.ai/robots/
// Yandex Bots - https://yandex.com/bots // Yandex Bots - https://yandex.com/bots
/(asana|ahrefssiteaudit|(?:bing|microsoft)preview|blueno|(?:chatgpt|claude|mistralai|perplexity)-user|cohere-ai|hubspot page fetcher|mastodon|(?:bitly|bufferlinkpreview|discord|duckassist|linkedin|pinterest|reddit|roger|siteaudit|twitter|uptimero|zoom)bot|google-site-verification|iframely|kakaotalk-scrap|meta-externalfetcher|y!?j-dlc|yandex(?:calendar|direct(?:dyn)?|fordomain|pagechecker|searchshop)|yadirectfetcher)\/([\w\.]+)/i, /(asana|ahrefssiteaudit|(?:bing|microsoft)preview|blueno|(?:chatgpt|claude|mistralai|perplexity)-user|cohere-ai|hubspot page fetcher|mastodon|(?:bitly|bufferlinkpreview|discord|duckassist|linkedin|pinterest|reddit|roger|siteaudit|twitter|uptimero|zoom)bot|google-site-verification|iframely|kakaotalk-scrap|meta-externalfetcher|y!?j-dlc|yandex(?:calendar|direct(?:dyn)?|fordomain|pagechecker|searchshop)|yadirectfetcher|whatsapp)\/([\w\.]+)/i,
// Bluesky // Bluesky
/(bluesky) cardyb\/([\w\.]+)/i, /(bluesky) cardyb\/([\w\.]+)/i,
// Nova Act - https://github.com/aws/nova-act
/agent-(novaact)\/([\w\.]+)/i,
// Skype // Skype
/(skypeuripreview) preview\/([\w\.]+)/i, /(skypeuripreview) preview\/([\w\.]+)/i,
// Slackbot - https://api.slack.com/robots // Slackbot - https://api.slack.com/robots
/(slack(?:bot)?(?:-imgproxy|-linkexpanding)?) ([\w\.]+)/i, /(slack(?:bot)?(?:-imgproxy|-linkexpanding)?) ([\w\.]+)/i
// WhatsApp
/(whatsapp)\/([\w\.]+)/i
], ],
[NAME, VERSION, [TYPE, FETCHER]], [NAME, VERSION, [TYPE, FETCHER]],
@@ -389,13 +389,15 @@ const MediaPlayers = Object.freeze({
const Libraries = Object.freeze({ const Libraries = Object.freeze({
browser : [ browser : [
// Apache-HttpClient/Axios/go-http-client/got/GuzzleHttp/Java[-HttpClient]/jsdom/libwww-perl/lua-resty-http/Needle/node-fetch/OkHttp/PHP-SOAP/PostmanRuntime/python-urllib/python-requests/Scrapy/superagent
[ [
/^(apache-httpclient|axios|(?:go|java)-http-client|got|guzzlehttp|java|libwww-perl|lua-resty-http|needle|node-(?:fetch|superagent)|okhttp|php-soap|postmanruntime|python-(?:httpx|urllib[23]?|requests)|scrapy)\/([\w\.]+)/i, // Apache-HttpClient/Axios/Bun/Dart/go-http-client/got/GuzzleHttp/hackney/Java[-HttpClient]/jsdom/libwww-perl/lua-resty-http/Needle/Node.js/node-fetch/OkHttp/PHP-SOAP/PostmanRuntime/python-urllib/python-requests/rest-client/Scrapy/superagent
/^(apache-httpclient|axios|bun|dart|deno|(?:go|java)-http-client|got|guzzlehttp|hackney|java|libwww-perl|lua-resty-http|needle|node(?:\.js|-fetch|-superagent)|okhttp|php-soap|postmanruntime|python-(?:httpx|urllib[23]?|requests)|rest-client|scrapy)\/([\w\.]+)/i,
/(adobeair|aiohttp|jsdom)\/([\w\.]+)/i, /(adobeair|aiohttp|jsdom)\/([\w\.]+)/i,
/(nutch)-([\w\.-]+)(\(|$)/i, /(nutch)-([\w\.-]+)(\(|$)/i,
/\((java)\/([\w\.]+)/i /\((java)\/([\w\.]+)/i
], [NAME, VERSION, [TYPE, LIBRARY]] ], [NAME, VERSION, [TYPE, LIBRARY]], [
/(node-fetch|undici)/i
], [NAME, [TYPE, LIBRARY]]
] ]
}); });

View File

@@ -4,12 +4,37 @@
import type { IResult } from "../main/ua-parser"; import type { IResult } from "../main/ua-parser";
export function getDeviceVendor(model: string): string | undefined;
export function isAppleSilicon(resultOrUA: IResult | string): boolean;
export function isAIBot(resultOrUA: IResult | string): boolean;
export function isBot(resultOrUA: IResult | string): boolean;
export function isChromeFamily(resultOrUA: IResult | string): boolean;
export function isElectron(): boolean;
export function isFromEU(): boolean;
export function isFrozenUA(ua: string): boolean; export function isFrozenUA(ua: string): boolean;
/**
* @deprecated Moved to `device-detection` submodule
*/
export function getDeviceVendor(model: string): string | undefined;
/**
* @deprecated Moved to `device-detection` submodule
*/
export function isAppleSilicon(resultOrUA: IResult | string): boolean;
/**
* @deprecated Moved to `bot-detection` submodule
*/
export function isAIBot(resultOrUA: IResult | string): boolean;
/**
* @deprecated Moved to `bot-detection` submodule
*/
export function isBot(resultOrUA: IResult | string): boolean;
/**
* @deprecated Moved to `browser-detection` submodule
*/
export function isChromeFamily(resultOrUA: IResult | string): boolean;
/**
* @deprecated Moved to `browser-detection` submodule
*/
export function isElectron(): boolean;
/**
* @deprecated Moved to `browser-detection` submodule
*/
export function isFromEU(): boolean;
/**
* @deprecated Moved to `browser-detection` submodule
*/
export function isStandalonePWA(): boolean; export function isStandalonePWA(): boolean;

View File

@@ -8,170 +8,54 @@
/*jshint esversion: 6 */ /*jshint esversion: 6 */
const { UAParser } = require('../main/ua-parser'); const { UAParser } = require('../main/ua-parser');
const { CPUArch, OSName, EngineName, Extension, BrowserType } = require('../enums/ua-parser-enums'); const { EngineName } = require('../enums/ua-parser-enums');
const { Bots, Crawlers } = require('../extensions/ua-parser-extensions'); const { getDeviceVendor: _getDeviceVendor, isAppleSilicon: _isAppleSilicon } = require('../device-detection/device-detection');
const { isFromEU } = require('detect-europe-js'); const { isBot: _isBot, isAICrawler } = require('../bot-detection/bot-detection');
const { isStandalonePWA: _isStandalonePWA } = require('../browser-detection/browser-detection');
const { isFromEU: _isFromEU } = require('../browser-detection/browser-detection');
const { isFrozenUA } = require('ua-is-frozen'); const { isFrozenUA } = require('ua-is-frozen');
const { isStandalonePWA } = require('is-standalone-pwa');
const { Crawler } = Extension.BrowserName;
const toResult = (value, head, ext) => typeof value === 'string' ? UAParser(value, head, ext) : value; /**
* @deprecated Moved to `device-detection` submodule
*/
const getDeviceVendor = _getDeviceVendor;
const getDeviceVendor = (model) => UAParser(`Mozilla/5.0 (Linux; Android 10; ${model}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36`).device.vendor; /**
* @deprecated Moved to `device-detection` submodule
*/
const isAppleSilicon = _isAppleSilicon;
const isAppleSilicon = (resultOrUA) => { /**
const res = toResult(resultOrUA); * @deprecated Moved to `bot-detection` submodule
if (res.os.is(OSName.MACOS)) { */
if (res.cpu.is(CPUArch.ARM)) { const isAIBot = isAICrawler;
return true;
}
if (typeof resultOrUA !== 'string' && typeof window !== 'undefined') {
try {
const canvas = document.createElement('canvas');
const webgl = canvas.getContext('webgl2') || canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
const debug = webgl.getExtension('WEBGL_debug_renderer_info');
const renderer = webgl.getParameter(debug.UNMASKED_RENDERER_WEBGL);
if (renderer.match(/apple m\d/i)) {
return true;
}
} catch {
return false;
}
}
}
return false;
}
const isAIBot = (resultOrUA) => [ /**
* @deprecated Moved to `bot-detection` submodule
*/
const isBot = _isBot;
// AI2 /**
Crawler.AI2_BOT, * @deprecated Moved to `browser-detection` submodule
*/
// Amazon const isChromeFamily = val => !!((typeof val === 'string' ? new UAParser(val).getEngine() : val.engine)?.is(EngineName.BLINK));
Crawler.AMAZON_BOT,
// Anthropic
Crawler.ANTHROPIC_AI,
Crawler.ANTHROPIC_CLAUDE_BOT,
Crawler.ANTHROPIC_CLAUDE_SEARCHBOT,
Crawler.ANTHROPIC_CLAUDE_WEB,
// Apple
Crawler.APPLE_BOT,
Crawler.APPLE_BOT_EXTENDED,
// Brave
Crawler.BRAVE_BOT,
// ByteDance
Crawler.BYTEDANCE_BYTESPIDER,
Crawler.BYTEDANCE_TIKTOKSPIDER,
// Cohere
Crawler.COHERE_TRAINING_DATA_CRAWLER,
// Common Crawl
Crawler.COMMON_CRAWL_CCBOT,
// Coveo
Crawler.COVEO_BOT,
// DataForSeo
Crawler.DATAFORSEO_BOT,
// DeepSeek
Crawler.DEEPSEEK_BOT,
// Diffbot
Crawler.DIFFBOT,
// Google
Crawler.GOOGLE_EXTENDED,
Crawler.GOOGLE_OTHER,
Crawler.GOOGLE_OTHER_IMAGE,
Crawler.GOOGLE_OTHER_VIDEO,
Crawler.GOOGLE_CLOUDVERTEXBOT,
// Hive AI
Crawler.HIVE_IMAGESIFTBOT,
// Huawei
Crawler.HUAWEI_PETALBOT,
Crawler.HUAWEI_PANGUBOT,
// Hugging Face
Crawler.HUGGINGFACE_BOT,
// Kangaroo
Crawler.KANGAROO_BOT,
// Mendable.ai
Crawler.FIRECRAWL_AGENT,
// Meta
Crawler.META_FACEBOOKBOT,
Crawler.META_EXTERNALAGENT,
// OpenAI
Crawler.OPENAI_GPTBOT,
Crawler.OPENAI_SEARCH_BOT,
// Perplexity
Crawler.PERPLEXITY_BOT,
// Replicate
Crawler.REPLICATE_BOT,
// Runpod
Crawler.RUNPOD_BOT,
// SB Intuitions
Crawler.SB_INTUITIONS_BOT,
// Semrush
Crawler.SEMRUSH_BOT_CONTENTSHAKE,
// Timpi
Crawler.TIMPI_BOT,
// Together AI
Crawler.TOGETHER_BOT,
// Velen.io
Crawler.HUNTER_VELENPUBLICWEBCRAWLER,
// Vercel
Crawler.VERCEL_V0BOT,
// Webz.io
Crawler.WEBZIO_OMGILI,
Crawler.WEBZIO_OMGILI_BOT,
Crawler.WEBZIO_EXTENDED,
// X
Crawler.XAI_BOT,
// You.com
Crawler.YOU_BOT,
// Zhipu AI
Crawler.ZHIPU_CHATGLM_SPIDER
]
.map((s) => s.toLowerCase())
.includes(String(toResult(resultOrUA, Crawlers).browser.name).toLowerCase());
const isBot = (resultOrUA) => [
BrowserType.CLI,
BrowserType.CRAWLER,
BrowserType.FETCHER,
BrowserType.LIBRARY
].includes(toResult(resultOrUA, Bots).browser.type);
const isChromeFamily = (resultOrUA) => toResult(resultOrUA).engine.is(EngineName.BLINK);
/**
* @deprecated Moved to `browser-detection` submodule
*/
const isElectron = () => !!(process?.versions?.hasOwnProperty('electron') || // node.js const isElectron = () => !!(process?.versions?.hasOwnProperty('electron') || // node.js
/ electron\//i.test(navigator?.userAgent)); // browser / electron\//i.test(navigator?.userAgent)); // browser
/**
* @deprecated Moved to `browser-detection` submodule
*/
const isFromEU = _isFromEU;
/**
* @deprecated Moved to `browser-detection` submodule
*/
const isStandalonePWA = _isStandalonePWA;
module.exports = { module.exports = {
getDeviceVendor, getDeviceVendor,
isAppleSilicon, isAppleSilicon,

View File

@@ -12,166 +12,34 @@
/*jshint esversion: 6 */ /*jshint esversion: 6 */
import { UAParser } from '../main/ua-parser.mjs'; import { UAParser } from '../main/ua-parser.mjs';
import { CPUArch, OSName, EngineName, Extension, BrowserType } from '../enums/ua-parser-enums.mjs'; import { EngineName } from '../enums/ua-parser-enums.mjs';
import { Bots, Crawlers } from '../extensions/ua-parser-extensions.mjs'; import { getDeviceVendor: _getDeviceVendor, isAppleSilicon: _isAppleSilicon } from '../device-detection/device-detection.mjs';
import { isBot: _isBot, isAICrawler } from '../bot-detection/bot-detection.mjs';
import { isFromEU } from 'detect-europe-js'; import { isFromEU } from 'detect-europe-js';
import { isFrozenUA } from 'ua-is-frozen'; import { isFrozenUA } from 'ua-is-frozen';
import { isStandalonePWA } from 'is-standalone-pwa'; import { isStandalonePWA } from 'is-standalone-pwa';
const { Crawler } = Extension.BrowserName;
const toResult = (value, head, ext) => typeof value === 'string' ? UAParser(value, head, ext) : value; /**
* @deprecated Moved to `device-detection` submodule
*/
const getDeviceVendor = _getDeviceVendor;
const getDeviceVendor = (model) => UAParser(`Mozilla/5.0 (Linux; Android 10; ${model}) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36`).device.vendor; /**
* @deprecated Moved to `device-detection` submodule
*/
const isAppleSilicon = _isAppleSilicon;
const isAppleSilicon = (resultOrUA) => { /**
const res = toResult(resultOrUA); * @deprecated Moved to `bot-detection` submodule
if (res.os.is(OSName.MACOS)) { */
if (res.cpu.is(CPUArch.ARM)) { const isAIBot = isAICrawler;
return true;
}
if (typeof resultOrUA !== 'string' && typeof window !== 'undefined') {
try {
const canvas = document.createElement('canvas');
const webgl = canvas.getContext('webgl2') || canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
const debug = webgl.getExtension('WEBGL_debug_renderer_info');
const renderer = webgl.getParameter(debug.UNMASKED_RENDERER_WEBGL);
if (renderer.match(/apple m\d/i)) {
return true;
}
} catch {
return false;
}
}
}
return false;
}
const isAIBot = (resultOrUA) => [ /**
* @deprecated Moved to `bot-detection` submodule
*/
const isBot = _isBot;
// AI2 const isChromeFamily = val => !!((typeof val === 'string' ? new UAParser(val).getEngine() : val.engine)?.is(EngineName.BLINK));
Crawler.AI2_BOT,
// Amazon
Crawler.AMAZON_BOT,
// Anthropic
Crawler.ANTHROPIC_AI,
Crawler.ANTHROPIC_CLAUDE_BOT,
Crawler.ANTHROPIC_CLAUDE_SEARCHBOT,
Crawler.ANTHROPIC_CLAUDE_WEB,
// Apple
Crawler.APPLE_BOT,
Crawler.APPLE_BOT_EXTENDED,
// Brave
Crawler.BRAVE_BOT,
// ByteDance
Crawler.BYTEDANCE_BYTESPIDER,
Crawler.BYTEDANCE_TIKTOKSPIDER,
// Cohere
Crawler.COHERE_TRAINING_DATA_CRAWLER,
// Common Crawl
Crawler.COMMON_CRAWL_CCBOT,
// Coveo
Crawler.COVEO_BOT,
// DataForSeo
Crawler.DATAFORSEO_BOT,
// DeepSeek
Crawler.DEEPSEEK_BOT,
// Diffbot
Crawler.DIFFBOT,
// Google
Crawler.GOOGLE_EXTENDED,
Crawler.GOOGLE_OTHER,
Crawler.GOOGLE_OTHER_IMAGE,
Crawler.GOOGLE_OTHER_VIDEO,
Crawler.GOOGLE_CLOUDVERTEXBOT,
// Hive AI
Crawler.HIVE_IMAGESIFTBOT,
// Huawei
Crawler.HUAWEI_PETALBOT,
Crawler.HUAWEI_PANGUBOT,
// Hugging Face
Crawler.HUGGINGFACE_BOT,
// Kangaroo
Crawler.KANGAROO_BOT,
// Mendable.ai
Crawler.FIRECRAWL_AGENT,
// Meta
Crawler.META_FACEBOOKBOT,
Crawler.META_EXTERNALAGENT,
// OpenAI
Crawler.OPENAI_GPTBOT,
Crawler.OPENAI_SEARCH_BOT,
// Perplexity
Crawler.PERPLEXITY_BOT,
// Replicate
Crawler.REPLICATE_BOT,
// Runpod
Crawler.RUNPOD_BOT,
// SB Intuitions
Crawler.SB_INTUITIONS_BOT,
// Semrush
Crawler.SEMRUSH_BOT_CONTENTSHAKE,
// Timpi
Crawler.TIMPI_BOT,
// Together AI
Crawler.TOGETHER_BOT,
// Velen.io
Crawler.HUNTER_VELENPUBLICWEBCRAWLER,
// Vercel
Crawler.VERCEL_V0BOT,
// Webz.io
Crawler.WEBZIO_OMGILI,
Crawler.WEBZIO_OMGILI_BOT,
Crawler.WEBZIO_EXTENDED,
// X
Crawler.XAI_BOT,
// You.com
Crawler.YOU_BOT,
// Zhipu AI
Crawler.ZHIPU_CHATGLM_SPIDER
]
.map((s) => s.toLowerCase())
.includes(String(toResult(resultOrUA, Crawlers).browser.name).toLowerCase());
const isBot = (resultOrUA) => [
BrowserType.CLI,
BrowserType.CRAWLER,
BrowserType.FETCHER,
BrowserType.LIBRARY
].includes(toResult(resultOrUA, Bots).browser.type);
const isChromeFamily = (resultOrUA) => toResult(resultOrUA).engine.is(EngineName.BLINK);
const isElectron = () => !!(process?.versions?.hasOwnProperty('electron') || // node.js const isElectron = () => !!(process?.versions?.hasOwnProperty('electron') || // node.js
/ electron\//i.test(navigator?.userAgent)); // browser / electron\//i.test(navigator?.userAgent)); // browser

View File

@@ -376,8 +376,8 @@
/(?:ms|\()(ie) ([\w\.]+)/i, // Internet Explorer /(?:ms|\()(ie) ([\w\.]+)/i, // Internet Explorer
// Blink/Webkit/KHTML based // Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser/QupZilla/Falkon/LG Browser/Otter/qutebrowser/Dooble/Palemoon // Blink/Webkit/KHTML based // Flock/RockMelt/Midori/Epiphany/Silk/Skyfire/Bolt/Iron/Iridium/PhantomJS/Bowser/QupZilla/Falkon/LG Browser/Otter/qutebrowser/Dooble/Palemoon
/(flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser|qupzilla|falkon|rekonq|puffin|brave|whale(?!.+naver)|qqbrowserlite|duckduckgo|klar|helio|(?=comodo_)?dragon|otter|dooble|(?:lg |qute)browser|palemoon)\/([-\w\.]+)/i, /(atlas|flock|rockmelt|midori|epiphany|silk|skyfire|ovibrowser|bolt|iron|vivaldi|iridium|phantomjs|bowser|qupzilla|falkon|rekonq|puffin|brave|whale(?!.+naver)|qqbrowserlite|duckduckgo|klar|helio|(?=comodo_)?dragon|otter|dooble|(?:lg |qute)browser|palemoon)\/([-\w\.]+)/i,
// Rekonq/Puffin/Brave/Whale/QQBrowserLite/QQ//Vivaldi/DuckDuckGo/Klar/Helio/Dragon // Atlas/Rekonq/Puffin/Brave/Whale/QQBrowserLite/QQ//Vivaldi/DuckDuckGo/Klar/Helio/Dragon
/(heytap|ovi|115|surf|qwant)browser\/([\d\.]+)/i, // HeyTap/Ovi/115/Surf /(heytap|ovi|115|surf|qwant)browser\/([\d\.]+)/i, // HeyTap/Ovi/115/Surf
/(qwant)(?:ios|mobile)\/([\d\.]+)/i, // Qwant /(qwant)(?:ios|mobile)\/([\d\.]+)/i, // Qwant
/(ecosia|weibo)(?:__| \w+@)([\d\.]+)/i // Ecosia/Weibo /(ecosia|weibo)(?:__| \w+@)([\d\.]+)/i // Ecosia/Weibo
@@ -852,10 +852,9 @@
], [MODEL, [VENDOR, MICROSOFT], [TYPE, CONSOLE]], [ ], [MODEL, [VENDOR, MICROSOFT], [TYPE, CONSOLE]], [
/(ouya)/i, // Ouya /(ouya)/i, // Ouya
/(nintendo) (\w+)/i, // Nintendo /(nintendo) (\w+)/i, // Nintendo
/(retroid) (pocket ([^\)]+))/i // Retroid Pocket /(retroid) (pocket ([^\)]+))/i, // Retroid Pocket
], [VENDOR, MODEL, [TYPE, CONSOLE]], [ /droid.+; ((shield|rgcube|gr0006))( bui|\))/i // Nvidia Portable/Anbernic/Logitech
/droid.+; (shield)( bui|\))/i // Nvidia Portable ], [[VENDOR, strMapper, { 'Nvidia': 'Shield', 'Anbernic': 'RGCUBE', 'Logitech': 'GR0006' }], MODEL, [TYPE, CONSOLE]], [
], [MODEL, [VENDOR, NVIDIA], [TYPE, CONSOLE]], [
/////////////////// ///////////////////
// WEARABLES // WEARABLES

View File

@@ -171,6 +171,16 @@
"major" : "0" "major" : "0"
} }
}, },
{
"desc" : "Atlas",
"ua" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Atlas/1.0",
"expect" :
{
"name" : "Atlas",
"version" : "1.0",
"major" : "1"
}
},
{ {
"desc" : "Avant", "desc" : "Avant",
"ua" : "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; GTB5; Avant Browser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", "ua" : "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; GTB5; Avant Browser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",

View File

@@ -0,0 +1,11 @@
[
{
"desc": "Anbernic",
"ua": "Mozilla/5.0 (Linux; Android 13; RGCUBE) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Mobile Safari/537.36",
"expect": {
"vendor": "Anbernic",
"model": "RGCUBE",
"type": "console"
}
}
]

View File

@@ -0,0 +1,11 @@
[
{
"desc": "Logitech",
"ua": "Dalvik/2.1.0 (Linux; U; Android 11; GR0006 Build/SQ02K.00.0018)",
"expect": {
"vendor": "Logitech",
"model": "GR0006",
"type": "console"
}
}
]

View File

@@ -289,6 +289,16 @@
"type" : "fetcher" "type" : "fetcher"
} }
}, },
{
"desc" : "NovaAct",
"ua" : "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36 Agent-NovaAct/0.9",
"expect" :
{
"name" : "NovaAct",
"version" : "0.9",
"type" : "fetcher"
}
},
{ {
"desc" : "Perplexity-User", "desc" : "Perplexity-User",
"ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Perplexity-User/1.0; +https://perplexity.ai/perplexity-user)", "ua" : "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Perplexity-User/1.0; +https://perplexity.ai/perplexity-user)",

View File

@@ -39,6 +39,36 @@
"type" : "library" "type" : "library"
} }
}, },
{
"desc" : "Bun",
"ua" : "Bun/1.0.6",
"expect" :
{
"name" : "Bun",
"version" : "1.0.6",
"type" : "library"
}
},
{
"desc" : "Dart",
"ua" : "Dart/3.5 (dart:io)",
"expect" :
{
"name" : "Dart",
"version" : "3.5",
"type" : "library"
}
},
{
"desc" : "Deno",
"ua" : "Deno/2.1.7",
"expect" :
{
"name" : "Deno",
"version" : "2.1.7",
"type" : "library"
}
},
{ {
"desc" : "go-http-client", "desc" : "go-http-client",
"ua" : "go-http-client/1.1", "ua" : "go-http-client/1.1",
@@ -59,6 +89,16 @@
"type" : "library" "type" : "library"
} }
}, },
{
"desc" : "hackney",
"ua" : "hackney/1.20.1",
"expect" :
{
"name" : "hackney",
"version" : "1.20.1",
"type" : "library"
}
},
{ {
"desc" : "GuzzleHttp", "desc" : "GuzzleHttp",
"ua" : "GuzzleHttp/6.5.5 curl/7.70.0 PHP/7.4.22", "ua" : "GuzzleHttp/6.5.5 curl/7.70.0 PHP/7.4.22",
@@ -129,6 +169,26 @@
"type" : "library" "type" : "library"
} }
}, },
{
"desc" : "Node.js",
"ua" : "Node.js/22",
"expect" :
{
"name" : "Node.js",
"version" : "22",
"type" : "library"
}
},
{
"desc" : "node-fetch",
"ua" : "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)",
"expect" :
{
"name" : "node-fetch",
"version" : "1.0",
"type" : "library"
}
},
{ {
"desc" : "Nutch", "desc" : "Nutch",
"ua" : "AliyunSecBot/Nutch-1.21-SNAPSHOT", "ua" : "AliyunSecBot/Nutch-1.21-SNAPSHOT",
@@ -149,16 +209,6 @@
"type" : "library" "type" : "library"
} }
}, },
{
"desc" : "node-fetch",
"ua" : "node-fetch/1.0 (+https://github.com/bitinn/node-fetch)",
"expect" :
{
"name" : "node-fetch",
"version" : "1.0",
"type" : "library"
}
},
{ {
"desc" : "PHP-SOAP", "desc" : "PHP-SOAP",
"ua" : "PHP-SOAP/7.4.33", "ua" : "PHP-SOAP/7.4.33",
@@ -219,6 +269,16 @@
"type" : "library" "type" : "library"
} }
}, },
{
"desc" : "rest-client",
"ua" : "rest-client/2.1.0 (linux-gnu x86_64) ruby/2.7.2p137",
"expect" :
{
"name" : "rest-client",
"version" : "2.1.0",
"type" : "library"
}
},
{ {
"desc" : "Scrapy", "desc" : "Scrapy",
"ua" : "Scrapy/1.5.0 (+https://scrapy.org)", "ua" : "Scrapy/1.5.0 (+https://scrapy.org)",
@@ -238,5 +298,15 @@
"version" : "5.0.2", "version" : "5.0.2",
"type" : "library" "type" : "library"
} }
},
{
"desc" : "undici",
"ua" : "undici",
"expect" :
{
"name" : "undici",
"version" : "undefined",
"type" : "library"
}
} }
] ]

View File

@@ -1,85 +0,0 @@
const assert = require('assert');
const { UAParser } = require('../../src/main/ua-parser');
const { getDeviceVendor, isAppleSilicon, isAIBot, isBot, isChromeFamily } = require('../../src/helpers/ua-parser-helpers');
const { Bots, Emails } = require('../../src/extensions/ua-parser-extensions');
const { DeviceVendor } = require('../../src/enums/ua-parser-enums');
describe('getDeviceVendor', () => {
it('Can guess the device vendor from a model name', () => {
const modelSM = 'SM-A605G';
const modelRedmi = 'Redmi Note 8';
const modelNexus = 'Nexus 6P';
const modelAquos = 'AQUOS-TVX19B';
assert.equal(getDeviceVendor(modelSM), DeviceVendor.SAMSUNG);
assert.equal(getDeviceVendor(modelRedmi), DeviceVendor.XIAOMI);
assert.equal(getDeviceVendor(modelNexus), DeviceVendor.HUAWEI);
assert.equal(getDeviceVendor(modelAquos), DeviceVendor.SHARP);
});
});
describe('isAppleSilicon', () => {
it('Can detect Apple Silicon device', () => {
// non-real ua
const macARM = 'Mozilla/5.0 (Macintosh; ARM; Mac OS X 10.15; rv:97.0) Gecko/20100101 Firefox/97.0';
const macIntel = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:97.0) Gecko/20100101 Firefox/97.0';
assert.equal(isAppleSilicon(UAParser(macIntel)), false);
assert.equal(isAppleSilicon(macIntel), false);
assert.equal(isAppleSilicon(UAParser(macARM)), true);
assert.equal(isAppleSilicon(macARM), true);
});
});
describe('isAIBot', () => {
it('Can detect AI Bots', () => {
const claudeBot = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)';
const firefox = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0';
const searchGPT = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; OAI-SearchBot/1.0; +https://openai.com/searchbot';
const semrushAI = 'Mozilla/5.0 (compatible; SemrushBot-OCOB/1; +https://www.semrush.com/bot/)';
assert.equal(isAIBot(UAParser(claudeBot, Bots)), true);
assert.equal(isAIBot(claudeBot), true);
assert.equal(isAIBot(firefox), false);
assert.equal(isAIBot(searchGPT), true);
assert.equal(isAIBot(semrushAI), true);
});
});
describe('isBot', () => {
it('Can detect Bots', () => {
// non-real ua
const ahrefsBot = 'Mozilla/5.0 (compatible; AhrefsBot/7.0; +http://ahrefs.com/robot/)';
const firefox = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0';
const scrapy = 'Scrapy/1.5.0 (+https://scrapy.org)';
const thunderbird = 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.13.0';
const botParser = new UAParser(firefox, { Bots, Emails });
assert.equal(isBot(botParser.getResult()), false);
assert.equal(isBot(botParser.setUA(ahrefsBot).getResult()), true);
assert.equal(isBot(botParser.setUA(scrapy).getResult()), true);
assert.equal(isBot(botParser.setUA(thunderbird).getResult()), false);
assert.equal(isBot(ahrefsBot), true);
assert.equal(isBot(firefox), false);
assert.equal(isBot(scrapy), true);
assert.equal(isBot(thunderbird), false);
});
});
describe('isChromeFamily', () => {
it('Can detect Chromium-based browser', () => {
const edge = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.2151.58';
const firefox = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0';
assert.equal(isChromeFamily(UAParser(edge)), true);
assert.equal(isChromeFamily(UAParser(firefox)), false);
assert.equal(isChromeFamily(edge), true);
assert.equal(isChromeFamily(firefox), false);
});
});

View File

@@ -0,0 +1,42 @@
const assert = require('assert');
const { UAParser } = require('../../../src/main/ua-parser');
const { isAICrawler, isBot } = require('../../../src/bot-detection/bot-detection');
const { Bots, Emails } = require('../../../src/extensions/ua-parser-extensions');
describe('isAICrawler()', () => {
it('Can detect AI Crawlers', () => {
// AI Crawlers
const claudeBot = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)';
const searchGPT = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; OAI-SearchBot/1.0; +https://openai.com/searchbot';
const semrushAI = 'Mozilla/5.0 (compatible; SemrushBot-OCOB/1; +https://www.semrush.com/bot/)';
assert.equal(isAICrawler(claudeBot), true);
assert.equal(isAICrawler(searchGPT), true);
assert.equal(isAICrawler(semrushAI), true);
// Non-AI Crawlers
const firefox = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0';
assert.equal(isAICrawler(firefox), false);
});
});
describe('isBot()', () => {
it('Can detect general Bots', () => {
// Bots
const ahrefsBot = 'Mozilla/5.0 (compatible; AhrefsBot/7.0; +http://ahrefs.com/robot/)';
const scrapy = 'Scrapy/1.5.0 (+https://scrapy.org)';
assert.equal(isBot(ahrefsBot), true);
assert.equal(isBot(scrapy), true);
// Non-bots
const firefox = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0';
const thunderbird = 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.13.0';
assert.equal(isBot(firefox), false);
assert.equal(isBot(thunderbird), false);
});
});

View File

@@ -0,0 +1,16 @@
const assert = require('assert');
const { UAParser } = require('../../../src/main/ua-parser');
const { isChromeFamily } = require('../../../src/browser-detection/browser-detection');
describe('isChromeFamily()', () => {
it('Can detect Chromium-based browser', () => {
const edge = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.2151.58';
const firefox = 'Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/111.0';
assert.equal(isChromeFamily(UAParser(edge)), true);
assert.equal(isChromeFamily(edge), true);
assert.equal(isChromeFamily(UAParser(firefox)), false);
assert.equal(isChromeFamily(firefox), false);
});
});

View File

@@ -0,0 +1,32 @@
const assert = require('assert');
const { UAParser } = require('../../../src/main/ua-parser');
const { getDeviceVendor, isAppleSilicon } = require('../../../src/device-detection/device-detection');
const { DeviceVendor } = require('../../../src/enums/ua-parser-enums');
describe('getDeviceVendor()', () => {
it('Can guess the device vendor from a model name', () => {
const sm = 'SM-A605G';
const redmi = 'Redmi Note 8';
const nexus = 'Nexus 6P';
const aquos = 'AQUOS-TVX19B';
assert.equal(getDeviceVendor(sm), DeviceVendor.SAMSUNG);
assert.equal(getDeviceVendor(redmi), DeviceVendor.XIAOMI);
assert.equal(getDeviceVendor(nexus), DeviceVendor.HUAWEI);
assert.equal(getDeviceVendor(aquos), DeviceVendor.SHARP);
});
});
describe('isAppleSilicon()', () => {
it('Can detect Apple Silicon device', () => {
const macARM = 'Mozilla/5.0 (Macintosh; ARM; Mac OS X 10.15; rv:97.0) Gecko/20100101 Firefox/97.0';
const macIntel = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:97.0) Gecko/20100101 Firefox/97.0';
assert.equal(isAppleSilicon(UAParser(macIntel)), false);
assert.equal(isAppleSilicon(macIntel), false);
assert.equal(isAppleSilicon(UAParser(macARM)), true);
assert.equal(isAppleSilicon(macARM), true);
});
});

View File

@@ -3,9 +3,9 @@ const assert = require('assert');
const parseJS = require('@babel/parser').parse; const parseJS = require('@babel/parser').parse;
const traverse = require('@babel/traverse').default; const traverse = require('@babel/traverse').default;
const safe = require('safe-regex'); const safe = require('safe-regex');
const { UAParser } = require('../../src/main/ua-parser'); const { UAParser } = require('../../../src/main/ua-parser');
const { Bots, CLIs, Crawlers, Emails, Fetchers, InApps, Libraries, Vehicles } = require('../../src/extensions/ua-parser-extensions'); const { Bots, CLIs, Crawlers, Emails, Fetchers, InApps, Libraries, Vehicles } = require('../../../src/extensions/ua-parser-extensions');
const { BrowserType, OSName, Extension } = require('../../src/enums/ua-parser-enums'); const { BrowserType, OSName, Extension } = require('../../../src/enums/ua-parser-enums');
const { CLI, Crawler, Email, Fetcher, Library } = Extension.BrowserName; const { CLI, Crawler, Email, Fetcher, Library } = Extension.BrowserName;
describe('Extensions', () => { describe('Extensions', () => {
@@ -19,7 +19,7 @@ describe('Extensions', () => {
['Vehicles', 'vehicle', Vehicles] ['Vehicles', 'vehicle', Vehicles]
] ]
.forEach(([desc, path, ext]) => { .forEach(([desc, path, ext]) => {
const tests = require(`../data/ua/extension/${path}.json`); const tests = require(`../../data/ua/extension/${path}.json`);
describe(desc, () => { describe(desc, () => {
tests.forEach((test) => { tests.forEach((test) => {
it(`Can detect ${test.desc}: "${test.ua}"`, () => { it(`Can detect ${test.desc}: "${test.ua}"`, () => {

View File

@@ -0,0 +1,13 @@
const assert = require('assert');
const { isFrozenUA } = require('../../../src/helpers/ua-parser-helpers');
describe('isFrozenUA()', () => {
it('matches supplied user-agent string with known frozen user-agent pattern', () => {
const regularMobileUA = "Mozilla/5.0 (Linux; Android 9; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.1234.56 Mobile Safari/537.36";
const frozenMobileUA = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Mobile Safari/537.36";
assert.equal(isFrozenUA(regularMobileUA), false);
assert.equal(isFrozenUA(frozenMobileUA), true);
});
});