From dc9661134d595dd45f84ce4e286c2f0d58fcd82e Mon Sep 17 00:00:00 2001 From: bohd4nx Date: Tue, 16 Jun 2026 01:19:33 +0300 Subject: [PATCH] Refactor cookie handling and service structure - Removed the old CookieResult model from pyfragment.core.models and created a new one in pyfragment.services.cookies.models. - Implemented get_cookies_from_browser function in pyfragment.services.cookies.service to extract cookies directly from supported browsers. - Updated imports across various modules to reflect the new structure and removed unused imports. - Cleaned up models in ads, anonymous_numbers, giveaways, marketplace, and purchases domains by removing unnecessary __all__ declarations. - Added new example scripts for cookie extraction and various purchase functionalities. - Consolidated constants into a new pyfragment.core.constants module for better organization. --- README.md | 13 +-- examples/client/get_cookies.py | 33 +++++++ ...remium_giveaway.py => premium_giveaway.py} | 0 .../{send_premium.py => purchase_premium.py} | 0 .../{send_stars.py => purchase_stars.py} | 0 ...un_stars_giveaway.py => stars_giveaway.py} | 0 ...p_ton_balance.py => topup_gram_balance.py} | 0 pyfragment/__init__.py | 17 ++-- pyfragment/client.py | 16 ++-- pyfragment/core/constants.py | 87 +++++++++++++++++++ pyfragment/core/constants/__init__.py | 64 -------------- pyfragment/core/constants/gram.py | 17 ---- pyfragment/core/constants/headers.py | 26 ------ pyfragment/core/constants/limits.py | 34 -------- pyfragment/core/constants/urls.py | 12 --- pyfragment/domains/ads/models.py | 3 - pyfragment/domains/ads/recharge.py | 2 +- pyfragment/domains/ads/service.py | 2 +- pyfragment/domains/ads/tonup.py | 2 +- .../domains/anonymous_numbers/__init__.py | 2 +- .../domains/anonymous_numbers/models.py | 3 - .../domains/anonymous_numbers/number.py | 2 +- .../domains/anonymous_numbers/service.py | 2 +- pyfragment/domains/giveaways/__init__.py | 2 +- pyfragment/domains/giveaways/giveaway.py | 4 +- pyfragment/domains/giveaways/models.py | 3 - pyfragment/domains/giveaways/service.py | 2 +- pyfragment/domains/marketplace/__init__.py | 2 +- pyfragment/domains/marketplace/models.py | 3 - pyfragment/domains/marketplace/search.py | 2 +- pyfragment/domains/marketplace/service.py | 2 +- pyfragment/domains/purchases/__init__.py | 2 +- pyfragment/domains/purchases/models.py | 3 - pyfragment/domains/purchases/purchase.py | 4 +- pyfragment/domains/purchases/service.py | 2 +- pyfragment/domains/tonapi/account.py | 4 +- pyfragment/domains/tonapi/models.py | 3 - pyfragment/domains/tonapi/transaction.py | 2 +- pyfragment/enums.py | 3 - pyfragment/exceptions.py | 2 +- pyfragment/services/__init__.py | 3 + pyfragment/services/cookies/__init__.py | 4 + .../{core => services/cookies}/models.py | 3 - .../cookies/service.py} | 4 +- pyproject.toml | 2 +- tests/002_test_client.py | 2 +- tests/004_test_stars.py | 2 +- tests/005_test_premium.py | 4 +- tests/006_test_topup.py | 2 +- tests/010_test_recharge_ads.py | 2 +- tests/014_test_cookies.py | 2 +- 51 files changed, 178 insertions(+), 234 deletions(-) create mode 100644 examples/client/get_cookies.py rename examples/purchase/{run_premium_giveaway.py => premium_giveaway.py} (100%) rename examples/purchase/{send_premium.py => purchase_premium.py} (100%) rename examples/purchase/{send_stars.py => purchase_stars.py} (100%) rename examples/purchase/{run_stars_giveaway.py => stars_giveaway.py} (100%) rename examples/purchase/{topup_ton_balance.py => topup_gram_balance.py} (100%) create mode 100644 pyfragment/core/constants.py delete mode 100644 pyfragment/core/constants/__init__.py delete mode 100644 pyfragment/core/constants/gram.py delete mode 100644 pyfragment/core/constants/headers.py delete mode 100644 pyfragment/core/constants/limits.py delete mode 100644 pyfragment/core/constants/urls.py create mode 100644 pyfragment/services/__init__.py create mode 100644 pyfragment/services/cookies/__init__.py rename pyfragment/{core => services/cookies}/models.py (90%) rename pyfragment/{core/cookies.py => services/cookies/service.py} (97%) diff --git a/README.md b/README.md index e2be12e..3bd9419 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,10 @@ pip install git+https://github.com/bohd4nx/pyfragment.git@dev ```python import asyncio + from pyfragment import FragmentClient -from pyfragment.models.enums import PaymentMethod +from pyfragment.enums import PaymentMethod + async def main() -> None: async with FragmentClient( @@ -58,14 +60,7 @@ async def main() -> None: recipient = "https://t.me/username" # also: @username, username stars = await client.purchase_stars(recipient, amount=500, payment_method=PaymentMethod.USDT_GRAM) - if stars.auto_paid: - print(f"Sent {stars.amount} Stars to {stars.username} | tx: {stars.transaction_id}") - else: - print(f"Manual payment required for {stars.username}") - print(f"Address: {stars.payment_address}") - print(f"Amount: {stars.payment_amount}") - print(f"Expires: {stars.payment_expires_at}") - print(f"QR link: {stars.payment_qr_link}") + print(f"Sent {stars.amount} Stars to {stars.username} | tx: {stars.transaction_id}") premium = await client.purchase_premium(recipient, months=6, payment_method=PaymentMethod.GRAM) print(f"Sent Premium {premium.amount}m to {premium.username} | tx: {premium.transaction_id}") diff --git a/examples/client/get_cookies.py b/examples/client/get_cookies.py new file mode 100644 index 0000000..a01c659 --- /dev/null +++ b/examples/client/get_cookies.py @@ -0,0 +1,33 @@ +""" +Example: extract Fragment cookies directly from your browser. + +get_cookies_from_browser() reads the Fragment session cookies from a locally +installed browser — no manual copy-paste required. + +Supported browsers: arc, brave, chrome, chromium, chromium_based, edge, + firefox, firefox_based, librewolf, opera, opera_gx, + safari, vivaldi. + +The returned CookieResult.cookies dict can be passed directly to FragmentClient. +""" + +from pyfragment import CookieError, get_cookies_from_browser + + +def main() -> None: + try: + result = get_cookies_from_browser("chrome") # or "firefox", "edge", "brave", ... + except CookieError as e: + print(f"Could not read cookies: {e}") + return + + print(f"Cookies expire: {result.expires}") + print(f"Keys found: {list(result.cookies.keys())}") + + # Pass the extracted cookies directly to FragmentClient + # async with FragmentClient(seed=SEED, api_key=API_KEY, cookies=result.cookies) as client: + # ... + + +if __name__ == "__main__": + main() diff --git a/examples/purchase/run_premium_giveaway.py b/examples/purchase/premium_giveaway.py similarity index 100% rename from examples/purchase/run_premium_giveaway.py rename to examples/purchase/premium_giveaway.py diff --git a/examples/purchase/send_premium.py b/examples/purchase/purchase_premium.py similarity index 100% rename from examples/purchase/send_premium.py rename to examples/purchase/purchase_premium.py diff --git a/examples/purchase/send_stars.py b/examples/purchase/purchase_stars.py similarity index 100% rename from examples/purchase/send_stars.py rename to examples/purchase/purchase_stars.py diff --git a/examples/purchase/run_stars_giveaway.py b/examples/purchase/stars_giveaway.py similarity index 100% rename from examples/purchase/run_stars_giveaway.py rename to examples/purchase/stars_giveaway.py diff --git a/examples/purchase/topup_ton_balance.py b/examples/purchase/topup_gram_balance.py similarity index 100% rename from examples/purchase/topup_ton_balance.py rename to examples/purchase/topup_gram_balance.py diff --git a/pyfragment/__init__.py b/pyfragment/__init__.py index 44162ce..d30e5f3 100644 --- a/pyfragment/__init__.py +++ b/pyfragment/__init__.py @@ -2,7 +2,13 @@ import logging from importlib.metadata import version from pyfragment.client import FragmentClient -from pyfragment.core.cookies import get_cookies_from_browser +from pyfragment.domains.ads.models import AdsRechargeResult, AdsTopupResult +from pyfragment.domains.anonymous_numbers.models import LoginCodeResult, TerminateSessionsResult +from pyfragment.domains.giveaways.models import PremiumGiveawayResult, StarsGiveawayResult +from pyfragment.domains.marketplace.models import GiftsResult, NumbersResult, UsernamesResult +from pyfragment.domains.purchases.models import PremiumResult, StarsResult +from pyfragment.domains.tonapi.models import WalletInfo +from pyfragment.enums import PaymentMethod, WalletVersion from pyfragment.exceptions import ( AlreadySubscribedError, AnonymousNumberError, @@ -20,14 +26,7 @@ from pyfragment.exceptions import ( VerificationError, WalletError, ) -from pyfragment.domains.anonymous_numbers.models import LoginCodeResult, TerminateSessionsResult -from pyfragment.core.models import CookieResult -from pyfragment.enums import PaymentMethod, WalletVersion -from pyfragment.domains.giveaways.models import PremiumGiveawayResult, StarsGiveawayResult -from pyfragment.domains.marketplace.models import GiftsResult, NumbersResult, UsernamesResult -from pyfragment.domains.ads.models import AdsRechargeResult, AdsTopupResult -from pyfragment.domains.purchases.models import PremiumResult, StarsResult -from pyfragment.domains.tonapi.models import WalletInfo +from pyfragment.services.cookies import CookieResult, get_cookies_from_browser logging.getLogger("pyfragment").addHandler(logging.NullHandler()) diff --git a/pyfragment/client.py b/pyfragment/client.py index 59aaf7a..3999b15 100644 --- a/pyfragment/client.py +++ b/pyfragment/client.py @@ -11,21 +11,21 @@ from pyfragment.core.constants import ( REQUIRED_COOKIE_KEYS, TONAPI_KEY_MIN_LENGTH, ) +from pyfragment.domains.ads.models import AdsRechargeResult, AdsTopupResult from pyfragment.domains.ads.service import AdsService +from pyfragment.domains.anonymous_numbers.models import LoginCodeResult, TerminateSessionsResult from pyfragment.domains.anonymous_numbers.service import AnonymousNumbersService from pyfragment.domains.base import raw_api_call -from pyfragment.domains.giveaways.service import GiveawaysService -from pyfragment.domains.marketplace.service import MarketplaceService -from pyfragment.domains.purchases.service import PurchasesService -from pyfragment.domains.tonapi.service import TonapiService -from pyfragment.exceptions import ConfigurationError, CookieError -from pyfragment.domains.anonymous_numbers.models import LoginCodeResult, TerminateSessionsResult -from pyfragment.enums import PaymentMethod, WalletVersion from pyfragment.domains.giveaways.models import PremiumGiveawayResult, StarsGiveawayResult +from pyfragment.domains.giveaways.service import GiveawaysService from pyfragment.domains.marketplace.models import GiftsResult, NumbersResult, UsernamesResult -from pyfragment.domains.ads.models import AdsRechargeResult, AdsTopupResult +from pyfragment.domains.marketplace.service import MarketplaceService from pyfragment.domains.purchases.models import PremiumResult, StarsResult +from pyfragment.domains.purchases.service import PurchasesService from pyfragment.domains.tonapi.models import WalletInfo +from pyfragment.domains.tonapi.service import TonapiService +from pyfragment.enums import PaymentMethod, WalletVersion +from pyfragment.exceptions import ConfigurationError, CookieError class FragmentClient: diff --git a/pyfragment/core/constants.py b/pyfragment/core/constants.py new file mode 100644 index 0000000..835869e --- /dev/null +++ b/pyfragment/core/constants.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +from typing import Any + +FRAGMENT_DOMAIN: str = "fragment.com" +FRAGMENT_BASE_URL: str = f"https://{FRAGMENT_DOMAIN}" + +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" +ADS_TOPUP_PAGE: str = f"{FRAGMENT_BASE_URL}/ads/topup" +NUMBERS_PAGE: str = f"{FRAGMENT_BASE_URL}/numbers" +GIFTS_PAGE: str = f"{FRAGMENT_BASE_URL}/gifts" + +DEFAULT_TIMEOUT: float = 30.0 + +# Fragment cookie keys required for authenticated API calls +REQUIRED_COOKIE_KEYS: tuple[str, ...] = ("stel_ssid", "stel_dt", "stel_token", "stel_ton_token") + +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": FRAGMENT_BASE_URL, + "priority": "u=1, i", + "sec-ch-ua": '"Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147"', + "sec-ch-ua-mobile": "?1", + "sec-ch-ua-platform": '"Android"', + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "same-origin", + "user-agent": ( + "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Mobile Safari/537.36" + ), + "x-requested-with": "XMLHttpRequest", +} + +# USDT-TON jetton master contract address on GRAM (ex TON) mainnet +USDT_GRAM_MASTER_ADDRESS: str = "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs" + +# TON Connect device info sent during wallet connection handshake +DEVICE_INFO: dict[str, Any] = { + "platform": "iphone", + "appName": "Tonkeeper", + "appVersion": "26.05.0", + "maxProtocolVersion": 2, + "features": [ + "SendTransaction", + {"name": "SendTransaction", "maxMessages": 255}, + {"name": "SignData", "types": ["text", "binary", "cell"]}, + ], +} + +# Stars: direct purchase per transaction +STARS_PURCHASE_MIN: int = 50 +STARS_PURCHASE_MAX: int = 10_000_000 + +# Stars: giveaway amount per winner +STARS_GIVEAWAY_MIN: int = 500 +STARS_GIVEAWAY_MAX: int = 1_000_000 + +# Stars giveaway winner count +STARS_WINNERS_MIN: int = 1 +STARS_WINNERS_MAX: int = 15 + +# Premium giveaway winner count +PREMIUM_WINNERS_MIN: int = 1 +PREMIUM_WINNERS_MAX: int = 24_000 + +# GRAM (ex TON) topup / Ads recharge amount +GRAM_TOPUP_MIN: int = 1 +GRAM_TOPUP_MAX: int = 1_000_000_000 + +# Minimum wallet balances required before broadcasting a transaction +MIN_GRAM_BALANCE: float = 0.33 +MIN_USDT_BALANCE: float = 0.75 + +# Premium subscription durations (months) +PREMIUM_MONTHS_VALID: frozenset[int] = frozenset({3, 6, 12}) + +# Mnemonic phrase valid word counts +MNEMONIC_WORD_COUNTS_VALID: frozenset[int] = frozenset({12, 24}) + +# Tonapi API key minimum length (tonapi.io) +TONAPI_KEY_MIN_LENGTH: int = 68 diff --git a/pyfragment/core/constants/__init__.py b/pyfragment/core/constants/__init__.py deleted file mode 100644 index 7dd2c93..0000000 --- a/pyfragment/core/constants/__init__.py +++ /dev/null @@ -1,64 +0,0 @@ -from __future__ import annotations - -from pyfragment.core.constants.gram import DEVICE_INFO, USDT_GRAM_MASTER_ADDRESS -from pyfragment.core.constants.headers import BASE_HEADERS, DEFAULT_TIMEOUT, REQUIRED_COOKIE_KEYS -from pyfragment.core.constants.limits import ( - GRAM_TOPUP_MAX, - GRAM_TOPUP_MIN, - MIN_GRAM_BALANCE, - MIN_USDT_BALANCE, - MNEMONIC_WORD_COUNTS_VALID, - PREMIUM_MONTHS_VALID, - PREMIUM_WINNERS_MAX, - PREMIUM_WINNERS_MIN, - STARS_GIVEAWAY_MAX, - STARS_GIVEAWAY_MIN, - STARS_PURCHASE_MAX, - STARS_PURCHASE_MIN, - STARS_WINNERS_MAX, - STARS_WINNERS_MIN, - TONAPI_KEY_MIN_LENGTH, -) -from pyfragment.core.constants.urls import ( - ADS_TOPUP_PAGE, - FRAGMENT_BASE_URL, - FRAGMENT_DOMAIN, - GIFTS_PAGE, - NUMBERS_PAGE, - PREMIUM_GIVEAWAY_PAGE, - PREMIUM_PAGE, - STARS_GIVEAWAY_PAGE, - STARS_PAGE, -) - -__all__ = [ - "ADS_TOPUP_PAGE", - "BASE_HEADERS", - "DEFAULT_TIMEOUT", - "DEVICE_INFO", - "FRAGMENT_BASE_URL", - "FRAGMENT_DOMAIN", - "GIFTS_PAGE", - "GRAM_TOPUP_MAX", - "GRAM_TOPUP_MIN", - "MIN_GRAM_BALANCE", - "MIN_USDT_BALANCE", - "MNEMONIC_WORD_COUNTS_VALID", - "NUMBERS_PAGE", - "PREMIUM_GIVEAWAY_PAGE", - "PREMIUM_MONTHS_VALID", - "PREMIUM_PAGE", - "PREMIUM_WINNERS_MAX", - "PREMIUM_WINNERS_MIN", - "REQUIRED_COOKIE_KEYS", - "STARS_GIVEAWAY_MAX", - "STARS_GIVEAWAY_MIN", - "STARS_GIVEAWAY_PAGE", - "STARS_PAGE", - "STARS_PURCHASE_MAX", - "STARS_PURCHASE_MIN", - "STARS_WINNERS_MAX", - "STARS_WINNERS_MIN", - "TONAPI_KEY_MIN_LENGTH", - "USDT_GRAM_MASTER_ADDRESS", -] diff --git a/pyfragment/core/constants/gram.py b/pyfragment/core/constants/gram.py deleted file mode 100644 index f1cc6cc..0000000 --- a/pyfragment/core/constants/gram.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import annotations - -from typing import Any - -USDT_GRAM_MASTER_ADDRESS: str = "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs" - -DEVICE_INFO: dict[str, Any] = { - "platform": "iphone", - "appName": "Tonkeeper", - "appVersion": "26.05.0", - "maxProtocolVersion": 2, - "features": [ - "SendTransaction", - {"name": "SendTransaction", "maxMessages": 255}, - {"name": "SignData", "types": ["text", "binary", "cell"]}, - ], -} diff --git a/pyfragment/core/constants/headers.py b/pyfragment/core/constants/headers.py deleted file mode 100644 index f0462fd..0000000 --- a/pyfragment/core/constants/headers.py +++ /dev/null @@ -1,26 +0,0 @@ -from __future__ import annotations - -from pyfragment.core.constants.urls import FRAGMENT_BASE_URL - -DEFAULT_TIMEOUT: float = 30.0 - -REQUIRED_COOKIE_KEYS: tuple[str, ...] = ("stel_ssid", "stel_dt", "stel_token", "stel_ton_token") - -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": FRAGMENT_BASE_URL, - "priority": "u=1, i", - "sec-ch-ua": '"Google Chrome";v="147", "Not.A/Brand";v="8", "Chromium";v="147"', - "sec-ch-ua-mobile": "?1", - "sec-ch-ua-platform": '"Android"', - "sec-fetch-dest": "empty", - "sec-fetch-mode": "cors", - "sec-fetch-site": "same-origin", - "user-agent": ( - "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) " - "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Mobile Safari/537.36" - ), - "x-requested-with": "XMLHttpRequest", -} diff --git a/pyfragment/core/constants/limits.py b/pyfragment/core/constants/limits.py deleted file mode 100644 index bb35890..0000000 --- a/pyfragment/core/constants/limits.py +++ /dev/null @@ -1,34 +0,0 @@ -from __future__ import annotations - -# Stars: direct purchase -STARS_PURCHASE_MIN: int = 50 -STARS_PURCHASE_MAX: int = 10_000_000 - -# Stars: giveaway per winner -STARS_GIVEAWAY_MIN: int = 500 -STARS_GIVEAWAY_MAX: int = 1_000_000 - -# Stars giveaway winners count -STARS_WINNERS_MIN: int = 1 -STARS_WINNERS_MAX: int = 15 - -# Premium giveaway winners count -PREMIUM_WINNERS_MIN: int = 1 -PREMIUM_WINNERS_MAX: int = 24_000 - -# GRAM (ex TON) topup / Ads recharge amount -GRAM_TOPUP_MIN: int = 1 -GRAM_TOPUP_MAX: int = 1_000_000_000 - -# Wallet minimum balances -MIN_GRAM_BALANCE: float = 0.33 -MIN_USDT_BALANCE: float = 0.75 - -# Premium subscription durations (months) -PREMIUM_MONTHS_VALID: frozenset[int] = frozenset({3, 6, 12}) - -# Mnemonic phrase valid word counts -MNEMONIC_WORD_COUNTS_VALID: frozenset[int] = frozenset({12, 24}) - -# Tonapi API key minimum length -TONAPI_KEY_MIN_LENGTH: int = 68 diff --git a/pyfragment/core/constants/urls.py b/pyfragment/core/constants/urls.py deleted file mode 100644 index cba12b0..0000000 --- a/pyfragment/core/constants/urls.py +++ /dev/null @@ -1,12 +0,0 @@ -from __future__ import annotations - -FRAGMENT_DOMAIN: str = "fragment.com" -FRAGMENT_BASE_URL: str = f"https://{FRAGMENT_DOMAIN}" - -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" -ADS_TOPUP_PAGE: str = f"{FRAGMENT_BASE_URL}/ads/topup" -NUMBERS_PAGE: str = f"{FRAGMENT_BASE_URL}/numbers" -GIFTS_PAGE: str = f"{FRAGMENT_BASE_URL}/gifts" diff --git a/pyfragment/domains/ads/models.py b/pyfragment/domains/ads/models.py index d6a0ef4..b73a2f4 100644 --- a/pyfragment/domains/ads/models.py +++ b/pyfragment/domains/ads/models.py @@ -20,6 +20,3 @@ class AdsRechargeResult: def __repr__(self) -> str: return f"AdsRechargeResult(amount={self.amount} GRAM (ex TON), tx='{self.transaction_id}')" - - -__all__ = ["AdsRechargeResult", "AdsTopupResult"] diff --git a/pyfragment/domains/ads/recharge.py b/pyfragment/domains/ads/recharge.py index e7fcee5..ab44e79 100644 --- a/pyfragment/domains/ads/recharge.py +++ b/pyfragment/domains/ads/recharge.py @@ -5,10 +5,10 @@ import logging from typing import TYPE_CHECKING from pyfragment.core.constants import ADS_TOPUP_PAGE, DEVICE_INFO, GRAM_TOPUP_MAX, GRAM_TOPUP_MIN +from pyfragment.domains.ads.models import AdsRechargeResult 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 -from pyfragment.domains.ads.models import AdsRechargeResult if TYPE_CHECKING: from pyfragment.client import FragmentClient diff --git a/pyfragment/domains/ads/service.py b/pyfragment/domains/ads/service.py index 66a47ac..7f26602 100644 --- a/pyfragment/domains/ads/service.py +++ b/pyfragment/domains/ads/service.py @@ -2,10 +2,10 @@ from __future__ import annotations from typing import TYPE_CHECKING +from pyfragment.domains.ads.models import AdsRechargeResult, AdsTopupResult from pyfragment.domains.ads.recharge import recharge_ads from pyfragment.domains.ads.tonup import topup_gram from pyfragment.domains.base import BaseService -from pyfragment.domains.ads.models import AdsRechargeResult, AdsTopupResult if TYPE_CHECKING: pass diff --git a/pyfragment/domains/ads/tonup.py b/pyfragment/domains/ads/tonup.py index 192a60f..e4f69d8 100644 --- a/pyfragment/domains/ads/tonup.py +++ b/pyfragment/domains/ads/tonup.py @@ -5,6 +5,7 @@ import logging from typing import TYPE_CHECKING from pyfragment.core.constants import ADS_TOPUP_PAGE, DEVICE_INFO, GRAM_TOPUP_MAX, GRAM_TOPUP_MIN +from pyfragment.domains.ads.models import AdsTopupResult 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 @@ -16,7 +17,6 @@ from pyfragment.exceptions import ( UserNotFoundError, VerificationError, ) -from pyfragment.domains.ads.models import AdsTopupResult if TYPE_CHECKING: from pyfragment.client import FragmentClient diff --git a/pyfragment/domains/anonymous_numbers/__init__.py b/pyfragment/domains/anonymous_numbers/__init__.py index d104f4d..ffbef1e 100644 --- a/pyfragment/domains/anonymous_numbers/__init__.py +++ b/pyfragment/domains/anonymous_numbers/__init__.py @@ -1,6 +1,6 @@ +from pyfragment.domains.anonymous_numbers.models import LoginCodeResult, TerminateSessionsResult from pyfragment.domains.anonymous_numbers.number import get_login_code, terminate_sessions, toggle_login_codes from pyfragment.domains.anonymous_numbers.service import AnonymousNumbersService -from pyfragment.domains.anonymous_numbers.models import LoginCodeResult, TerminateSessionsResult __all__ = [ "AnonymousNumbersService", diff --git a/pyfragment/domains/anonymous_numbers/models.py b/pyfragment/domains/anonymous_numbers/models.py index 1d78977..1c1c9fc 100644 --- a/pyfragment/domains/anonymous_numbers/models.py +++ b/pyfragment/domains/anonymous_numbers/models.py @@ -21,6 +21,3 @@ class TerminateSessionsResult: def __repr__(self) -> str: return f"TerminateSessionsResult(number='{self.number}', message={self.message!r})" - - -__all__ = ["LoginCodeResult", "TerminateSessionsResult"] diff --git a/pyfragment/domains/anonymous_numbers/number.py b/pyfragment/domains/anonymous_numbers/number.py index 3f43da2..d68e031 100644 --- a/pyfragment/domains/anonymous_numbers/number.py +++ b/pyfragment/domains/anonymous_numbers/number.py @@ -5,9 +5,9 @@ import logging from typing import TYPE_CHECKING from pyfragment.core.constants import NUMBERS_PAGE +from pyfragment.domains.anonymous_numbers.models import LoginCodeResult, TerminateSessionsResult from pyfragment.domains.anonymous_numbers.parser import parse_login_code from pyfragment.exceptions import AnonymousNumberError, FragmentAPIError, FragmentError, UnexpectedError -from pyfragment.domains.anonymous_numbers.models import LoginCodeResult, TerminateSessionsResult if TYPE_CHECKING: from pyfragment.client import FragmentClient diff --git a/pyfragment/domains/anonymous_numbers/service.py b/pyfragment/domains/anonymous_numbers/service.py index 96ec5e5..11b60a2 100644 --- a/pyfragment/domains/anonymous_numbers/service.py +++ b/pyfragment/domains/anonymous_numbers/service.py @@ -2,9 +2,9 @@ from __future__ import annotations from typing import TYPE_CHECKING +from pyfragment.domains.anonymous_numbers.models import LoginCodeResult, TerminateSessionsResult from pyfragment.domains.anonymous_numbers.number import get_login_code, terminate_sessions, toggle_login_codes from pyfragment.domains.base import BaseService -from pyfragment.domains.anonymous_numbers.models import LoginCodeResult, TerminateSessionsResult if TYPE_CHECKING: pass diff --git a/pyfragment/domains/giveaways/__init__.py b/pyfragment/domains/giveaways/__init__.py index aeadd05..0d814a1 100644 --- a/pyfragment/domains/giveaways/__init__.py +++ b/pyfragment/domains/giveaways/__init__.py @@ -1,6 +1,6 @@ from pyfragment.domains.giveaways.giveaway import giveaway_premium, giveaway_stars -from pyfragment.domains.giveaways.service import GiveawaysService from pyfragment.domains.giveaways.models import PremiumGiveawayResult, StarsGiveawayResult +from pyfragment.domains.giveaways.service import GiveawaysService __all__ = [ "GiveawaysService", diff --git a/pyfragment/domains/giveaways/giveaway.py b/pyfragment/domains/giveaways/giveaway.py index 4c6f3b0..dc5e9e6 100644 --- a/pyfragment/domains/giveaways/giveaway.py +++ b/pyfragment/domains/giveaways/giveaway.py @@ -17,9 +17,11 @@ from pyfragment.core.constants import ( STARS_WINNERS_MAX, STARS_WINNERS_MIN, ) +from pyfragment.domains.giveaways.models import PremiumGiveawayResult, StarsGiveawayResult 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 +from pyfragment.enums import PaymentMethod from pyfragment.exceptions import ( ConfigurationError, FragmentAPIError, @@ -28,8 +30,6 @@ from pyfragment.exceptions import ( UserNotFoundError, VerificationError, ) -from pyfragment.enums import PaymentMethod -from pyfragment.domains.giveaways.models import PremiumGiveawayResult, StarsGiveawayResult if TYPE_CHECKING: from pyfragment.client import FragmentClient diff --git a/pyfragment/domains/giveaways/models.py b/pyfragment/domains/giveaways/models.py index 3c9c02c..c8e9523 100644 --- a/pyfragment/domains/giveaways/models.py +++ b/pyfragment/domains/giveaways/models.py @@ -29,6 +29,3 @@ class PremiumGiveawayResult: f"PremiumGiveawayResult(channel='{self.channel}', winners={self.winners}, " f"amount={self.amount} months per winner, tx='{self.transaction_id}')" ) - - -__all__ = ["PremiumGiveawayResult", "StarsGiveawayResult"] diff --git a/pyfragment/domains/giveaways/service.py b/pyfragment/domains/giveaways/service.py index 6166769..b0e1e27 100644 --- a/pyfragment/domains/giveaways/service.py +++ b/pyfragment/domains/giveaways/service.py @@ -4,8 +4,8 @@ from typing import TYPE_CHECKING from pyfragment.domains.base import BaseService from pyfragment.domains.giveaways.giveaway import giveaway_premium, giveaway_stars -from pyfragment.enums import PaymentMethod from pyfragment.domains.giveaways.models import PremiumGiveawayResult, StarsGiveawayResult +from pyfragment.enums import PaymentMethod if TYPE_CHECKING: pass diff --git a/pyfragment/domains/marketplace/__init__.py b/pyfragment/domains/marketplace/__init__.py index 90dfcb6..b80db15 100644 --- a/pyfragment/domains/marketplace/__init__.py +++ b/pyfragment/domains/marketplace/__init__.py @@ -1,6 +1,6 @@ +from pyfragment.domains.marketplace.models import GiftsResult, NumbersResult, UsernamesResult from pyfragment.domains.marketplace.search import search_gifts, search_numbers, search_usernames from pyfragment.domains.marketplace.service import MarketplaceService -from pyfragment.domains.marketplace.models import GiftsResult, NumbersResult, UsernamesResult __all__ = [ "GiftsResult", diff --git a/pyfragment/domains/marketplace/models.py b/pyfragment/domains/marketplace/models.py index d773095..794961a 100644 --- a/pyfragment/domains/marketplace/models.py +++ b/pyfragment/domains/marketplace/models.py @@ -29,6 +29,3 @@ class GiftsResult: def __repr__(self) -> str: return f"GiftsResult(items={len(self.items)}, next_offset={self.next_offset!r})" - - -__all__ = ["GiftsResult", "NumbersResult", "UsernamesResult"] diff --git a/pyfragment/domains/marketplace/search.py b/pyfragment/domains/marketplace/search.py index 7d28876..9c24ddb 100644 --- a/pyfragment/domains/marketplace/search.py +++ b/pyfragment/domains/marketplace/search.py @@ -4,9 +4,9 @@ import logging from typing import TYPE_CHECKING, Any from pyfragment.core.constants import FRAGMENT_BASE_URL, GIFTS_PAGE, NUMBERS_PAGE +from pyfragment.domains.marketplace.models import GiftsResult, NumbersResult, UsernamesResult from pyfragment.domains.marketplace.parser import parse_auction_rows, parse_gift_items from pyfragment.exceptions import FragmentAPIError, FragmentError, UnexpectedError -from pyfragment.domains.marketplace.models import GiftsResult, NumbersResult, UsernamesResult if TYPE_CHECKING: from pyfragment.client import FragmentClient diff --git a/pyfragment/domains/marketplace/service.py b/pyfragment/domains/marketplace/service.py index b8510a2..c65416c 100644 --- a/pyfragment/domains/marketplace/service.py +++ b/pyfragment/domains/marketplace/service.py @@ -3,8 +3,8 @@ from __future__ import annotations from typing import TYPE_CHECKING from pyfragment.domains.base import BaseService -from pyfragment.domains.marketplace.search import search_gifts, search_numbers, search_usernames from pyfragment.domains.marketplace.models import GiftsResult, NumbersResult, UsernamesResult +from pyfragment.domains.marketplace.search import search_gifts, search_numbers, search_usernames if TYPE_CHECKING: pass diff --git a/pyfragment/domains/purchases/__init__.py b/pyfragment/domains/purchases/__init__.py index 9950f0a..767e117 100644 --- a/pyfragment/domains/purchases/__init__.py +++ b/pyfragment/domains/purchases/__init__.py @@ -1,5 +1,5 @@ +from pyfragment.domains.purchases.models import PremiumResult, StarsResult from pyfragment.domains.purchases.purchase import purchase_premium, purchase_stars from pyfragment.domains.purchases.service import PurchasesService -from pyfragment.domains.purchases.models import PremiumResult, StarsResult __all__ = ["PremiumResult", "PurchasesService", "StarsResult", "purchase_premium", "purchase_stars"] diff --git a/pyfragment/domains/purchases/models.py b/pyfragment/domains/purchases/models.py index 1ac4a9b..1e93ce4 100644 --- a/pyfragment/domains/purchases/models.py +++ b/pyfragment/domains/purchases/models.py @@ -21,6 +21,3 @@ class StarsResult: def __repr__(self) -> str: return f"StarsResult(username='{self.username}', amount={self.amount} stars, tx='{self.transaction_id}')" - - -__all__ = ["PremiumResult", "StarsResult"] diff --git a/pyfragment/domains/purchases/purchase.py b/pyfragment/domains/purchases/purchase.py index b60a868..e774718 100644 --- a/pyfragment/domains/purchases/purchase.py +++ b/pyfragment/domains/purchases/purchase.py @@ -14,8 +14,10 @@ from pyfragment.core.constants import ( STARS_PURCHASE_MIN, ) from pyfragment.domains.payments import parse_required_payment_amount +from pyfragment.domains.purchases.models import PremiumResult, StarsResult from pyfragment.domains.tonapi.account import get_account_info from pyfragment.domains.tonapi.transaction import process_transaction +from pyfragment.enums import PaymentMethod from pyfragment.exceptions import ( AlreadySubscribedError, ConfigurationError, @@ -25,8 +27,6 @@ from pyfragment.exceptions import ( UserNotFoundError, VerificationError, ) -from pyfragment.enums import PaymentMethod -from pyfragment.domains.purchases.models import PremiumResult, StarsResult if TYPE_CHECKING: from pyfragment.client import FragmentClient diff --git a/pyfragment/domains/purchases/service.py b/pyfragment/domains/purchases/service.py index 1650280..8fb1ac9 100644 --- a/pyfragment/domains/purchases/service.py +++ b/pyfragment/domains/purchases/service.py @@ -3,9 +3,9 @@ from __future__ import annotations from typing import TYPE_CHECKING from pyfragment.domains.base import BaseService +from pyfragment.domains.purchases.models import PremiumResult, StarsResult from pyfragment.domains.purchases.purchase import purchase_premium, purchase_stars from pyfragment.enums import PaymentMethod -from pyfragment.domains.purchases.models import PremiumResult, StarsResult if TYPE_CHECKING: pass diff --git a/pyfragment/domains/tonapi/account.py b/pyfragment/domains/tonapi/account.py index 52cd42b..7e094ae 100644 --- a/pyfragment/domains/tonapi/account.py +++ b/pyfragment/domains/tonapi/account.py @@ -10,9 +10,9 @@ from tonutils.contracts.jetton import get_wallet_address_get_method, get_wallet_ from tonutils.exceptions import ProviderResponseError from pyfragment.core.constants import MIN_GRAM_BALANCE, MIN_USDT_BALANCE, USDT_GRAM_MASTER_ADDRESS -from pyfragment.exceptions import WalletError -from pyfragment.enums import WALLET_CLASSES from pyfragment.domains.tonapi.models import WalletInfo +from pyfragment.enums import WALLET_CLASSES +from pyfragment.exceptions import WalletError if TYPE_CHECKING: from pyfragment.client import FragmentClient diff --git a/pyfragment/domains/tonapi/models.py b/pyfragment/domains/tonapi/models.py index 5f15a51..4b51e72 100644 --- a/pyfragment/domains/tonapi/models.py +++ b/pyfragment/domains/tonapi/models.py @@ -15,6 +15,3 @@ class WalletInfo: f"WalletInfo(address='{self.address}', state='{self.state}', " f"gram_balance={self.gram_balance} GRAM (ex TON), usdt_balance={self.usdt_balance} USDT)" ) - - -__all__ = ["WalletInfo"] diff --git a/pyfragment/domains/tonapi/transaction.py b/pyfragment/domains/tonapi/transaction.py index 2abf824..1e8bacf 100644 --- a/pyfragment/domains/tonapi/transaction.py +++ b/pyfragment/domains/tonapi/transaction.py @@ -12,8 +12,8 @@ from tonutils.clients import TonapiClient from tonutils.exceptions import ProviderResponseError from pyfragment.domains.tonapi.account import check_gram_payment_balance, check_usdt_payment_balance -from pyfragment.exceptions import ParseError, TransactionError, WalletError from pyfragment.enums import WALLET_CLASSES, PaymentMethod +from pyfragment.exceptions import ParseError, TransactionError, WalletError if TYPE_CHECKING: from pyfragment.client import FragmentClient diff --git a/pyfragment/enums.py b/pyfragment/enums.py index 3d7a94f..ac672e5 100644 --- a/pyfragment/enums.py +++ b/pyfragment/enums.py @@ -55,6 +55,3 @@ class SupportedBrowser(StrEnum): OPERA_GX = "opera_gx" SAFARI = "safari" VIVALDI = "vivaldi" - - -__all__ = ["PaymentMethod", "SupportedBrowser", "WALLET_CLASSES", "WalletVersion"] diff --git a/pyfragment/exceptions.py b/pyfragment/exceptions.py index e49a7b1..5c1e765 100644 --- a/pyfragment/exceptions.py +++ b/pyfragment/exceptions.py @@ -1,6 +1,6 @@ from __future__ import annotations -from pyfragment.core.constants.limits import ( +from pyfragment.core.constants import ( GRAM_TOPUP_MAX, GRAM_TOPUP_MIN, MNEMONIC_WORD_COUNTS_VALID, diff --git a/pyfragment/services/__init__.py b/pyfragment/services/__init__.py new file mode 100644 index 0000000..fef3009 --- /dev/null +++ b/pyfragment/services/__init__.py @@ -0,0 +1,3 @@ +from pyfragment.services.cookies import CookieResult, get_cookies_from_browser + +__all__ = ["CookieResult", "get_cookies_from_browser"] diff --git a/pyfragment/services/cookies/__init__.py b/pyfragment/services/cookies/__init__.py new file mode 100644 index 0000000..c14fdcd --- /dev/null +++ b/pyfragment/services/cookies/__init__.py @@ -0,0 +1,4 @@ +from pyfragment.services.cookies.models import CookieResult +from pyfragment.services.cookies.service import get_cookies_from_browser + +__all__ = ["CookieResult", "get_cookies_from_browser"] diff --git a/pyfragment/core/models.py b/pyfragment/services/cookies/models.py similarity index 90% rename from pyfragment/core/models.py rename to pyfragment/services/cookies/models.py index 6c218ce..aec4314 100644 --- a/pyfragment/core/models.py +++ b/pyfragment/services/cookies/models.py @@ -10,6 +10,3 @@ class CookieResult: def __repr__(self) -> str: return f"CookieResult(cookies={self.cookies!r}, expires={self.expires!r})" - - -__all__ = ["CookieResult"] diff --git a/pyfragment/core/cookies.py b/pyfragment/services/cookies/service.py similarity index 97% rename from pyfragment/core/cookies.py rename to pyfragment/services/cookies/service.py index 2676909..c88ed40 100644 --- a/pyfragment/core/cookies.py +++ b/pyfragment/services/cookies/service.py @@ -5,9 +5,9 @@ from datetime import datetime, timezone from typing import Any from pyfragment.core.constants import FRAGMENT_BASE_URL, FRAGMENT_DOMAIN, REQUIRED_COOKIE_KEYS -from pyfragment.exceptions import CookieError -from pyfragment.core.models import CookieResult from pyfragment.enums import SupportedBrowser +from pyfragment.exceptions import CookieError +from pyfragment.services.cookies.models import CookieResult try: import rookiepy diff --git a/pyproject.toml b/pyproject.toml index 615706b..dbc5a69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,5 +106,5 @@ ignore_missing_imports = true [[tool.mypy.overrides]] # rookiepy = None assignment is valid when the package is installed (type: ModuleType) # but mypy on CI (without rookiepy) sees it as Any and flags the comment as unused. -module = "pyfragment.core.cookies" +module = "pyfragment.services.cookies.service" warn_unused_ignores = false diff --git a/tests/002_test_client.py b/tests/002_test_client.py index c813236..91660ab 100644 --- a/tests/002_test_client.py +++ b/tests/002_test_client.py @@ -5,7 +5,7 @@ import json import pytest from pyfragment import ConfigurationError, CookieError, FragmentClient -from pyfragment.core.constants.limits import MNEMONIC_WORD_COUNTS_VALID, TONAPI_KEY_MIN_LENGTH +from pyfragment.core.constants import MNEMONIC_WORD_COUNTS_VALID, TONAPI_KEY_MIN_LENGTH from tests.shared import VALID_API_KEY, VALID_COOKIES, VALID_SEED # Client init tests diff --git a/tests/004_test_stars.py b/tests/004_test_stars.py index 18dd193..97f73b0 100644 --- a/tests/004_test_stars.py +++ b/tests/004_test_stars.py @@ -7,7 +7,7 @@ import pytest import pyfragment.domains.giveaways.giveaway as _giveaway_stars_mod import pyfragment.domains.purchases.purchase as _purchase_stars_mod from pyfragment import ConfigurationError, FragmentClient, StarsGiveawayResult, StarsResult, UserNotFoundError -from pyfragment.core.constants.limits import ( +from pyfragment.core.constants import ( STARS_GIVEAWAY_MAX, STARS_GIVEAWAY_MIN, STARS_PURCHASE_MAX, diff --git a/tests/005_test_premium.py b/tests/005_test_premium.py index 235b282..64af05b 100644 --- a/tests/005_test_premium.py +++ b/tests/005_test_premium.py @@ -7,9 +7,9 @@ import pytest import pyfragment.domains.giveaways.giveaway as _giveaway_premium_mod import pyfragment.domains.purchases.purchase as _purchase_premium_mod from pyfragment import ConfigurationError, FragmentClient, PremiumGiveawayResult, PremiumResult, UserNotFoundError -from pyfragment.core.constants.limits import PREMIUM_MONTHS_VALID, PREMIUM_WINNERS_MAX, PREMIUM_WINNERS_MIN -from pyfragment.exceptions import AlreadySubscribedError +from pyfragment.core.constants import PREMIUM_MONTHS_VALID, PREMIUM_WINNERS_MAX, PREMIUM_WINNERS_MIN from pyfragment.enums import PaymentMethod +from pyfragment.exceptions import AlreadySubscribedError from tests.shared import FAKE_ACCOUNT, FAKE_RECIPIENT, FAKE_REQ_ID, FAKE_TRANSACTION, FAKE_TX_HASH # Premium purchase validation tests diff --git a/tests/006_test_topup.py b/tests/006_test_topup.py index 0b06b7a..eea5bdc 100644 --- a/tests/006_test_topup.py +++ b/tests/006_test_topup.py @@ -6,7 +6,7 @@ import pytest import pyfragment.domains.ads.tonup as _topup_gram_mod from pyfragment import AdsTopupResult, ConfigurationError, FragmentClient, UserNotFoundError -from pyfragment.core.constants.limits import GRAM_TOPUP_MAX, GRAM_TOPUP_MIN +from pyfragment.core.constants import GRAM_TOPUP_MAX, GRAM_TOPUP_MIN from tests.shared import FAKE_ACCOUNT, FAKE_RECIPIENT, FAKE_REQ_ID, FAKE_TRANSACTION, FAKE_TX_HASH # Topup GRAM (ex TON) validation tests diff --git a/tests/010_test_recharge_ads.py b/tests/010_test_recharge_ads.py index 283e1de..bb31094 100644 --- a/tests/010_test_recharge_ads.py +++ b/tests/010_test_recharge_ads.py @@ -6,7 +6,7 @@ import pytest import pyfragment.domains.ads.recharge as _recharge_ads_mod from pyfragment import AdsRechargeResult, ConfigurationError, FragmentClient -from pyfragment.core.constants.limits import GRAM_TOPUP_MAX, GRAM_TOPUP_MIN +from pyfragment.core.constants import GRAM_TOPUP_MAX, GRAM_TOPUP_MIN from tests.shared import FAKE_ACCOUNT, FAKE_ADS_ACCOUNT, FAKE_REQ_ID, FAKE_TRANSACTION, FAKE_TX_HASH # recharge_ads validation tests diff --git a/tests/014_test_cookies.py b/tests/014_test_cookies.py index a390e29..1041de5 100644 --- a/tests/014_test_cookies.py +++ b/tests/014_test_cookies.py @@ -22,7 +22,7 @@ def _mock_rookiepy(jar: list[dict[str, str]] | None = None) -> MagicMock: return mock -PATCH = "pyfragment.core.cookies.rookiepy" +PATCH = "pyfragment.services.cookies.service.rookiepy" # unsupported browser tests