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 CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright © 2025 Kiko Beats <josefrancisco.verdu@gmail.com> (kikobeats.com)
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
- # is-antibot
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
- <p align="center">
4
- <br>
5
- <img src="https://i.imgur.com/Mh13XWB.gif" alt="is-antibot">
6
- <br>
7
- </p>
8
-
9
- ![Last version](https://img.shields.io/github/tag/kikobeats/is-antibot.svg?style=flat-square)
10
- [![Coverage Status](https://img.shields.io/coveralls/kikobeats/is-antibot.svg?style=flat-square)](https://coveralls.io/github/kikobeats/is-antibot)
6
+ ![Last version](https://img.shields.io/github/tag/microlinkhq/is-antibot.svg?style=flat-square)
7
+ [![Coverage Status](https://img.shields.io/coveralls/microlinkhq/is-antibot.svg?style=flat-square)](https://coveralls.io/github/microlinkhq/is-antibot)
11
8
  [![NPM Status](https://img.shields.io/npm/dm/is-antibot.svg?style=flat-square)](https://www.npmjs.org/package/is-antibot)
12
9
 
13
- **NOTE:** more badges availables in [shields.io](https://shields.io/)
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
- > Detect Captcha responses
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
- ```js
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
- Lorem ipsum.
42
-
43
- #### options
30
+ ```js
31
+ const isAntibot = require('is-antibot')
44
32
 
45
- ##### foo
33
+ const response = await fetch('https://example.com')
34
+ const { detected, provider } = isAntibot(response)
46
35
 
47
- Type: `boolean`<br>
48
- Default: `false`
36
+ if (detected) {
37
+ console.log(`Antibot detected: ${provider}`)
38
+ }
39
+ ```
49
40
 
50
- Lorem ipsum.
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** © [Kiko Beats](https://kikobeats.com), released under the [MIT](https://github.com/kikobeats/is-antibot/blob/master/LICENSE.md) License.<br>
55
- Authored and maintained by [Kiko Beats](https://kikobeats.com) with help from [contributors](https://github.com/kikobeats/is-antibot/contributors).
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
- > [kikobeats.com](https://kikobeats.com) · GitHub [Kiko Beats](https://github.com/kikobeats) · Twitter [@kikobeats](https://twitter.com/kikobeats)
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": "Detect Captcha responses",
4
- "homepage": "https://github.com/kikobeats/is-antibot",
5
- "version": "0.0.4",
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
- "name": "Kiko Beats",
8
- "email": "josefrancisco.verdu@gmail.com",
9
- "url": "https://kikobeats.com"
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/kikobeats/is-antibot.git"
22
+ "url": "git+https://github.com/microlinkhq/is-antibot.git"
14
23
  },
15
24
  "bugs": {
16
- "url": "https://github.com/kikobeats/is-antibot/issues"
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": "standard-version -a",
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:tags": "git push --follow-tags origin HEAD:master",
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
- module.exports = ({ url, htmlDom, headers = {} } = {}) => {
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['cf-mitigated'] === 'challenge') {
9
- debug({ provider: 'cloudflare' })
10
- return true
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['x-vercel-mitigated'] === 'challenge') {
15
- debug({ provider: 'vercel' })
16
- return true
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['akamai-cache-status']?.startsWith('Error')) {
21
- debug({ provider: 'akamai' })
22
- return true
25
+ if (getHeader(headers, 'akamai-cache-status')?.startsWith('Error')) {
26
+ detected = true
27
+ provider = 'akamai'
23
28
  }
24
29
 
25
- const parsedUrl = parseUrl(url)
26
-
27
- if (parsedUrl.domainWithoutSuffix === 'reddit') {
28
- const condition = htmlDom.text().includes('Prove your humanity')
29
- if (condition) {
30
- debug({ provider: 'reddit' })
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
- return false
38
+ debug({ detected, provider })
39
+ return { detected, provider }
36
40
  }
37
41
 
38
42
  module.exports.debug = debug