-
-
Notifications
You must be signed in to change notification settings - Fork 18
/
hooman.js
141 lines (133 loc) · 5.23 KB
/
hooman.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
'use strict';
const got = require('got');
const solveChallenge = require('./lib/core');
const solveCaptcha = require('./lib/captcha');
const delay = require('./lib/delay');
const log = require('./lib/logging');
const { CookieJar } = require('tough-cookie');
const UserAgent = require('user-agents');
const cookieJar = new CookieJar(); // Automatically parse and store cookies
const challengeInProgress = {};
// Got instance to handle cloudflare bypass
const instance = got.extend({
cookieJar,
retry: {
limit: 2,
statusCodes: [408, 413, 429, 500, 502, 504, 521, 522, 524],
}, // Do not retry 503, we will handle it
cloudflareRetry: 5, // Prevent cloudflare loop
notFoundRetry: 1, // Handle redirect issue
captchaRetry: 1, // Max retry on captcha
onCaptcha: null, // Custom function to handle captcha
captchaKey: process.env.HOOMAN_CAPTCHA_KEY,
rucaptcha: process.env.HOOMAN_RUCAPTCHA,
http2: false, // http2 doesn't work well with proxies
headers: {
'accept-encoding': 'gzip, deflate',
'accept-language': 'en-US,en;q=0.5',
'cache-control': 'max-age=0',
'upgrade-insecure-requests': '1',
'user-agent': new UserAgent().toString(), // Use random user agent between sessions
}, // Mimic browser environment
hooks: {
beforeRequest: [
async (options) => {
// In case the host is in challenge progress wait
while (challengeInProgress[options.url.host]) {
await delay(1000);
}
// Add required headers to mimic browser environment
options.headers.host = options.url.host;
options.headers.origin = options.url.origin;
options.headers.referer = options.url.href;
},
],
afterResponse: [
async (response) => {
if (
// If site is not hosted on cloudflare skip
response.statusCode === 503 &&
response.headers.server === 'cloudflare' &&
response.request.options.cloudflareRetry > 0 &&
response.body.includes('jschl-answer') &&
response.body.includes('var s')
) {
// Solve js challange
const host = response.request.options.url.host;
if (challengeInProgress[host]) {
log.info('Waiting for js-challenge to be solved: ' + host);
while (challengeInProgress[host]) {
await delay(1000);
}
log.info('JS-Challenge were solved and waiting is over, refreshing: ' + host);
return instance(response.request.options);
}
log.info('Solving js-challenge: ' + response.url);
challengeInProgress[host] = true;
const data = await solveChallenge(response.url, response.body).finally(() => {
setTimeout(() => {
if (challengeInProgress[host]) {
log.info('Clear js-challenge in progress: ' + host);
delete challengeInProgress[host];
}
}, 2000);
});
response.request.options.cloudflareRetry--;
return instance({ ...response.request.options, ...data });
} else if (
// Handle redirect issue for cloudflare
response.statusCode === 404 &&
response.headers.server === 'cloudflare' &&
response.request.options.notFoundRetry > 0 &&
response.url.includes('?__cf_chl_jschl_tk')
) {
// Do not retry again
return instance({
url: response.url.split('?__cf_chl_jschl_tk')[0],
notFoundRetry: response.request.options.notFoundRetry - 1,
});
} else if (
response.statusCode === 403 &&
response.headers.server === 'cloudflare' &&
response.request.options.captchaKey &&
response.request.options.captchaRetry > 0 &&
response.body.includes('cf_captcha_kind')
) {
// Solve g/hCaptcha
// If there are captcha solving in progress for current domain do not request for solving
const host = response.request.options.url.host;
if (challengeInProgress[host] && !response.request.options.ignoreInProgress) {
log.info('Waiting for captcha to be solved: ' + host);
while (challengeInProgress[host]) {
await delay(1000);
}
log.info('Captcha were solved and waiting is over, refreshing: ' + host);
return instance(response.request.options);
}
challengeInProgress[host] = true;
const captchaData = await solveCaptcha(response).finally(() => {
setTimeout(() => {
if (challengeInProgress[host]) {
log.info('Clear captcha in progress: ' + host);
delete challengeInProgress[host];
}
}, 2000);
});
// Submit captcha data
if (captchaData) {
log.info('Submit captcha: ' + captchaData.url);
return instance({
...response.request.options,
...captchaData,
captchaRetry: response.request.options.captchaRetry - 1,
ignoreInProgress: true,
});
}
}
return response;
},
],
},
mutableDefaults: true, // Defines if config can be changed later
});
module.exports = instance;