├── .gitignore ├── README.md ├── index.js ├── package.json ├── src └── utils.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vscode 3 | test.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hcaptcha-solver 2 | 3 | A library to solve hcaptcha challenges 4 | 5 | ## Install 6 | 7 | ```bash 8 | npm install hcaptcha-solver 9 | ``` 10 | 11 | ## Quick Example 12 | 13 | ```js 14 | const solveCaptcha = require('hcaptcha-solver'); 15 | 16 | (async () => { 17 | try { 18 | const response = await solveCaptcha('https://captcha-protected-site.com'); 19 | console.log(response); 20 | // F0_eyJ0eXAiOiJKV1Q... 21 | } catch (error) { 22 | console.log(error); 23 | } 24 | })(); 25 | ``` 26 | 27 | ## Credits 28 | 29 | - Thanks to [Futei](https://github.com/Futei/SineCaptcha) 30 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const request = require('request-promise-native'); 2 | const Url = require('url'); 3 | const vm = require('vm'); 4 | const { 5 | randomFromRange, 6 | randomTrueFalse, 7 | delay, 8 | uuid, 9 | getRandomUserAgent 10 | } = require('./src/utils'); 11 | 12 | function getMouseMovements(timestamp) { 13 | let lastMovement = timestamp; 14 | const motionCount = randomFromRange(1000, 10000); 15 | const mouseMovements = []; 16 | for (let i = 0; i < motionCount; i++) { 17 | lastMovement += randomFromRange(0, 10); 18 | mouseMovements.push([randomFromRange(0, 500), randomFromRange(0, 500), lastMovement]); 19 | } 20 | return mouseMovements; 21 | } 22 | 23 | async function hsl(req) { 24 | const hsl = await request.get('https://assets.hcaptcha.com/c/500c658/hsl.js'); 25 | return new Promise((resolve, reject) => { 26 | const code = ` 27 | var self = {}; 28 | function atob(a) { 29 | return new Buffer(a, 'base64').toString('binary'); 30 | } 31 | 32 | ${hsl} 33 | 34 | hsl('${req}').then(resolve).catch(reject) 35 | `; 36 | vm.runInNewContext(code, { 37 | Buffer, 38 | resolve, 39 | reject, 40 | }); 41 | }); 42 | } 43 | 44 | 45 | async function tryToSolve(sitekey, host) { 46 | const userAgent = getRandomUserAgent(); 47 | const headers = { 48 | 'User-Agent': userAgent 49 | }; 50 | 51 | let response = await request({ 52 | method: 'get', 53 | headers, 54 | json: true, 55 | url: `https://hcaptcha.com/checksiteconfig?host=${host}&sitekey=${sitekey}&sc=1&swa=0` 56 | }); 57 | 58 | let timestamp = Date.now() + randomFromRange(30, 120); 59 | response = await request({ 60 | method: 'post', 61 | headers, 62 | json: true, 63 | url: 'https://hcaptcha.com/getcaptcha', 64 | form: { 65 | sitekey, 66 | host, 67 | n: await hsl(response.c.req), 68 | c: JSON.stringify(response.c), 69 | motionData: { 70 | st: timestamp, 71 | dct: timestamp, 72 | mm: getMouseMovements(timestamp) 73 | } 74 | } 75 | }); 76 | 77 | if (response.generated_pass_UUID) { 78 | return response.generated_pass_UUID; 79 | } 80 | 81 | const key = response.key; 82 | const tasks = response.tasklist; 83 | const job = response.request_type; 84 | timestamp = Date.now() + randomFromRange(30, 120); 85 | const answers = tasks.reduce((accum, t) => ({ ...accum, [t.task_key]: randomTrueFalse() }), {}); 86 | const captchaResponse = { 87 | answers, 88 | sitekey, 89 | serverdomain: host, 90 | job_mode: job, 91 | motionData: { 92 | st: timestamp, 93 | dct: timestamp, 94 | mm: getMouseMovements(timestamp) 95 | } 96 | }; 97 | 98 | response = await request(`https://hcaptcha.com/checkcaptcha/${key}`, { 99 | method: 'post', 100 | headers, 101 | json: true, 102 | form: captchaResponse 103 | }); 104 | 105 | if (response.generated_pass_UUID) { 106 | return response.generated_pass_UUID; 107 | } 108 | } 109 | 110 | async function solveCaptcha(url, options = {}) { 111 | const { gentleMode, timeoutInMs = 12000000 } = options; 112 | const { hostname } = Url.parse(url); 113 | const siteKey = uuid(); 114 | const startingTime = Date.now(); 115 | 116 | while (true) { 117 | try { 118 | const result = await tryToSolve(siteKey, hostname); 119 | if (result) { 120 | return result; 121 | } 122 | } catch (e) { 123 | if (e.statusCode === 429) { 124 | // reached rate limit, wait 30 sec 125 | await delay(30000); 126 | } else { 127 | throw e; 128 | } 129 | } 130 | if (Date.now() - startingTime > timeoutInMs) { 131 | throw new Error('captcha resolution timeout'); 132 | } 133 | if (gentleMode) { 134 | // wait a bit to avoid rate limit errors 135 | delay(3000); 136 | } 137 | } 138 | } 139 | 140 | module.exports = solveCaptcha; 141 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hcaptcha-solver", 3 | "version": "1.0.2", 4 | "description": "A library to solve hCaptcha challenges", 5 | "author": "Jimmy Laurent", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/JimmyLaurent/hcaptcha-solver.git" 9 | }, 10 | "main": "index.js", 11 | "license": "MIT", 12 | "keywords": [ 13 | "hcaptcha", 14 | "bypass", 15 | "solve" 16 | ], 17 | "prettier": { 18 | "printWidth": 100, 19 | "singleQuote": true, 20 | "trailingComma": "none", 21 | "parser": "flow", 22 | "semi": true, 23 | "tabWidth": 2 24 | }, 25 | "dependencies": { 26 | "request": "^2.88.2", 27 | "request-promise-native": "^1.0.8" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | function randomFromRange(start, end) { 2 | return Math.round(Math.random() * (end - start) + start); 3 | } 4 | 5 | function randomTrueFalse() { 6 | return randomFromRange(0, 1) ? 'true' : 'false'; 7 | } 8 | 9 | const delay = (ms) => new Promise((r) => setTimeout(r, ms)); 10 | 11 | function uuid(a) { 12 | return a 13 | ? (a ^ ((Math.random() * 16) >> (a / 4))).toString(16) 14 | : ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid); 15 | } 16 | 17 | function getRandomUserAgent() { 18 | // TODO 19 | return 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36'; 20 | } 21 | 22 | module.exports = { randomFromRange, randomTrueFalse, delay, uuid, getRandomUserAgent }; 23 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | ajv@^6.5.5: 6 | version "6.12.2" 7 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" 8 | dependencies: 9 | fast-deep-equal "^3.1.1" 10 | fast-json-stable-stringify "^2.0.0" 11 | json-schema-traverse "^0.4.1" 12 | uri-js "^4.2.2" 13 | 14 | asn1@~0.2.3: 15 | version "0.2.4" 16 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" 17 | dependencies: 18 | safer-buffer "~2.1.0" 19 | 20 | assert-plus@1.0.0, assert-plus@^1.0.0: 21 | version "1.0.0" 22 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 23 | 24 | asynckit@^0.4.0: 25 | version "0.4.0" 26 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 27 | 28 | aws-sign2@~0.7.0: 29 | version "0.7.0" 30 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" 31 | 32 | aws4@^1.8.0: 33 | version "1.10.0" 34 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.0.tgz#a17b3a8ea811060e74d47d306122400ad4497ae2" 35 | 36 | bcrypt-pbkdf@^1.0.0: 37 | version "1.0.2" 38 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" 39 | dependencies: 40 | tweetnacl "^0.14.3" 41 | 42 | caseless@~0.12.0: 43 | version "0.12.0" 44 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" 45 | 46 | combined-stream@^1.0.6, combined-stream@~1.0.6: 47 | version "1.0.8" 48 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" 49 | dependencies: 50 | delayed-stream "~1.0.0" 51 | 52 | core-util-is@1.0.2: 53 | version "1.0.2" 54 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 55 | 56 | dashdash@^1.12.0: 57 | version "1.14.1" 58 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" 59 | dependencies: 60 | assert-plus "^1.0.0" 61 | 62 | delayed-stream@~1.0.0: 63 | version "1.0.0" 64 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 65 | 66 | ecc-jsbn@~0.1.1: 67 | version "0.1.2" 68 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" 69 | dependencies: 70 | jsbn "~0.1.0" 71 | safer-buffer "^2.1.0" 72 | 73 | extend@~3.0.2: 74 | version "3.0.2" 75 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 76 | 77 | extsprintf@1.3.0: 78 | version "1.3.0" 79 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" 80 | 81 | extsprintf@^1.2.0: 82 | version "1.4.0" 83 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" 84 | 85 | fast-deep-equal@^3.1.1: 86 | version "3.1.3" 87 | resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" 88 | 89 | fast-json-stable-stringify@^2.0.0: 90 | version "2.1.0" 91 | resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" 92 | 93 | forever-agent@~0.6.1: 94 | version "0.6.1" 95 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 96 | 97 | form-data@~2.3.2: 98 | version "2.3.3" 99 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" 100 | dependencies: 101 | asynckit "^0.4.0" 102 | combined-stream "^1.0.6" 103 | mime-types "^2.1.12" 104 | 105 | getpass@^0.1.1: 106 | version "0.1.7" 107 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" 108 | dependencies: 109 | assert-plus "^1.0.0" 110 | 111 | har-schema@^2.0.0: 112 | version "2.0.0" 113 | resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" 114 | 115 | har-validator@~5.1.3: 116 | version "5.1.3" 117 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" 118 | dependencies: 119 | ajv "^6.5.5" 120 | har-schema "^2.0.0" 121 | 122 | http-signature@~1.2.0: 123 | version "1.2.0" 124 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" 125 | dependencies: 126 | assert-plus "^1.0.0" 127 | jsprim "^1.2.2" 128 | sshpk "^1.7.0" 129 | 130 | is-typedarray@~1.0.0: 131 | version "1.0.0" 132 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 133 | 134 | isstream@~0.1.2: 135 | version "0.1.2" 136 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 137 | 138 | jsbn@~0.1.0: 139 | version "0.1.1" 140 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" 141 | 142 | json-schema-traverse@^0.4.1: 143 | version "0.4.1" 144 | resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" 145 | 146 | json-schema@0.2.3: 147 | version "0.2.3" 148 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" 149 | 150 | json-stringify-safe@~5.0.1: 151 | version "5.0.1" 152 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 153 | 154 | jsprim@^1.2.2: 155 | version "1.4.1" 156 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" 157 | dependencies: 158 | assert-plus "1.0.0" 159 | extsprintf "1.3.0" 160 | json-schema "0.2.3" 161 | verror "1.10.0" 162 | 163 | lodash@^4.17.15: 164 | version "4.17.15" 165 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" 166 | 167 | mime-db@1.44.0: 168 | version "1.44.0" 169 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" 170 | 171 | mime-types@^2.1.12, mime-types@~2.1.19: 172 | version "2.1.27" 173 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" 174 | dependencies: 175 | mime-db "1.44.0" 176 | 177 | oauth-sign@~0.9.0: 178 | version "0.9.0" 179 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" 180 | 181 | performance-now@^2.1.0: 182 | version "2.1.0" 183 | resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" 184 | 185 | psl@^1.1.28: 186 | version "1.8.0" 187 | resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" 188 | 189 | punycode@^2.1.0, punycode@^2.1.1: 190 | version "2.1.1" 191 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" 192 | 193 | qs@~6.5.2: 194 | version "6.5.2" 195 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" 196 | 197 | request-promise-core@1.1.3: 198 | version "1.1.3" 199 | resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9" 200 | dependencies: 201 | lodash "^4.17.15" 202 | 203 | request-promise-native@^1.0.8: 204 | version "1.0.8" 205 | resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36" 206 | dependencies: 207 | request-promise-core "1.1.3" 208 | stealthy-require "^1.1.1" 209 | tough-cookie "^2.3.3" 210 | 211 | request@^2.88.2: 212 | version "2.88.2" 213 | resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" 214 | dependencies: 215 | aws-sign2 "~0.7.0" 216 | aws4 "^1.8.0" 217 | caseless "~0.12.0" 218 | combined-stream "~1.0.6" 219 | extend "~3.0.2" 220 | forever-agent "~0.6.1" 221 | form-data "~2.3.2" 222 | har-validator "~5.1.3" 223 | http-signature "~1.2.0" 224 | is-typedarray "~1.0.0" 225 | isstream "~0.1.2" 226 | json-stringify-safe "~5.0.1" 227 | mime-types "~2.1.19" 228 | oauth-sign "~0.9.0" 229 | performance-now "^2.1.0" 230 | qs "~6.5.2" 231 | safe-buffer "^5.1.2" 232 | tough-cookie "~2.5.0" 233 | tunnel-agent "^0.6.0" 234 | uuid "^3.3.2" 235 | 236 | safe-buffer@^5.0.1, safe-buffer@^5.1.2: 237 | version "5.2.1" 238 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 239 | 240 | safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: 241 | version "2.1.2" 242 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 243 | 244 | sshpk@^1.7.0: 245 | version "1.16.1" 246 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" 247 | dependencies: 248 | asn1 "~0.2.3" 249 | assert-plus "^1.0.0" 250 | bcrypt-pbkdf "^1.0.0" 251 | dashdash "^1.12.0" 252 | ecc-jsbn "~0.1.1" 253 | getpass "^0.1.1" 254 | jsbn "~0.1.0" 255 | safer-buffer "^2.0.2" 256 | tweetnacl "~0.14.0" 257 | 258 | stealthy-require@^1.1.1: 259 | version "1.1.1" 260 | resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b" 261 | 262 | tough-cookie@^2.3.3, tough-cookie@~2.5.0: 263 | version "2.5.0" 264 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" 265 | dependencies: 266 | psl "^1.1.28" 267 | punycode "^2.1.1" 268 | 269 | tunnel-agent@^0.6.0: 270 | version "0.6.0" 271 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 272 | dependencies: 273 | safe-buffer "^5.0.1" 274 | 275 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: 276 | version "0.14.5" 277 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 278 | 279 | uri-js@^4.2.2: 280 | version "4.2.2" 281 | resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" 282 | dependencies: 283 | punycode "^2.1.0" 284 | 285 | uuid@^3.3.2: 286 | version "3.4.0" 287 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" 288 | 289 | verror@1.10.0: 290 | version "1.10.0" 291 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" 292 | dependencies: 293 | assert-plus "^1.0.0" 294 | core-util-is "1.0.2" 295 | extsprintf "^1.2.0" 296 | --------------------------------------------------------------------------------