Skip to content
Open
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
83 changes: 83 additions & 0 deletions user_scanner/user_scan/gaming/stackb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import html
import json
import re
from urllib.parse import quote

from user_scanner.core.helpers import get_random_user_agent
from user_scanner.core.orchestrator import generic_validate
from user_scanner.core.result import Result


def validate_stackb(user: str) -> Result:
user = user.lower()
profile_url = f"https://stackb.net/@{user}"
url = f"https://stackb.net/@{quote(user, safe='')}"

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

def process(response):
response_text = response.text

if response.status_code == 404 and (
"Страница не найдена" in response_text
or re.search(r">\s*404\s*<", response_text)
):
return Result.available()

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

profile = {}
for json_match in re.finditer(r'<script type="application/ld\+json">(.*?)</script>', response_text, re.DOTALL):
try:
data = json.loads(html.unescape(json_match.group(1)))
except json.JSONDecodeError:
continue

if data.get("@type") == "ProfilePage":
entity = data.get("mainEntity")
if isinstance(entity, dict):
profile = entity
break

og_type_match = re.search(r'<meta [^>]*property=["\']og:type["\'][^>]*content=["\']([^"\']*)', response_text, re.IGNORECASE)
og_type = html.unescape(og_type_match.group(1)).strip() if og_type_match else None

canonical_match = re.search(r'<link [^>]*rel=["\']canonical["\'][^>]*href=["\']([^"\']*)', response_text, re.IGNORECASE)
canonical_url = html.unescape(canonical_match.group(1)).strip() if canonical_match else None

if (
og_type == "profile"
and canonical_url == profile_url
and profile.get("identifier") == f"@{user}"
):
extra = {}
description_match = re.search(r'<meta [^>]*property=["\']og:description["\'][^>]*content=["\']([^"\']*)', response_text, re.IGNORECASE)
stats_text = html.unescape(description_match.group(1)).strip() if description_match else None

if name := profile.get("name"): extra["display_name"] = name
if description := profile.get("description"): extra["bio"] = description
if image := profile.get("image"): extra["avatar"] = image

if stats_text:
if rank_match := re.search(r"Ранг:\s*([^\.]+)", stats_text):
extra["rank"] = rank_match.group(1).strip()
if followers_match := re.search(r"Подписчики:\s*(\d+)", stats_text):
extra["followers"] = int(followers_match.group(1))

extra["profile_url"] = profile_url
return Result.taken(extra=extra)

return Result.error("Unexpected response body")

return generic_validate(
url,
process,
headers=headers,
show_url=url,
follow_redirects=True,
)
86 changes: 86 additions & 0 deletions user_scanner/user_scan/other/pedsovet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import html
import re

from user_scanner.core.helpers import get_random_user_agent
from user_scanner.core.orchestrator import generic_validate
from user_scanner.core.result import Result


def validate_pedsovet(user: str) -> Result:
url = f"https://pedsovet.su/index/8-0-{user}"

if not re.match(r"^[a-z0-9_@]+$", user, re.IGNORECASE):
return Result.error(
"Usernames can only contain letters, numbers, underscores and at signs",
url=url,
)

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

def process(response):
response_text = response.text

if "Пользователь не найден" in response_text:
return Result.available()

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

login_match = re.search(r"<p>Логин:\s*([^<]+)</p>", response_text, re.IGNORECASE)
found_login = html.unescape(login_match.group(1)).strip() if login_match else None

if (
found_login
and found_login.lower() == user.lower()
and "Ссылка на профиль:" in response_text
):
extra = {"login": found_login}

if id_match := re.search(r"/index/8-(\d+)", response_text):
extra["id"] = id_match.group(1)
extra["profile_url"] = f"https://pedsovet.su/index/8-{id_match.group(1)}"

for label, key in (
("Группа пользователей", "group"),
("Регистрация", "registered"),
):
if field_match := re.search(rf"<p>{re.escape(label)}:\s*(.*?)</p>", response_text, re.IGNORECASE | re.DOTALL):
value = re.sub(r"<[^>]+>", "", field_match.group(1))
extra[key] = html.unescape(value).strip()

if last_login_match := re.search(r"Последний вход\s*([^<]+)", response_text, re.IGNORECASE):
extra["last_login"] = html.unescape(last_login_match.group(1)).strip()

if avatar_match := re.search(
r'<div class="user">\s*<center><img\s+src="([^"]+)"', response_text, re.IGNORECASE
):
avatar = html.unescape(avatar_match.group(1)).strip()
if avatar.startswith("//"):
avatar = "https:" + avatar
elif avatar.startswith("/"):
avatar = "https://pedsovet.su" + avatar
extra["avatar"] = avatar

if about_match := re.search(
r'<div class="osebe">О себе</div>.*?<p class="clr">(.*?)</p>', response_text, re.IGNORECASE | re.DOTALL
):
about = re.sub(r"<[^>]+>", "", about_match.group(1))
about = html.unescape(about).strip()
if about != "Пользователь пока ничего не сообщил о себе.":
extra["about"] = about

return Result.taken(extra=extra)

return Result.error("Unexpected response body")

return generic_validate(
url,
process,
headers=headers,
show_url=url,
follow_redirects=True,
)
65 changes: 65 additions & 0 deletions user_scanner/user_scan/shopping/yaga_co_za.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import html
import json
import re
from urllib.parse import quote

from user_scanner.core.helpers import get_random_user_agent
from user_scanner.core.orchestrator import generic_validate
from user_scanner.core.result import Result


def validate_yaga_co_za(user: str) -> Result:
user = user.lower()
url = f"https://www.yaga.co.za/{quote(user, safe='')}"

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

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

match = re.search(
r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>',
response.text,
re.DOTALL,
)
if not match:
return Result.error("Could not find Next.js data")

try:
data = json.loads(html.unescape(match.group(1)))
except json.JSONDecodeError:
return Result.error("Could not parse Next.js data")

page_props = data.get("props", {}).get("pageProps", {})
shop = page_props.get("initialShop")

if shop is None:
return Result.available()

if shop.get("activeSlug") != user:
return Result.error("Unexpected shop slug")

extra = {}
owner = shop.get("owner") or {}
if shop_id := shop.get("id"): extra["id"] = shop_id
if name := shop.get("name"): extra["name"] = name
if description := shop.get("description"): extra["description"] = description
if first_name := owner.get("firstName"): extra["owner_first_name"] = first_name
if last_name := owner.get("lastName"): extra["owner_last_name"] = last_name

return Result.taken(extra=extra)

return generic_validate(
url,
process,
headers=headers,
show_url=url,
follow_redirects=True,
)
65 changes: 65 additions & 0 deletions user_scanner/user_scan/shopping/yaga_ee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import html
import json
import re
from urllib.parse import quote

from user_scanner.core.helpers import get_random_user_agent
from user_scanner.core.orchestrator import generic_validate
from user_scanner.core.result import Result


def validate_yaga_ee(user: str) -> Result:
user = user.lower()
url = f"https://www.yaga.ee/{quote(user, safe='')}"

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

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

match = re.search(
r'<script id="__NEXT_DATA__" type="application/json">(.*?)</script>',
response.text,
re.DOTALL,
)
if not match:
return Result.error("Could not find Next.js data")

try:
data = json.loads(html.unescape(match.group(1)))
except json.JSONDecodeError:
return Result.error("Could not parse Next.js data")

page_props = data.get("props", {}).get("pageProps", {})
shop = page_props.get("initialShop")

if shop is None:
return Result.available()

if shop.get("activeSlug") != user:
return Result.error("Unexpected shop slug")

extra = {}
owner = shop.get("owner") or {}
if shop_id := shop.get("id"): extra["id"] = shop_id
if name := shop.get("name"): extra["name"] = name
if description := shop.get("description"): extra["description"] = description
if first_name := owner.get("firstName"): extra["owner_first_name"] = first_name
if last_name := owner.get("lastName"): extra["owner_last_name"] = last_name

return Result.taken(extra=extra)

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