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
+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")