UNPKG

puppeteer-ghost

Version:

puppeteer library to bypass bot detection

453 lines (422 loc) 18.2 kB
// src/browser.js import { addExtra } from "puppeteer-extra"; import StealthPlugin from "puppeteer-extra-plugin-stealth"; import UserPreferencesPlugin from "puppeteer-extra-plugin-user-preferences"; import rebrowserPuppeteer from "rebrowser-puppeteer"; // src/config.js var DEFAULT_LAUNCH_OPTIONS = { defaultViewport: null, headless: false, browser: "chrome", args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-blink-features=AutomationControlled", "--disable-notifications", "--disable-extensions", "--disable-webrtc", "--disable-webrtc-encryption", "--disable-webrtc-hw-encoding", "--disable-webrtc-hw-decoding", "--disable-webrtc-multiple-routes", "--disable-webrtc-hide-local-ips-with-mdns", "--disable-webrtc-apm-downmix-capture-audio-method", "--disable-chrome-wide-echo-cancellation", "--disable-webrtc-allow-wgc-screen-capturer", "--disable-webrtc-allow-wgc-window-capturer", "--disable-webrtc-wgc-require-border", "--disable-webrtc-hw-vp8-encoding", "--disable-webrtc-hw-vp9-encoding", "--disable-rtc-smoothness-algorithm", "--disable-webrtc-stun-origin", "--enforce-webrtc-ip-permission-check", "--force-webrtc-ip-handling-policy=disable_non_proxied_udp", "--disable-media-stream", "--disable-getUserMedia-screen-capturing", "--disable-background-media-suspend", "--disable-background-timer-throttling", "--disable-renderer-backgrounding", "--disable-backgrounding-occluded-windows", "--disable-ipc-flooding-protection", "--deny-permission-prompts", "--disable-permissions-api", "--disable-media-device-access", "--block-new-web-contents", "--disable-default-apps", "--disable-media-device-enumeration", "--disable-save-password-bubble", "--disable-features=PasswordLeakDetection,WebRTC,MediaDevices,GetUserMedia,RTCPeerConnection,RTCDataChannel", "--disable-webrtc-network-predictor", "--disable-webrtc-stun-probe-trial", "--disable-webrtc-use-pipewire", "--disable-webrtc-logs", "--disable-webrtc-event-logging", "--disable-webrtc-remote-event-log", "--disable-webrtc-apm-debug-dump", "--disable-webrtc-apm-in-audio-service"] }; var CHROME_PATHS = ["C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/usr/bin/google-chrome", "/usr/bin/google-chrome-stable"]; var USER_PREFERENCES = { userPrefs: { profile: { password_manager_leak_detection: false } } }; // src/utils.js var findChromePath = async () => { for (const path of CHROME_PATHS) { try { await import("fs").then((fs) => fs.promises.access(path)); return path; } catch { } } return null; }; var processProxyConfig = (proxy) => { if (!(proxy == null ? void 0 : proxy.server)) return null; const server = proxy.server.replace("http://", "").replace("https://", ""); const auth = proxy.username && proxy.password ? { username: proxy.username, password: proxy.password } : null; return { server, auth }; }; var randomDelay = (min, max) => { return min + Math.random() * (max - min); }; var randomPosition = (box) => { const x = box.x + box.width * (0.3 + Math.random() * 0.4); const y = box.y + box.height * (0.3 + Math.random() * 0.4); return { x, y }; }; var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); // src/page.js var getWebRTCBlockScript = () => ` (() => { const noop = () => {}; const noopAsync = () => Promise.reject(new DOMException('Operation not supported', 'NotSupportedError')); const noopConstructor = function() { throw new TypeError('Illegal constructor'); }; const webrtcAPIs = [ 'RTCPeerConnection', 'RTCDataChannel', 'webkitRTCPeerConnection', 'mozRTCPeerConnection', 'RTCIceCandidate', 'RTCSessionDescription', 'RTCStatsReport', 'RTCRtpSender', 'RTCRtpReceiver', 'RTCRtpTransceiver', 'RTCDtlsTransport', 'RTCIceTransport', 'RTCSctpTransport', 'RTCRtpContributingSource', 'RTCRtpSynchronizationSource', 'RTCDTMFSender', 'RTCDTMFToneChangeEvent', 'RTCDataChannelEvent', 'RTCPeerConnectionIceEvent', 'RTCPeerConnectionIceErrorEvent', 'RTCTrackEvent', 'RTCErrorEvent', 'RTCCertificate' ]; const blockAPI = (obj, prop, value = undefined) => { try { if (obj[prop]) { delete obj[prop]; } Object.defineProperty(obj, prop, { get: () => value, set: noop, configurable: false, enumerable: false, writable: false }); } catch (e) {} }; webrtcAPIs.forEach(api => { blockAPI(window, api, noopConstructor); if (window[api]) { try { window[api] = noopConstructor; } catch (e) {} } }); const navigatorAPIs = ['getUserMedia', 'webkitGetUserMedia', 'mozGetUserMedia', 'msGetUserMedia']; navigatorAPIs.forEach(api => { blockAPI(navigator, api); if (navigator[api]) { try { navigator[api] = undefined; } catch (e) {} } }); blockAPI(navigator, 'mediaDevices'); if (navigator.mediaDevices) { try { navigator.mediaDevices = undefined; } catch (e) {} } if (navigator.permissions && navigator.permissions.query) { const originalQuery = navigator.permissions.query; navigator.permissions.query = (params) => { if (params && (params.name === 'camera' || params.name === 'microphone')) { return Promise.resolve({ state: 'denied', onchange: null }); } return originalQuery.call(navigator.permissions, params); }; } const originalCreateElement = document.createElement; document.createElement = function(tagName) { const element = originalCreateElement.call(this, tagName); if (tagName.toLowerCase() === 'iframe') { const originalSrc = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'src') || Object.getOwnPropertyDescriptor(Element.prototype, 'src'); if (originalSrc && originalSrc.set) { Object.defineProperty(element, 'src', { get: originalSrc.get, set: function(value) { if (value && (value.includes('stun:') || value.includes('turn:'))) { return; } return originalSrc.set.call(this, value); }, configurable: true, enumerable: true }); } } return element; }; const blockWebRTCCompletely = () => { webrtcAPIs.forEach(api => { if (window[api]) { try { delete window[api]; window[api] = noopConstructor; Object.defineProperty(window, api, { get: () => noopConstructor, set: noop, configurable: false, enumerable: false }); } catch (e) {} } }); if (navigator.mediaDevices) { try { delete navigator.mediaDevices; navigator.mediaDevices = undefined; Object.defineProperty(navigator, 'mediaDevices', { get: () => undefined, set: noop, configurable: false, enumerable: false }); } catch (e) {} } navigatorAPIs.forEach(api => { if (navigator[api]) { try { delete navigator[api]; navigator[api] = undefined; Object.defineProperty(navigator, api, { get: () => undefined, set: noop, configurable: false, enumerable: false }); } catch (e) {} } }); ['MediaStream', 'MediaStreamTrack', 'MediaDevices'].forEach(api => { if (window[api]) { try { delete window[api]; window[api] = noopConstructor; Object.defineProperty(window, api, { get: () => noopConstructor, set: noop, configurable: false, enumerable: false }); } catch (e) {} } }); }; blockWebRTCCompletely(); const observer = new MutationObserver(blockWebRTCCompletely); observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true }); setInterval(blockWebRTCCompletely, 10); ['DOMContentLoaded', 'load', 'beforeunload', 'pageshow'].forEach(event => { document.addEventListener(event, blockWebRTCCompletely, true); }); if (typeof window.addEventListener === 'function') { window.addEventListener('beforeunload', blockWebRTCCompletely, true); } const originalFetch = window.fetch; window.fetch = function(...args) { const url = args[0]; if (typeof url === 'string' && (url.includes('stun:') || url.includes('turn:') || url.includes('webrtc'))) { return Promise.reject(new Error('WebRTC blocked')); } return originalFetch.apply(this, args); }; const originalXMLHttpRequest = window.XMLHttpRequest; window.XMLHttpRequest = function() { const xhr = new originalXMLHttpRequest(); const originalOpen = xhr.open; xhr.open = function(method, url, ...args) { if (typeof url === 'string' && (url.includes('stun:') || url.includes('turn:') || url.includes('webrtc'))) { throw new Error('WebRTC blocked'); } return originalOpen.apply(this, [method, url, ...args]); }; return xhr; }; if (window.WebSocket) { const originalWebSocket = window.WebSocket; window.WebSocket = function(url, ...args) { if (typeof url === 'string' && (url.includes('stun:') || url.includes('turn:') || url.includes('webrtc'))) { throw new Error('WebRTC blocked'); } return new originalWebSocket(url, ...args); }; } const overrideProperty = (obj, prop, value) => { try { Object.defineProperty(obj, prop, { get: () => value, set: () => {}, configurable: false, enumerable: false }); } catch (e) {} }; overrideProperty(window, 'RTCPeerConnection', undefined); overrideProperty(window, 'webkitRTCPeerConnection', undefined); overrideProperty(window, 'mozRTCPeerConnection', undefined); overrideProperty(navigator, 'getUserMedia', undefined); overrideProperty(navigator, 'webkitGetUserMedia', undefined); overrideProperty(navigator, 'mozGetUserMedia', undefined); overrideProperty(navigator, 'mediaDevices', undefined); if (typeof Object.freeze === 'function') { try { Object.freeze(navigator); Object.freeze(window); } catch (e) {} } })(); `; var enhancePage = async (page, proxyAuth = null) => { if (proxyAuth) { await page.authenticate(proxyAuth); } await page.evaluateOnNewDocument(` (() => { 'use strict'; const noop = () => {}; const noopConstructor = function() { throw new TypeError('Illegal constructor'); }; const apis = ['RTCPeerConnection', 'webkitRTCPeerConnection', 'mozRTCPeerConnection', 'RTCDataChannel']; apis.forEach(api => { try { delete window[api]; Object.defineProperty(window, api, { get: () => undefined, set: noop, configurable: false, enumerable: false }); } catch (e) {} }); const navApis = ['getUserMedia', 'webkitGetUserMedia', 'mozGetUserMedia', 'mediaDevices']; navApis.forEach(api => { try { delete navigator[api]; Object.defineProperty(navigator, api, { get: () => undefined, set: noop, configurable: false, enumerable: false }); } catch (e) {} }); })(); `); const client = await page.createCDPSession(); await client.send("Network.enable"); try { await client.send("Browser.setPermission", { permission: "camera", setting: "denied" }); await client.send("Browser.setPermission", { permission: "microphone", setting: "denied" }); } catch (e) { } try { await client.send("Network.setUserAgentOverride", { userAgent: await page.evaluate(() => navigator.userAgent), platform: "Win32" }); } catch (e) { } await client.send("Page.setWebLifecycleState", { state: "active" }); await page.evaluateOnNewDocument(` (() => { const webRTCPolicy = { 'webRTCIPHandlingPolicy': 'disable_non_proxied_udp', 'webRTCMultipleRoutesEnabled': false, 'webRTCNonProxiedUdpEnabled': false }; if (chrome && chrome.privacy && chrome.privacy.network) { try { chrome.privacy.network.webRTCIPHandlingPolicy.set({ value: 'disable_non_proxied_udp' }); } catch (e) {} } })(); `); await page.evaluateOnNewDocument(getWebRTCBlockScript); await page.evaluate(() => { const forceBlock = (obj, prop) => { try { delete obj[prop]; Object.defineProperty(obj, prop, { get: () => void 0, set: () => { }, configurable: false, enumerable: false, writable: false }); } catch (e) { } }; ["RTCPeerConnection", "webkitRTCPeerConnection", "mozRTCPeerConnection", "RTCDataChannel", "RTCIceCandidate", "RTCSessionDescription", "MediaStream", "MediaStreamTrack", "MediaDevices"].forEach((api) => { forceBlock(window, api); }); ["getUserMedia", "webkitGetUserMedia", "mozGetUserMedia", "mediaDevices"].forEach((api) => { forceBlock(navigator, api); }); if (window.navigator && window.navigator.getUserMedia) { window.navigator.getUserMedia = void 0; } if (window.navigator && window.navigator.mediaDevices) { window.navigator.mediaDevices = void 0; } }); page.forceBlockWebRTC = async () => { await page.evaluate(getWebRTCBlockScript); }; page.click = async (selector, options = {}) => { await page.waitForSelector(selector, { visible: true }); const elementHandle = await page.$(selector); const box = await elementHandle.boundingBox(); if (!box) { throw new Error(`Element ${selector} not found or not visible`); } const { x, y } = randomPosition(box); await page.mouse.move(x, y); await sleep(randomDelay(50, 150)); await page.mouse.down(options); await sleep(randomDelay(30, 80)); await page.mouse.up(options); }; page.type = async (selector, text, options = {}) => { await page.click(selector); const delay = options.delay !== void 0 ? options.delay : randomDelay(10, 50); for (const char of text) { await page.keyboard.type(char, { delay }); } }; return page; }; // src/browser.js var createPuppeteer = () => { const puppeteer2 = addExtra(rebrowserPuppeteer); puppeteer2.use(StealthPlugin()); puppeteer2.use(UserPreferencesPlugin(USER_PREFERENCES)); return puppeteer2; }; var enhanceBrowser = (browser, proxyAuth = null) => { browser.newPage = async () => { const pages = await browser.pages(); const page = pages[0]; return enhancePage(page, proxyAuth); }; return browser; }; var launchBrowser = async (options = {}) => { const puppeteer2 = createPuppeteer(); const originalLaunch = puppeteer2.launch.bind(puppeteer2); const mergedOptions = { ...DEFAULT_LAUNCH_OPTIONS, ...options }; const proxyConfig = processProxyConfig(mergedOptions.proxy); if (proxyConfig) { mergedOptions.args.push(`--proxy-server=${proxyConfig.server}`); delete mergedOptions.proxy; } if (!mergedOptions.executablePath) { const chromePath = await findChromePath(); if (chromePath) { mergedOptions.executablePath = chromePath; } } const browser = await originalLaunch(mergedOptions); return enhanceBrowser(browser, proxyConfig == null ? void 0 : proxyConfig.auth); }; // src/index.js var puppeteer = createPuppeteer(); puppeteer.launch = launchBrowser; var index_default = puppeteer; export { index_default as default }; //# sourceMappingURL=index.js.map