Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions user_scanner/email_scan/adult/motherless.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import httpx
from user_scanner.core.result import Result


async def _check(email: str) -> Result:
show_url = "https://motherless.xxx"
url = "https://motherless.xxx/register/checkemail"

headers = {
"User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36",
"X-Requested-With": "XMLHttpRequest",
"Origin": show_url,
"Referer": show_url + "/register",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
}

try:
async with httpx.AsyncClient(http2=True, timeout=15.0) as client:
response = await client.post(url, data={"email": email}, headers=headers)

if response.status_code == 429:
return Result.error("Rate limited, wait for a few minutes")

if response.status_code != 200:
return Result.error(f"HTTP Error: {response.status_code}")

body = response.text

# The field check reuses "not-available" for both existing accounts
# and rejected input (e.g. "Invalid Host.").
if 'class="not-available"' in body:
if "invalid" in body.lower():
return Result.error("Email address rejected by Motherless")
return Result.taken(url=show_url)

if 'class="available"' in body:
return Result.available(url=show_url)

return Result.error("Unexpected response body, report it via GitHub issues")

except Exception as e:
return Result.error(e)


async def validate_motherless(email: str) -> Result:
return await _check(email)
75 changes: 75 additions & 0 deletions user_scanner/user_scan/adult/motherless.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import re
from user_scanner.core.orchestrator import generic_validate, make_request, Result

CHECK_URL = "https://motherless.xxx/register/checkusername"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Mobile Safari/537.36",
"X-Requested-With": "XMLHttpRequest",
"Origin": "https://motherless.xxx",
"Referer": "https://motherless.xxx/register",
}


def validate_motherless(user):
show_url = f"https://motherless.xxx/m/{user}"

def process(response):
if response.status_code != 200:
return Result.error(f"Unexpected status: {response.status_code}", url=show_url)

body = response.text

# The field check reuses "not-available" for both taken names and
# rejected input (e.g. "Username is invalid.").
if 'class="not-available"' in body:
if "invalid" in body.lower():
return Result.error("Username rejected by Motherless", url=show_url)
return Result.taken(extra=_fetch_profile(show_url), url=show_url)

if 'class="available"' in body:
return Result.available(url=show_url)

return Result.error("Unexpected response body, report it via GitHub issues", url=show_url)

return generic_validate(
CHECK_URL, process, show_url=show_url, method="POST", data={"username": user}, headers=HEADERS
)


def _fetch_profile(profile_url: str) -> dict:
"""The checkusername endpoint only reports availability, so pull the public
member page for metadata. Best-effort: a failed fetch yields no extra."""
try:
response = make_request(profile_url)
if response.status_code != 200:
return {}
return _extract_profile(response.text)
except Exception:
return {}


def _extract_profile(html_text: str) -> dict:
extra = {}

# The public member page renders two field blocks: "profile-stats" rows
# (<span>Label:</span> value) and "profile-member-info" rows
# (<strong>Label</strong> <span>value</span>). Pull every row rather than a
# fixed subset so whatever the member fills in is captured. Zero counts are
# dropped as noise.
for row in re.finditer(r'<div class="profile-stats">\s*<span>([^<]+?):\s*</span>\s*(.*?)\s*</div>', html_text, re.DOTALL):
label = re.sub(r"\s+", " ", row.group(1)).strip()
value = re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", row.group(2))).strip()
if value and value != "0":
extra[label] = value

for row in re.finditer(r'<div class="profile-member-info[^"]*">\s*<strong>([^<]+?)</strong>\s*<span>\s*(.*?)\s*</span>', html_text, re.DOTALL):
label = re.sub(r"\s+", " ", row.group(1)).strip()
value = re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", row.group(2))).strip()
if value:
extra[label] = value

avatar = re.search(r'og:image"\s+content="([^"]+)"', html_text, re.IGNORECASE)
if avatar:
extra["avatar"] = avatar.group(1)

return extra
Loading