Username Scanning Module Function Signature in user-scanner

A username scanning module must expose exactly one validator function named validate_<sitename> that accepts a username string and returns a Result object indicating availability, taken status with metadata, or an error.

The kaifcodec/user-scanner repository enforces a strict contract for all username scanning modules. This guide walks through the expected function signature, return type requirements, and implementation patterns derived directly from the source code.

The Required Function Signature

Every username scanning module must implement a single validator function following this exact pattern:

def validate_<sitename>(user: str) -> Result:
    ...

The naming convention is mandatory: prefix validate_ followed by the lowercase site identifier. For example, validate_github, validate_twitter, or validate_linkedin.

Parameter and Return Specifications

Element Specification
Parameter user Raw username string supplied by the caller
Return type Instance of user_scanner.core.result.Result
Exception handling No exceptions allowed — all errors must return via Result.error()

The Result Object: Three Factory Methods

The Result class in user_scanner/core/result.py provides three static factory methods for constructing return values:

  1. Result.available() — The username does not exist on the platform
  2. Result.taken(extra={...}, media={...}) — The username exists; optional extra dict holds structured metadata (fullname, location, bio), and media dict contains image URLs
  3. Result.error("message") — Any unexpected condition: network failures, rate limits, or parsing errors

Error Handling Rule

Validators must not raise exceptions. The following pattern is forbidden:


# INCORRECT — do not use

raise ValueError("unexpected response")
return Result.error("unexpected response")  # CORRECT

Implementation Patterns from core/orchestrator.py

Most modules leverage helper utilities to maintain concise, DRY code. The two primary helpers are defined in user_scanner/core/orchestrator.py:

  • generic_validate — Standard HTTP validation with configurable request parameters
  • impersonate_validate — For bot-protected sites requiring browser impersonation

Minimal Validator Example (GitHub)

from user_scanner.core.orchestrator import generic_validate
from user_scanner.core.result import Result
import re

def validate_github(user: str) -> Result:
    """Check whether a GitHub username exists."""
    url = f"https://github.com/{user}"
    show_url = f"https://github.com/{user}"

    def process(resp):
        # Username available: 404 or explicit "Not Found"

        if resp.status_code == 404 or "Not Found" in resp.text:
            return Result.available()

        # Username taken: profile header present

        if resp.status_code == 200 and 'class="vcard-fullname"' in resp.text:
            extra = {}
            m = re.search(r'class="vcard-fullname">([^<]+)</', resp.text)
            if m:
                extra["fullname"] = m.group(1).strip()
            return Result.taken(
                extra=extra,
                media={"avatar": f"https://github.com/{user}.png"}
            )

        # Unexpected state

        return Result.error(f"Unexpected status {resp.status_code}")

    return generic_validate(url, process, show_url=show_url, follow_redirects=True)

Bot-Protected Site Pattern (Snapchat)

For platforms protected by Cloudflare or similar services, use impersonate_validate from user_scanner/core/impersonate.py:

from user_scanner.core.impersonate import impersonate_validate
from user_scanner.core.result import Result

def validate_snapchat(user: str) -> Result:
    """Validate Snapchat usernames behind Cloudflare protection."""
    url = f"https://www.snapchat.com/add/{user}"

    def process(resp):
        if resp.status_code == 404 and "Sorry, we couldn't find" in resp.text:
            return Result.available()
        if resp.status_code == 200 and "profile-card" in resp.text:
            return Result.taken()
        return Result.error(f"Unexpected status {resp.status_code}")

    return impersonate_validate(
        url,
        process,
        warmup_url="https://www.snapchat.com/",
        impersonate="chrome",
        show_url=url,
    )

Critical Implementation Requirements

According to the contributing guidelines in CONTRIBUTING.md, every username scanning module must satisfy these five rules:

  1. Exact naming — Function name must be validate_<sitename> with matching lowercase filename
  2. Explicit verification — Never rely solely on HTTP 200; validators must identify unique markers confirming both taken and available states
  3. Metadata extraction — Include rich structured data in Result.taken() when profiles are found
  4. Error containment — Return Result.error() for all failure paths; exceptions break the orchestration
  5. Safe URL construction — Use params arguments rather than f-strings for user-controlled data in request parameters

Key Source Files

Purpose Path
Contribution guidelines — signature definition CONTRIBUTING.md
Result class implementation user_scanner/core/result.py
Validation helpers (generic_validate) user_scanner/core/orchestrator.py
Browser impersonation helper user_scanner/core/impersonate.py
Reference implementation (Reddit) user_scanner/user_scan/social/reddit.py
Async pattern reference (Mastodon email) user_scanner/email_scan/social/mastodon.py

Summary

  • Function signature: def validate_<sitename>(user: str) -> Result
  • Return exclusively via Result.available(), Result.taken(), or Result.error()
  • Never raise exceptions — express all errors through Result.error()
  • Use helpers from core/orchestrator.py and core/impersonate.py for clean implementations
  • Follow naming conventions exactly: filename matches validate_ prefix target

Frequently Asked Questions

What happens if a validator raises an exception instead of returning Result.error()?

The orchestration system in user_scanner/core/orchestrator.py expects all validators to return Result objects. An unhandled exception will propagate uncaught, potentially crashing the scanning batch or leaving the caller without proper status information. Always wrap error conditions in Result.error().

Can I use async/await in a username scanning module?

Yes. The repository includes async patterns as demonstrated in user_scanner/email_scan/social/mastodon.py. For async validators, use async def and ensure the orchestrator calling the function handles await properly. The same Result return type contract applies.

How do I choose between generic_validate and impersonate_validate?

Use generic_validate for standard sites without aggressive bot protection. Use impersonate_validate when the target site employs Cloudflare, DataDome, or similar anti-bot measures that reject programmatic HTTP clients. The impersonate helper uses browser fingerprinting to bypass these protections.

What metadata should I include in Result.taken()?

Include any publicly visible profile information that enriches the output: fullname, location, bio, joined_date in extra, and avatar/profile image URLs in media. The exact fields depend on what the target platform exposes in its HTML or API response.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →