Skip to content
Open
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
50 changes: 39 additions & 11 deletions user_scanner/user_scan/adult/bdsmsingles.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,37 @@
import hashlib
import re
from user_scanner.core.orchestrator import generic_validate, Result

from user_scanner.core.orchestrator import Result, generic_validate, make_request

def validate_bdsmsingles(user):

def validate_bdsmsingles(user: str) -> Result:
url = f"https://www.bdsmsingles.com/members/{user}/"
show_url = url

def process(response):
if response.status_code == 202:
challenge = re.search(
r'prefix="([0-9a-f]+)",win="(\d+)",diff=(\d+)', response.text
)
if not challenge or challenge.group(3) != "4":
return Result.error("Unsupported browser challenge.", url=show_url)

prefix, window, _ = challenge.groups()
nonce = next(
(
value
for value in range(1 << 20)
if hashlib.sha256(f"{prefix}{value}".encode())
.hexdigest()
.startswith("0000")
),
None,
)
if nonce is None:
return Result.error("Could not solve browser challenge.", url=show_url)

response = make_request(url, cookies={"_jscp": f"{window}.{nonce}"})

if response.status_code == 200 and "<title>Profile" in response.text:
extra = {}
media = {}
Expand All @@ -18,7 +43,8 @@ def process(response):

# Info / Orientation
info_match = re.search(
r"</h2>\s*<p>\s*(.*?)\s*</p>", response.text, re.DOTALL)
r"</h2>\s*<p>\s*(.*?)\s*</p>", response.text, re.DOTALL
)
if info_match:
# Clean up multiple whitespaces
cleaned_info = re.sub(r"\s+", " ", info_match.group(1)).strip()
Expand All @@ -28,16 +54,17 @@ def process(response):

# Avatar (if not default nophoto svg)
avatar_match = re.search(
r'src="(https://media\.bdsmsingles\.com/images/user_photo/[^"]+)"', response.text)
r'src="(https://media\.bdsmsingles\.com/images/user_photo/[^"]+)"',
response.text,
)
if avatar_match and "nophoto" not in avatar_match.group(1):
media["image"] = avatar_match.group(1)

# Table fields extraction
def extract_table_field(field_name):
# Search for field label followed by its value div
pattern = rf"{field_name}</div>\s*<div style=\"[^\"]*width:\s*60%;[^\"]*\">\s*(.*?)\s*</div>"
match = re.search(pattern, response.text,
re.DOTALL | re.IGNORECASE)
match = re.search(pattern, response.text, re.DOTALL | re.IGNORECASE)
return match.group(1).strip() if match else None

first_name = extract_table_field("First name")
Expand All @@ -62,10 +89,11 @@ def extract_table_field(field_name):

return Result.taken(extra=extra, media=media, url=show_url)

# No confirmed not-found marker exists: /members/ sits behind an active
# JS challenge, and the site name matches its own home page, its login
# redirect and the challenge page alike — keying a miss on any of them
# reports every handle as free.
return Result.error("Unexpected response body, report it via GitHub issues.", url=show_url)
if response.status_code == 302 and response.headers.get("location") == "/":
return Result.available(url=show_url)

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

return generic_validate(url, process, show_url=show_url)
Loading