mirror of
https://github.com/bohd4nx/FragmentAPI.git
synced 2026-07-25 06:14:29 +00:00
feat: add payment method support for premium and stars purchases, including validation and tests
This commit is contained in:
+23
-15
@@ -15,6 +15,7 @@ from pyfragment.types import (
|
||||
CookieError,
|
||||
CookieResult,
|
||||
FragmentAPIError,
|
||||
# exceptions
|
||||
FragmentError,
|
||||
FragmentPageError,
|
||||
GiftsResult,
|
||||
@@ -22,9 +23,12 @@ from pyfragment.types import (
|
||||
NumbersResult,
|
||||
OperationError,
|
||||
ParseError,
|
||||
# literal types
|
||||
PaymentMethod,
|
||||
PremiumGiveawayResult,
|
||||
PremiumResult,
|
||||
StarsGiveawayResult,
|
||||
# results
|
||||
StarsResult,
|
||||
TerminateSessionsResult,
|
||||
TransactionError,
|
||||
@@ -41,31 +45,35 @@ __version__: str = version("pyfragment")
|
||||
__all__ = [
|
||||
"__version__",
|
||||
"FragmentClient",
|
||||
"AdsRechargeResult",
|
||||
# results
|
||||
"StarsResult",
|
||||
"StarsGiveawayResult",
|
||||
"PremiumResult",
|
||||
"PremiumGiveawayResult",
|
||||
"WalletInfo",
|
||||
"AdsTopupResult",
|
||||
"AdsRechargeResult",
|
||||
"CookieResult",
|
||||
"GiftsResult",
|
||||
"LoginCodeResult",
|
||||
"NumbersResult",
|
||||
"PremiumGiveawayResult",
|
||||
"PremiumResult",
|
||||
"StarsGiveawayResult",
|
||||
"StarsResult",
|
||||
"TerminateSessionsResult",
|
||||
"UsernamesResult",
|
||||
"WalletInfo",
|
||||
"ClientError",
|
||||
"ConfigurationError",
|
||||
"CookieError",
|
||||
"CookieResult",
|
||||
"FragmentAPIError",
|
||||
# exceptions
|
||||
"FragmentError",
|
||||
"FragmentAPIError",
|
||||
"FragmentPageError",
|
||||
"ConfigurationError",
|
||||
"UserNotFoundError",
|
||||
"WalletError",
|
||||
"VerificationError",
|
||||
"TransactionError",
|
||||
"AnonymousNumberError",
|
||||
"ClientError",
|
||||
"CookieError",
|
||||
"OperationError",
|
||||
"ParseError",
|
||||
"TransactionError",
|
||||
"UnexpectedError",
|
||||
"UserNotFoundError",
|
||||
"VerificationError",
|
||||
"WalletError",
|
||||
# literal types
|
||||
"PaymentMethod",
|
||||
]
|
||||
|
||||
+25
-6
@@ -36,6 +36,7 @@ from pyfragment.types.constants import (
|
||||
FRAGMENT_BASE_URL,
|
||||
REQUIRED_COOKIE_KEYS,
|
||||
SUPPORTED_WALLET_VERSIONS,
|
||||
PaymentMethod,
|
||||
WalletVersion,
|
||||
)
|
||||
from pyfragment.utils.http import fragment_request, get_fragment_hash, make_headers
|
||||
@@ -125,31 +126,45 @@ class FragmentClient:
|
||||
def __repr__(self) -> str:
|
||||
return f"FragmentClient(wallet_version='{self.wallet_version}', cookies={len(self.cookies)} keys)"
|
||||
|
||||
async def purchase_premium(self, username: str, months: int, show_sender: bool = True) -> PremiumResult:
|
||||
async def purchase_premium(
|
||||
self,
|
||||
username: str,
|
||||
months: int,
|
||||
show_sender: bool = True,
|
||||
payment_method: PaymentMethod = "ton",
|
||||
) -> PremiumResult:
|
||||
"""Gift Telegram Premium to a user.
|
||||
|
||||
Args:
|
||||
username: Recipient's Telegram username (with or without ``@``).
|
||||
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"``.
|
||||
|
||||
Returns:
|
||||
:class:`PremiumResult` with ``transaction_id``, ``username``, and ``amount``.
|
||||
"""
|
||||
return await purchase_premium(self, username, months, show_sender)
|
||||
return await purchase_premium(self, username, months, show_sender, payment_method)
|
||||
|
||||
async def purchase_stars(self, username: str, amount: int, show_sender: bool = True) -> StarsResult:
|
||||
async def purchase_stars(
|
||||
self,
|
||||
username: str,
|
||||
amount: int,
|
||||
show_sender: bool = True,
|
||||
payment_method: PaymentMethod = "ton",
|
||||
) -> StarsResult:
|
||||
"""Send Telegram Stars to a user.
|
||||
|
||||
Args:
|
||||
username: Recipient's Telegram username (with or without ``@``).
|
||||
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"``.
|
||||
|
||||
Returns:
|
||||
:class:`StarsResult` with ``transaction_id``, ``username``, and ``amount``.
|
||||
"""
|
||||
return await purchase_stars(self, username, amount, show_sender)
|
||||
return await purchase_stars(self, username, amount, show_sender, payment_method)
|
||||
|
||||
async def topup_ton(self, username: str, amount: int, show_sender: bool = True) -> AdsTopupResult:
|
||||
"""Top up TON to a recipient's Telegram balance.
|
||||
@@ -191,6 +206,7 @@ class FragmentClient:
|
||||
channel: str,
|
||||
winners: int,
|
||||
amount: int,
|
||||
payment_method: PaymentMethod = "ton",
|
||||
) -> StarsGiveawayResult:
|
||||
"""Run a Telegram Stars giveaway for a channel.
|
||||
|
||||
@@ -198,18 +214,20 @@ class FragmentClient:
|
||||
channel: Channel username (with or without ``@``).
|
||||
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"``.
|
||||
|
||||
Returns:
|
||||
:class:`StarsGiveawayResult` with ``transaction_id``, ``channel``,
|
||||
``winners``, and ``amount``.
|
||||
"""
|
||||
return await giveaway_stars(self, channel, winners, amount)
|
||||
return await giveaway_stars(self, channel, winners, amount, payment_method)
|
||||
|
||||
async def giveaway_premium(
|
||||
self,
|
||||
channel: str,
|
||||
winners: int,
|
||||
months: int = 3,
|
||||
payment_method: PaymentMethod = "ton",
|
||||
) -> PremiumGiveawayResult:
|
||||
"""Run a Telegram Premium giveaway for a channel.
|
||||
|
||||
@@ -217,12 +235,13 @@ class FragmentClient:
|
||||
channel: Channel username (with or without ``@``).
|
||||
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"``.
|
||||
|
||||
Returns:
|
||||
:class:`PremiumGiveawayResult` with ``transaction_id``, ``channel``,
|
||||
``winners``, and ``amount``.
|
||||
"""
|
||||
return await giveaway_premium(self, channel, winners, months)
|
||||
return await giveaway_premium(self, channel, winners, months, payment_method)
|
||||
|
||||
async def get_login_code(self, number: str) -> LoginCodeResult:
|
||||
"""Fetch the current pending login code for an anonymous number.
|
||||
|
||||
@@ -12,7 +12,7 @@ from pyfragment.types import (
|
||||
UserNotFoundError,
|
||||
VerificationError,
|
||||
)
|
||||
from pyfragment.types.constants import DEVICE, PREMIUM_GIVEAWAY_PAGE
|
||||
from pyfragment.types.constants import DEVICE, PREMIUM_GIVEAWAY_PAGE, SUPPORTED_PAYMENT_METHODS, PaymentMethod
|
||||
from pyfragment.utils import get_account_info, process_transaction
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -24,6 +24,7 @@ async def giveaway_premium(
|
||||
channel: str,
|
||||
winners: int,
|
||||
months: int = 3,
|
||||
payment_method: PaymentMethod = "ton",
|
||||
) -> PremiumGiveawayResult:
|
||||
"""Run a Telegram Premium giveaway for a channel.
|
||||
|
||||
@@ -32,6 +33,7 @@ async def giveaway_premium(
|
||||
channel: Channel username (with or without ``@``).
|
||||
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"``.
|
||||
|
||||
Returns:
|
||||
:class:`PremiumGiveawayResult` with ``transaction_id``, ``channel``,
|
||||
@@ -47,6 +49,13 @@ async def giveaway_premium(
|
||||
raise ConfigurationError(ConfigurationError.INVALID_WINNERS_PREMIUM)
|
||||
if months not in (3, 6, 12):
|
||||
raise ConfigurationError(ConfigurationError.INVALID_MONTHS)
|
||||
if payment_method not in SUPPORTED_PAYMENT_METHODS:
|
||||
raise ConfigurationError(
|
||||
ConfigurationError.INVALID_PAYMENT_METHOD.format(
|
||||
method=payment_method,
|
||||
supported=", ".join(sorted(SUPPORTED_PAYMENT_METHODS)),
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
result = await client.call(
|
||||
@@ -60,7 +69,12 @@ async def giveaway_premium(
|
||||
|
||||
result = await client.call(
|
||||
"initGiveawayPremiumRequest",
|
||||
{"recipient": recipient, "quantity": str(winners), "months": str(months)},
|
||||
{
|
||||
"recipient": recipient,
|
||||
"quantity": str(winners),
|
||||
"months": str(months),
|
||||
"payment_method": payment_method,
|
||||
},
|
||||
page_url=PREMIUM_GIVEAWAY_PAGE,
|
||||
)
|
||||
req_id = result.get("req_id")
|
||||
|
||||
@@ -12,7 +12,7 @@ from pyfragment.types import (
|
||||
UserNotFoundError,
|
||||
VerificationError,
|
||||
)
|
||||
from pyfragment.types.constants import DEVICE, STARS_GIVEAWAY_PAGE
|
||||
from pyfragment.types.constants import DEVICE, STARS_GIVEAWAY_PAGE, SUPPORTED_PAYMENT_METHODS, PaymentMethod
|
||||
from pyfragment.utils import get_account_info, process_transaction
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -24,6 +24,7 @@ async def giveaway_stars(
|
||||
channel: str,
|
||||
winners: int,
|
||||
amount: int,
|
||||
payment_method: PaymentMethod = "ton",
|
||||
) -> StarsGiveawayResult:
|
||||
"""Run a Telegram Stars giveaway for a channel.
|
||||
|
||||
@@ -32,6 +33,7 @@ async def giveaway_stars(
|
||||
channel: Channel username (with or without ``@``).
|
||||
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"``.
|
||||
|
||||
Returns:
|
||||
:class:`StarsGiveawayResult` with ``transaction_id``, ``channel``,
|
||||
@@ -47,6 +49,13 @@ async def giveaway_stars(
|
||||
raise ConfigurationError(ConfigurationError.INVALID_WINNERS_STARS)
|
||||
if not isinstance(amount, int) or not (500 <= amount <= 1_000_000):
|
||||
raise ConfigurationError(ConfigurationError.INVALID_STARS_PER_WINNER)
|
||||
if payment_method not in SUPPORTED_PAYMENT_METHODS:
|
||||
raise ConfigurationError(
|
||||
ConfigurationError.INVALID_PAYMENT_METHOD.format(
|
||||
method=payment_method,
|
||||
supported=", ".join(sorted(SUPPORTED_PAYMENT_METHODS)),
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
result = await client.call("searchStarsGiveawayRecipient", {"query": channel}, page_url=STARS_GIVEAWAY_PAGE)
|
||||
@@ -56,7 +65,12 @@ async def giveaway_stars(
|
||||
|
||||
result = await client.call(
|
||||
"initGiveawayStarsRequest",
|
||||
{"recipient": recipient, "quantity": str(winners), "stars": str(amount)},
|
||||
{
|
||||
"recipient": recipient,
|
||||
"quantity": str(winners),
|
||||
"stars": str(amount),
|
||||
"payment_method": payment_method,
|
||||
},
|
||||
page_url=STARS_GIVEAWAY_PAGE,
|
||||
)
|
||||
req_id = result.get("req_id")
|
||||
|
||||
@@ -13,14 +13,20 @@ from pyfragment.types import (
|
||||
UserNotFoundError,
|
||||
VerificationError,
|
||||
)
|
||||
from pyfragment.types.constants import DEVICE, PREMIUM_PAGE
|
||||
from pyfragment.types.constants import DEVICE, PREMIUM_PAGE, SUPPORTED_PAYMENT_METHODS, PaymentMethod
|
||||
from pyfragment.utils import get_account_info, process_transaction
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pyfragment.client import FragmentClient
|
||||
|
||||
|
||||
async def purchase_premium(client: FragmentClient, username: str, months: int, show_sender: bool = True) -> PremiumResult:
|
||||
async def purchase_premium(
|
||||
client: FragmentClient,
|
||||
username: str,
|
||||
months: int,
|
||||
show_sender: bool = True,
|
||||
payment_method: PaymentMethod = "ton",
|
||||
) -> PremiumResult:
|
||||
"""Gift Telegram Premium to a user.
|
||||
|
||||
Args:
|
||||
@@ -28,6 +34,7 @@ async def purchase_premium(client: FragmentClient, username: str, months: int, s
|
||||
username: Recipient's Telegram username (with or without ``@``).
|
||||
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"``.
|
||||
|
||||
Returns:
|
||||
:class:`PremiumResult` with ``transaction_id``, ``username``, and ``amount``.
|
||||
@@ -40,6 +47,13 @@ async def purchase_premium(client: FragmentClient, username: str, months: int, s
|
||||
"""
|
||||
if months not in (3, 6, 12):
|
||||
raise ConfigurationError(ConfigurationError.INVALID_MONTHS)
|
||||
if payment_method not in SUPPORTED_PAYMENT_METHODS:
|
||||
raise ConfigurationError(
|
||||
ConfigurationError.INVALID_PAYMENT_METHOD.format(
|
||||
method=payment_method,
|
||||
supported=", ".join(sorted(SUPPORTED_PAYMENT_METHODS)),
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
result = await client.call("searchPremiumGiftRecipient", {"query": username, "months": months}, page_url=PREMIUM_PAGE)
|
||||
@@ -52,7 +66,11 @@ async def purchase_premium(client: FragmentClient, username: str, months: int, s
|
||||
{"mode": "new", "lv": "false", "dh": str(int(time.time()))},
|
||||
page_url=PREMIUM_PAGE,
|
||||
)
|
||||
result = await client.call("initGiftPremiumRequest", {"recipient": recipient, "months": months}, page_url=PREMIUM_PAGE)
|
||||
result = await client.call(
|
||||
"initGiftPremiumRequest",
|
||||
{"recipient": recipient, "months": months, "payment_method": payment_method},
|
||||
page_url=PREMIUM_PAGE,
|
||||
)
|
||||
req_id = result.get("req_id")
|
||||
if not req_id:
|
||||
raise FragmentAPIError(FragmentAPIError.NO_REQUEST_ID.format(context="Premium purchase"))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pyfragment.types import (
|
||||
@@ -12,14 +13,16 @@ from pyfragment.types import (
|
||||
UserNotFoundError,
|
||||
VerificationError,
|
||||
)
|
||||
from pyfragment.types.constants import DEVICE, STARS_PAGE
|
||||
from pyfragment.types.constants import DEVICE, STARS_PAGE, SUPPORTED_PAYMENT_METHODS, PaymentMethod
|
||||
from pyfragment.utils import get_account_info, process_transaction
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pyfragment.client import FragmentClient
|
||||
|
||||
|
||||
async def purchase_stars(client: FragmentClient, username: str, amount: int, show_sender: bool = True) -> StarsResult:
|
||||
async def purchase_stars(
|
||||
client: FragmentClient, username: str, amount: int, show_sender: bool = True, payment_method: PaymentMethod = "ton"
|
||||
) -> StarsResult:
|
||||
"""Send Telegram Stars to a user.
|
||||
|
||||
Args:
|
||||
@@ -27,6 +30,7 @@ async def purchase_stars(client: FragmentClient, username: str, amount: int, sho
|
||||
username: Recipient's Telegram username (with or without ``@``).
|
||||
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"``.
|
||||
|
||||
Returns:
|
||||
:class:`StarsResult` with ``transaction_id``, ``username``, and ``amount``.
|
||||
@@ -39,6 +43,13 @@ async def purchase_stars(client: FragmentClient, username: str, amount: int, sho
|
||||
"""
|
||||
if not isinstance(amount, int) or not (50 <= amount <= 1_000_000):
|
||||
raise ConfigurationError(ConfigurationError.INVALID_STARS_AMOUNT)
|
||||
if payment_method not in SUPPORTED_PAYMENT_METHODS:
|
||||
raise ConfigurationError(
|
||||
ConfigurationError.INVALID_PAYMENT_METHOD.format(
|
||||
method=payment_method,
|
||||
supported=", ".join(sorted(SUPPORTED_PAYMENT_METHODS)),
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
result = await client.call("searchStarsRecipient", {"query": username, "quantity": ""}, page_url=STARS_PAGE)
|
||||
@@ -46,7 +57,16 @@ async def purchase_stars(client: FragmentClient, username: str, amount: int, sho
|
||||
if not recipient:
|
||||
raise UserNotFoundError(UserNotFoundError.NOT_FOUND.format(username=username))
|
||||
|
||||
result = await client.call("initBuyStarsRequest", {"recipient": recipient, "quantity": amount}, page_url=STARS_PAGE)
|
||||
await client.call(
|
||||
"updateStarsBuyState",
|
||||
{"mode": "new", "lv": "false", "dh": str(int(time.time()))},
|
||||
page_url=STARS_PAGE,
|
||||
)
|
||||
result = await client.call(
|
||||
"initBuyStarsRequest",
|
||||
{"recipient": recipient, "quantity": amount, "payment_method": payment_method},
|
||||
page_url=STARS_PAGE,
|
||||
)
|
||||
req_id = result.get("req_id")
|
||||
if not req_id:
|
||||
raise FragmentAPIError(FragmentAPIError.NO_REQUEST_ID.format(context="Stars purchase"))
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from pyfragment.types.constants import PaymentMethod
|
||||
from pyfragment.types.exceptions import (
|
||||
AnonymousNumberError,
|
||||
ClientError,
|
||||
@@ -61,4 +62,6 @@ __all__ = [
|
||||
"TerminateSessionsResult",
|
||||
"UsernamesResult",
|
||||
"WalletInfo",
|
||||
# literal types
|
||||
"PaymentMethod",
|
||||
]
|
||||
|
||||
@@ -5,6 +5,10 @@ from typing import Any, Literal, get_args
|
||||
|
||||
from tonutils.contracts.wallet import WalletV4R2, WalletV5R1
|
||||
|
||||
# Payment methods
|
||||
PaymentMethod = Literal["ton", "usdt_ton"]
|
||||
SUPPORTED_PAYMENT_METHODS: frozenset[str] = frozenset(get_args(PaymentMethod))
|
||||
|
||||
# Single source of truth for supported wallet versions
|
||||
WalletVersion = Literal["V4R2", "V5R1"]
|
||||
SUPPORTED_WALLET_VERSIONS: frozenset[str] = frozenset(get_args(WalletVersion))
|
||||
@@ -74,12 +78,15 @@ BASE_HEADERS: dict[str, str] = {
|
||||
"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 (iPhone; CPU iPhone OS 18_5 like Mac OS X) "
|
||||
"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1"
|
||||
"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",
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ class ConfigurationError(ClientError):
|
||||
INVALID_WINNERS_STARS = "Invalid winners count: must be an integer between 1 and 5."
|
||||
INVALID_WINNERS_PREMIUM = "Invalid winners count: must be an integer between 1 and 24 000."
|
||||
INVALID_STARS_PER_WINNER = "Invalid Stars per winner: must be an integer between 500 and 1 000 000."
|
||||
INVALID_PAYMENT_METHOD = "Invalid payment method '{method}'. Supported values: {supported}."
|
||||
|
||||
|
||||
class CookieError(ClientError):
|
||||
|
||||
Reference in New Issue
Block a user