Create index.js
Browse files
index.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const {addExtra} = require('puppeteer-extra');
|
| 2 |
+
const StealthPlugin = require('puppeteer-extra-plugin-stealth');
|
| 3 |
+
const rebrowserPuppeteer = require('rebrowser-puppeteer');
|
| 4 |
+
|
| 5 |
+
const puppeteer = addExtra(rebrowserPuppeteer);
|
| 6 |
+
puppeteer.use(StealthPlugin());
|
| 7 |
+
const Koa = require('koa');
|
| 8 |
+
const bodyParser = require('koa-bodyparser');
|
| 9 |
+
const app = new Koa();
|
| 10 |
+
app.use(bodyParser());
|
| 11 |
+
const jsesc = require('jsesc');
|
| 12 |
+
|
| 13 |
+
const requestHeadersToRemove = [
|
| 14 |
+
"host", "user-agent", "accept-encoding", "content-length",
|
| 15 |
+
"forwarded", "x-forwarded-proto", "x-forwarded-for", "x-cloud-trace-context"
|
| 16 |
+
];
|
| 17 |
+
const responseHeadersToRemove = ["Accept-Ranges", "Content-Length", "Keep-Alive", "Connection", "content-encoding", "set-cookie"];
|
| 18 |
+
|
| 19 |
+
(async () => {
|
| 20 |
+
let options = {
|
| 21 |
+
headless: "new",
|
| 22 |
+
args: [
|
| 23 |
+
'--no-sandbox',
|
| 24 |
+
'--disable-setuid-sandbox',
|
| 25 |
+
'--disable-blink-features=AutomationControlled'
|
| 26 |
+
],
|
| 27 |
+
ignoreDefaultArgs: [
|
| 28 |
+
'--enable-automation',
|
| 29 |
+
'--disable-popup-blocking'
|
| 30 |
+
]
|
| 31 |
+
};
|
| 32 |
+
if (process.env.PUPPETEER_SKIP_CHROMIUM_DOWNLOAD)
|
| 33 |
+
options.executablePath = '/usr/bin/chromium-browser';
|
| 34 |
+
if (process.env.PUPPETEER_HEADFUL)
|
| 35 |
+
options.headless = false;
|
| 36 |
+
if (process.env.PUPPETEER_USERDATADIR)
|
| 37 |
+
options.userDataDir = process.env.PUPPETEER_USERDATADIR;
|
| 38 |
+
if (process.env.PUPPETEER_PROXY)
|
| 39 |
+
options.args.push(`--proxy-server=${process.env.PUPPETEER_PROXY}`);
|
| 40 |
+
const browser = await puppeteer.launch(options);
|
| 41 |
+
app.use(async ctx => {
|
| 42 |
+
if (ctx.query.url) {
|
| 43 |
+
const url = decodeURIComponent(ctx.url.replace("/?url=", ""));
|
| 44 |
+
if (process.env.DEBUG) {
|
| 45 |
+
console.log(`[DEBUG] URL: ${url}`);
|
| 46 |
+
}
|
| 47 |
+
let responseBody;
|
| 48 |
+
let responseData;
|
| 49 |
+
let responseHeaders;
|
| 50 |
+
const page = await browser.newPage();
|
| 51 |
+
|
| 52 |
+
await page.removeAllListeners('request');
|
| 53 |
+
await page.setRequestInterception(true);
|
| 54 |
+
let requestHeaders = ctx.headers;
|
| 55 |
+
requestHeadersToRemove.forEach(header => {
|
| 56 |
+
delete requestHeaders[header];
|
| 57 |
+
});
|
| 58 |
+
page.on('request', (request) => {
|
| 59 |
+
requestHeaders = Object.assign({}, request.headers(), requestHeaders);
|
| 60 |
+
if (process.env.DEBUG) {
|
| 61 |
+
console.log(`[DEBUG] requested headers: \n${JSON.stringify(requestHeaders)}`);
|
| 62 |
+
}
|
| 63 |
+
if (ctx.method == "POST") {
|
| 64 |
+
request.continue({
|
| 65 |
+
headers: requestHeaders,
|
| 66 |
+
'method': 'POST',
|
| 67 |
+
'postData': ctx.request.rawBody
|
| 68 |
+
});
|
| 69 |
+
} else {
|
| 70 |
+
request.continue({ headers: requestHeaders });
|
| 71 |
+
}
|
| 72 |
+
});
|
| 73 |
+
|
| 74 |
+
const client = await page.target().createCDPSession();
|
| 75 |
+
await client.send('Network.setRequestInterception', {
|
| 76 |
+
patterns: [{
|
| 77 |
+
urlPattern: '*',
|
| 78 |
+
resourceType: 'Document',
|
| 79 |
+
interceptionStage: 'HeadersReceived'
|
| 80 |
+
}],
|
| 81 |
+
});
|
| 82 |
+
|
| 83 |
+
await client.on('Network.requestIntercepted', async e => {
|
| 84 |
+
let obj = { interceptionId: e.interceptionId };
|
| 85 |
+
if (e.isDownload) {
|
| 86 |
+
await client.send('Network.getResponseBodyForInterception', {
|
| 87 |
+
interceptionId: e.interceptionId
|
| 88 |
+
}).then((result) => {
|
| 89 |
+
if (result.base64Encoded) {
|
| 90 |
+
responseData = Buffer.from(result.body, 'base64');
|
| 91 |
+
}
|
| 92 |
+
});
|
| 93 |
+
obj['errorReason'] = 'BlockedByClient';
|
| 94 |
+
responseHeaders = e.responseHeaders;
|
| 95 |
+
}
|
| 96 |
+
await client.send('Network.continueInterceptedRequest', obj);
|
| 97 |
+
if (e.isDownload)
|
| 98 |
+
await page.close();
|
| 99 |
+
});
|
| 100 |
+
try {
|
| 101 |
+
let response;
|
| 102 |
+
let tryCount = 0;
|
| 103 |
+
response = await page.goto(url, { timeout: 30000, waitUntil: 'domcontentloaded' });
|
| 104 |
+
ctx.status = response.status();
|
| 105 |
+
responseBody = await response.text();
|
| 106 |
+
responseData = await response.buffer();
|
| 107 |
+
while (responseBody.includes(process.env.CHALLENGE_MATCH || "challenge-platform") && tryCount <= 10) {
|
| 108 |
+
newResponse = await page.waitForNavigation({ timeout: 30000, waitUntil: 'domcontentloaded' });
|
| 109 |
+
if (newResponse) response = newResponse;
|
| 110 |
+
responseBody = await response.text();
|
| 111 |
+
responseData = await response.buffer();
|
| 112 |
+
tryCount++;
|
| 113 |
+
}
|
| 114 |
+
responseHeaders = await response.headers();
|
| 115 |
+
const cookies = await page.cookies();
|
| 116 |
+
if (cookies)
|
| 117 |
+
cookies.forEach(cookie => {
|
| 118 |
+
const { name, value, secure, expires, domain, ...options } = cookie;
|
| 119 |
+
ctx.cookies.set(cookie.name, cookie.value, options);
|
| 120 |
+
});
|
| 121 |
+
} catch (error) {
|
| 122 |
+
if (!error.toString().includes("ERR_BLOCKED_BY_CLIENT")) {
|
| 123 |
+
ctx.status = 500;
|
| 124 |
+
ctx.body = error;
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
await page.close();
|
| 129 |
+
if (responseHeaders) {
|
| 130 |
+
responseHeadersToRemove.forEach(header => delete responseHeaders[header]);
|
| 131 |
+
Object.keys(responseHeaders).forEach(header => ctx.set(header, jsesc(responseHeaders[header])));
|
| 132 |
+
}
|
| 133 |
+
if (process.env.DEBUG) {
|
| 134 |
+
console.log(`[DEBUG] response headers: \n${JSON.stringify(responseHeaders)}`);
|
| 135 |
+
}
|
| 136 |
+
if (process.env.DEBUG_BODY) {
|
| 137 |
+
console.log(`[DEBUG] body: \n${responseData}`);
|
| 138 |
+
}
|
| 139 |
+
ctx.body = responseData;
|
| 140 |
+
}
|
| 141 |
+
else {
|
| 142 |
+
ctx.body = "Please specify the URL in the 'url' query string.";
|
| 143 |
+
}
|
| 144 |
+
});
|
| 145 |
+
app.listen(process.env.PORT || 3000, process.env.ADDRESS || "::");
|
| 146 |
+
})();
|