diff --git a/pyfragment/client.py b/pyfragment/client.py index 7d7963a..36b7f84 100644 --- a/pyfragment/client.py +++ b/pyfragment/client.py @@ -4,6 +4,7 @@ import json from typing import Any, cast from pyfragment.core.constants import ( + BASE_HEADERS, DEFAULT_TIMEOUT, FRAGMENT_BASE_URL, MNEMONIC_WORD_COUNTS_VALID, @@ -40,6 +41,7 @@ class FragmentClient: cookies: Fragment session cookies as a dict or JSON string. wallet_version: Wallet contract version — ``"V4R2"`` or ``"V5R1"`` (default). timeout: HTTP request timeout in seconds. Defaults to ``30.0``. + headers: Custom HTTP request headers. If omitted, :data:`BASE_HEADERS` is used. Raises: ConfigurationError: If ``seed``, ``api_key``, or ``wallet_version`` are missing or invalid. @@ -104,6 +106,7 @@ class FragmentClient: cookies: dict[str, Any] | str, wallet_version: str = "V5R1", timeout: float = DEFAULT_TIMEOUT, + headers: dict[str, str] | None = None, ) -> None: self._validate_required(seed, api_key) parsed_cookies = self._parse_cookies(cookies) @@ -115,6 +118,7 @@ class FragmentClient: self.cookies: dict[str, Any] = parsed_cookies self.wallet_version: WalletVersion = version self.timeout: float = timeout + self.headers: dict[str, str] = headers if headers is not None else BASE_HEADERS self.marketplace = MarketplaceService(self) self.purchases = PurchasesService(self) self.giveaways = GiveawaysService(self) @@ -391,4 +395,4 @@ class FragmentClient: page_url="https://fragment.com/premium/gift", ) """ - return await raw_api_call(self.cookies, self.timeout, method, data, page_url) + return await raw_api_call(self.cookies, self.timeout, method, data, page_url, self.headers) diff --git a/pyfragment/core/constants/__init__.py b/pyfragment/core/constants/__init__.py index a49d4a3..4e5be9a 100644 --- a/pyfragment/core/constants/__init__.py +++ b/pyfragment/core/constants/__init__.py @@ -14,6 +14,8 @@ from pyfragment.core.constants.limits import ( STARS_PURCHASE_MIN, STARS_WINNERS_MAX, STARS_WINNERS_MIN, + TON_TOPUP_MAX, + TON_TOPUP_MIN, TONAPI_KEY_MIN_LENGTH, ) from pyfragment.core.constants.ton import DEVICE_INFO, USDT_TON_MASTER_ADDRESS @@ -56,5 +58,7 @@ __all__ = [ "STARS_WINNERS_MAX", "STARS_WINNERS_MIN", "TONAPI_KEY_MIN_LENGTH", + "TON_TOPUP_MAX", + "TON_TOPUP_MIN", "USDT_TON_MASTER_ADDRESS", ] diff --git a/pyfragment/core/constants/limits.py b/pyfragment/core/constants/limits.py index 1b71c3e..b054c80 100644 --- a/pyfragment/core/constants/limits.py +++ b/pyfragment/core/constants/limits.py @@ -2,7 +2,7 @@ from __future__ import annotations # Stars: direct purchase STARS_PURCHASE_MIN: int = 50 -STARS_PURCHASE_MAX: int = 10_000 +STARS_PURCHASE_MAX: int = 10_000_000 # Stars: giveaway per winner STARS_GIVEAWAY_MIN: int = 500 @@ -10,12 +10,20 @@ STARS_GIVEAWAY_MAX: int = 1_000_000 # Stars giveaway winners count STARS_WINNERS_MIN: int = 1 -STARS_WINNERS_MAX: int = 5 +STARS_WINNERS_MAX: int = 15 # Premium giveaway winners count PREMIUM_WINNERS_MIN: int = 1 PREMIUM_WINNERS_MAX: int = 24_000 +# TON topup / Ads recharge amount +TON_TOPUP_MIN: int = 1 +TON_TOPUP_MAX: int = 1_000_000_000 + +# Wallet minimum balances +MIN_TON_BALANCE: float = 0.33 +MIN_USDT_BALANCE: float = 0.75 + # Premium subscription durations (months) PREMIUM_MONTHS_VALID: frozenset[int] = frozenset({3, 6, 12}) @@ -24,7 +32,3 @@ MNEMONIC_WORD_COUNTS_VALID: frozenset[int] = frozenset({12, 24}) # Tonapi API key minimum length TONAPI_KEY_MIN_LENGTH: int = 68 - -# Wallet minimum balances -MIN_TON_BALANCE: float = 0.33 -MIN_USDT_BALANCE: float = 0.75 diff --git a/pyfragment/domains/ads/recharge.py b/pyfragment/domains/ads/recharge.py index b79d988..c79806a 100644 --- a/pyfragment/domains/ads/recharge.py +++ b/pyfragment/domains/ads/recharge.py @@ -4,7 +4,7 @@ import json import logging from typing import TYPE_CHECKING -from pyfragment.core.constants import ADS_TOPUP_PAGE, DEVICE_INFO +from pyfragment.core.constants import ADS_TOPUP_PAGE, DEVICE_INFO, TON_TOPUP_MAX, TON_TOPUP_MIN from pyfragment.domains.tonapi.account import get_account_info from pyfragment.domains.tonapi.transaction import process_transaction from pyfragment.exceptions import ConfigurationError, FragmentAPIError, FragmentError, UnexpectedError, VerificationError @@ -18,7 +18,7 @@ logger = logging.getLogger(__name__) async def recharge_ads(client: FragmentClient, account: str, amount: int) -> AdsRechargeResult: - if not isinstance(amount, int) or not (1 <= amount <= 1_000_000_000): + if not isinstance(amount, int) or not (TON_TOPUP_MIN <= amount <= TON_TOPUP_MAX): raise ConfigurationError(ConfigurationError.INVALID_TON_AMOUNT) try: diff --git a/pyfragment/domains/ads/tonup.py b/pyfragment/domains/ads/tonup.py index df3d8c0..40d999b 100644 --- a/pyfragment/domains/ads/tonup.py +++ b/pyfragment/domains/ads/tonup.py @@ -4,7 +4,7 @@ import json import logging from typing import TYPE_CHECKING -from pyfragment.core.constants import ADS_TOPUP_PAGE, DEVICE_INFO +from pyfragment.core.constants import ADS_TOPUP_PAGE, DEVICE_INFO, TON_TOPUP_MAX, TON_TOPUP_MIN from pyfragment.domains.payments import parse_required_payment_amount from pyfragment.domains.tonapi.account import get_account_info from pyfragment.domains.tonapi.transaction import process_transaction @@ -26,7 +26,7 @@ logger = logging.getLogger(__name__) async def topup_ton(client: FragmentClient, username: str, amount: int, show_sender: bool = True) -> AdsTopupResult: - if not isinstance(amount, int) or not (1 <= amount <= 1_000_000_000): + if not isinstance(amount, int) or not (TON_TOPUP_MIN <= amount <= TON_TOPUP_MAX): raise ConfigurationError(ConfigurationError.INVALID_TON_AMOUNT) try: diff --git a/pyfragment/domains/base.py b/pyfragment/domains/base.py index 6dce538..2e02aa7 100644 --- a/pyfragment/domains/base.py +++ b/pyfragment/domains/base.py @@ -20,14 +20,16 @@ async def raw_api_call( method: str, data: dict[str, Any] | None, page_url: str, + headers: dict[str, str] | None = None, ) -> dict[str, Any]: + base = headers if headers is not None else BASE_HEADERS payload = {"method": method, **(data or {})} - headers = {**BASE_HEADERS, "referer": page_url, "x-aj-referer": page_url} + call_headers = {**base, "referer": page_url, "x-aj-referer": page_url} logger.debug("Starting Fragment API call '%s' on %s", method, page_url) try: async with httpx.AsyncClient(cookies=cookies, timeout=timeout) as session: - fragment_hash = await get_fragment_hash(cookies, headers, page_url, timeout) - response = await fragment_request(session, fragment_hash, headers, payload) + fragment_hash = await get_fragment_hash(cookies, call_headers, page_url, timeout) + response = await fragment_request(session, fragment_hash, call_headers, payload) logger.debug("Completed Fragment API call '%s' with response keys: %s", method, sorted(response.keys())) return response except Exception: