Skip to content

CAPTCHA solvers

The live portals are Securimage-CAPTCHA gated. Solving is pluggable: implement the CaptchaSolver ABC, or use one of the bundled solvers. See the CAPTCHA guide.

CaptchaSolver (base class)

CaptchaSolver

Bases: ABC

Base class for solving eCourts Securimage CAPTCHAs.

solve abstractmethod async

solve(image_bytes: bytes) -> str

Given raw CAPTCHA image bytes, return the solved text.

Parameters:

Name Type Description Default
image_bytes bytes

PNG/JPEG bytes of the CAPTCHA image.

required

Returns:

Type Description
str

The CAPTCHA text as a string.

Source code in src/bharat_courts/captcha/base.py
@abstractmethod
async def solve(self, image_bytes: bytes) -> str:
    """Given raw CAPTCHA image bytes, return the solved text.

    Args:
        image_bytes: PNG/JPEG bytes of the CAPTCHA image.

    Returns:
        The CAPTCHA text as a string.
    """
    ...

OCRCaptchaSolver

OCRCaptchaSolver

OCRCaptchaSolver(
    preprocess: bool = False, threshold: int = 128
)

Bases: CaptchaSolver

CAPTCHA solver using ddddocr for Securimage CAPTCHAs.

Requires the ocr extra: pip install bharat-courts[ocr]

Uses ddddocr (deep-learning CAPTCHA recognition) which works well with eCourts' Securimage CAPTCHAs. Optionally applies Pillow preprocessing to improve accuracy on noisy images.

Initialize the OCR solver.

Parameters:

Name Type Description Default
preprocess bool

Apply Pillow preprocessing before OCR.

False
threshold int

Binarization threshold (0-255) for preprocessing.

128
Source code in src/bharat_courts/captcha/ocr.py
def __init__(self, preprocess: bool = False, threshold: int = 128):
    """Initialize the OCR solver.

    Args:
        preprocess: Apply Pillow preprocessing before OCR.
        threshold: Binarization threshold (0-255) for preprocessing.
    """
    if not HAS_DDDDOCR:
        raise ImportError(
            "ddddocr is required for OCR CAPTCHA solving. "
            "Install with: pip install bharat-courts[ocr]"
        )
    self._ocr = ddddocr.DdddOcr(show_ad=False)
    self._preprocess = preprocess and HAS_PILLOW
    self._threshold = threshold

solve async

solve(image_bytes: bytes) -> str

Recognize CAPTCHA text from image bytes.

Returns an empty string if the OCR output isn't a valid 6-char alphanumeric — eCourts portals reject anything else with a length-validation error, so we short-circuit and signal upstream callers to skip this attempt.

Source code in src/bharat_courts/captcha/ocr.py
async def solve(self, image_bytes: bytes) -> str:
    """Recognize CAPTCHA text from image bytes.

    Returns an empty string if the OCR output isn't a valid 6-char
    alphanumeric — eCourts portals reject anything else with a
    length-validation error, so we short-circuit and signal upstream
    callers to skip this attempt.
    """
    if self._preprocess:
        image_bytes = self._preprocess_image(image_bytes)
    result = self._ocr.classification(image_bytes)
    result = result.strip() if isinstance(result, str) else ""
    if len(result) != _EXPECTED_LENGTH or not result.isalnum():
        logger.warning(
            "OCR CAPTCHA decoded %d chars (expected %d): %r",
            len(result),
            _EXPECTED_LENGTH,
            result,
        )
        return ""
    return result

ONNXCaptchaSolver

ONNXCaptchaSolver

ONNXCaptchaSolver(model_path: str | Path | None = None)

Bases: CaptchaSolver

CAPTCHA solver using ONNX Runtime for Securimage CAPTCHAs.

Requires the onnx extra: pip install bharat-courts[onnx]

Uses a pre-trained ONNX model (captchabreaker from HuggingFace). The model is lazily downloaded on first use to ~/.cache/bharat-courts/.

Parameters:

Name Type Description Default
model_path str | Path | None

Optional path to a custom ONNX model file. If not provided, downloads the default captchabreaker model.

None
Source code in src/bharat_courts/captcha/onnx.py
def __init__(self, model_path: str | Path | None = None):
    if not HAS_ONNX:
        raise ImportError(
            "onnxruntime is required for ONNX CAPTCHA solving. "
            "Install with: pip install bharat-courts[onnx]"
        )
    if not HAS_PILLOW:
        raise ImportError(
            "Pillow is required for ONNX CAPTCHA solving. "
            "Install with: pip install bharat-courts[onnx]"
        )
    self._model_path = Path(model_path) if model_path else None
    self._session: ort.InferenceSession | None = None

    # Fail fast: download model now so auth errors surface immediately
    self._ensure_model()

solve async

solve(image_bytes: bytes) -> str

Recognize CAPTCHA text from image bytes using ONNX model.

Returns the recognized text if it's exactly 6 characters, otherwise returns empty string to trigger a client retry.

Source code in src/bharat_courts/captcha/onnx.py
async def solve(self, image_bytes: bytes) -> str:
    """Recognize CAPTCHA text from image bytes using ONNX model.

    Returns the recognized text if it's exactly 6 characters,
    otherwise returns empty string to trigger a client retry.
    """
    session = self._get_session()
    input_tensor = self._preprocess(image_bytes)

    input_name = session.get_inputs()[0].name
    outputs = session.run(None, {input_name: input_tensor})

    # outputs[0] shape: (batch, timesteps, num_classes)
    logits = outputs[0][0].tolist()
    text = _ctc_greedy_decode(logits)

    if len(text) != _EXPECTED_LENGTH:
        logger.warning(
            "ONNX CAPTCHA decoded %d chars (expected %d): %r",
            len(text),
            _EXPECTED_LENGTH,
            text,
        )
        return ""

    return text

ManualCaptchaSolver

ManualCaptchaSolver

ManualCaptchaSolver(
    callback: (
        Callable[[bytes], str | Awaitable[str]] | None
    ) = None,
)

Bases: CaptchaSolver

Solver that asks a human to read the CAPTCHA.

By default, saves the image to a temp file and prompts on stdin. Pass a custom callback for GUI or web-based workflows.

Source code in src/bharat_courts/captcha/manual.py
def __init__(self, callback: Callable[[bytes], str | Awaitable[str]] | None = None):
    self._callback = callback