cdp-patches

repository·main·Indexed 41 days ago

https://github.com/kaliiiiiiiiii-vinyzu/cdp-patches

A utility to bypass bot detection by dispatching mouse and keyboard input events at the OS level instead of through the Chrome DevTools Protocol (CDP). It prevents 'Input Domain Leaks' where page coordinates equal screen coordinates. Provides SyncInput and AsyncInput classes for integration with headful browsers, Playwright, Botright, Selenium, and selenium-driverless.

Tokens
5.7K
Snippets
16
Records
21
Agent score
49%

What's inside cdp-patches

  1. Limitations of Input Event methods

    main

    When using Input Event methods to bypass CDP-Leaks, be aware of the following constraints:

    1. Active Tab Only: Because Chrome does not recognize Input Events for specific tabs, these methods can only be applied to the currently active tab.
    2. Process Control: While Chrome tabs have their own Process IDs (PIDs), they cannot be controlled via Input Events as they function as engines.
    3. Keyboard State (Windows): On Windows, manually pressing SHIFT or CAPSLOCK will affect the behavior of input.type(text) commands.
  2. Understand the concept of Input Domain Leaks

    main

    CDP-Leaks in the Input domain occur because Chrome's default behavior for CDP input commands sets page coordinates equal to screen coordinates. In a standard browser window, page coordinates (e.pageX, e.pageY) should not equal screen coordinates (e.screenX, e.screenY) unless the browser is in full-screen mode. This discrepancy can be used to detect automated interactions (bots).

    var is_bot = (e.pageY == e.screenY && e.pageX == e.screenX)
    if (is_bot && 1 >= outerHeight - innerHeight){ // fullscreen
        is_bot = false
    }
  3. How Input Domain Leaks work and why to use CDP-Patches

    main

    CDP (Chrome DevTools Protocol) input commands often cause a detectable leak where page coordinates (e.pageY, e.pageX) equal screen coordinates (e.screenY, e.screenX), unless Chrome is in fullscreen. This behavior can be used to identify bots.

    Additionally, CDP cannot dispatch CoalescedEvents.

    cdp-patches bypasses these leaks by dispatching input events at the OS-level instead of using CDP commands. This makes the interactions appear more like genuine user input.

    Limitations:

    • Headful only: Because it uses OS-level events, it only works with headful browsers.
    • Active tab only: Chrome does not recognize Input Events targeted at specific tabs; therefore, these methods only affect the currently active tab.
    • Manual modifiers: On Windows, manually pressing SHIFT or CAPSLOCK will affect the behavior of input.type(text).
  4. Use AsyncInput with Selenium-Driverless

    main

    For asynchronous input operations, use selenium-driverless and wrap the driver in an AsyncInput object. Note that AsyncInput must be awaited during initialization.

    When using selenium-driverless, you can leverage built-in methods like locator.mid_location() to find the center coordinates of an element before passing them to async_input.click().

    import asyncio
    from selenium_driverless import webdriver
    from cdp_patches.input import AsyncInput
    
    async def main():
        async with webdriver.Chrome(...) as driver:
            async_input = await AsyncInput(browser=driver)
            
            # Example: Click Button
            locator = await driver.find_element(By.XPATH, "//button")
            x, y = await locator.mid_location()
            
            await async_input.click("left", x, y)
    
    asyncio.run(main())
  5. Install cdp-patches

    main

    You can install the core package via PyPI. If you require full linting capabilities (which includes dependencies like playwright, botright, selenium, and selenium_driverless), install the automation_linting extra.

    Note: This package uses OS-level events and can only be used with headful browsers.

    # Standard installation
    pip install cdp-patches
    
    # Installation with automation linting extras
    pip install cdp-patches[automation_linting]
  6. Use SyncInput with Sync Playwright

    main

    To perform input operations (like clicks) in a synchronous Playwright environment, use the SyncInput class. You can initialize it by passing a browser instance or a Playwright Context. This allows you to bypass standard automation signals by interacting with the browser via CDP patches at specific coordinates.

    from playwright.sync_api import sync_playwright
    from cdp_patches.input import SyncInput
    
    with sync_playwright() as playwright:
        browser = playwright.chromium.launch()
        page = browser.new_page()
        # Initialize SyncInput with a browser or context
        sync_input = SyncInput(browser=browser)
    
        # Example: Click at specific coordinates
        sync_input.click("left", x, y)
  7. Use AsyncInput with Async Playwright or Botright

    main

    For asynchronous environments (using playwright.async_api or Botright), use the AsyncInput class. Note that AsyncInput is initialized using await. Like the sync version, it accepts a browser or Context instance.

    import asyncio
    from playwright.async_api import async_playwright
    from cdp_patches.input import AsyncInput
    
    async def main():
        async with async_playwright() as playwright:
            browser = await playwright.chromium.launch()
            page = await browser.new_page()
            # Initialize AsyncInput with await
            async_input = await AsyncInput(browser=browser)
    
            # Example: Click at specific coordinates
            await async_input.click("left", x, y)
    
    asyncio.run(main())
  8. Install cdp-patches via pip

    main

    You can install the core package from PyPI using pip. If you require full automation linting support (which includes dependencies for playwright, botright, selenium, and selenium_driverless), install the automation_linting extra.

    # Standard installation
    pip install cdp-patches
    
    # Installation with full automation linting support
    pip install cdp-patches[automation_linting]
  9. Use SyncInput with Selenium

    main

    To perform input operations (like clicking) synchronously using Selenium, wrap your webdriver.Chrome instance in a SyncInput object from cdp_patches.input.

    To avoid detection and reduce noise, it is recommended to configure ChromeOptions to disable logging and automation switches.

    from selenium import webdriver
    from cdp_patches.input import SyncInput
    
    options = webdriver.ChromeOptions()
    # disable logs & automation
    options.add_experimental_option("excludeSwitches", ["enable-logging", "enable-automation"])
    options.add_experimental_option("useAutomationExtension", False)
    options.add_argument("--log-level=3")
    
    with webdriver.Chrome(options=options) as driver:
        sync_input = SyncInput(browser=driver)
        # Use sync_input to perform actions
        sync_input.click("left", x, y)
  10. Calculate element center coordinates in Selenium

    main

    Since standard Selenium WebElement objects do not provide a direct 'center' coordinate, you can use this helper function to calculate the x, y midpoint of an element based on its location and size properties.

    from selenium.webdriver.remote.webelement import WebElement
    
    def get_locator_pos(locator: WebElement):
        location = locator.location
        size = locator.size
        assert location, size
    
        x, y, width, height = location.get("x"), location.get("y"), size.get("width"), size.get("height")
        assert x and y and width and height
    
        x, y = x + width // 2, y + height // 2
        return x, y
  11. Calculate element center coordinates for input

    main

    To use click methods effectively, you often need to convert a Playwright Locator into specific x, y coordinates. You can do this by retrieving the bounding_box() of the locator and calculating the center point.

    # Sync version
    def get_locator_pos(locator: Locator):
        bounding_box = locator.bounding_box()
        assert bounding_box
        x, y, width, height = bounding_box.get("x"), bounding_box.get("y"), bounding_box.get("width"), bounding_box.get("height")
        assert x and y and width and height
        return x + width // 2, y + height // 2
    
    # Async version
    async def get_locator_pos(locator: Locator):
        bounding_box = await locator.bounding_box()
        assert bounding_box
        x, y, width, height = bounding_box.get("x"), bounding_box.get("y"), bounding_box.get("width"), bounding_box.get("height")
        assert x and y and width and height
        return x + width // 2, y + height // 2
  12. Initialize AsyncInput

    main

    To perform asynchronous browser interactions, initialize the AsyncInput class. You can connect to an existing Chrome instance using its Process ID (pid) or by passing an existing asynchronous browser instance (such as Playwright or Selenium Driverless). If a browser instance is provided, the scale_factor is automatically determined.

    await cdp_patches.input.AsyncInput(
        pid=1234, 
        browser=pw_browser, 
        emulate_behaviour=True
    )