mirror of
https://github.com/bohd4nx/FragmentAPI.git
synced 2026-07-25 06:14:29 +00:00
feat: Add gift and number search functionality to FragmentClient
- Implemented `search_gifts` method for searching gifts in the Fragment marketplace. - Implemented `search_numbers` method for searching anonymous Telegram numbers. - Added new example scripts for searching gifts, numbers, and usernames. - Updated HTML parsing logic to handle gift items and number listings. - Added unit tests for the new search methods and updated existing tests for usernames and numbers. - Removed obsolete test file for auction searches.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from pyfragment.utils.decoder import clean_decode
|
||||
from pyfragment.utils.html import parse_auction_rows, parse_login_code
|
||||
from pyfragment.utils.html import parse_auction_rows, parse_gift_items, parse_login_code
|
||||
from pyfragment.utils.http import (
|
||||
execute_transaction_request,
|
||||
fragment_request,
|
||||
@@ -12,6 +12,7 @@ from pyfragment.utils.wallet import get_account_info, process_transaction
|
||||
__all__ = [
|
||||
"clean_decode",
|
||||
"parse_auction_rows",
|
||||
"parse_gift_items",
|
||||
"parse_login_code",
|
||||
"execute_transaction_request",
|
||||
"fragment_request",
|
||||
|
||||
@@ -16,6 +16,15 @@ DATETIME_SHORT_RE = re.compile(r'<time[^>]+datetime="([^"]+)"[^>]*data-relative=
|
||||
# Matches numeric-only values (plain integers, formatted prices like "150,492", phone numbers like "+888 0088 8888")
|
||||
NUMERIC_RE = re.compile(r"^\+?[\d,. ]+$")
|
||||
|
||||
# Gift grid item parsing
|
||||
GRID_ITEM_RE = re.compile(r'<a\b[^>]*class="[^"]*tm-grid-item[^"]*"[^>]*>(.*?)</a>', re.DOTALL)
|
||||
GRID_HREF_RE = re.compile(r'href="(/gift/([^?"]+))')
|
||||
GRID_NAME_RE = re.compile(r'class="item-name">([^<]+)<')
|
||||
GRID_NUM_RE = re.compile(r'class="item-num">[^#]*#(\w+)<')
|
||||
GRID_PRICE_RE = re.compile(r'class="[^"]*tm-grid-item-value[^"]*icon-ton[^"]*"[^>]*>\s*([0-9][^<]*?)\s*<')
|
||||
GRID_STATUS_RE = re.compile(r'class="[^"]*tm-grid-item-status[^"]*"[^>]*>\s*([^<]+?)\s*<')
|
||||
GRID_DATETIME_RE = re.compile(r'<time[^>]+datetime="([^"]+)"')
|
||||
|
||||
|
||||
def parse_login_code(html: str) -> tuple[str | None, int]:
|
||||
"""Extract the pending login code and active session count from a Fragment numbers page HTML snippet.
|
||||
@@ -45,7 +54,7 @@ def parse_auction_rows(html: str) -> list[dict[str, Any]]:
|
||||
- ``status`` — human-readable Fragment label (e.g. ``"On auction"``, ``"For sale"``).
|
||||
- ``price`` — price in TON formatted to two decimal places (e.g. ``"7.00"``),
|
||||
or ``None`` if not listed.
|
||||
- ``ends_at`` — ISO 8601 datetime string of auction end, or ``None``.
|
||||
- ``date`` — ISO 8601 datetime string: auction end date, sale date, or listing date, or ``None``.
|
||||
|
||||
Returns:
|
||||
List of item dicts, one per table row.
|
||||
@@ -81,9 +90,9 @@ def parse_auction_rows(html: str) -> list[dict[str, Any]]:
|
||||
except ValueError:
|
||||
price = raw_price
|
||||
|
||||
# Auction end datetime (ISO 8601)
|
||||
# Datetime (ISO 8601) — auction end, sale date, or listing date.
|
||||
time_m = DATETIME_RE.search(row) or DATETIME_SHORT_RE.search(row)
|
||||
ends_at: str | None = time_m.group(1) if time_m else None
|
||||
date: str | None = time_m.group(1) if time_m else None
|
||||
|
||||
items.append(
|
||||
{
|
||||
@@ -91,7 +100,62 @@ def parse_auction_rows(html: str) -> list[dict[str, Any]]:
|
||||
"name": name,
|
||||
"status": status,
|
||||
"price": price,
|
||||
"ends_at": ends_at,
|
||||
"date": date,
|
||||
}
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def parse_gift_items(html: str) -> tuple[list[dict[str, Any]], int | None]:
|
||||
"""Parse Fragment gifts grid HTML into structured item dicts.
|
||||
|
||||
Extracts each ``<a class="tm-grid-item">`` block and returns a list of dicts
|
||||
with the following keys:
|
||||
|
||||
- ``slug`` — URL path segment (e.g. ``"gift/plushpepe-1821"``).
|
||||
- ``name`` — display name with number (e.g. ``"Plush Pepe #1821"``).
|
||||
- ``status`` — human-readable Fragment label (e.g. ``"Sold"``, ``"For sale"``).
|
||||
- ``price`` — price in TON formatted to two decimal places, or ``None``.
|
||||
- ``date`` — ISO 8601 datetime of the sale/listing, or ``None``.
|
||||
|
||||
Returns:
|
||||
Tuple of ``(items, next_offset)`` where ``next_offset`` is an integer
|
||||
page offset from ``data-next-offset``, or ``None`` on the last page.
|
||||
"""
|
||||
items: list[dict[str, Any]] = []
|
||||
for item_match in GRID_ITEM_RE.finditer(html):
|
||||
block = item_match.group(0)
|
||||
|
||||
href_m = GRID_HREF_RE.search(block)
|
||||
if not href_m:
|
||||
continue
|
||||
slug = href_m.group(1).lstrip("/") # e.g. "gift/plushpepe-1821"
|
||||
|
||||
name_m = GRID_NAME_RE.search(block)
|
||||
num_m = GRID_NUM_RE.search(block)
|
||||
item_name = name_m.group(1).strip() if name_m else slug
|
||||
item_num = f" #{num_m.group(1)}" if num_m else ""
|
||||
name = f"{item_name}{item_num}"
|
||||
|
||||
status_m = GRID_STATUS_RE.search(block)
|
||||
status: str | None = status_m.group(1).strip() if status_m else None
|
||||
|
||||
price_m = GRID_PRICE_RE.search(block)
|
||||
price: str | None = None
|
||||
if price_m:
|
||||
raw_price = price_m.group(1).strip().replace(",", "")
|
||||
try:
|
||||
price = f"{float(raw_price):.2f}"
|
||||
except ValueError:
|
||||
price = raw_price
|
||||
|
||||
time_m = GRID_DATETIME_RE.search(block)
|
||||
date: str | None = time_m.group(1) if time_m else None
|
||||
|
||||
items.append({"slug": slug, "name": name, "status": status, "price": price, "date": date})
|
||||
|
||||
# Pagination offset from data-next-offset attribute
|
||||
next_offset_m = re.search(r'data-next-offset="(\d+)"', html)
|
||||
next_offset = int(next_offset_m.group(1)) if next_offset_m else None
|
||||
|
||||
return items, next_offset
|
||||
|
||||
Reference in New Issue
Block a user