puppeteer-ghost
Version:
puppeteer library to bypass bot detection
453 lines (422 loc) • 18.2 kB
JavaScript
// 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