mirror of
https://github.com/vibe-existing/pyfragment.git
synced 2026-07-25 06:54:31 +00:00
feat: Enhance Fragment API with improved error handling and clean_decode utility; (#1) update version and dependencies
This commit is contained in:
@@ -20,6 +20,10 @@ logs/
|
||||
# Environment variables
|
||||
.env
|
||||
|
||||
# Test files
|
||||
tests/
|
||||
*.test.py
|
||||
|
||||
# System files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
-180
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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")
|
||||
@@ -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
@@ -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
|
||||
Reference in New Issue
Block a user