feat: implement client.call() method for raw Fragment API requests; add example and unit tests

This commit is contained in:
bohd4nx
2026-03-21 16:45:52 +02:00
parent c7b6c6c933
commit 25d70a77ed
5 changed files with 154 additions and 12 deletions
+41 -2
View File
@@ -1,5 +1,7 @@
import json
from typing import cast
from typing import Any, cast
import httpx
from pyfragment.methods.giveaway_premium import giveaway_premium
from pyfragment.methods.giveaway_stars import giveaway_stars
@@ -14,8 +16,15 @@ from pyfragment.types import (
StarsResult,
WalletInfo,
)
from pyfragment.types.constants import DEFAULT_TIMEOUT, REQUIRED_COOKIE_KEYS, SUPPORTED_WALLET_VERSIONS, WalletVersion
from pyfragment.types.constants import (
DEFAULT_TIMEOUT,
FRAGMENT_BASE_URL,
REQUIRED_COOKIE_KEYS,
SUPPORTED_WALLET_VERSIONS,
WalletVersion,
)
from pyfragment.types.results import PremiumGiveawayResult, StarsGiveawayResult
from pyfragment.utils.http import fragment_request, get_fragment_hash, make_headers
from pyfragment.utils.wallet import get_wallet_info
@@ -187,3 +196,33 @@ class FragmentClient:
``winners``, and ``amount``.
"""
return await giveaway_premium(self, channel, winners, months)
async def call(
self, method: str, data: dict[str, Any] | None = None, *, page_url: str = FRAGMENT_BASE_URL
) -> dict[str, Any]:
"""Send a raw request to the Fragment API.
Useful for accessing undocumented or future Fragment API methods
without waiting for a library update.
Args:
method: Fragment API method name, e.g. ``"searchPremiumGiftRecipient"``.
data: Additional form-data fields to include in the request body.
page_url: Fragment page URL used to derive the API hash and headers.
Defaults to ``FRAGMENT_BASE_URL`` (``"https://fragment.com"``).
Returns:
Raw parsed JSON response as a dict.
Example::
result = await client.call(
"searchPremiumGiftRecipient",
{"query": "@username", "months": 3},
page_url="https://fragment.com/premium/gift",
)
"""
headers = make_headers(page_url)
async with httpx.AsyncClient(cookies=self.cookies, timeout=self.timeout) as session:
fragment_hash = await get_fragment_hash(self.cookies, headers, page_url, self.timeout)
return await fragment_request(session, fragment_hash, headers, {"method": method, **(data or {})})
+7 -6
View File
@@ -20,11 +20,12 @@ DEFAULT_TIMEOUT: float = 30.0
REQUIRED_COOKIE_KEYS: tuple[str, ...] = ("stel_ssid", "stel_dt", "stel_token", "stel_ton_token")
# Fragment page URLs
STARS_PAGE: str = "https://fragment.com/stars/buy"
STARS_GIVEAWAY_PAGE: str = "https://fragment.com/stars/giveaway"
PREMIUM_PAGE: str = "https://fragment.com/premium/gift"
PREMIUM_GIVEAWAY_PAGE: str = "https://fragment.com/premium/giveaway"
TON_PAGE: str = "https://fragment.com/ads/topup"
FRAGMENT_BASE_URL: str = "https://fragment.com"
STARS_PAGE: str = f"{FRAGMENT_BASE_URL}/stars/buy"
STARS_GIVEAWAY_PAGE: str = f"{FRAGMENT_BASE_URL}/stars/giveaway"
PREMIUM_PAGE: str = f"{FRAGMENT_BASE_URL}/premium/gift"
PREMIUM_GIVEAWAY_PAGE: str = f"{FRAGMENT_BASE_URL}/premium/giveaway"
TON_PAGE: str = f"{FRAGMENT_BASE_URL}/ads/topup"
# Tonkeeper device fingerprint — serialized once, reused in every tx_data payload.
DEVICE: str = json.dumps(
@@ -47,7 +48,7 @@ BASE_HEADERS: dict[str, str] = {
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-language": "en-US,en;q=0.9,uk;q=0.8,ru;q=0.7",
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"origin": "https://fragment.com",
"origin": FRAGMENT_BASE_URL,
"priority": "u=1, i",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
+4 -4
View File
@@ -4,10 +4,10 @@ from typing import Any
import httpx
from pyfragment.types import FragmentPageError, ParseError, VerificationError
from pyfragment.types.constants import BASE_HEADERS, DEFAULT_TIMEOUT
from pyfragment.types.constants import BASE_HEADERS, DEFAULT_TIMEOUT, FRAGMENT_BASE_URL
def make_headers(page_url: str) -> dict[str, str]:
def make_headers(page_url: str = FRAGMENT_BASE_URL) -> dict[str, str]:
return {**BASE_HEADERS, "referer": page_url, "x-aj-referer": page_url}
@@ -44,7 +44,7 @@ async def get_fragment_hash(
page_headers.update(
{
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"referer": "https://fragment.com/",
"referer": f"{FRAGMENT_BASE_URL}/",
"sec-fetch-dest": "document",
"sec-fetch-mode": "navigate",
"upgrade-insecure-requests": "1",
@@ -105,7 +105,7 @@ async def fragment_request(
Parsed API response as a dict.
"""
resp = await session.post(
f"https://fragment.com/api?hash={fragment_hash}",
f"{FRAGMENT_BASE_URL}/api?hash={fragment_hash}",
headers=headers,
data=data,
)