Spaces:
Running
Add enhanced stealth and immediate screenshot after click
Browse files- Added comprehensive browser stealth techniques:
* Enhanced browser launch args to avoid automation detection
* More realistic user agent (Mac instead of Windows)
* Added locale, timezone, geolocation
* Comprehensive JavaScript overrides for navigator properties
* Chrome runtime object simulation
* WebGL vendor spoofing
- Added iframe detection and clicking:
* CAPTCHA is often inside cross-origin iframes
* _try_click_iframe method detects and clicks inside iframes
* Calculates relative coordinates within iframe
- Immediate screenshot after click:
* Screenshot taken 1 second after each click
* No need to wait for 3-second auto-refresh
* Shows result of your action immediately
* Also takes screenshot on error for debugging
This should help with CAPTCHA checkbox clicking!
- copilot_portal.py +145 -8
|
@@ -37,32 +37,121 @@ class CopilotPortal:
|
|
| 37 |
return False
|
| 38 |
|
| 39 |
async def initialize(self):
|
| 40 |
-
"""Initialize the browser and navigate to Copilot."""
|
| 41 |
if self.is_initialized:
|
| 42 |
return
|
| 43 |
|
| 44 |
try:
|
| 45 |
-
logger.info("🚀 Portal: Launching browser...")
|
| 46 |
self.playwright = await async_playwright().start()
|
| 47 |
|
|
|
|
| 48 |
self.browser = await self.playwright.chromium.launch(
|
| 49 |
-
headless=True,
|
| 50 |
args=[
|
| 51 |
"--disable-blink-features=AutomationControlled",
|
|
|
|
|
|
|
| 52 |
"--no-sandbox",
|
| 53 |
"--disable-dev-shm-usage",
|
| 54 |
"--disable-gpu",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
],
|
| 56 |
)
|
| 57 |
|
|
|
|
| 58 |
self.context = await self.browser.new_context(
|
| 59 |
viewport={"width": 1280, "height": 800},
|
| 60 |
-
user_agent="Mozilla/5.0 (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
)
|
| 62 |
|
| 63 |
-
#
|
| 64 |
await self.context.add_init_script("""
|
|
|
|
| 65 |
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
""")
|
| 67 |
|
| 68 |
self.page = await self.context.new_page()
|
|
@@ -203,19 +292,67 @@ class CopilotPortal:
|
|
| 203 |
logger.error(f"Refresh error: {e}")
|
| 204 |
|
| 205 |
async def click_at_coordinates(self, x: float, y: float):
|
| 206 |
-
"""Click at specific coordinates on the page."""
|
| 207 |
if not self.page:
|
| 208 |
logger.error("Portal: No page available for click")
|
| 209 |
return
|
| 210 |
|
| 211 |
try:
|
| 212 |
logger.info(f"Portal: Clicking at coordinates ({x}, {y})")
|
|
|
|
|
|
|
| 213 |
await self.page.mouse.click(x, y)
|
| 214 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
await self.take_screenshot()
|
| 216 |
-
logger.info("Portal: Click completed")
|
|
|
|
| 217 |
except Exception as e:
|
| 218 |
logger.error(f"Portal click error: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
|
| 220 |
async def close(self):
|
| 221 |
"""Close the browser."""
|
|
|
|
| 37 |
return False
|
| 38 |
|
| 39 |
async def initialize(self):
|
| 40 |
+
"""Initialize the browser and navigate to Copilot with enhanced stealth."""
|
| 41 |
if self.is_initialized:
|
| 42 |
return
|
| 43 |
|
| 44 |
try:
|
| 45 |
+
logger.info("🚀 Portal: Launching browser with stealth...")
|
| 46 |
self.playwright = await async_playwright().start()
|
| 47 |
|
| 48 |
+
# Enhanced browser args for stealth
|
| 49 |
self.browser = await self.playwright.chromium.launch(
|
| 50 |
+
headless=True,
|
| 51 |
args=[
|
| 52 |
"--disable-blink-features=AutomationControlled",
|
| 53 |
+
"--disable-features=IsolateOrigins,site-per-process",
|
| 54 |
+
"--disable-site-isolation-trials",
|
| 55 |
"--no-sandbox",
|
| 56 |
"--disable-dev-shm-usage",
|
| 57 |
"--disable-gpu",
|
| 58 |
+
"--disable-web-security",
|
| 59 |
+
"--disable-features=BlockInsecurePrivateNetworkRequests",
|
| 60 |
+
"--disable-features=InterestCohort",
|
| 61 |
+
"--window-size=1280,800",
|
| 62 |
+
"--start-maximized",
|
| 63 |
+
"--force-color-profile=srgb",
|
| 64 |
+
"--disable-background-timer-throttling",
|
| 65 |
+
"--disable-backgrounding-occluded-windows",
|
| 66 |
+
"--disable-renderer-backgrounding",
|
| 67 |
],
|
| 68 |
)
|
| 69 |
|
| 70 |
+
# More realistic browser context
|
| 71 |
self.context = await self.browser.new_context(
|
| 72 |
viewport={"width": 1280, "height": 800},
|
| 73 |
+
user_agent="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
| 74 |
+
locale="en-US",
|
| 75 |
+
timezone_id="America/New_York",
|
| 76 |
+
geolocation={"latitude": 40.7128, "longitude": -74.0060}, # NYC
|
| 77 |
+
permissions=["geolocation"],
|
| 78 |
+
color_scheme="light",
|
| 79 |
+
reduced_motion="no-preference",
|
| 80 |
)
|
| 81 |
|
| 82 |
+
# Enhanced stealth script
|
| 83 |
await self.context.add_init_script("""
|
| 84 |
+
// Override navigator properties
|
| 85 |
Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
|
| 86 |
+
Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
|
| 87 |
+
Object.defineProperty(navigator, 'languages', {get: () => ['en-US', 'en']});
|
| 88 |
+
|
| 89 |
+
// Override permissions
|
| 90 |
+
const originalQuery = window.navigator.permissions.query;
|
| 91 |
+
window.navigator.permissions.query = (parameters) => (
|
| 92 |
+
parameters.name === 'notifications' ?
|
| 93 |
+
Promise.resolve({ state: Notification.permission }) :
|
| 94 |
+
originalQuery(parameters)
|
| 95 |
+
);
|
| 96 |
+
|
| 97 |
+
// Add Chrome runtime
|
| 98 |
+
window.chrome = {
|
| 99 |
+
runtime: {
|
| 100 |
+
OnInstalledReason: {
|
| 101 |
+
CHROME_UPDATE: "chrome_update",
|
| 102 |
+
INSTALL: "install",
|
| 103 |
+
SHARED_MODULE_UPDATE: "shared_module_update",
|
| 104 |
+
UPDATE: "update"
|
| 105 |
+
},
|
| 106 |
+
OnRestartRequiredReason: {
|
| 107 |
+
APP_UPDATE: "app_update",
|
| 108 |
+
OS_UPDATE: "os_update",
|
| 109 |
+
PERIODIC: "periodic"
|
| 110 |
+
},
|
| 111 |
+
PlatformArch: {
|
| 112 |
+
ARM: "arm",
|
| 113 |
+
ARM64: "arm64",
|
| 114 |
+
MIPS: "mips",
|
| 115 |
+
MIPS64: "mips64",
|
| 116 |
+
X86_32: "x86-32",
|
| 117 |
+
X86_64: "x86-64"
|
| 118 |
+
},
|
| 119 |
+
PlatformNaclArch: {
|
| 120 |
+
ARM: "arm",
|
| 121 |
+
MIPS: "mips",
|
| 122 |
+
MIPS64: "mips64",
|
| 123 |
+
MIPS64_EL: "mips64el",
|
| 124 |
+
ARM64: "arm64",
|
| 125 |
+
X86_32: "x86-32",
|
| 126 |
+
X86_64: "x86-64"
|
| 127 |
+
},
|
| 128 |
+
PlatformOs: {
|
| 129 |
+
ANDROID: "android",
|
| 130 |
+
CROS: "cros",
|
| 131 |
+
LINUX: "linux",
|
| 132 |
+
MAC: "mac",
|
| 133 |
+
OPENBSD: "openbsd",
|
| 134 |
+
WIN: "win"
|
| 135 |
+
},
|
| 136 |
+
RequestUpdateCheckStatus: {
|
| 137 |
+
NO_UPDATE: "no_update",
|
| 138 |
+
THROTTLED: "throttled",
|
| 139 |
+
UPDATE_AVAILABLE: "update_available"
|
| 140 |
+
}
|
| 141 |
+
}
|
| 142 |
+
};
|
| 143 |
+
|
| 144 |
+
// Override WebGL
|
| 145 |
+
const getParameter = WebGLRenderingContext.prototype.getParameter;
|
| 146 |
+
WebGLRenderingContext.prototype.getParameter = function(parameter) {
|
| 147 |
+
if (parameter === 37445) {
|
| 148 |
+
return 'Intel Inc.';
|
| 149 |
+
}
|
| 150 |
+
if (parameter === 37446) {
|
| 151 |
+
return 'Intel Iris OpenGL Engine';
|
| 152 |
+
}
|
| 153 |
+
return getParameter(parameter);
|
| 154 |
+
};
|
| 155 |
""")
|
| 156 |
|
| 157 |
self.page = await self.context.new_page()
|
|
|
|
| 292 |
logger.error(f"Refresh error: {e}")
|
| 293 |
|
| 294 |
async def click_at_coordinates(self, x: float, y: float):
|
| 295 |
+
"""Click at specific coordinates on the page and immediately take screenshot."""
|
| 296 |
if not self.page:
|
| 297 |
logger.error("Portal: No page available for click")
|
| 298 |
return
|
| 299 |
|
| 300 |
try:
|
| 301 |
logger.info(f"Portal: Clicking at coordinates ({x}, {y})")
|
| 302 |
+
|
| 303 |
+
# First try clicking on main page
|
| 304 |
await self.page.mouse.click(x, y)
|
| 305 |
+
|
| 306 |
+
# Wait a short moment for the page to react
|
| 307 |
+
await asyncio.sleep(0.5)
|
| 308 |
+
|
| 309 |
+
# Check if there's an iframe at that location (CAPTCHA is often in iframe)
|
| 310 |
+
iframe_clicked = await self._try_click_iframe(x, y)
|
| 311 |
+
|
| 312 |
+
if iframe_clicked:
|
| 313 |
+
logger.info("Portal: Clicked inside iframe")
|
| 314 |
+
|
| 315 |
+
# Wait a bit more for any CAPTCHA processing
|
| 316 |
+
await asyncio.sleep(1)
|
| 317 |
+
|
| 318 |
+
# Take screenshot immediately
|
| 319 |
await self.take_screenshot()
|
| 320 |
+
logger.info("Portal: Click completed, screenshot taken")
|
| 321 |
+
|
| 322 |
except Exception as e:
|
| 323 |
logger.error(f"Portal click error: {e}")
|
| 324 |
+
# Still try to take screenshot on error
|
| 325 |
+
try:
|
| 326 |
+
await self.take_screenshot()
|
| 327 |
+
except:
|
| 328 |
+
pass
|
| 329 |
+
|
| 330 |
+
async def _try_click_iframe(self, x: float, y: float) -> bool:
|
| 331 |
+
"""Try to click inside iframes at the given coordinates."""
|
| 332 |
+
try:
|
| 333 |
+
# Get all iframes
|
| 334 |
+
iframes = await self.page.query_selector_all('iframe')
|
| 335 |
+
|
| 336 |
+
for iframe in iframes:
|
| 337 |
+
try:
|
| 338 |
+
# Check if iframe is visible and contains the coordinates
|
| 339 |
+
box = await iframe.bounding_box()
|
| 340 |
+
if box and box['x'] <= x <= box['x'] + box['width'] and box['y'] <= y <= box['y'] + box['height']:
|
| 341 |
+
# Click inside the iframe
|
| 342 |
+
frame = await iframe.content_frame()
|
| 343 |
+
if frame:
|
| 344 |
+
# Calculate relative coordinates
|
| 345 |
+
rel_x = x - box['x']
|
| 346 |
+
rel_y = y - box['y']
|
| 347 |
+
await frame.mouse.click(rel_x, rel_y)
|
| 348 |
+
return True
|
| 349 |
+
except:
|
| 350 |
+
continue
|
| 351 |
+
|
| 352 |
+
return False
|
| 353 |
+
except Exception as e:
|
| 354 |
+
logger.error(f"Iframe click error: {e}")
|
| 355 |
+
return False
|
| 356 |
|
| 357 |
async def close(self):
|
| 358 |
"""Close the browser."""
|