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

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions user_scanner/user_scan/creative/artstation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import urllib.parse
from user_scanner.core.impersonate import impersonate_validate
from user_scanner.core.orchestrator import Result

def validate_artstation(user: str) -> Result:
encoded_user = urllib.parse.quote(user)
url = f"https://www.artstation.com/users/{encoded_user}.json"
show_url = f"https://www.artstation.com/{encoded_user}"

def process(response) -> Result:
if response.status_code == 404:
return Result.available(url=show_url)

if response.status_code == 200:
try:
data = response.json()
if isinstance(data, dict) and (data.get("username") or data.get("full_name") or data.get("id")):
extra: dict[str, str] = {}
media: dict[str, str] = {}

if data.get("full_name"):
extra["name"] = str(data["full_name"])
if data.get("headline"):
extra["headline"] = str(data["headline"])
if data.get("city"):
extra["city"] = str(data["city"])
if data.get("country"):
extra["country"] = str(data["country"])
if data.get("followers_count") is not None:
extra["followers"] = str(data["followers_count"])
if data.get("large_avatar_url"):
media["avatar"] = str(data["large_avatar_url"])

return Result.taken(url=show_url, extra=extra, media=media)
except Exception:
pass
return Result.error("ArtStation 200 response missing valid user payload")

return Result.error(f"Unexpected status code: {response.status_code}")

return impersonate_validate(
url,
process,
warmup_url="https://www.artstation.com/",
impersonate="chrome120",
show_url=show_url,
)
67 changes: 67 additions & 0 deletions user_scanner/user_scan/dev/gnome.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import urllib.parse
from user_scanner.core.helpers import get_random_user_agent
from user_scanner.core.orchestrator import Result, generic_validate

def validate_gnome(user: str) -> Result:
encoded_user = urllib.parse.quote(user)
url = f"https://discourse.gnome.org/u/{encoded_user}.json"
show_url = f"https://discourse.gnome.org/u/{encoded_user}"

headers = {
"User-Agent": get_random_user_agent(),
"Accept": "application/json",
}

def process(response) -> Result:
if response.status_code == 404:
try:
data = response.json()
if (
data.get("error_type") == "not_found"
or any("not be found" in str(err).lower() for err in data.get("errors", []))
):
return Result.available(url=show_url)
except Exception:
pass
return Result.error("Discourse 404 response missing expected error payload")

if response.status_code == 200:
try:
data = response.json()
user_obj = data.get("user")
if user_obj and isinstance(user_obj, dict) and user_obj.get("username"):
extra: dict[str, str] = {}
media: dict[str, str] = {}

if user_obj.get("name"):
extra["name"] = str(user_obj["name"])
if user_obj.get("title"):
extra["title"] = str(user_obj["title"])
if user_obj.get("location"):
extra["location"] = str(user_obj["location"])
if user_obj.get("website_name") or user_obj.get("website"):
extra["website"] = str(user_obj.get("website") or user_obj.get("website_name"))
if user_obj.get("trust_level") is not None:
extra["trust_level"] = str(user_obj["trust_level"])
if user_obj.get("badge_count") is not None:
extra["badges"] = str(user_obj["badge_count"])
if user_obj.get("created_at"):
extra["joined"] = str(user_obj["created_at"])
if user_obj.get("bio_raw") or user_obj.get("bio_cooked"):
extra["bio"] = str(user_obj.get("bio_raw") or user_obj.get("bio_cooked"))

avatar_template = user_obj.get("avatar_template")
if avatar_template:
avatar_url = avatar_template.replace("{size}", "240")
if avatar_url.startswith("/"):
avatar_url = f"https://discourse.gnome.org{avatar_url}"
media["avatar"] = avatar_url

return Result.taken(url=show_url, extra=extra, media=media)
except Exception:
pass
return Result.error("Discourse 200 response missing valid user payload")

return Result.error(f"Unexpected status code: {response.status_code}")

return generic_validate(url, process, headers=headers, show_url=show_url, follow_redirects=True)
49 changes: 49 additions & 0 deletions user_scanner/user_scan/dev/wakatime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import re
import urllib.parse
from user_scanner.core.helpers import get_random_user_agent
from user_scanner.core.orchestrator import Result, generic_validate

def validate_wakatime(user: str) -> Result:
encoded_user = urllib.parse.quote(user)
url = f"https://wakatime.com/@{encoded_user}"
show_url = f"https://wakatime.com/@{encoded_user}"

headers = {
"User-Agent": get_random_user_agent(),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
}

def process(response) -> Result:
if response.status_code == 404:
if "Page Not Found" in response.text or "404: Not Found" in response.text or "404" in response.text:
return Result.available(url=show_url)
return Result.error("404 received without expected not-found markers")

if response.status_code == 200:
if f"@{user.lower()}" in response.text.lower() or "wakatime.com/@" in response.text:
extra: dict[str, str] = {}
media: dict[str, str] = {}

title_match = re.search(r'<title>(.+?)</title>', response.text)
if title_match:
title_clean = title_match.group(1).replace("- WakaTime", "").strip()
extra["title"] = title_clean

desc_match = re.search(r'<meta property="og:description" content="([^"]+)"', response.text)
if desc_match:
extra["bio"] = desc_match.group(1).strip()

img_match = re.search(r'<meta property="og:image" content="([^"]+)"', response.text)
if img_match:
media["avatar"] = img_match.group(1).strip()

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

if "Page Not Found" in response.text or "404: Not Found" in response.text:
return Result.available(url=show_url)

return Result.error("Unable to verify WakaTime profile structure")

return Result.error(f"Unexpected status code: {response.status_code}")

return generic_validate(url, process, headers=headers, show_url=show_url, follow_redirects=True)
62 changes: 62 additions & 0 deletions user_scanner/user_scan/gaming/geocaching.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import re
import urllib.parse
from user_scanner.core.helpers import get_random_user_agent
from user_scanner.core.orchestrator import Result, generic_validate

def validate_geocaching(user: str) -> Result:
encoded_user = urllib.parse.quote(user)
url = f"https://www.geocaching.com/p/default.aspx?u={encoded_user}"
show_url = f"https://www.geocaching.com/p/default.aspx?u={encoded_user}"

headers = {
"User-Agent": get_random_user_agent(),
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
}

def process(response) -> Result:
if response.status_code == 404:
if "Error 404" in response.text or "404" in response.text or "DNF" in response.text:
return Result.available(url=show_url)
return Result.error("404 received without expected not-found markers")

if response.status_code == 200:
user_lower = user.lower()
text_lower = response.text.lower()

if (
f"default.aspx?u={user_lower}" in text_lower
or "hax-profile" in response.text
or "lblmembername" in text_lower
or "window.publicprofile" in text_lower
):
extra: dict[str, str] = {}
media: dict[str, str] = {}

name_match = re.search(r'<span id="[^"]*lblMemberName">([^<]+)</span>', response.text)
if name_match:
extra["name"] = name_match.group(1).strip()

avatar_match = re.search(r'id="[^"]*uxProfilePhoto"[^>]+src="([^"]+)"', response.text)
if avatar_match:
avatar_url = avatar_match.group(1).strip()
if avatar_url.startswith("/"):
avatar_url = f"https://www.geocaching.com{avatar_url}"
media["avatar"] = avatar_url

banner_match = re.search(r'id="[^"]*uxBannerPhoto"[^>]+src="([^"]+)"', response.text)
if banner_match:
banner_url = banner_match.group(1).strip()
if banner_url.startswith("/"):
banner_url = f"https://www.geocaching.com{banner_url}"
media["banner"] = banner_url

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

if "Error 404" in response.text or "DNF" in response.text:
return Result.available(url=show_url)

return Result.error("Unable to verify Geocaching profile structure")

return Result.error(f"Unexpected status code: {response.status_code}")

return generic_validate(url, process, headers=headers, show_url=show_url, follow_redirects=True)
57 changes: 57 additions & 0 deletions user_scanner/user_scan/social/letterboxd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import re
import urllib.parse
from user_scanner.core.impersonate import impersonate_validate
from user_scanner.core.orchestrator import Result

def validate_letterboxd(user: str) -> Result:
encoded_user = urllib.parse.quote(user)
url = f"https://letterboxd.com/{encoded_user}/"
show_url = f"https://letterboxd.com/{encoded_user}/"

def process(response) -> Result:
if response.status_code == 404:
if "Letterboxd - Not Found" in response.text or "Page not found" in response.text or "404" in response.text:
return Result.available(url=show_url)
return Result.error("404 received without expected not-found markers")

if response.status_code == 200:
user_lower = user.lower()
text_lower = response.text.lower()

if (
f"letterboxd.com/{user_lower}/" in text_lower
or f'data-person="{user_lower}"' in text_lower
or 'class="profile' in text_lower
or "person-summary" in text_lower
):
extra: dict[str, str] = {}
media: dict[str, str] = {}

title_match = re.search(r'<meta property="og:title" content="([^"]+)"', response.text)
if title_match:
extra["name"] = title_match.group(1).replace("’s profile", "").replace("'s profile", "").replace("• Letterboxd", "").strip()

desc_match = re.search(r'<meta property="og:description" content="([^"]+)"', response.text)
if desc_match:
extra["bio"] = desc_match.group(1).strip()

avatar_match = re.search(r'<meta property="og:image" content="([^"]+)"', response.text)
if avatar_match:
media["avatar"] = avatar_match.group(1).strip()

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

if "Letterboxd - Not Found" in response.text or "Page not found" in response.text:
return Result.available(url=show_url)

return Result.error("Unable to verify Letterboxd profile content")

return Result.error(f"Unexpected status code: {response.status_code}")

return impersonate_validate(
url,
process,
warmup_url="https://letterboxd.com/",
impersonate="chrome120",
show_url=show_url,
)
Loading