How to Implement the Standard Module Interface for a New Website in Holehe
A Holehe module is a single async function placed in holehe/modules/ that receives an email, an httpx client, and an output list, then appends a standardized result dictionary.
Holehe is an OSINT tool that discovers where an email address has been registered across hundreds of websites. Adding support for new services requires understanding its standard module interface—a strict contract between your code and the core runner. This guide walks through implementing that interface based on the actual source code in megadose/holehe.
How Holehe Discovers and Runs Modules
The core runner in holehe/core.py dynamically loads every module without hardcoded lists. Two functions make this possible:
import_submodules(lines 37–48) traversesholehe.modulesusingpkgutil.walk_packagesget_functions(lines 50–63) extracts callable coroutines from imported modules
Modules run inside a Trio nursery via launch_module (lines 66–78), which handles exceptions by converting them to rate-limit entries.
Required Elements of the Standard Module Interface
File Location and Structure
Place your module in an appropriate subpackage under holehe/modules/. Common categories include social_media, shopping, programming, and products. The file name becomes the function name.
holehe/
└── modules/
└── social_media/
└── example.py # Defines async def example(...)
Mandatory Imports
Start with these imports to access core constants and utilities:
from holehe.core import *
from holehe.localuseragent import * # Only if you need random User-Agents
Function Signature
Define exactly one coroutine with this signature (core.py expects this pattern):
async def <sitename>(email, client, out):
| Parameter | Type | Purpose |
|---|---|---|
email |
str |
Target email address to check |
client |
httpx.AsyncClient |
Preconfigured async HTTP client from the core runner |
out |
list |
Mutable list where you must append the result dictionary |
Result Dictionary Schema
Append a dictionary with these exact keys (twitter.py lines 17–23):
| Key | Type | Description |
|---|---|---|
name |
str |
Site identifier (usually matches function name) |
domain |
str |
Domain of the service (e.g., "twitter.com") |
method |
str |
HTTP usage pattern: "register", "login", "recovery", etc. |
frequent_rate_limit |
bool |
Hint that this service aggressively rate-limits |
rateLimit |
bool |
True if current request was blocked/errored |
exists |
bool |
True if email is registered on this service |
emailrecovery |
str ` |
None` |
phoneNumber |
str ` |
None` |
others |
any |
Additional metadata from the response |
Complete Implementation Steps
- Create the file at
holehe/modules/<category>/<sitename>.py - Copy a working template from Twitter or Instagram
- Define
async def <sitename>(email, client, out)with exact parameter names - Implement HTTP logic using the supplied
client(GET/POST as needed) - Parse the response to determine registration status
- Append to
outwith all required dictionary keys - Handle exceptions by setting
rateLimit=Trueandexists=False
Minimal Working Example
# holehe/modules/social_media/example.py
from holehe.core import *
from holehe.localuseragent import *
async def example(email, client, out):
name = "example"
domain = "example.com"
method = "register"
frequent_rate_limit = False
try:
resp = await client.get(
"https://api.example.com/check_email",
params={"email": email},
headers={"User-Agent": await get_random_user_agent()},
)
data = resp.json()
# Adjust condition to match service's response format
exists = data.get("email_registered", False)
out.append({
"name": name,
"domain": domain,
"method": method,
"frequent_rate_limit": frequent_rate_limit,
"rateLimit": False,
"exists": exists,
"emailrecovery": None,
"phoneNumber": None,
"others": None,
})
except Exception:
# Any failure becomes a rate-limit entry
out.append({
"name": name,
"domain": domain,
"method": method,
"frequent_rate_limit": frequent_rate_limit,
"rateLimit": True,
"exists": False,
"emailrecovery": None,
"phoneNumber": None,
"others": None,
})
Advanced Pattern: Handling CSRF Tokens and Sessions
For services requiring session state, examine the Instagram module (instagram.py lines 11–74):
async def instagram(email, client, out):
# ... setup constants ...
headers = {
"User-Agent": random.choice(ua),
"Accept": "*/*",
# ... additional headers
}
# First request: fetch CSRF token
try:
await client.get("https://www.instagram.com/", headers=headers)
# Extract csrftoken from cookies...
# Second request: actual check with token
data = {
"email": email,
# ... other fields
}
response = await client.post(
"https://www.instagram.com/api/v1/web/accounts/web_create_ajax/attempt/",
data=data,
headers=headers,
)
# Parse response branches for exists/rateLimit cases
except Exception:
# Return rate-limit structure
pass
Key Implementation Rules
- Never print or write files — output only via
out.append() - Never modify global state — the function must be reentrant
- Always catch exceptions — uncaught errors break the Trio nursery
- Use
client, don't create your own — the core manages connection pooling and limits - Match the function name to the file name —
get_functionsrelies on this convention
Core Source Files Reference
| File | Purpose in Module Interface |
|---|---|
holehe/core.py |
import_submodules (L37–48), get_functions (L50–63), launch_module (L66–78) |
holehe/modules/__init__.py |
Package marker enabling pkgutil.walk_packages traversal |
holehe/localuseragent.py |
get_random_user_agent() for request headers |
holehe/modules/social_media/twitter.py |
Reference implementation showing minimal valid module |
holehe/modules/social_media/instagram.py |
Complex example with CSRF tokens and POST handling |
Summary
- The standard module interface requires one async function with signature
(email, client, out) - Place files in
holehe/modules/<category>/<name>.pyand name the function<name> - Must append a dictionary with 9 specific keys:
name,domain,method,frequent_rate_limit,rateLimit,exists,emailrecovery,phoneNumber,others - Handle all exceptions by setting
rateLimit=Trueandexists=False - No side effects: use only the
outlist for output, never print or write files - The core automatically discovers new modules via
import_submodulesinholehe/core.py
Frequently Asked Questions
What happens if my module raises an uncaught exception?
The launch_module wrapper (core.py L66–78) catches all exceptions and converts them to a rate-limit result. Your module won't crash the runner, but you lose diagnostic information. Always wrap HTTP logic in try/except blocks to distinguish true rate limits from parsing errors.
Can I use synchronous libraries like requests in my module?
No. The core runner uses Trio for concurrency and passes an httpx.AsyncClient. Blocking calls would freeze the entire nursery. Use await client.get()/await client.post() exclusively. For CPU-bound parsing, consider trio.to_thread.run_sync.
How do I test my module without running the full Holehe suite?
Import your function directly and mock the dependencies:
import httpx
import trio
async def test_example():
out = []
async with httpx.AsyncClient() as client:
await your_module.example("test@example.com", client, out)
print(out[0])
trio.run(test_example)
This validates your interface compliance before submitting.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →