Source code for vlrdevapi.players.profile

"""Player profile functionality."""

from __future__ import annotations

from bs4 import BeautifulSoup

from .models import Profile, SocialLink, Team
from ._parser import _parse_month_year
from ..config import get_config
from ..countries import map_country_code
from ..fetcher import fetch_html
from ..exceptions import NetworkError
from ..utils import extract_text, absolute_url, extract_id_from_url

_config = get_config()


[docs] def profile(player_id: int, timeout: float | None = None) -> Profile | None: """ Get player profile information. Args: player_id: Player ID timeout: Request timeout in seconds Returns: Player profile or None if not found Example: >>> import vlrdevapi as vlr >>> profile = vlr.players.profile(player_id=123) >>> print(f"{profile.handle} from {profile.country}") """ url = f"{_config.vlr_base}/player/{player_id}" effective_timeout = timeout if timeout is not None else _config.default_timeout try: html = fetch_html(url, effective_timeout) except NetworkError: return None soup = BeautifulSoup(html, "lxml") header = soup.select_one(".player-header") handle = extract_text(header.select_one("h1.wf-title")) if header else None real_name = extract_text(header.select_one(".player-real-name")) if header else None avatar_url = None if header: avatar_img = header.select_one(".wf-avatar img") if avatar_img: src_val = avatar_img.get("src") src = src_val if isinstance(src_val, str) else None if src: avatar_url = absolute_url(src) # Parse socials socials: list[SocialLink] = [] if header: for anchor in header.select("a[href]"): href_val = anchor.get("href") href = href_val if isinstance(href_val, str) else None label = extract_text(anchor) if href and label: url_or = absolute_url(href) or href socials.append(SocialLink(label=label, url=url_or)) # Parse country country = None if header: flag = header.select_one(".flag") if flag: classes_val = flag.get("class") classes: list[str] = list(classes_val) if isinstance(classes_val, (list, tuple)) else [] for cls in classes: if cls.startswith("mod-") and cls != "mod-dark": code: str = cls.removeprefix("mod-") country = map_country_code(code) break # Parse current teams current_teams: list[Team] = [] label = None for h2 in soup.select("h2.wf-label.mod-large"): text = extract_text(h2) or "" if "current teams" in text.lower(): label = h2 break if label: card = label.find_next("div", class_="wf-card") if card: for anchor in card.select("a.wf-module-item"): href_val = anchor.get("href") href = href_val.strip("/") if isinstance(href_val, str) else "" team_id = extract_id_from_url(href, "team") if href else None name_el = anchor.select_one("div[style][style*='font-weight']") or anchor team_name = extract_text(name_el).strip() if name_el else None role_el = anchor.select_one("span.wf-tag") role = extract_text(role_el).strip().title() if role_el else "Player" joined_date = None for meta in anchor.select(".ge-text-light"): text = extract_text(meta) if "joined" in text.lower(): joined_date = _parse_month_year(text) break current_teams.append(Team( id=team_id, name=team_name, role=role, joined_date=joined_date, left_date=None, )) # Parse aliases aliases: list[str] | None = None if header: # Look for spans that contain the text "aliases:" for span in header.find_all('span'): span_text = extract_text(span) if span_text and 'aliases:' in span_text.lower(): if ":" in span_text: aliases_part = span_text.split(":")[1].strip() # Split by commas and clean up each alias aliases = [alias.strip().strip('"\'') for alias in aliases_part.split(",")] # Remove empty strings if any aliases = [alias for alias in aliases if alias] break # Parse past teams past_teams: list[Team] = [] label = None for h2 in soup.select("h2.wf-label.mod-large"): text = extract_text(h2) or "" if "past teams" in text.lower(): label = h2 break if label: card = label.find_next("div", class_="wf-card") if card: for anchor in card.select("a.wf-module-item"): href_val = anchor.get("href") href = href_val.strip("/") if isinstance(href_val, str) else "" team_id = extract_id_from_url(href, "team") if href else None name_el = anchor.select_one("div[style][style*='font-weight']") or anchor team_name = extract_text(name_el).strip() if name_el else None role_el = anchor.select_one("span.wf-tag") role = extract_text(role_el).strip().title() if role_el else "Player" joined_date = None left_date = None for meta in anchor.select(".ge-text-light"): text = extract_text(meta) if "-" in text or "–" in text: normalized = text.replace("\u2013", "-").replace("–", "-") parts = [part.strip() for part in normalized.split("-") if part.strip()] if parts: joined_date = _parse_month_year(parts[0]) if len(parts) > 1 and "present" not in parts[1].lower(): left_date = _parse_month_year(parts[1]) break past_teams.append(Team( id=team_id, name=team_name, role=role, joined_date=joined_date, left_date=left_date, )) return Profile( player_id=player_id, handle=handle, real_name=real_name, country=country, avatar_url=avatar_url, aliases=aliases, socials=socials, current_teams=current_teams, past_teams=past_teams, )