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.
- package/README.md +18 -13
- package/package.json +2 -1
- 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
|
-
|
|
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://
|
|
68
|
-
const
|
|
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
|
-
|
|
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
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
7
|
-
typeof headers.get === 'function'
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
|
27
|
-
const
|
|
28
|
-
return
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
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(
|
|
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(
|
|
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 (
|
|
75
|
+
if (hasCookie('_abck=')) {
|
|
66
76
|
return createResult(true, 'akamai')
|
|
67
77
|
}
|
|
68
78
|
|
|
69
|
-
// Akamai: Bot Manager API namespace (bmak) in
|
|
70
|
-
if (
|
|
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(
|
|
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(
|
|
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 (
|
|
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(
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
|
185
|
-
if (
|
|
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
|
|
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
|
|
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 (
|
|
192
|
+
if (htmlHas('sucuri')) {
|
|
210
193
|
return createResult(true, 'sucuri')
|
|
211
194
|
}
|
|
212
195
|
|
|
213
|
-
// ThreatMetrix: Check for 'ThreatMetrix' in
|
|
196
|
+
// ThreatMetrix: Check for 'ThreatMetrix' in html
|
|
214
197
|
// Reference: https://github.com/scrapfly/Antibot-Detector/blob/main/detectors/antibot/threatmetrix.json
|
|
215
|
-
if (
|
|
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 (
|
|
203
|
+
if (urlHas('fp/check.js')) {
|
|
221
204
|
return createResult(true, 'threatmetrix')
|
|
222
205
|
}
|
|
223
206
|
|
|
224
|
-
// Meetrics: Check for 'meetrics' text in response
|
|
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 (
|
|
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 (
|
|
214
|
+
if (urlHas('meetrics\\.com', true)) {
|
|
232
215
|
return createResult(true, 'meetrics')
|
|
233
216
|
}
|
|
234
217
|
|
|
235
|
-
// Ocule: Check for ocule.co.uk in
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
250
|
-
(
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
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 (
|
|
242
|
+
if (htmlHas('grecaptcha')) {
|
|
261
243
|
return createResult(true, 'recaptcha')
|
|
262
244
|
}
|
|
263
245
|
|
|
264
|
-
// reCAPTCHA: Check for g-recaptcha container class in
|
|
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 (
|
|
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 (
|
|
254
|
+
if (urlHas('hcaptcha\\.com', true)) {
|
|
273
255
|
return createResult(true, 'hcaptcha')
|
|
274
256
|
}
|
|
275
257
|
|
|
276
|
-
// hCaptcha: Check for hcaptcha object in
|
|
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 (
|
|
260
|
+
if (htmlHas('hcaptcha')) {
|
|
279
261
|
return createResult(true, 'hcaptcha')
|
|
280
262
|
}
|
|
281
263
|
|
|
282
|
-
// hCaptcha: Check for h-captcha container class in
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
290
|
+
if (htmlHas('geetest')) {
|
|
316
291
|
return createResult(true, 'geetest')
|
|
317
292
|
}
|
|
318
293
|
|
|
319
|
-
// GeeTest: Check for gt.js script in
|
|
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 (
|
|
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
|
|
334
|
-
if (
|
|
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
|
|
339
|
-
if (
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
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
|
|
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 (
|
|
350
|
+
if (urlHas('punish\\?x5secdata', true)) {
|
|
390
351
|
return createResult(true, 'aliexpress-captcha')
|
|
391
352
|
}
|
|
392
353
|
|
|
393
|
-
// AliExpress CAPTCHA: Check for x5secdata in
|
|
394
|
-
if (
|
|
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 (
|
|
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 (
|
|
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
|
|
419
|
-
if (
|
|
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 (
|
|
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.
|
|
433
|
-
module.exports.
|
|
396
|
+
module.exports.createTestPattern = createTestPattern
|
|
397
|
+
module.exports.createHasCookie = createHasCookie
|