is-antibot 0.0.4 → 1.0.0
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/LICENSE.md +1 -1
- package/README.md +27 -36
- package/package.json +45 -25
- package/src/index.js +24 -20
package/LICENSE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright © 2025
|
|
3
|
+
Copyright © 2025 Microlink <hello@microlink.io> (https://microlink.io)
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
<picture>
|
|
2
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/microlinkhq/cdn/raw/master/dist/logo/banner-dark.png">
|
|
3
|
+
<img alt="microlink cdn" src="https://github.com/microlinkhq/cdn/raw/master/dist/logo/banner.png" align="center">
|
|
4
|
+
</picture>
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
<img src="https://i.imgur.com/Mh13XWB.gif" alt="is-antibot">
|
|
6
|
-
<br>
|
|
7
|
-
</p>
|
|
8
|
-
|
|
9
|
-

|
|
10
|
-
[](https://coveralls.io/github/kikobeats/is-antibot)
|
|
6
|
+

|
|
7
|
+
[](https://coveralls.io/github/microlinkhq/is-antibot)
|
|
11
8
|
[](https://www.npmjs.org/package/is-antibot)
|
|
12
9
|
|
|
13
|
-
|
|
10
|
+
> Identify if a response is an antibot challenge from CloudFlare, Akamai, DataDome, Vercel, and more.
|
|
11
|
+
|
|
12
|
+
## Why
|
|
13
|
+
|
|
14
|
+
Websites receiving massive quantities of traffic throughout the day, like LinkedIn, Instagram, or YouTube, have sophisticated antibot systems to prevent automated access.
|
|
15
|
+
|
|
16
|
+
When you try to fetch the HTML of these sites without the right tools, you often hit a 403 Forbidden, 429 Too Many Requests, or a "Please prove you're human" challenge, leaving you with a response that contains no useful data.
|
|
14
17
|
|
|
15
|
-
|
|
18
|
+
**is-antibot** is a lightweight, vendor-agnostic JavaScript library that identifies when a response is actually an antibot challenge, helping you understand when and why your request was blocked.
|
|
16
19
|
|
|
17
20
|
## Install
|
|
18
21
|
|
|
@@ -22,36 +25,24 @@ $ npm install is-antibot --save
|
|
|
22
25
|
|
|
23
26
|
## Usage
|
|
24
27
|
|
|
25
|
-
|
|
26
|
-
const isCaptcha = require('is-antibot')
|
|
27
|
-
|
|
28
|
-
isCaptcha('do something')
|
|
29
|
-
// => return something
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## API
|
|
33
|
-
|
|
34
|
-
### isCaptcha(input, [options])
|
|
35
|
-
|
|
36
|
-
#### input
|
|
37
|
-
|
|
38
|
-
*Required*<br>
|
|
39
|
-
Type: `string`
|
|
28
|
+
The library is designed for evaluating a HTTP response:
|
|
40
29
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
#### options
|
|
30
|
+
```js
|
|
31
|
+
const isAntibot = require('is-antibot')
|
|
44
32
|
|
|
45
|
-
|
|
33
|
+
const response = await fetch('https://example.com')
|
|
34
|
+
const { detected, provider } = isAntibot(response)
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
if (detected) {
|
|
37
|
+
console.log(`Antibot detected: ${provider}`)
|
|
38
|
+
}
|
|
39
|
+
```
|
|
49
40
|
|
|
50
|
-
|
|
41
|
+
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.
|
|
51
42
|
|
|
52
43
|
## License
|
|
53
44
|
|
|
54
|
-
**is-antibot** © [
|
|
55
|
-
Authored and maintained by [
|
|
45
|
+
**is-antibot** © [microlink.io](https://microlink.io), released under the [MIT](https://github.com/microlinkhq/is-antibot/blob/master/LICENSE.md) License.<br>
|
|
46
|
+
Authored and maintained by [microlink.io](https://microlink.io) with help from [contributors](https://github.com/microlinkhq/is-antibot/contributors).
|
|
56
47
|
|
|
57
|
-
> [
|
|
48
|
+
> [microlink.io](https://microlink.io) · GitHub [microlink.io](https://github.com/microlinkhq) · X [@microlinkhq](https://x.com/microlinkhq)
|
package/package.json
CHANGED
|
@@ -1,44 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "is-antibot",
|
|
3
|
-
"description": "
|
|
4
|
-
"homepage": "https://github.com/
|
|
5
|
-
"version": "0.0
|
|
3
|
+
"description": "Identify if a response is an antibot challenge from CloudFlare, Akamai, DataDome, Vercel, and more.",
|
|
4
|
+
"homepage": "https://github.com/microlinkhq/is-antibot",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.js"
|
|
8
|
+
},
|
|
6
9
|
"author": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"url": "https://
|
|
10
|
+
"email": "hello@microlink.io",
|
|
11
|
+
"name": "Microlink",
|
|
12
|
+
"url": "https://microlink.io"
|
|
10
13
|
},
|
|
14
|
+
"contributors": [
|
|
15
|
+
{
|
|
16
|
+
"name": "Kiko Beats",
|
|
17
|
+
"email": "josefrancisco.verdu@gmail.com"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
11
20
|
"repository": {
|
|
12
21
|
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/
|
|
22
|
+
"url": "git+https://github.com/microlinkhq/is-antibot.git"
|
|
14
23
|
},
|
|
15
24
|
"bugs": {
|
|
16
|
-
"url": "https://github.com/
|
|
25
|
+
"url": "https://github.com/microlinkhq/is-antibot/issues"
|
|
17
26
|
},
|
|
18
27
|
"keywords": [
|
|
28
|
+
"akamai",
|
|
29
|
+
"antibot",
|
|
30
|
+
"bot",
|
|
19
31
|
"captcha",
|
|
20
32
|
"challenge",
|
|
21
|
-
"cloudflare"
|
|
33
|
+
"cloudflare",
|
|
34
|
+
"datadome",
|
|
35
|
+
"detection",
|
|
36
|
+
"scraper",
|
|
37
|
+
"scraping",
|
|
38
|
+
"vercel",
|
|
39
|
+
"waf"
|
|
22
40
|
],
|
|
23
41
|
"dependencies": {
|
|
24
|
-
"@metascraper/helpers": "~5.49.15",
|
|
25
42
|
"debug-logfmt": "~1.4.7"
|
|
26
43
|
},
|
|
27
|
-
"files": [
|
|
28
|
-
"src"
|
|
29
|
-
],
|
|
30
|
-
"ava": {
|
|
31
|
-
"files": [
|
|
32
|
-
"test/**/*.js",
|
|
33
|
-
"!test/util.js"
|
|
34
|
-
]
|
|
35
|
-
},
|
|
36
44
|
"devDependencies": {
|
|
37
45
|
"@commitlint/cli": "latest",
|
|
38
46
|
"@commitlint/config-conventional": "latest",
|
|
39
47
|
"@ksmithut/prettier-standard": "latest",
|
|
40
48
|
"ava": "latest",
|
|
41
49
|
"c8": "latest",
|
|
50
|
+
"ci-publish": "latest",
|
|
51
|
+
"conventional-changelog-cli": "latest",
|
|
42
52
|
"finepack": "latest",
|
|
43
53
|
"git-authors-cli": "latest",
|
|
44
54
|
"github-generate-release": "latest",
|
|
@@ -50,7 +60,16 @@
|
|
|
50
60
|
"engines": {
|
|
51
61
|
"node": ">= 20"
|
|
52
62
|
},
|
|
63
|
+
"files": [
|
|
64
|
+
"src"
|
|
65
|
+
],
|
|
53
66
|
"license": "MIT",
|
|
67
|
+
"ava": {
|
|
68
|
+
"files": [
|
|
69
|
+
"test/**/*.js",
|
|
70
|
+
"!test/util.js"
|
|
71
|
+
]
|
|
72
|
+
},
|
|
54
73
|
"commitlint": {
|
|
55
74
|
"extends": [
|
|
56
75
|
"@commitlint/config-conventional"
|
|
@@ -61,9 +80,6 @@
|
|
|
61
80
|
]
|
|
62
81
|
}
|
|
63
82
|
},
|
|
64
|
-
"exports": {
|
|
65
|
-
".": "./src/index.js"
|
|
66
|
-
},
|
|
67
83
|
"nano-staged": {
|
|
68
84
|
"*.js": [
|
|
69
85
|
"prettier-standard",
|
|
@@ -82,11 +98,15 @@
|
|
|
82
98
|
"contributors": "(npx git-authors-cli && npx finepack && git add package.json && git commit -m 'build: contributors' --no-verify) || true",
|
|
83
99
|
"coverage": "c8 report --reporter=text-lcov > coverage/lcov.info",
|
|
84
100
|
"lint": "standard",
|
|
85
|
-
"postrelease": "npm run release:tags && npm run release:github && npm publish",
|
|
101
|
+
"postrelease": "npm run release:tags && npm run release:github && (ci-publish || npm publish --access=public)",
|
|
86
102
|
"pretest": "npm run lint",
|
|
87
|
-
"release": "
|
|
103
|
+
"release": "pnpm run release:version && pnpm run release:changelog && pnpm run release:commit && pnpm run release:tag",
|
|
104
|
+
"release:changelog": "conventional-changelog -p conventionalcommits -i CHANGELOG.md -s",
|
|
105
|
+
"release:commit": "git add package.json CHANGELOG.md && git commit -m \"chore(release): $(node -p \"require('./package.json').version\")\"",
|
|
88
106
|
"release:github": "github-generate-release",
|
|
89
|
-
"release:
|
|
107
|
+
"release:tag": "git tag -a v$(node -p \"require('./package.json').version\") -m \"v$(node -p \"require('./package.json').version\")\"",
|
|
108
|
+
"release:tags": "git push origin HEAD:master --follow-tags",
|
|
109
|
+
"release:version": "standard-version --skip.changelog --skip.commit --skip.tag",
|
|
90
110
|
"test": "c8 ava"
|
|
91
111
|
}
|
|
92
112
|
}
|
package/src/index.js
CHANGED
|
@@ -1,38 +1,42 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { parseUrl } = require('@metascraper/helpers')
|
|
4
3
|
const debug = require('debug-logfmt')('is-antibot')
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
const getHeader = (headers, name) =>
|
|
6
|
+
typeof headers.get === 'function' ? headers.get(name) : headers[name]
|
|
7
|
+
|
|
8
|
+
module.exports = ({ headers = {} } = {}) => {
|
|
9
|
+
let detected = false
|
|
10
|
+
let provider = null
|
|
11
|
+
|
|
7
12
|
// https://developers.cloudflare.com/cloudflare-challenges/challenge-types/challenge-pages/detect-response/
|
|
8
|
-
if (headers
|
|
9
|
-
|
|
10
|
-
|
|
13
|
+
if (getHeader(headers, 'cf-mitigated') === 'challenge') {
|
|
14
|
+
detected = true
|
|
15
|
+
provider = 'cloudflare'
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
// https://github.com/glizzykingdreko/Vercel-Attack-Mode-Solver
|
|
14
|
-
if (headers
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
if (getHeader(headers, 'x-vercel-mitigated') === 'challenge') {
|
|
20
|
+
detected = true
|
|
21
|
+
provider = 'vercel'
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
// https://techdocs.akamai.com/property-mgr/docs/return-cache-status
|
|
20
|
-
if (headers
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
if (getHeader(headers, 'akamai-cache-status')?.startsWith('Error')) {
|
|
26
|
+
detected = true
|
|
27
|
+
provider = 'akamai'
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return true
|
|
32
|
-
}
|
|
30
|
+
// https://docs.datadome.co/reference/validate-request
|
|
31
|
+
// 1: Soft challenge / JS redirect / interstitial
|
|
32
|
+
// 2: Hard challenge / HTML redirect / CAPTCHA
|
|
33
|
+
if (['1', '2'].includes(getHeader(headers, 'x-dd-b'))) {
|
|
34
|
+
detected = true
|
|
35
|
+
provider = 'datadome'
|
|
33
36
|
}
|
|
34
37
|
|
|
35
|
-
|
|
38
|
+
debug({ detected, provider })
|
|
39
|
+
return { detected, provider }
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
module.exports.debug = debug
|