Source code for ogu_api.resources.users

from __future__ import annotations

import tls_client.response

from ..errors import OGUParseError
from ..models import UserProfile
from ._base import ResourceBase

__all__ = ['UsersResource']


[docs] class UsersResource(ResourceBase): """Profile lookup by username or numeric user id. The high-level ``get_by_*`` methods return a :class:`~ogu_api.UserProfile` dataclass with parsed integer fields. The ``get_page_*`` variants return the raw ``tls_client.response.Response`` so callers can feed the HTML to the static ``extract_*`` parsers themselves. """
[docs] async def get_by_username(self, username: str) -> UserProfile: """Fetch a user's profile by username. Hits the slug-rewritten URL ``/{username}``. Args: username: Forum username (case-insensitive). Returns: Parsed :class:`~ogu_api.UserProfile`. Raises: OGUParseError: The page rendered, but no ``user_id`` could be extracted (usually a banned, deleted, or non-existent user). OGUNotFoundError: Profile page returned 404. Example: >>> user = await client.users.get_by_username('forgivenforget') >>> print(user.user_id, user.credits, user.reputation) """ response = await self._http.get(f'/{username}') return self._parse_profile(response.text)
[docs] async def get_by_id(self, user_id: str | int) -> UserProfile: """Fetch a user's profile by numeric user id. Hits ``/member.php?action=profile&uid={user_id}``. Args: user_id: Numeric user id (the value shown by :meth:`extract_user_id`). Returns: Parsed :class:`~ogu_api.UserProfile`. Raises: OGUParseError: The page rendered, but no ``user_id`` could be extracted. """ response = await self._http.get(f'/member.php?action=profile&uid={user_id}') return self._parse_profile(response.text)
[docs] async def get_page_by_username(self, username: str) -> tls_client.response.Response: """Fetch the raw profile-page response by username (no parsing).""" return await self._http.get(f'/{username}')
[docs] async def get_page_by_id(self, user_id: str | int) -> tls_client.response.Response: """Fetch the raw profile-page response by uid (no parsing).""" return await self._http.get(f'/member.php?action=profile&uid={user_id}')
@classmethod def _parse_profile(cls, page_html: str) -> UserProfile: raw_user_id = cls.extract_user_id(page_html) if raw_user_id is None: raise OGUParseError('Could not extract user_id from profile page') return UserProfile( user_id = _to_int(raw_user_id), username = cls.extract_username(page_html), reputation = _to_int(cls.extract_reputation(page_html)), vouches = _to_int(cls.extract_vouches(page_html)), credits = _to_int(cls.extract_credits(page_html)), )
[docs] @staticmethod def extract_user_id(page_html: str) -> str | None: """Pull the numeric user id from a profile page (text inside ``#profileLink``).""" soup = ResourceBase._soup(page_html) profile_link = soup.find('a', id = 'profileLink') return profile_link.get_text(strip = True) if profile_link else None
[docs] @staticmethod def extract_username(page_html: str) -> str | None: """Pull the canonical-cased username from ``<span class="usernamefont">``.""" soup = ResourceBase._soup(page_html) username_span = soup.find('span', attrs = {'class': 'usernamefont'}) return username_span.get_text(strip = True) if username_span else None
[docs] @staticmethod def extract_reputation(page_html: str) -> str: """Pull the positive-reputation count off a profile page (``'0'`` if absent).""" soup = ResourceBase._soup(page_html) rep_link = soup.find( 'a', class_ = 'reputation_positive', href = lambda link: link and 'reputation.php' in link, ) if not rep_link: return '0' strong = rep_link.find('strong', {'class': 'reputation_positive'}) return strong.get_text(strip = True) if strong else '0'
[docs] @staticmethod def extract_vouches(page_html: str) -> str: """Pull the vouch count from the profile sidebar (``'0'`` if absent).""" soup = ResourceBase._soup(page_html) for link in soup.find_all('a', href = lambda href: href and 'vouches.php' in href): strong = link.find('strong', {'class': 'reputation_positive'}) if strong: return strong.get_text(strip = True) return '0'
[docs] @staticmethod def extract_credits(page_html: str) -> str: """Pull the credits balance off a profile page (``'0'`` if absent).""" soup = ResourceBase._soup(page_html) for link in soup.find_all('a', href = lambda href: href and 'credits.php' in href): text = link.get_text(strip = True).replace(',', '') if text.isdigit(): return text return '0'
def _to_int(value: str | None) -> int: if value is None: return 0 cleaned = value.replace(',', '').strip() if cleaned.startswith(('-', '+')): sign, body = cleaned[0], cleaned[1:] else: sign, body = '', cleaned if not body.isdigit(): return 0 return int(sign + body)