Damru Python API Reference
Damru exposes four primary Python entry points — AsyncDamru, Damru, DamruPool, and DamruPoolSync — each of which returns a standard Playwright BrowserContext with all 8 stealth layers already applied, so existing Playwright code requires no changes beyond the context manager.
This page covers every class, parameter, method, and helper exported by the damru package. For installation and environment setup, see Installation. For CLI commands, see CLI Reference.
Requirements
| Requirement | Detail |
|---|---|
| Host | Ubuntu 24.04 LTS (native) or Ubuntu WSL2 with Damru’s bundled kernel |
| Android backend | Rooted Redroid in Docker (MuMu Player is experimental and not recommended) |
| Python | 3.10 or higher |
| Playwright | >=1.40, <1.60 |
| Other dependencies | requests, pysocks |
| Optional | curl_cffi (highly recommended for TLS/JA3 impersonation via damru.bypass) |
Core Classes
AsyncDamru — Recommended
AsyncDamru is the primary asynchronous context manager. It orchestrates the 8 stealth layers — Android props, GPU binary patching, syscall interception, CDP overrides, worker interception, Chrome flag patching, OS-level evasions, and display spoofing — then returns a Playwright BrowserContext.
Import and Basic Usage
from damru import AsyncDamru
async with AsyncDamru(device="pixel_8_pro", proxy="socks5://...") as context:
page = await context.new_page()
await page.goto("https://creepjs.com")
__init__ Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
device | str | "random" | Device profile slug, marketing name, or model (e.g., "samsung_galaxy_s24_ultra", "Pixel 8 Pro", "pixel_8_pro"). "random" picks from the premium pool. |
profile_tier | str | "premium" (from config) | Random pool when device is unset or "random". Options: premium, premium_verified, premium_new, medium, experimental, extended, all. Explicit named devices ignore this filter. |
serial | str | None | ADB serial of the target worker. If None, auto-detects virtual devices: TCP Redroid endpoints first, then emulator-*. Physical USB serials are refused by default. |
proxy | str | None | SOCKS5 or HTTP proxy URL for GeoIP resolution and Python-side proxy checks. Used to derive Android timezone and locale when http_proxy is not set. |
http_proxy | str | None | Android system HTTP proxy as host:port or http://user:pass@host:port. When present, Damru resolves GeoIP through this path because it is the route Chrome actually uses. |
timezone | str | None | Force a specific IANA timezone (e.g., "America/New_York"). Leave unset to auto-derive from proxy exit. |
locale | str | None | Force a specific BCP-47 locale (e.g., "en-US", "pt-BR"). Leave unset to auto-derive from proxy country. |
webrtc_block | bool | True | Drops all outgoing UDP WebRTC traffic at the kernel level via iptables. Set to False to enable proxy IP spoofing for WebRTC instead of blocking. |
debug | bool | False | Enables verbose console logging for OS patches and CDP overrides. |
Proxy and timezone notes:
- Leave
timezoneandlocaleunset — Damru derives them from the active proxy exit at session start and rechecks through Chrome after CDP connects. - If both
proxyandhttp_proxyare provided, GeoIP is resolved throughhttp_proxy(the Chrome route). - Rotating residential proxies are rechecked after CDP connects so the browser timezone follows the actual exit IP.
- Auto locale selection covers standard ISO country codes and CLDR exceptional territory codes. Countries with multiple common phone locales may rotate between valid variants (e.g.,
en-PH/fil-PH,en-IN/hi-IN).
Methods and Properties
| Name | Type | Description |
|---|---|---|
await new_page() | method | Creates a new stealth-hardened Playwright Page. Damru normalizes the new Chrome tab before returning, so you can navigate immediately. |
browser | property | Access the underlying Playwright Browser object. |
pages | property | List of currently open Playwright Page objects. |
profile | property | Returns the active DamruProfile (device specs, User-Agent, client hints, etc.). |
Damru — Synchronous
Damru is the blocking wrapper around AsyncDamru, suitable for traditional scripts and multi-threaded environments. Methods and parameters are identical to AsyncDamru but synchronous — no await.
Usage
from damru import Damru
with Damru(device="pixel_8_pro") as context:
page = context.new_page()
page.goto("https://bot.sannysoft.com/")
page.wait_for_timeout(5000)
page.screenshot(path="sannysoft.png")
print("Passed Sannysoft!")
Exception Handling
DamruError
All Damru-specific failures — ADB connection errors, rooting failures, binary patching errors — raise DamruError.
from damru import Damru, DamruError
try:
with Damru() as browser:
page = browser.new_page()
page.goto("https://example.com")
except DamruError as e:
print(f"Damru failed to initialize: {e}")
Pool Management
DamruPool — Async
DamruPool orchestrates parallel automation across multiple Redroid Docker containers, managing container lifecycles, port forwarding, and proxy rotation automatically.
Usage
from damru import DamruPool
async with DamruPool(mode="auto", max_devices=2) as pool:
async with pool.session() as context:
page = await context.new_page()
await page.goto("https://example.com")
print(await page.title())
__init__ Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
max_devices | int | config NUM_DEVICES (default 10) | Number of concurrent Redroid containers to maintain. Use 0 in "manual" mode to use every detected ADB device. |
mode | str | config MODE | Deployment mode: "auto" manages Redroid via Docker (production path), "manual" uses existing ADB devices, "mumu" is experimental. |
proxy | str | config PROXY | Single proxy shared by all pool sessions. |
proxies | list[str] | config PROXIES | Per-worker proxy list. Pool rotates through these by slot index. |
http_proxy | str | config HTTP_PROXY | Android system HTTP proxy when it differs from the browser proxy. GeoIP is resolved through this path when set. |
http_proxies | list[str] | config HTTP_PROXIES | Per-worker Android HTTP proxy list. |
device | str | config DEVICE | Fixed device profile for all sessions, or None/"random" for per-session random profiles. |
profile_tier | str | "premium" (from config) | Random pool for sessions without a fixed device. |
timezone | str | config TIMEZONE | Force timezone instead of deriving from proxy. |
locale | str | config LOCALE | Force locale instead of deriving from proxy country. |
chrome_apk | str | config CHROME_APK | Chrome APK directory for raw/unbaked Redroid. Leave unset to auto-discover from the APK bundle and allow Chrome rotation. |
wsl_distro | str | config WSL_DISTRO | Windows only: WSL distro that owns Docker/Redroid. |
pool.session() — Context Manager
Provides an AsyncDamru context (a Playwright BrowserContext with all stealth layers applied) from an available container in the pool.
async with pool.session() as context:
page = await context.new_page()
await page.goto("https://example.com")
pool.open_url() — One-Shot Navigation
Convenience wrapper for single stealth navigations without managing the session context manually:
title = await pool.open_url("127.0.0.1:5600", "https://example.com", proxy="socks5://user:pass@host:1080")
print(f"Title: {title}")
DamruPoolSync — Synchronous
The synchronous counterpart to DamruPool, suitable for multi-threading frameworks such as concurrent.futures.
Usage
from damru import DamruPoolSync
proxies = [
"socks5://proxy1:1080",
"socks5://proxy2:1080",
"socks5://proxy3:1080",
]
with DamruPoolSync(mode="auto", max_devices=3, proxies=proxies) as pool:
with pool.session() as context:
page = context.new_page()
page.goto("https://example.com")
print(page.title())
Parameters are identical to DamruPool.
Device Management
Profile Database
Damru ships 155 real Android device profiles in damru/devices.py. For the full generated list including GPU families, chipsets, screen variants, and Android SDK versions, see the Device Profiles reference (upstream: docs/DEVICE_PROFILES.md).
Profile pool sizes:
| Pool | Profiles | How to activate |
|---|---|---|
| Premium (default) | 100 | device="random" or profile_tier="premium" |
| Medium | 38 | profile_tier="medium" |
| Experimental | 17 | profile_tier="experimental" |
| All | 155 | profile_tier="all" |
Explicit named devices (device="Nokia C32", device="nokia_c32") are never filtered by the tier setting.
list_device_names()
Return all device profile names in the database.
from damru import list_device_names
names = list_device_names()
print(names[:5]) # e.g., ['Samsung Galaxy S24 Ultra', 'Samsung Galaxy S24', ...]
get_device(name_or_slug)
Return the AndroidDevice specification for a single profile by marketing name, model, or slug.
from damru import get_device
pixel = get_device("pixel_8_pro")
print(f"Cores: {pixel.hardware_concurrency}, RAM: {pixel.device_memory}GB")
print(f"GPU: {pixel.webgl_renderer}")
print(f"Screen: {pixel.screen_width}x{pixel.screen_height} @{pixel.dpi}dpi")
get_random_device()
Return a randomly selected AndroidDevice profile, optionally filtered by Android version or profile tier.
from damru import get_random_device
# Random premium profile
phone = get_random_device()
# Random Android 13 profile from any tier
phone_13 = get_random_device(android_version="13")
# Random medium-tier profile
medium = get_random_device(profile_tier="medium")
get_devices_by_tier(tier)
Return all profiles in a given tier.
from damru import get_devices_by_tier
all_profiles = get_devices_by_tier("all")
medium_profiles = get_devices_by_tier("medium")
Valid tier names: "premium", "premium_verified", "premium_new", "medium", "experimental", "extended", "all".
Async Profile Helper
force_device_profile(serial, device, **kwargs)
Apply a device profile to an already-running rooted worker without opening a full Playwright session. Equivalent to python -m damru force-profile from the CLI.
from damru import force_device_profile
result = await force_device_profile(
"127.0.0.1:5600",
"pixel_8_pro",
timezone="America/New_York",
locale="en-US",
)
print(result.description)
Parameters:
| Parameter | Type | Description |
|---|---|---|
serial | str | ADB serial of the target worker |
device | str | Profile slug, name, or model |
timezone | str | IANA timezone override |
locale | str | BCP-47 locale override |
configure_chrome | bool | Set to False to skip Chrome command-line/preferences (--no-chrome) |
configure_gpu | bool | Set to False to skip native Vulkan GPU layer |
configure_memory | bool | Set to False to skip native memory preload |
clear_proxy | bool | Clear the worker’s current Android HTTP proxy |
browser_package | str | Target package. Use "org.chromium.webview_shell" for WebView Shell harnesses |
force_device_profile applies Android props, release string, timezone, locale, display size/density, CPU core spoofing, native Vulkan GPU spoofing, memory preload, and Chromium command-line/preferences by default. CDP runtime overrides (active-page specific) remain part of the runtime harness.
Advanced Configuration
Tune Damru’s global behavior via damru.config before initialization:
import damru.config
# WSL2 settings (Windows only)
damru.config.WSL_DISTRO = "Ubuntu-22.04"
damru.config.WSL_USERNAME = "my-wsl-user"
damru.config.WSL_PASSWORD = "" # compatibility only
# Chrome APK path for raw/unbaked Redroid
damru.config.CHROME_APK = "/home/damru/chrome-apks/148.0.7778.178"
For first-run setup, prefer the CLI commands:
python -m damru setup
python -m damru install-image
python -m damru check-env
python -m damru check preflight
Advanced Modules
damru.bypass — Edge-Layer TLS Impersonation
Defeat CDN TLS fingerprinting and WAFs that inspect JA3/TLS handshake characteristics by replaying document navigations through curl_cffi browser impersonation.
from damru import AsyncDamru
from damru.bypass import arm_bypass_async
async with AsyncDamru() as browser:
page = await browser.new_page()
# Persistent TLS interception for all document navigations on this domain
await arm_bypass_async(page, domain="target-site.com")
# Document navigations now use randomized browser TLS hashes
await page.goto("https://target-site.com")
curl_cffi is a separate install (pip install curl_cffi) and is not required for normal Damru sessions that rely on the 8 native stealth layers.
Cookbook — Common Patterns
Multi-Page Scraping in One Session
All pages opened within a single AsyncDamru context share the same spoofed device identity and IP:
import asyncio
from damru import AsyncDamru
async def main():
async with AsyncDamru(device="random") as browser:
page1 = await browser.new_page()
page2 = await browser.new_page()
await asyncio.gather(
page1.goto("https://google.com"),
page2.goto("https://bing.com"),
)
asyncio.run(main())
Authenticated Proxy with Android HTTP Bridge
Android system proxy supports HTTP CONNECT. If your provider gives SOCKS5 for Python-side checks but Android Chrome must use a local HTTP bridge, pass both:
from damru import AsyncDamru
async with AsyncDamru(
device="pixel_8_pro",
proxy="socks5://user:pass@proxy.example:824",
http_proxy="172.17.0.1:18888",
) as browser:
page = await browser.new_page()
await page.goto("https://demo.fingerprint.com/playground")
Damru resolves timezone and locale through http_proxy because that is the path Chrome actually uses. Do not set timezone or locale manually unless they match the current proxy exit.
Multi-Container Pool with Proxy Rotation
import asyncio
from damru import DamruPool
async def main():
proxy = "socks5://user:pass@residential-proxy.com:1080"
async with DamruPool(max_devices=2) as pool:
async with pool.session() as ctx:
page = await ctx.new_page()
await page.goto("https://example.com")
print(await page.title())
# One-shot convenience wrapper
title = await pool.open_url("127.0.0.1:5600", "https://shopee.com.br/", proxy=proxy)
print(f"Title: {title}")
asyncio.run(main())
Synchronous Pool with Per-Worker Proxies
from damru import DamruPoolSync
proxies = [
"socks5://proxy1:1080",
"socks5://proxy2:1080",
"socks5://proxy3:1080",
]
with DamruPoolSync(mode="auto", max_devices=3, proxies=proxies) as pool:
for i in range(3):
with pool.session() as context:
page = context.new_page()
page.goto("https://example.com/scrape-target")
print(f"Worker {i}: {page.title()}")
Enable Debug Logging
async with AsyncDamru(debug=True) as browser:
page = await browser.new_page()
await page.goto("https://example.com")
Debug mode logs exact root commands (resetprop, iptables, wm size/wm density), binary patch steps, and CDP override calls.
Error Handling
from damru import AsyncDamru, DamruError
async def main():
try:
async with AsyncDamru(device="pixel_8_pro") as browser:
page = await browser.new_page()
await page.goto("https://example.com")
except DamruError as e:
print(f"Damru error: {e}")
asyncio.run(main())
WebView Shell Support
For WebView harness validation, apply a profile with --browser-package org.chromium.webview_shell via the CLI or pass browser_package="org.chromium.webview_shell" to force_device_profile. Damru writes /data/local/tmp/webview-command-line and patches app_webview/pref_store instead of Chrome’s paths. Chrome CDP remains the primary automation API; WebView Shell support is for WebView-specific debugging.
python -m damru force-profile --serial 127.0.0.1:5600 --device pixel_8_pro \
--browser-package org.chromium.webview_shell
adb -s 127.0.0.1:5600 shell am start \
-n org.chromium.webview_shell/.WebViewBrowserActivity \
-a android.intent.action.VIEW -d https://example.com
For a custom Android app that embeds the system WebView, apply Android-level profile hardening with --no-chrome and launch the app separately:
python -m damru force-profile --serial 127.0.0.1:5600 --device pixel_8_pro \
--no-chrome --proxy socks5://user:pass@host:port
adb -s 127.0.0.1:5600 shell am start \
-n com.example.webview/.MainActivity \
-a android.intent.action.VIEW -d https://example.com
Experimental Flags
Set these environment variables before starting a session to enable optional experimental layers:
| Flag | Description |
|---|---|
DAMRU_EXPERIMENTAL_SENSOR_HAL=1 | Native Android sensors (gyro/accel/mag) at 50–100 Hz via AIDL HAL. Requires baking the sensor HAL into the image once. Best stealth. |
DAMRU_EXPERIMENTAL_HIDL_SENSOR_HAL=1 | Same but via older HIDL interface. Fallback if AIDL is broken on your image. |
DAMRU_ENABLE_NATIVE_SENSOR_HAL=1 | Activates the sensor HAL at container start. Required when SENSOR_HAL=1. |
DAMRU_EXPERIMENTAL_CDP_SENSORS=1 | Browser-level sensor faking via CDP. No image rebuild needed. Generic readings only. |
DAMRU_EXPERIMENTAL_BATTERY_DUMPSYS=1 | Battery spoof via Android dumpsys. |
DAMRU_EXPERIMENTAL_BATTERY_SPOOF=1 | Battery spoof via settings put global. |
DAMRU_EXPERIMENTAL_WORKER_CORE_CDP=1 | Enable CDP worker-target auto-attach for multi-threaded JS. May destabilize Chrome. |
DAMRU_EXPERIMENTAL_RAW_WORKER_CDP=1 | Skip reattach flow; keep raw CDP attached. Auto-set by --mode cdp. |
DAMRU_PROFILE_TIER=all | Equivalent to profile_tier="all" — includes medium and experimental device profiles in random selection. |
# Linux/WSL
export DAMRU_EXPERIMENTAL_SENSOR_HAL=1
export DAMRU_ENABLE_NATIVE_SENSOR_HAL=1
python your_script.py
Related
- Getting Started — quickstart and first session
- Installation — full host setup
- CLI Reference — all
python -m damrusubcommands