is-antibot 1.3.2 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +18 -13
  2. package/package.json +2 -1
  3. package/src/index.js +123 -159
package/README.md CHANGED
@@ -59,32 +59,37 @@ $ npm install is-antibot --save
59
59
 
60
60
  ## Usage
61
61
 
62
- The library is designed for evaluating a HTTP response:
62
+ Just pass `headers`, `html`, and `url` from any HTTP response:
63
63
 
64
64
  ```js
65
65
  const isAntibot = require('is-antibot')
66
66
 
67
- const response = await fetch('https://example.com')
68
- const { detected, provider } = isAntibot(response)
67
+ const response = await fetch('https://www.linkedin.com/in/kikobeats/')
68
+ const html = await response.text()
69
+
70
+ const { detected, provider } = isAntibot({
71
+ headers: response.headers,
72
+ html,
73
+ url: response.url
74
+ })
69
75
 
70
76
  if (detected) {
71
77
  console.log(`Antibot detected: ${provider}`)
72
78
  }
73
79
  ```
74
80
 
75
- The library expects a [Fetch Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) object, a [Node.js Response](https://nodejs.org/api/http.html#class-httpincomingmessage) object, or an object representing HTTP response headers as input.
76
-
77
- You can also pass optional `body` and `url` parameters for enhanced detection:
81
+ It also works with [got](https://github.com/sindresorhus/got) or any library where `body` is a string:
78
82
 
79
83
  ```js
80
- const result = isAntibot({
81
- headers: response.headers,
82
- body: await response.text(),
83
- url: response.url
84
- })
85
- ```
84
+ const response = await got('https://www.linkedin.com/in/kikobeats/')
85
+ .catch(error => errorresponse)
86
+
87
+ const { detected, provider } = isAntibot(response)
86
88
 
87
- ### Response
89
+ if (detected) {
90
+ console.log(`Antibot detected: ${provider}`)
91
+ }
92
+ ```
88
93
 
89
94
  The library returns an object with the following properties:
90
95
 
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "is-antibot",
3
3
  "description": "Identify if a response is an antibot challenge from CloudFlare, Akamai, DataDome, Vercel, PerimeterX, Shape Security, and more, including CAPTCHA providers like reCAPTCHA and hCaptcha.",
4
4
  "homepage": "https://github.com/microlinkhq/is-antibot",
5
- "version": "1.3.2",
5
+ "version": "1.3.4",
6
6
  "exports": {
7
7
  ".": "./src/index.js"
8
8
  },
@@ -69,6 +69,7 @@
69
69
  "finepack": "latest",
70
70
  "git-authors-cli": "latest",
71
71
  "github-generate-release": "latest",
72
+ "got": "11",
72
73
  "nano-staged": "latest",
73
74
  "simple-git-hooks": "latest",
74
75
  "standard": "latest",
package/src/index.js CHANGED
@@ -3,19 +3,24 @@
3
3
  const { splitSetCookieString } = require('cookie-es')
4
4
  const debug = require('debug-logfmt')('is-antibot')
5
5
 
6
- const getHeader = (headers, name) =>
7
- typeof headers.get === 'function' ? headers.get(name) : headers[name]
8
-
9
- const testPattern = (value, pattern, isRegex = false) => {
10
- if (!value) return false
11
- if (isRegex) {
12
- try {
13
- return new RegExp(pattern, 'i').test(value)
14
- } catch {
15
- return false
6
+ const createGetHeader = headers =>
7
+ typeof headers.get === 'function'
8
+ ? name => headers.get(name)
9
+ : name => headers[name]
10
+
11
+ const createTestPattern = value => {
12
+ if (!value) return () => false
13
+ const lowerValue = value.toLowerCase()
14
+ return (pattern, isRegex = false) => {
15
+ if (isRegex) {
16
+ try {
17
+ return new RegExp(pattern, 'i').test(value)
18
+ } catch {
19
+ return false
20
+ }
16
21
  }
22
+ return lowerValue.includes(pattern.toLowerCase())
17
23
  }
18
- return value.toLowerCase().includes(pattern.toLowerCase())
19
24
  }
20
25
 
21
26
  const createResult = (detected, provider) => {
@@ -23,90 +28,90 @@ const createResult = (detected, provider) => {
23
28
  return { detected, provider }
24
29
  }
25
30
 
26
- const testSetCookie = (headers, pattern) => {
27
- const cookiesString = getHeader(headers, 'set-cookie')
28
- return splitSetCookieString(cookiesString).some(c => c.startsWith(pattern))
31
+ const createHasCookie = headers => {
32
+ const getHeader = createGetHeader(headers)
33
+ return pattern =>
34
+ splitSetCookieString(getHeader('set-cookie')).some(c =>
35
+ c.startsWith(pattern)
36
+ )
29
37
  }
30
38
 
31
- module.exports = ({ headers = {}, body = '', url = '' } = {}) => {
39
+ const detect = ({ headers = {}, html = '', url = '' } = {}) => {
40
+ const getHeader = createGetHeader(headers)
41
+ const hasCookie = createHasCookie(headers)
42
+ const htmlHas = createTestPattern(html)
43
+ const urlHas = createTestPattern(url)
44
+
32
45
  // CloudFlare: Check for cf-mitigated header with 'challenge' value
33
46
  // Official docs: https://developers.cloudflare.com/cloudflare-challenges/challenge-types/challenge-pages/detect-response/
34
- if (getHeader(headers, 'cf-mitigated') === 'challenge') {
47
+ if (getHeader('cf-mitigated') === 'challenge') {
35
48
  return createResult(true, 'cloudflare')
36
49
  }
37
50
 
38
51
  // Cloudflare: cf_clearance cookie indicates Cloudflare challenge flow
39
- if (testSetCookie(headers, 'cf_clearance=')) {
52
+ if (hasCookie('cf_clearance=')) {
40
53
  return createResult(true, 'cloudflare')
41
54
  }
42
55
 
43
56
  // Vercel: Check for x-vercel-mitigated header with 'challenge' value
44
57
  // Solver reference: https://github.com/glizzykingdreko/Vercel-Attack-Mode-Solver
45
- if (getHeader(headers, 'x-vercel-mitigated') === 'challenge') {
58
+ if (getHeader('x-vercel-mitigated') === 'challenge') {
46
59
  return createResult(true, 'vercel')
47
60
  }
48
61
 
49
62
  // Akamai: Check for akamai-cache-status header starting with 'Error'
50
63
  // Official docs: https://techdocs.akamai.com/property-mgr/docs/return-cache-status
51
- if (getHeader(headers, 'akamai-cache-status')?.startsWith('Error')) {
64
+ if (getHeader('akamai-cache-status')?.startsWith('Error')) {
52
65
  return createResult(true, 'akamai')
53
66
  }
54
67
 
55
68
  // Akamai: Check for additional identifying headers (akamai-grn, x-akamai-session-info)
56
69
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/akamai.json
57
- if (
58
- getHeader(headers, 'akamai-grn') ||
59
- getHeader(headers, 'x-akamai-session-info')
60
- ) {
70
+ if (getHeader('akamai-grn') || getHeader('x-akamai-session-info')) {
61
71
  return createResult(true, 'akamai')
62
72
  }
63
73
 
64
74
  // Akamai: _abck bot manager tracking cookie
65
- if (testSetCookie(headers, '_abck=')) {
75
+ if (hasCookie('_abck=')) {
66
76
  return createResult(true, 'akamai')
67
77
  }
68
78
 
69
- // Akamai: Bot Manager API namespace (bmak) in body
70
- if (body && testPattern(body, 'bmak.')) {
79
+ // Akamai: Bot Manager API namespace (bmak) in html
80
+ if (htmlHas('bmak.')) {
71
81
  return createResult(true, 'akamai')
72
82
  }
73
83
 
74
84
  // DataDome: Check for x-dd-b header with values '1' (soft challenge) or '2' (hard challenge/CAPTCHA)
75
85
  // Official docs: https://docs.datadome.co/reference/validate-request
76
- if (['1', '2'].includes(getHeader(headers, 'x-dd-b'))) {
86
+ if (['1', '2'].includes(getHeader('x-dd-b'))) {
77
87
  return createResult(true, 'datadome')
78
88
  }
79
89
 
80
90
  // DataDome: Check for x-datadome or x-datadome-cid header presence
81
91
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/datadome.json
82
- if (getHeader(headers, 'x-datadome') || getHeader(headers, 'x-datadome-cid')) {
92
+ if (getHeader('x-datadome') || getHeader('x-datadome-cid')) {
83
93
  return createResult(true, 'datadome')
84
94
  }
85
95
 
86
96
  // DataDome: datadome tracking cookie
87
- if (testSetCookie(headers, 'datadome=')) {
97
+ if (hasCookie('datadome=')) {
88
98
  return createResult(true, 'datadome')
89
99
  }
90
100
 
91
101
  // PerimeterX: Check for X-PX-Authorization header (primary indicator)
92
102
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/perimeterx.json#L71-L84
93
- if (getHeader(headers, 'x-px-authorization')) {
103
+ if (getHeader('x-px-authorization')) {
94
104
  return createResult(true, 'perimeterx')
95
105
  }
96
106
 
97
- // PerimeterX: Check for window._pxAppId, pxInit, or _pxAction in body
107
+ // PerimeterX: Check for window._pxAppId, pxInit, or _pxAction in html
98
108
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/perimeterx.json#L130-L137
99
- if (
100
- body &&
101
- (testPattern(body, 'window._pxAppId') ||
102
- testPattern(body, 'pxInit') ||
103
- testPattern(body, '_pxAction'))
104
- ) {
109
+ if (htmlHas('window._pxAppId') || htmlHas('pxInit') || htmlHas('_pxAction')) {
105
110
  return createResult(true, 'perimeterx')
106
111
  }
107
112
 
108
113
  // PerimeterX: _px3 or _pxhd cookies
109
- if (testSetCookie(headers, '_px3=') || testSetCookie(headers, '_pxhd=')) {
114
+ if (hasCookie('_px3=') || hasCookie('_pxhd=')) {
110
115
  return createResult(true, 'perimeterx')
111
116
  }
112
117
 
@@ -120,314 +125,273 @@ module.exports = ({ headers = {}, body = '', url = '' } = {}) => {
120
125
  }
121
126
  }
122
127
 
123
- // Shape Security: Check for 'shapesecurity' text in response body
128
+ // Shape Security: Check for 'shapesecurity' text in response html
124
129
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/shapesecurity.json#L136-L142
125
- if (body && testPattern(body, 'shapesecurity')) {
130
+ if (htmlHas('shapesecurity')) {
126
131
  return createResult(true, 'shapesecurity')
127
132
  }
128
133
 
129
134
  // Kasada: Check for x-kasada or x-kasada-challenge headers
130
135
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/kasada.json#L57-L85
131
- if (
132
- getHeader(headers, 'x-kasada') ||
133
- getHeader(headers, 'x-kasada-challenge')
134
- ) {
136
+ if (getHeader('x-kasada') || getHeader('x-kasada-challenge')) {
135
137
  return createResult(true, 'kasada')
136
138
  }
137
139
 
138
- // Kasada: Check for __kasada global object or kasada.js script in body
140
+ // Kasada: Check for __kasada global object or kasada.js script in html
139
141
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/kasada.json#L117-L144
140
- if (
141
- body &&
142
- (testPattern(body, '__kasada') || testPattern(body, 'kasada.js'))
143
- ) {
142
+ if (htmlHas('__kasada') || htmlHas('kasada.js')) {
144
143
  return createResult(true, 'kasada')
145
144
  }
146
145
 
147
146
  // Imperva/Incapsula: Check for x-cdn header with 'Incapsula' value or x-iinfo header
148
147
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/incapsula.json#L86-L109
149
- if (
150
- getHeader(headers, 'x-cdn') === 'Incapsula' ||
151
- getHeader(headers, 'x-iinfo')
152
- ) {
148
+ if (getHeader('x-cdn') === 'Incapsula' || getHeader('x-iinfo')) {
153
149
  return createResult(true, 'imperva')
154
150
  }
155
151
 
156
- // Imperva/Incapsula: Check for 'incapsula' or 'imperva' text in response body
152
+ // Imperva/Incapsula: Check for 'incapsula' or 'imperva' text in response html
157
153
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/incapsula.json#L111-L124
158
- if (
159
- body &&
160
- (testPattern(body, 'incapsula') || testPattern(body, 'imperva'))
161
- ) {
154
+ if (htmlHas('incapsula') || htmlHas('imperva')) {
162
155
  return createResult(true, 'imperva')
163
156
  }
164
157
 
165
158
  // Imperva/Incapsula: incap_ses_, visid_incap_, or reese84 cookies
166
159
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/incapsula.json
167
160
  if (
168
- testSetCookie(headers, 'incap_ses_') ||
169
- testSetCookie(headers, 'visid_incap_') ||
170
- testSetCookie(headers, 'reese84=')
161
+ hasCookie('incap_ses_') ||
162
+ hasCookie('visid_incap_') ||
163
+ hasCookie('reese84=')
171
164
  ) {
172
165
  return createResult(true, 'imperva')
173
166
  }
174
167
 
175
168
  // Reblaze: rbzid or rbzsessionid cookies
176
169
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/reblaze.json
177
- if (
178
- testSetCookie(headers, 'rbzid=') ||
179
- testSetCookie(headers, 'rbzsessionid=')
180
- ) {
170
+ if (hasCookie('rbzid=') || hasCookie('rbzsessionid=')) {
181
171
  return createResult(true, 'reblaze')
182
172
  }
183
173
 
184
- // Reblaze: Check for 'reblaze' text in response body
185
- if (body && testPattern(body, 'reblaze')) {
174
+ // Reblaze: Check for 'reblaze' text in response html
175
+ if (htmlHas('reblaze')) {
186
176
  return createResult(true, 'reblaze')
187
177
  }
188
178
 
189
- // Cheq: Check for CheqSdk or cheqzone.com in body
179
+ // Cheq: Check for CheqSdk or cheqzone.com in html
190
180
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/cheq.json
191
- if (
192
- body &&
193
- (testPattern(body, 'CheqSdk') || testPattern(body, 'cheqzone.com'))
194
- ) {
181
+ if (htmlHas('CheqSdk') || htmlHas('cheqzone.com')) {
195
182
  return createResult(true, 'cheq')
196
183
  }
197
184
 
198
185
  // Cheq: Check for cheqzone.com or cheq.ai in URL
199
- if (
200
- url &&
201
- (testPattern(url, 'cheqzone\\.com', true) ||
202
- testPattern(url, 'cheq\\.ai', true))
203
- ) {
186
+ if (urlHas('cheqzone\\.com', true) || urlHas('cheq\\.ai', true)) {
204
187
  return createResult(true, 'cheq')
205
188
  }
206
189
 
207
- // Sucuri: Check for 'sucuri' text in response body
190
+ // Sucuri: Check for 'sucuri' text in response html
208
191
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/sucuri.json
209
- if (body && testPattern(body, 'sucuri')) {
192
+ if (htmlHas('sucuri')) {
210
193
  return createResult(true, 'sucuri')
211
194
  }
212
195
 
213
- // ThreatMetrix: Check for 'ThreatMetrix' in body
196
+ // ThreatMetrix: Check for 'ThreatMetrix' in html
214
197
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/threatmetrix.json
215
- if (body && testPattern(body, 'ThreatMetrix')) {
198
+ if (htmlHas('ThreatMetrix')) {
216
199
  return createResult(true, 'threatmetrix')
217
200
  }
218
201
 
219
202
  // ThreatMetrix: Check for fp/check.js fingerprint endpoint in URL
220
- if (url && testPattern(url, 'fp/check.js')) {
203
+ if (urlHas('fp/check.js')) {
221
204
  return createResult(true, 'threatmetrix')
222
205
  }
223
206
 
224
- // Meetrics: Check for 'meetrics' text in response body
207
+ // Meetrics: Check for 'meetrics' text in response html
225
208
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/meetrics.json
226
- if (body && testPattern(body, 'meetrics')) {
209
+ if (htmlHas('meetrics')) {
227
210
  return createResult(true, 'meetrics')
228
211
  }
229
212
 
230
213
  // Meetrics: Check for meetrics.com in URL
231
- if (url && testPattern(url, 'meetrics\\.com', true)) {
214
+ if (urlHas('meetrics\\.com', true)) {
232
215
  return createResult(true, 'meetrics')
233
216
  }
234
217
 
235
- // Ocule: Check for ocule.co.uk in body
218
+ // Ocule: Check for ocule.co.uk in html
236
219
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/ocule.json
237
- if (body && testPattern(body, 'ocule.co.uk')) {
220
+ if (htmlHas('ocule.co.uk')) {
238
221
  return createResult(true, 'ocule')
239
222
  }
240
223
 
241
224
  // Ocule: Check for ocule.co.uk in URL
242
- if (url && testPattern(url, 'ocule\\.co\\.uk', true)) {
225
+ if (urlHas('ocule\\.co\\.uk', true)) {
243
226
  return createResult(true, 'ocule')
244
227
  }
245
228
 
246
229
  // reCAPTCHA: Check for recaptcha/api, google.com/recaptcha, gstatic.com/recaptcha, or recaptcha.net in URL
247
230
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/recaptcha.json#L13-L48
248
231
  if (
249
- url &&
250
- (testPattern(url, 'recaptcha/api') ||
251
- testPattern(url, 'google\\.com/recaptcha', true) ||
252
- testPattern(url, 'gstatic.com/recaptcha') ||
253
- testPattern(url, 'recaptcha.net'))
232
+ urlHas('recaptcha/api') ||
233
+ urlHas('google\\.com/recaptcha', true) ||
234
+ urlHas('gstatic.com/recaptcha') ||
235
+ urlHas('recaptcha.net')
254
236
  ) {
255
237
  return createResult(true, 'recaptcha')
256
238
  }
257
239
 
258
- // reCAPTCHA: Check for grecaptcha global object in body (primary JavaScript indicator)
240
+ // reCAPTCHA: Check for grecaptcha global object in html (primary JavaScript indicator)
259
241
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/recaptcha.json#L51-L58
260
- if (body && testPattern(body, 'grecaptcha')) {
242
+ if (htmlHas('grecaptcha')) {
261
243
  return createResult(true, 'recaptcha')
262
244
  }
263
245
 
264
- // reCAPTCHA: Check for g-recaptcha container class in body
246
+ // reCAPTCHA: Check for g-recaptcha container class in html
265
247
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/recaptcha.json#L66-L73
266
- if (body && testPattern(body, 'g-recaptcha')) {
248
+ if (htmlHas('g-recaptcha')) {
267
249
  return createResult(true, 'recaptcha')
268
250
  }
269
251
 
270
252
  // hCaptcha: Check for hcaptcha.com domain in URL
271
253
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/hcaptcha.json#L13-L22
272
- if (url && testPattern(url, 'hcaptcha\\.com', true)) {
254
+ if (urlHas('hcaptcha\\.com', true)) {
273
255
  return createResult(true, 'hcaptcha')
274
256
  }
275
257
 
276
- // hCaptcha: Check for hcaptcha object in body
258
+ // hCaptcha: Check for hcaptcha object in html
277
259
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/hcaptcha.json#L42-L50
278
- if (body && testPattern(body, 'hcaptcha')) {
260
+ if (htmlHas('hcaptcha')) {
279
261
  return createResult(true, 'hcaptcha')
280
262
  }
281
263
 
282
- // hCaptcha: Check for h-captcha container class in body
264
+ // hCaptcha: Check for h-captcha container class in html
283
265
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/hcaptcha.json#L51-L58
284
- if (body && testPattern(body, 'h-captcha')) {
266
+ if (htmlHas('h-captcha')) {
285
267
  return createResult(true, 'hcaptcha')
286
268
  }
287
269
 
288
270
  // FunCaptcha (Arkose Labs): Check for arkoselabs.com or funcaptcha in URL
289
271
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/funcaptcha.json#L13-L40
290
- if (
291
- url &&
292
- (testPattern(url, 'arkoselabs\\.com', true) ||
293
- testPattern(url, 'funcaptcha'))
294
- ) {
272
+ if (urlHas('arkoselabs\\.com', true) || urlHas('funcaptcha')) {
295
273
  return createResult(true, 'funcaptcha')
296
274
  }
297
275
 
298
- // FunCaptcha (Arkose Labs): Check for funcaptcha or arkose text in body
276
+ // FunCaptcha (Arkose Labs): Check for funcaptcha or arkose text in html
299
277
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/funcaptcha.json#L42-L55
300
- if (
301
- body &&
302
- (testPattern(body, 'funcaptcha') || testPattern(body, 'arkose'))
303
- ) {
278
+ if (htmlHas('funcaptcha') || htmlHas('arkose')) {
304
279
  return createResult(true, 'funcaptcha')
305
280
  }
306
281
 
307
282
  // GeeTest: Check for geetest.com domain in URL
308
283
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/geetest.json#L13-L43
309
- if (url && testPattern(url, 'geetest\\.com', true)) {
284
+ if (urlHas('geetest\\.com', true)) {
310
285
  return createResult(true, 'geetest')
311
286
  }
312
287
 
313
- // GeeTest: Check for geetest object or text in body
288
+ // GeeTest: Check for geetest object or text in html
314
289
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/geetest.json#L45-L52
315
- if (body && testPattern(body, 'geetest')) {
290
+ if (htmlHas('geetest')) {
316
291
  return createResult(true, 'geetest')
317
292
  }
318
293
 
319
- // GeeTest: Check for gt.js script in body
294
+ // GeeTest: Check for gt.js script in html
320
295
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/geetest.json#L53-L60
321
- if (body && testPattern(body, 'gt.js')) {
296
+ if (htmlHas('gt.js')) {
322
297
  return createResult(true, 'geetest')
323
298
  }
324
299
 
325
300
  // Cloudflare Turnstile: Check for challenges.cloudflare.com/turnstile in URL
326
- if (
327
- url &&
328
- testPattern(url, 'challenges\\.cloudflare\\.com/turnstile', true)
329
- ) {
301
+ if (urlHas('challenges\\.cloudflare\\.com/turnstile', true)) {
330
302
  return createResult(true, 'cloudflare-turnstile')
331
303
  }
332
304
 
333
- // Cloudflare Turnstile: Check for cf-turnstile class in body
334
- if (body && testPattern(body, 'cf-turnstile')) {
305
+ // Cloudflare Turnstile: Check for cf-turnstile class in html
306
+ if (htmlHas('cf-turnstile')) {
335
307
  return createResult(true, 'cloudflare-turnstile')
336
308
  }
337
309
 
338
- // Cloudflare Turnstile: Check for turnstile text in body
339
- if (body && testPattern(body, 'turnstile')) {
310
+ // Cloudflare Turnstile: Check for turnstile text in html
311
+ if (htmlHas('turnstile')) {
340
312
  return createResult(true, 'cloudflare-turnstile')
341
313
  }
342
314
 
343
315
  // Friendly Captcha: Check for friendlycaptcha.com in URL
344
316
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/friendlycaptcha.json
345
- if (url && testPattern(url, 'friendlycaptcha\\.com', true)) {
317
+ if (urlHas('friendlycaptcha\\.com', true)) {
346
318
  return createResult(true, 'friendly-captcha')
347
319
  }
348
320
 
349
- // Friendly Captcha: Check for frc-captcha container or friendlyChallenge object in body
350
- if (
351
- body &&
352
- (testPattern(body, 'frc-captcha') ||
353
- testPattern(body, 'friendlyChallenge'))
354
- ) {
321
+ // Friendly Captcha: Check for frc-captcha container or friendlyChallenge object in html
322
+ if (htmlHas('frc-captcha') || htmlHas('friendlyChallenge')) {
355
323
  return createResult(true, 'friendly-captcha')
356
324
  }
357
325
 
358
326
  // Captcha.eu: Check for captcha.eu in URL
359
327
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/captchaeu.json
360
- if (url && testPattern(url, 'captcha\\.eu', true)) {
328
+ if (urlHas('captcha\\.eu', true)) {
361
329
  return createResult(true, 'captcha-eu')
362
330
  }
363
331
 
364
- // Captcha.eu: Check for CaptchaEU or captchaeu in body
365
- if (
366
- body &&
367
- (testPattern(body, 'CaptchaEU') || testPattern(body, 'captchaeu'))
368
- ) {
332
+ // Captcha.eu: Check for CaptchaEU or captchaeu in html
333
+ if (htmlHas('CaptchaEU') || htmlHas('captchaeu')) {
369
334
  return createResult(true, 'captcha-eu')
370
335
  }
371
336
 
372
337
  // QCloud Captcha (Tencent): Check for turing.captcha.qcloud.com in URL
373
338
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/qcloud.json
374
- if (url && testPattern(url, 'turing\\.captcha\\.qcloud\\.com', true)) {
339
+ if (urlHas('turing\\.captcha\\.qcloud\\.com', true)) {
375
340
  return createResult(true, 'qcloud-captcha')
376
341
  }
377
342
 
378
- // QCloud Captcha: Check for TencentCaptcha or turing.captcha in body
379
- if (
380
- body &&
381
- (testPattern(body, 'TencentCaptcha') ||
382
- testPattern(body, 'turing.captcha'))
383
- ) {
343
+ // QCloud Captcha: Check for TencentCaptcha or turing.captcha in html
344
+ if (htmlHas('TencentCaptcha') || htmlHas('turing.captcha')) {
384
345
  return createResult(true, 'qcloud-captcha')
385
346
  }
386
347
 
387
348
  // AliExpress CAPTCHA: Check for punish?x5secdata in URL
388
349
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/captcha/aliexpress.json
389
- if (url && testPattern(url, 'punish\\?x5secdata', true)) {
350
+ if (urlHas('punish\\?x5secdata', true)) {
390
351
  return createResult(true, 'aliexpress-captcha')
391
352
  }
392
353
 
393
- // AliExpress CAPTCHA: Check for x5secdata in body
394
- if (body && testPattern(body, 'x5secdata')) {
354
+ // AliExpress CAPTCHA: Check for x5secdata in html
355
+ if (htmlHas('x5secdata')) {
395
356
  return createResult(true, 'aliexpress-captcha')
396
357
  }
397
358
 
398
359
  // LinkedIn: trkCode=bf cookie ("bot filter") is set when LinkedIn blocks a request
399
- if (testSetCookie(headers, 'trkCode=bf')) {
360
+ if (hasCookie('trkCode=bf')) {
400
361
  return createResult(true, 'linkedin')
401
362
  }
402
363
 
403
364
  // YouTube: empty title pattern indicates a degraded response requiring BotGuard JS attestation
404
365
  // Normal pages have `<title>Video Title - YouTube</title>`, bots get `<title> - YouTube</title>`
405
- if (body && testPattern(body, '<title>\\s*-\\s*YouTube<\\/title>', true)) {
366
+ if (htmlHas('<title>\\s*-\\s*YouTube<\\/title>', true)) {
406
367
  return createResult(true, 'youtube')
407
368
  }
408
369
 
409
370
  // AWS WAF: Check for x-amzn-waf-action or x-amzn-requestid headers
410
371
  // Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/aws-waf.json
411
- if (
412
- getHeader(headers, 'x-amzn-waf-action') ||
413
- getHeader(headers, 'x-amzn-requestid')
414
- ) {
372
+ if (getHeader('x-amzn-waf-action') || getHeader('x-amzn-requestid')) {
415
373
  return createResult(true, 'aws-waf')
416
374
  }
417
375
 
418
- // AWS WAF: Check for aws-waf or awswaf text in body
419
- if (body && (testPattern(body, 'aws-waf') || testPattern(body, 'awswaf'))) {
376
+ // AWS WAF: Check for aws-waf or awswaf text in html
377
+ if (htmlHas('aws-waf') || htmlHas('awswaf')) {
420
378
  return createResult(true, 'aws-waf')
421
379
  }
422
380
 
423
381
  // AWS WAF: aws-waf-token cookie
424
- if (testSetCookie(headers, 'aws-waf-token=')) {
382
+ if (hasCookie('aws-waf-token=')) {
425
383
  return createResult(true, 'aws-waf')
426
384
  }
427
385
 
428
386
  return createResult(false, null)
429
387
  }
430
388
 
389
+ const isAntibot = (input = {}) => {
390
+ const { headers, html, body, url } = input
391
+ return detect({ headers, html: html || body, url })
392
+ }
393
+
394
+ module.exports = isAntibot
431
395
  module.exports.debug = debug
432
- module.exports.testPattern = testPattern
433
- module.exports.testSetCookie = testSetCookie
396
+ module.exports.createTestPattern = createTestPattern
397
+ module.exports.createHasCookie = createHasCookie