feat: Enhance Fragment API with improved error handling and clean_decode utility; (#1) update version and dependencies

This commit is contained in:
bohd4nx
2025-11-24 19:52:22 +02:00
parent 49cf8843bc
commit 76473993e2
10 changed files with 112 additions and 231 deletions
+4
View File
@@ -20,6 +20,10 @@ logs/
# Environment variables
.env
# Test files
tests/
*.test.py
# System files
.DS_Store
Thumbs.db
-180
View File
@@ -1,180 +0,0 @@
<div align="center">
<img src="fragment.svg" alt="Fragment Logo" width="120" height="120" style="border-radius: 24px;">
<h1 style="margin-top: 24px;">💎 Fragment API от @bohd4nx</h1>
<p style="font-size: 18px; margin-bottom: 24px;">
<b>Автоматизация пополнений TON, покупки Telegram Premium и Stars через Fragment.com</b>
</p>
[Сообщить об ошибке](https://github.com/bohd4nx/fragmentapi/issues) · [Запросить функцию](https://github.com/bohd4nx/fragmentapi/issues) · [**Донат TON**](https://app.tonkeeper.com/transfer/UQBUAa7KCx1ifmoEy6lF7j-822Dm_cE1j9SR7UWteu3jzukV)
</div>
---
## ✨ Возможности
- 💰 **Пополнение TON для рекламы** - Отправка TON для рекламных кампаний и покупки подарков (1-1,000,000,000 TON)
- 👑 **Подарки Telegram Premium** - Покупка Premium подписок(3, 6 или 12 месяцев)
-**Покупка Telegram Stars** - Покупка Stars для пользователей (50-1,000,000 Stars)
## 🚀 Быстрый старт
### 1. Установка
```bash
git clone https://github.com/bohd4nx/FragmentAPI.git
cd FragmentAPI
pip install -r requirements.txt
```
### 2. Настройка
Скопируйте пример конфигурации и отредактируйте:
```bash
cp .env.example .env
```
Отредактируйте файл `.env`:
```env
COOKIES = your_fragment_cookies_here
SEED = word1 word2 word3 ... word24
HASH = your_fragment_hash_here
API_KEY = your_ton_api_key_here
```
### 3. Получение необходимых данных
#### 🍪 Куки Fragment.com
**Предварительные требования**: Войдите в свой аккаунт Telegram и подключите TON кошелёк, который хотите использовать для платежей.
1. **Установите расширение Cookie Editor**:
- Скачайте из [Chrome Web Store](https://chromewebstore.google.com/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)
- Добавьте расширение в ваш браузер
2. **Извлечение куки**:
- Откройте [Fragment.com](https://fragment.com) и убедитесь, что вы вошли в систему
- Полностью обновите страницу
- Нажмите на иконку расширения Cookie Editor
- Нажмите кнопку **"Export"**
- Выберите формат **"Header String"**
- Скопируйте результат → вставьте в `COOKIES` в вашем `.env` файле
- **Важно**: Заключите значение в кавычки, чтобы точки с запятой не интерпретировались как комментарии
**Ожидаемый формат**:
```env
COOKIES = "stel_token=<TOKEN>;stel_dt=<STEL_DT>;stel_ssid=<SSID>;stel_ton_token=<TON_TOKEN>"
```
#### 🔐 Seed-фраза TON кошелька
**Если у вас ещё нет TON кошелька**:
1. **Скачайте Tonkeeper**:
- iOS: [App Store](https://apps.apple.com/app/tonkeeper/id1587742107)
- Android: [Google Play](https://play.google.com/store/apps/details?id=com.ton_keeper)
2. **Создайте новый кошелёк**:
- Откройте приложение Tonkeeper
- Нажмите **"Создать новый кошелёк"**
- **ВАЖНО**: Запишите вашу 24-словную seed-фразу на бумаге
- Храните её в безопасном месте - никогда не делитесь ей ни с кем!
- Завершите настройку кошелька
3. **Получите вашу seed-фразу**:
- Если у вас уже есть кошелёк, перейдите в Настройки → Резервное копирование
- Введите ваш пароль
- Скопируйте 24 слова → вставьте в `SEED` в вашем `.env` файле
**Формат**: `слово1 слово2 слово3 ... слово24`
#### 🔗 Хеш Fragment
**Предварительные требования**: Сначала завершите настройку куки выше.
1. **Подключите кошелёк к Fragment**:
- Перейдите на [Fragment.com](https://fragment.com)
- Убедитесь, что ваш TON кошелёк подключён
- Войдите с вашим аккаунтом Telegram
2. **Извлечение хеша**:
- Откройте **Инструменты разработчика** (F12)
- Нажмите на вкладку **"Сеть"**
- Перейдите к [покупке Stars](https://fragment.com/stars/buy)
- Выберите получателя как **"Buy for myself"**
- Обновите страницу (Ctrl+R / Cmd+R)
- Найдите сетевые запросы, содержащие `?hash=`
- Найдите значение хеша и скопируйте его → вставьте в `HASH` в вашем `.env` файле
**Ожидаемый формат**: `?hash=<ваш_хеш_здесь>`
#### 🔑 API ключ TON
1. **Получите API ключ**:
- Посетите [TON Console](https://tonconsole.com)
- Создайте аккаунт и войдите
- Сгенерируйте новый API ключ
- Скопируйте ключ → вставьте в `API_KEY` в вашем `.env` файле
**Альтернатива**: Вы также можете использовать [TON API](https://tonapi.io) для получения API ключа.
### 4. Использование
#### Запуск примеров
```bash
python main.py
```
#### Программное использование
```python
from app.methods import FragmentTon, FragmentPremium, FragmentStars
# Пополнение TON для рекламы
ton_client = FragmentTon()
result = await ton_client.topup_ton("@username", 5)
# Покупка Premium
premium_client = FragmentPremium()
result = await premium_client.buy_premium("@username", 6)
# Покупка Stars
stars_client = FragmentStars()
result = await stars_client.buy_stars("@username", 50)
```
### Поддерживаемые операции
| Операция | Метод | Параметры | Ограничения |
| ------------------- | ------------------------------- | ---------------------------------- | ------------------- |
| **Пополнение TON** | `topup_ton(username, amount)` | Имя пользователя, количество TON | 1-1,000,000,000 TON |
| **Подарок Premium** | `buy_premium(username, months)` | Имя пользователя, длительность | 3, 6 или 12 месяцев |
| **Покупка Stars** | `buy_stars(username, amount)` | Имя пользователя, количество Stars | 50-1,000,000 Stars |
### Форматы имён пользователей
Все методы принимают различные форматы имён пользователей:
- `@username` (с @)
- `username` (без @)
<div align="center">
### Made with ❤️ by [@bohd4nx](https://t.me/bohd4nx)
**Поставьте ⭐ этому репозиторию, если он оказался полезным!**
</div>
+2 -2
View File
@@ -1,4 +1,4 @@
__title__ = "Fragment API by @bohd4nx"
__version__ = "2025.1.1"
__version__ = "2025.1.2"
__author__ = "Bohdan (bohd4nx)"
__timestamp__ = "2025-11-03T12:00:00Z"
__timestamp__ = "2025-11-24T12:00:00Z"
+16 -16
View File
@@ -1,7 +1,5 @@
import base64
import logging
import re
import string
import time
import httpx
@@ -9,7 +7,7 @@ from tonutils.client import TonapiClient
from tonutils.wallet import WalletV5R1
from app.core.config import Config
from app.utils import TransactionProcessor, WalletLinker, ApiClient
from app.utils import TransactionProcessor, WalletLinker, ApiClient, clean_decode
logger = logging.getLogger(__name__)
@@ -31,20 +29,10 @@ class FragmentPremium:
'x-requested-with': 'XMLHttpRequest'
}
self.transaction_processor = TransactionProcessor(self.config, self._clean_decode)
self.transaction_processor = TransactionProcessor(self.config, clean_decode)
self.wallet_linker = WalletLinker(self.config, self.headers, self.transaction_processor)
self.api_client = ApiClient(self.config, self.headers, self.wallet_linker)
@staticmethod
def _clean_decode(s):
b = base64.b64decode(re.sub(r'[^A-Za-z0-9+/=]', '', s.strip()) + "=" * (-len(s) % 4))
t = next(
(b[i:].decode('utf-8', 'ignore') for i in range(20) if
b[i:].decode('utf-8', 'ignore').startswith("Telegram Premium")),
b.decode('utf-8', 'ignore')
)
return ''.join(c for c in t if c in string.printable or c in '\n\r\t ').strip()
async def _get_account_info(self):
client = TonapiClient(api_key=self.config['api_key'], is_testnet=False)
wallet, pub_key, _, _ = WalletV5R1.from_mnemonic(client=client, mnemonic=self.config['seed'])
@@ -67,7 +55,12 @@ class FragmentPremium:
search_data = {"query": username, "months": months, "method": "searchPremiumGiftRecipient"}
search_resp = await client.post(f"https://fragment.com/api?hash={self.config['hash']}",
headers=self.headers, data=search_data)
search_result = search_resp.json()
try:
search_result = search_resp.json()
except Exception as e:
logger.error(f"Failed to parse search response: {e}")
return {"success": False, "error": f"Invalid response from Fragment API: {str(e)}"}
recipient = search_result.get("found", {}).get("recipient")
if not recipient:
@@ -80,7 +73,14 @@ class FragmentPremium:
init_data = {"recipient": recipient, "months": months, "method": "initGiftPremiumRequest"}
init_resp = await client.post(f"https://fragment.com/api?hash={self.config['hash']}",
headers=self.headers, data=init_data)
init_result = init_resp.json()
try:
init_result = init_resp.json()
except Exception as e:
logger.error(f"Failed to parse init response: {e}")
logger.error(f"Response status: {init_resp.status_code}")
logger.error(f"Response content: {init_resp.content[:200]}")
return {"success": False, "error": f"Invalid response from Fragment API: {str(e)}"}
req_id = init_result.get("req_id")
if not req_id:
+16 -13
View File
@@ -1,6 +1,5 @@
import base64
import logging
import re
import time
import httpx
@@ -8,7 +7,7 @@ from tonutils.client import TonapiClient
from tonutils.wallet import WalletV5R1
from app.core.config import Config
from app.utils import TransactionProcessor, WalletLinker, ApiClient
from app.utils import TransactionProcessor, WalletLinker, ApiClient, clean_decode
logger = logging.getLogger(__name__)
@@ -30,18 +29,10 @@ class FragmentStars:
'x-requested-with': 'XMLHttpRequest'
}
self.transaction_processor = TransactionProcessor(self.config, self._clean_decode)
self.transaction_processor = TransactionProcessor(self.config, clean_decode)
self.wallet_linker = WalletLinker(self.config, self.headers, self.transaction_processor)
self.api_client = ApiClient(self.config, self.headers, self.wallet_linker)
@staticmethod
def _clean_decode(s):
s = re.sub(r'[^A-Za-z0-9+/=]', '', s.strip())
b = base64.b64decode(s + "=" * (-len(s) % 4))
t = b.decode('utf-8', 'ignore')
m = re.search(r'(\d+\s+Telegram\s+Stars.*?Ref#[A-Za-z0-9]+)', t, re.S)
return m.group(1).strip() if m else t.strip()
async def _get_account_info(self):
client = TonapiClient(api_key=self.config['api_key'], is_testnet=False)
wallet, pub_key, _, _ = WalletV5R1.from_mnemonic(client=client, mnemonic=self.config['seed'])
@@ -64,7 +55,12 @@ class FragmentStars:
search_data = {"query": username, "quantity": "", "method": "searchStarsRecipient"}
search_resp = await client.post(f"https://fragment.com/api?hash={self.config['hash']}",
headers=self.headers, data=search_data)
search_result = search_resp.json()
try:
search_result = search_resp.json()
except Exception as e:
logger.error(f"Failed to parse search response: {e}")
return {"success": False, "error": f"Invalid response from Fragment API: {str(e)}"}
recipient = search_result.get("found", {}).get("recipient")
if not recipient:
@@ -73,7 +69,14 @@ class FragmentStars:
init_data = {"recipient": recipient, "quantity": amount, "method": "initBuyStarsRequest"}
init_resp = await client.post(f"https://fragment.com/api?hash={self.config['hash']}",
headers=self.headers, data=init_data)
init_result = init_resp.json()
try:
init_result = init_resp.json()
except Exception as e:
logger.error(f"Failed to parse init response: {e}")
logger.error(f"Response status: {init_resp.status_code}")
logger.error(f"Response content: {init_resp.content[:200]}")
return {"success": False, "error": f"Invalid response from Fragment API: {str(e)}"}
req_id = init_result.get("req_id")
if not req_id:
+16 -12
View File
@@ -1,6 +1,5 @@
import base64
import logging
import re
import time
import httpx
@@ -8,7 +7,7 @@ from tonutils.client import TonapiClient
from tonutils.wallet import WalletV5R1
from app.core.config import Config
from app.utils import TransactionProcessor, WalletLinker, ApiClient
from app.utils import TransactionProcessor, WalletLinker, ApiClient, clean_decode
logger = logging.getLogger(__name__)
@@ -30,17 +29,10 @@ class FragmentTon:
'x-requested-with': 'XMLHttpRequest'
}
self.transaction_processor = TransactionProcessor(self.config, self._clean_decode)
self.transaction_processor = TransactionProcessor(self.config, clean_decode)
self.wallet_linker = WalletLinker(self.config, self.headers, self.transaction_processor)
self.api_client = ApiClient(self.config, self.headers, self.wallet_linker)
@staticmethod
def _clean_decode(s):
t = base64.b64decode(re.sub(r'[^A-Za-z0-9+/=]', '', s.strip()) + "=" * (-len(s) % 4)).decode('utf-8',
errors='ignore')
t = ''.join(c for c in t if c.isprintable() or c.isspace())
return t.lstrip("r0N").strip()
async def _get_account_info(self):
client = TonapiClient(api_key=self.config['api_key'], is_testnet=False)
wallet, pub_key, _, _ = WalletV5R1.from_mnemonic(client=client, mnemonic=self.config['seed'])
@@ -67,7 +59,12 @@ class FragmentTon:
search_data = {"query": username, "method": "searchAdsTopupRecipient"}
search_resp = await client.post(f"https://fragment.com/api?hash={self.config['hash']}",
headers=self.headers, data=search_data)
search_result = search_resp.json()
try:
search_result = search_resp.json()
except Exception as e:
logger.error(f"Failed to parse search response: {e}")
return {"success": False, "error": f"Invalid response from Fragment API: {str(e)}"}
recipient = search_result.get("found", {}).get("recipient")
if not recipient:
@@ -76,7 +73,14 @@ class FragmentTon:
init_data = {"recipient": recipient, "amount": amount, "method": "initAdsTopupRequest"}
init_resp = await client.post(f"https://fragment.com/api?hash={self.config['hash']}",
headers=self.headers, data=init_data)
init_result = init_resp.json()
try:
init_result = init_resp.json()
except Exception as e:
logger.error(f"Failed to parse init response: {e}")
logger.error(f"Response status: {init_resp.status_code}")
logger.error(f"Response content: {init_resp.content[:200]}")
return {"success": False, "error": f"Invalid response from Fragment API: {str(e)}"}
req_id = init_result.get("req_id")
if not req_id:
+5 -4
View File
@@ -1,5 +1,6 @@
from .client import ApiClient
from .transaction import TransactionProcessor
from .wallet import WalletLinker
from app.utils.client import ApiClient
from app.utils.decoder import clean_decode
from app.utils.transaction import TransactionProcessor
from app.utils.wallet import WalletLinker
__all__ = ['TransactionProcessor', 'WalletLinker', 'ApiClient']
__all__ = ['TransactionProcessor', 'WalletLinker', 'ApiClient', 'clean_decode']
+49
View File
@@ -0,0 +1,49 @@
import base64
import logging
import re
import string
logger = logging.getLogger(__name__)
def clean_decode(payload: str) -> str:
logger.debug(f"Original payload: {payload}")
# Decode raw bytes first
clean = ''.join(c for c in payload if c.isalnum() or c in '+/=')
clean += '=' * (-len(clean) % 4)
raw_bytes = base64.b64decode(clean)
logger.debug(f"Raw decoded bytes: {raw_bytes}")
# 1. Clean Base64
s = re.sub(r'[^A-Za-z0-9+/=]', '', payload.strip())
s += '=' * (-len(s) % 4)
# 2. Base64 -> bytes
b = base64.b64decode(s)
# 3. Decode UTF-8, ignoring invalid bytes
t = b.decode('utf-8', errors='ignore')
# 4. Remove binary characters, keep only printable + whitespace
t = ''.join(c for c in t if c in string.printable or c.isspace())
# 5. Extract main text with Ref#
match = re.search(r'([0-9]*\s*Telegram .*?Ref#[A-Za-z0-9]+)', t, re.S)
if match:
result = match.group(1).strip()
else:
result = t.strip()
logger.debug(f"Cleaned result: {result}")
return result
# payloads = [
# "te6ccgEBAgEALwABTgAAAAAxMDAwMDAwIFRlbGVncmFtIFN0YXJzIAoKUmVmI1RQb01wegEABkM3ZQ",
# "te6ccgEBAgEANAABTgAAAABUZWxlZ3JhbSBQcmVtaXVtIGZvciAxIHllYXIgCgpSZWYjcgEAEE9OQnM2cmNt",
# "te6ccgEBAgEAMAABTgAAAABUZWxlZ3JhbSBhY2NvdW50IHRvcCB1cCAKClJlZiNrMXpDRQEACFkxd3g"
# ]
# for p in payloads:
# logger.debug("\n" + clean_decode(p) + "\n")
+3 -3
View File
@@ -12,7 +12,7 @@ async def topup_ton_example():
ton_client = FragmentTon()
# @bohd4nx - target username, 5 - TON amount (integer 1-1000000000 (one billion))
result = await ton_client.topup_ton("@bohd4nx", 5)
result = await ton_client.topup_ton("@bohd4nx", 1000000000)
if result["success"]:
data = result["data"]
@@ -27,7 +27,7 @@ async def buy_premium_example():
premium_client = FragmentPremium()
# @bohd4nx - target username, 6 - months duration (3, 6, or 12 only)
result = await premium_client.buy_premium("@bohd4nx", 6)
result = await premium_client.buy_premium("@bohd4nx", 12)
if result["success"]:
data = result["data"]
@@ -42,7 +42,7 @@ async def buy_stars_example():
stars_client = FragmentStars()
# @bohd4nx - target username, 50 - stars amount (integer 50-1000000 (one million))
result = await stars_client.buy_stars("@bohd4nx", 50)
result = await stars_client.buy_stars("@bohd4nx", 1000000)
if result["success"]:
data = result["data"]
+1 -1
View File
@@ -1,4 +1,4 @@
python-dotenv==1.2.1
asyncio==4.0.0
httpx==0.28.1
tonutils==0.5.3
tonutils==0.5.5