From 311222d4788655220292abcb3e1520bf0d34269f Mon Sep 17 00:00:00 2001 From: bohd4nx Date: Mon, 11 May 2026 12:12:07 +0300 Subject: [PATCH] feat: add payment method options for purchases and giveaways, enhance documentation and examples --- CHANGELOG.md | 38 ++++++++++++++ README.md | 61 +++++++---------------- examples/purchase/run_premium_giveaway.py | 12 ++++- examples/purchase/run_stars_giveaway.py | 12 ++++- examples/purchase/send_premium.py | 12 ++++- examples/purchase/send_stars.py | 12 ++++- pyfragment/client.py | 8 +-- pyfragment/methods/giveaway_premium.py | 2 +- pyfragment/methods/giveaway_stars.py | 2 +- pyfragment/methods/purchase_premium.py | 2 +- pyfragment/methods/purchase_stars.py | 2 +- pyproject.toml | 20 +++++--- 12 files changed, 115 insertions(+), 68 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5423c88..98881cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,33 @@ and this project uses [Calendar Versioning](https://calver.org/) (`YYYY.MINOR.MI --- +## [Unreleased] + +### Added + +- `payment_method` option (`"ton"` / `"usdt_ton"`) for: + - `purchase_stars()` + - `purchase_premium()` + - `giveaway_stars()` + - `giveaway_premium()` + +### Changed + +- Added runtime validation for `payment_method` via `SUPPORTED_PAYMENT_METHODS` and `ConfigurationError.INVALID_PAYMENT_METHOD` +- Updated method docstrings to explicitly document recipient/channel formats: + - `@username` / `username` / `https://t.me/username` + +### Tests + +- Extended stars and premium test suites to cover: + - invalid payment method + - payment method propagation to `init*Request` payloads + - accepted query formats (`@`, plain username, `t.me` link) + +### Documentation + +- Simplified `README` usage example + ## [2026.2.1] — 2026-05-03 ### Fixed @@ -48,37 +75,44 @@ and this project uses [Calendar Versioning](https://calver.org/) (`YYYY.MINOR.MI ### Added **Giveaways** + - `giveaway_stars(channel, winners, amount)` — Stars giveaway; 1–5 winners, 500–1 000 000 stars each - `giveaway_premium(channel, winners, months)` — Premium giveaway; 1–24 000 winners, 3/6/12 months each - `StarsGiveawayResult`, `PremiumGiveawayResult` result types **Telegram Ads** + - `recharge_ads(account, amount)` — top up a Telegram Ads account; 1–1 000 000 000 TON - `AdsRechargeResult` result type **Marketplace** + - `search_usernames(query?, sort?, filter?, offset_id?)` — search Fragment usernames; `sort`: `price_desc / price_asc / listed / ending`, `filter`: `auction / sale / sold` - `search_numbers(query?, sort?, filter?, offset_id?)` — search Fragment anonymous numbers; same `sort` / `filter` / pagination semantics - `search_gifts(query?, collection?, sort?, filter?, view?, attr?, offset?)` — search Fragment gifts; `attr` accepts `{"Model": ["Foosball"], "Backdrop": ["Celtic Blue"]}` - `UsernamesResult`, `NumbersResult`, `GiftsResult` result types **Anonymous numbers** + - `get_login_code(number)` — fetch the current pending login code - `toggle_login_codes(number, can_receive)` — enable or disable login code delivery - `terminate_sessions(number)` — terminate all active Telegram sessions (two-step flow handled internally) - `LoginCodeResult`, `TerminateSessionsResult` result types; `AnonymousNumberError` exception **Raw API** + - `FragmentClient.call(method, data, *, page_url)` — raw request to any Fragment API method - `FRAGMENT_BASE_URL` constant — base URL shared across all page constants and headers **Examples** + - `examples/client/` — `wallet_info.py` (wallet info), `raw_api_call.py` (raw API call) - `examples/numbers/` — `manage_number.py` (login code fetch, session termination) - `examples/auctions/` — `search_usernames.py`, `search_numbers.py`, `search_gifts.py` (marketplace search with pagination) - `examples/purchase/` — `send_stars.py`, `send_premium.py`, `topup_ton_balance.py`, `run_stars_giveaway.py`, `run_premium_giveaway.py`, `recharge_ads_balance.py` ### Changed + - All result types now expose a unified `amount` field (`months` and `stars` removed) - `__repr__` includes the unit — `3 months`, `500 stars`, etc. - `timestamp` removed from all result dataclasses @@ -91,15 +125,18 @@ and this project uses [Calendar Versioning](https://calver.org/) (`YYYY.MINOR.MI ## [2026.0.2] — 2026-03-20 ### Added + - `timeout` parameter on `FragmentClient` (default `30.0` s) — passed through to every HTTP request ### Changed + - Cookie validation: narrowed type internally so no `# type: ignore` is needed in `FragmentClient.__init__` - `WALLET_CLASSES` typed as `dict[str, Any]` so mypy resolves `from_mnemonic` correctly - All four `examples/` files updated to `async with FragmentClient`, f-strings, and aligned error messages - README usage section rewritten with a single comprehensive `async with` example ### Fixed + - mypy: missing return path in `process_transaction` after retry loop - mypy: `cookies` union-attr error in `FragmentClient.__init__` @@ -108,6 +145,7 @@ and this project uses [Calendar Versioning](https://calver.org/) (`YYYY.MINOR.MI ## [2026.0.1] — 2026-03-16 ### Added + - Initial stable release of `pyfragment` - `FragmentClient` — async client for the Fragment.com API with context manager support (`async with`) - `purchase_premium(username, months)` — purchase Telegram Premium for any user (3, 6, or 12 months) diff --git a/README.md b/README.md index bb94671..c676e60 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,14 @@

Fragment API

- Async Python client for the Fragment API — a unified toolkit to manage Telegram assets: purchase Stars and Premium, top up TON and Ads balances, run giveaways, manage anonymous numbers, and explore the marketplace for usernames, numbers, and gifts. + Async Python client for the Fragment API. Buy Stars and Premium, top up TON and Ads balances, run giveaways, manage anonymous numbers, and search Fragment listings.

[![PyPI version](https://img.shields.io/pypi/v/pyfragment?style=flat&color=blue)](https://pypi.org/project/pyfragment/) -[![PyPI downloads](https://img.shields.io/pypi/dm/pyfragment?style=flat&color=brightgreen)](https://pypi.org/project/pyfragment/) +[![PyPI Downloads](https://static.pepy.tech/personalized-badge/pyfragment?period=total&units=INTERNATIONAL_SYSTEM&left_color=GREY&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/pyfragment) [![Python](https://img.shields.io/badge/Python-3.10+-3776AB?style=flat&logo=python&logoColor=white)](https://python.org) -[![License](https://img.shields.io/github/license/bohd4nx/pyfragment?style=flat&color=lightgrey)](LICENSE) -[![Stars](https://img.shields.io/github/stars/bohd4nx/pyfragment?style=flat&color=yellow)](https://github.com/bohd4nx/pyfragment/stargazers) -[![CI](https://img.shields.io/github/actions/workflow/status/bohd4nx/pyfragment/ci.yml?style=flat&label=tests&logo=github)](https://github.com/bohd4nx/pyfragment/actions) +[![Tests](https://img.shields.io/github/actions/workflow/status/bohd4nx/pyfragment/ci.yml?style=flat&label=tests&logo=github)](https://github.com/bohd4nx/pyfragment/actions) +[![License](https://img.shields.io/github/license/bohd4nx/pyfragment?style=flat&color=green)](https://github.com/bohd4nx/pyfragment/blob/master/LICENSE) [Report Bug](https://github.com/bohd4nx/pyfragment/issues) · [Request Feature](https://github.com/bohd4nx/pyfragment/issues) · [**Donate TON**](https://app.tonkeeper.com/transfer/UQCppfw5DxWgdVHf3zkmZS8k1mt9oAUYxQLwq2fz3nhO8No5) @@ -75,22 +74,13 @@ Refresh when you get authentication errors. ```python import asyncio -from pyfragment import ( - FragmentClient, - FragmentError, # base — catches everything below - UserNotFoundError, # username doesn't exist on Fragment - WalletError, # insufficient balance or misconfiguration - CookieError, # cookies are missing or expired - TransactionError, # on-chain broadcast failed - ConfigurationError, # invalid argument (months, amount, etc.) - FragmentAPIError, # unexpected Fragment API response -) +from pyfragment import FragmentClient async def main() -> None: async with FragmentClient( - seed="word1 word2 ... word24", # 24-word TON wallet mnemonic - api_key="YOUR_TONAPI_KEY", # from tonconsole.com + seed="word1 word2 ... word24", + api_key="YOUR_TONAPI_KEY", cookies={ "stel_ssid": "...", "stel_dt": "...", @@ -98,40 +88,23 @@ async def main() -> None: "stel_ton_token": "...", }, ) as client: - try: - # Purchase 6 months of Telegram Premium - result = await client.purchase_premium("@username", months=6) - print(f"{result.amount} months of Premium successfully sent to {result.username} | tx: {result.transaction_id}") + recipient = "https://t.me/username" # also supports: @username, username - # Purchase 500 Stars - result = await client.purchase_stars("@username", amount=500) - print(f"{result.amount} Stars successfully sent to {result.username} | tx: {result.transaction_id}") + stars = await client.purchase_stars(recipient, amount=500, payment_method="usdt_ton") + print(f"Stars sent: {stars.amount} to {stars.username} | tx: {stars.transaction_id}") - # Top up 10 TON to Telegram balance - # wallet must hold at least amount + ~0.056 TON for gas - result = await client.topup_ton("@username", amount=10) - print(f"{result.amount} TON successfully sent to {result.username} | tx: {result.transaction_id}") - - except UserNotFoundError: - print(f"User was not found on fragment.com — check the username and try again.") - except WalletError as e: - print(f"Wallet error — insufficient balance or misconfiguration: {e}") - except CookieError: - print("Authentication failed — session cookies are missing or expired. Refresh them and retry.") - except TransactionError as e: - print(f"Transaction failed to broadcast on-chain: {e}") - except ConfigurationError as e: - print(f"Invalid argument: {e}") - except FragmentAPIError as e: - print(f"Unexpected response from Fragment API: {e}") - except FragmentError as e: - # catch-all for any other pyfragment error - print(f"Unexpected error: {e}") + premium = await client.purchase_premium(recipient, months=6, payment_method="ton") + print(f"Premium sent: {premium.amount} months to {premium.username} | tx: {premium.transaction_id}") asyncio.run(main()) ``` +Full runnable examples: + +- https://github.com/bohd4nx/pyfragment/tree/master/examples +- `examples/` + ---
diff --git a/examples/purchase/run_premium_giveaway.py b/examples/purchase/run_premium_giveaway.py index d0a39b8..9fb8565 100644 --- a/examples/purchase/run_premium_giveaway.py +++ b/examples/purchase/run_premium_giveaway.py @@ -3,6 +3,8 @@ Example: run a Telegram Premium giveaway for a channel. winners must be an integer between 1 and 24 000. months (Premium duration per winner) must be 3, 6, or 12. +payment_method can be "ton" or "usdt_ton". +Channel can be "@channel", "channel", or "https://t.me/channel". """ import asyncio @@ -24,15 +26,21 @@ COOKIES = { "stel_ton_token": "YOUR_STEL_TON_TOKEN", } -CHANNEL = "@channel" +CHANNEL = "https://t.me/channel" WINNERS = 10 # 1–24 000 MONTHS = 3 # 3, 6 or 12 +PAYMENT_METHOD = "ton" # "ton" or "usdt_ton" async def main() -> None: async with FragmentClient(seed=SEED, api_key=API_KEY, cookies=COOKIES) as client: try: - result = await client.giveaway_premium(CHANNEL, winners=WINNERS, months=MONTHS) + result = await client.giveaway_premium( + CHANNEL, + winners=WINNERS, + months=MONTHS, + payment_method=PAYMENT_METHOD, + ) except UserNotFoundError: print(f"Channel {CHANNEL} was not found on fragment.com — check the username and try again.") return diff --git a/examples/purchase/run_stars_giveaway.py b/examples/purchase/run_stars_giveaway.py index 5a73310..7f98ed9 100644 --- a/examples/purchase/run_stars_giveaway.py +++ b/examples/purchase/run_stars_giveaway.py @@ -3,6 +3,8 @@ Example: run a Telegram Stars giveaway for a channel. winners must be an integer between 1 and 5. amount (stars per winner) must be an integer between 500 and 1 000 000. +payment_method can be "ton" or "usdt_ton". +Channel can be "@channel", "channel", or "https://t.me/channel". """ import asyncio @@ -24,15 +26,21 @@ COOKIES = { "stel_ton_token": "YOUR_STEL_TON_TOKEN", } -CHANNEL = "@channel" +CHANNEL = "https://t.me/channel" WINNERS = 3 # 1–5 AMOUNT = 1000 # 500–1 000 000 stars per winner +PAYMENT_METHOD = "usdt_ton" # "ton" or "usdt_ton" async def main() -> None: async with FragmentClient(seed=SEED, api_key=API_KEY, cookies=COOKIES) as client: try: - result = await client.giveaway_stars(CHANNEL, winners=WINNERS, amount=AMOUNT) + result = await client.giveaway_stars( + CHANNEL, + winners=WINNERS, + amount=AMOUNT, + payment_method=PAYMENT_METHOD, + ) except UserNotFoundError: print(f"Channel {CHANNEL} was not found on fragment.com — check the username and try again.") return diff --git a/examples/purchase/send_premium.py b/examples/purchase/send_premium.py index f82f1ed..a28cfa7 100644 --- a/examples/purchase/send_premium.py +++ b/examples/purchase/send_premium.py @@ -3,6 +3,8 @@ Example: purchase Telegram Premium for a user. Supported durations: 3, 6, or 12 months. Set show_sender=False to send anonymously. +payment_method can be "ton" or "usdt_ton". +Username can be "@username", "username", or "https://t.me/username". """ import asyncio @@ -24,14 +26,20 @@ COOKIES = { "stel_ton_token": "YOUR_STEL_TON_TOKEN", } -USERNAME = "@username" +USERNAME = "https://t.me/username" MONTHS = 3 # 3, 6 or 12 +PAYMENT_METHOD = "ton" # "ton" or "usdt_ton" async def main() -> None: async with FragmentClient(seed=SEED, api_key=API_KEY, cookies=COOKIES) as client: try: - result = await client.purchase_premium(USERNAME, months=MONTHS, show_sender=True) + result = await client.purchase_premium( + USERNAME, + months=MONTHS, + show_sender=True, + payment_method=PAYMENT_METHOD, + ) except UserNotFoundError: print(f"User {USERNAME} was not found on fragment.com — check the username and try again.") return diff --git a/examples/purchase/send_stars.py b/examples/purchase/send_stars.py index 76b616a..dbe5664 100644 --- a/examples/purchase/send_stars.py +++ b/examples/purchase/send_stars.py @@ -3,6 +3,8 @@ Example: purchase Telegram Stars for a user. Amount must be an integer between 50 and 1 000 000. Set show_sender=False to send anonymously. +payment_method can be "ton" or "usdt_ton". +Username can be "@username", "username", or "https://t.me/username". """ import asyncio @@ -24,14 +26,20 @@ COOKIES = { "stel_ton_token": "YOUR_STEL_TON_TOKEN", } -USERNAME = "@username" +USERNAME = "https://t.me/username" AMOUNT = 500 # 50–1 000 000 stars +PAYMENT_METHOD = "usdt_ton" # "ton" or "usdt_ton" async def main() -> None: async with FragmentClient(seed=SEED, api_key=API_KEY, cookies=COOKIES) as client: try: - result = await client.purchase_stars(USERNAME, amount=AMOUNT, show_sender=True) + result = await client.purchase_stars( + USERNAME, + amount=AMOUNT, + show_sender=True, + payment_method=PAYMENT_METHOD, + ) except UserNotFoundError: print(f"User {USERNAME} was not found on fragment.com — check the username and try again.") return diff --git a/pyfragment/client.py b/pyfragment/client.py index 43e9adc..2243957 100644 --- a/pyfragment/client.py +++ b/pyfragment/client.py @@ -136,7 +136,7 @@ class FragmentClient: """Gift Telegram Premium to a user. Args: - username: Recipient's Telegram username (with or without ``@``). + username: Recipient identifier — ``@username``, ``username``, or ``https://t.me/username``. months: Duration — ``3``, ``6``, or ``12``. show_sender: Show your name as the sender. Defaults to ``True``. payment_method: Payment currency — ``"ton"`` (default) or ``"usdt_ton"``. @@ -156,7 +156,7 @@ class FragmentClient: """Send Telegram Stars to a user. Args: - username: Recipient's Telegram username (with or without ``@``). + username: Recipient identifier — ``@username``, ``username``, or ``https://t.me/username``. amount: Number of stars — integer from ``50`` to ``1 000 000``. show_sender: Show your name as the gift sender. Defaults to ``True``. payment_method: Payment currency — ``"ton"`` (default) or ``"usdt_ton"``. @@ -211,7 +211,7 @@ class FragmentClient: """Run a Telegram Stars giveaway for a channel. Args: - channel: Channel username (with or without ``@``). + channel: Channel identifier — ``@channel``, ``channel``, or ``https://t.me/channel``. winners: Number of winners — integer from ``1`` to ``5``. amount: Stars each winner receives — integer from ``500`` to ``1 000 000``. payment_method: Payment currency — ``"ton"`` (default) or ``"usdt_ton"``. @@ -232,7 +232,7 @@ class FragmentClient: """Run a Telegram Premium giveaway for a channel. Args: - channel: Channel username (with or without ``@``). + channel: Channel identifier — ``@channel``, ``channel``, or ``https://t.me/channel``. winners: Number of winners — positive integer. months: Premium duration per winner — ``3``, ``6``, or ``12``. Defaults to ``3``. payment_method: Payment currency — ``"ton"`` (default) or ``"usdt_ton"``. diff --git a/pyfragment/methods/giveaway_premium.py b/pyfragment/methods/giveaway_premium.py index 79534c4..c9b8a0d 100644 --- a/pyfragment/methods/giveaway_premium.py +++ b/pyfragment/methods/giveaway_premium.py @@ -30,7 +30,7 @@ async def giveaway_premium( Args: client: Authenticated :class:`FragmentClient` instance. - channel: Channel username (with or without ``@``). + channel: Channel identifier — ``@channel``, ``channel``, or ``https://t.me/channel``. winners: Number of winners — integer from ``1`` to ``24 000``. months: Premium duration per winner — ``3``, ``6``, or ``12``. Defaults to ``3``. payment_method: Payment currency — ``"ton"`` (default) or ``"usdt_ton"``. diff --git a/pyfragment/methods/giveaway_stars.py b/pyfragment/methods/giveaway_stars.py index 77ed64d..83369a8 100644 --- a/pyfragment/methods/giveaway_stars.py +++ b/pyfragment/methods/giveaway_stars.py @@ -30,7 +30,7 @@ async def giveaway_stars( Args: client: Authenticated :class:`FragmentClient` instance. - channel: Channel username (with or without ``@``). + channel: Channel identifier — ``@channel``, ``channel``, or ``https://t.me/channel``. winners: Number of winners — integer from ``1`` to ``5``. amount: Stars each winner receives — integer from ``500`` to ``1 000 000``. payment_method: Payment currency — ``"ton"`` (default) or ``"usdt_ton"``. diff --git a/pyfragment/methods/purchase_premium.py b/pyfragment/methods/purchase_premium.py index 79edd42..6ba68d3 100644 --- a/pyfragment/methods/purchase_premium.py +++ b/pyfragment/methods/purchase_premium.py @@ -31,7 +31,7 @@ async def purchase_premium( Args: client: Authenticated :class:`FragmentClient` instance. - username: Recipient's Telegram username (with or without ``@``). + username: Recipient identifier — ``@username``, ``username``, or ``https://t.me/username``. months: Premium duration — ``3``, ``6``, or ``12``. show_sender: Show your name as the gift sender. Defaults to ``True``. payment_method: Payment currency — ``"ton"`` (default) or ``"usdt_ton"``. diff --git a/pyfragment/methods/purchase_stars.py b/pyfragment/methods/purchase_stars.py index c6af17f..27dfc58 100644 --- a/pyfragment/methods/purchase_stars.py +++ b/pyfragment/methods/purchase_stars.py @@ -27,7 +27,7 @@ async def purchase_stars( Args: client: Authenticated :class:`FragmentClient` instance. - username: Recipient's Telegram username (with or without ``@``). + username: Recipient identifier — ``@username``, ``username``, or ``https://t.me/username``. amount: Number of Stars to send — integer from ``50`` to ``1 000 000``. show_sender: Show your name as the gift sender. Defaults to ``True``. payment_method: Payment currency — ``"ton"`` (default) or ``"usdt_ton"``. diff --git a/pyproject.toml b/pyproject.toml index d9fe781..3ed8463 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "pyfragment" version = "2026.2.1" -description = "Async Python client for the Fragment API — a unified toolkit to manage Telegram assets: purchase Stars and Premium, top up TON and Ads balances, run giveaways, manage anonymous numbers, and explore the marketplace for usernames, numbers, and gifts." +description = "Async Python client for the Fragment API. Buy Stars and Premium, top up TON and Ads balances, run giveaways, manage anonymous numbers, and search Fragment listings." readme = "README.md" license = { text = "MIT" } requires-python = ">=3.10" @@ -14,19 +14,23 @@ keywords = [ "fragment", "fragment-api", "telegram", + "telegram-api", "telegram-stars", "telegram-premium", + "telegram-giveaway", + "telegram-ads", "ton", + "ton-blockchain", "tonkeeper", "tonapi", - "crypto", - "blockchain", + "anonymous-numbers", + "username-auctions", + "gift-marketplace", + "crypto-payments", + "nft-marketplace", "web3", - "giveaway", - "anonymous-number", - "username", - "nft", - "async", + "python-client", + "typed", "asyncio", ] classifiers = [