|
import asyncio, aiohttp, random, json |
|
from time import time |
|
from urllib.parse import unquote |
|
from typing import Any, Dict |
|
from aiohttp_proxy import ProxyConnector |
|
from better_proxy import Proxy |
|
from pyrogram import Client |
|
from pyrogram.errors import Unauthorized, UserDeactivated, AuthKeyUnregistered |
|
from pyrogram.raw.functions.messages import RequestWebView |
|
|
|
from bot.utils.logger import log |
|
from bot.utils.settings import config |
|
from .headers import headers |
|
|
|
class CryptoBot: |
|
def __init__(self, tg_client: Client): |
|
self.session_name = tg_client.name |
|
self.tg_client = tg_client |
|
self.errors = 0 |
|
|
|
async def get_tg_web_data(self, proxy: str | None) -> str: |
|
if proxy: |
|
proxy = Proxy.from_str(proxy) |
|
proxy_dict = dict( |
|
scheme=proxy.protocol, |
|
hostname=proxy.host, |
|
port=proxy.port, |
|
username=proxy.login, |
|
password=proxy.password |
|
) |
|
else: |
|
proxy_dict = None |
|
|
|
self.tg_client.proxy = proxy_dict |
|
|
|
try: |
|
if not self.tg_client.is_connected: |
|
try: |
|
await self.tg_client.connect() |
|
except (Unauthorized, UserDeactivated, AuthKeyUnregistered) as error: |
|
raise RuntimeError(str(error)) from error |
|
web_view = await self.tg_client.invoke(RequestWebView( |
|
peer=await self.tg_client.resolve_peer('BlumCryptoBot'), |
|
bot=await self.tg_client.resolve_peer('BlumCryptoBot'), |
|
platform='android', |
|
from_bot_menu=False, |
|
url='https://telegram.blum.codes/' |
|
)) |
|
auth_url = web_view.url |
|
tg_web_data = unquote( |
|
string=auth_url.split('tgWebAppData=', maxsplit=1)[1].split('&tgWebAppVersion', maxsplit=1)[0]) |
|
if self.tg_client.is_connected: |
|
await self.tg_client.disconnect() |
|
|
|
return tg_web_data |
|
|
|
except RuntimeError as error: |
|
raise error |
|
|
|
except Exception as error: |
|
log.error(f"{self.session_name} | Authorization error: {error}") |
|
await asyncio.sleep(delay=3) |
|
|
|
async def login(self, init_data: str) -> tuple[str | bool, str]: |
|
url = 'https://user-domain.blum.codes/api/v1/auth/provider/PROVIDER_TELEGRAM_MINI_APP' |
|
try: |
|
log.info(f"{self.session_name} | Trying to login...") |
|
self.http_client.headers.pop('Authorization', None) |
|
json_data = {'query': init_data} |
|
response = await self.http_client.post(url, json=json_data) |
|
response.raise_for_status() |
|
response_json = await response.json() |
|
token = response_json.get('token', {}) |
|
access_token = token.get('access', '') |
|
refresh_token = token.get('refresh', '') |
|
log.success(f"{self.session_name} | Login successful") |
|
return access_token, refresh_token |
|
except aiohttp.ClientResponseError as error: |
|
if error.status == 401: self.authorized = False |
|
self.errors += 1 |
|
log.error(f"{self.session_name} | Login http error: {error}") |
|
await asyncio.sleep(delay=3) |
|
return False, '' |
|
except Exception as error: |
|
log.error(f"{self.session_name} | Login error: {error}") |
|
await asyncio.sleep(delay=3) |
|
return False, '' |
|
|
|
async def get_profile(self) -> Dict[str, Any]: |
|
url = 'https://game-domain.blum.codes/api/v1/user/balance' |
|
try: |
|
await self.http_client.options(url) |
|
response = await self.http_client.get(url) |
|
response.raise_for_status() |
|
response_json = await response.json() |
|
return response_json |
|
except aiohttp.ClientResponseError as error: |
|
if error.status == 401: self.authorized = False |
|
self.errors += 1 |
|
log.error(f"{self.session_name} | Profile data http error: {error}") |
|
await asyncio.sleep(delay=3) |
|
return {} |
|
except Exception as error: |
|
log.error(f"{self.session_name} | Profile data error: {error}") |
|
await asyncio.sleep(delay=3) |
|
return {} |
|
|
|
async def daily_reward(self) -> bool: |
|
url = 'https://game-domain.blum.codes/api/v1/daily-reward?offset=-180' |
|
await self.http_client.options(url) |
|
response = await self.http_client.post(url) |
|
content_type = response.headers.get('Content-Type', '') |
|
try: |
|
if 'application/json' in content_type: |
|
response_json = await response.json() |
|
if response_json.get('message', '') != 'same day': |
|
log.warning(f"{self.session_name} | Unknown response in daily reward: {str(response_json)}") |
|
return False |
|
else: |
|
response_text = await response.text() |
|
if response_text == 'OK': |
|
return True |
|
else: |
|
log.warning(f"{self.session_name} | Unknown response in daily reward: {response_text}") |
|
return False |
|
except aiohttp.ClientResponseError as error: |
|
if error.status == 401: self.authorized = False |
|
log.error(f"{self.session_name} | Daily reward http error: {str(error)}") |
|
return False |
|
except Exception as error: |
|
log.error(f"{self.session_name} | Daily reward error: {str(error)}") |
|
return False |
|
|
|
async def farming_claim(self) -> bool: |
|
url = 'https://game-domain.blum.codes/api/v1/farming/claim' |
|
try: |
|
await self.http_client.options(url) |
|
response = await self.http_client.post(url) |
|
response.raise_for_status() |
|
response_json = await response.json() |
|
balance = response_json.get('availableBalance', False) |
|
if balance is not False: |
|
self.balance = int(float(balance)) |
|
return True |
|
else: return False |
|
except aiohttp.ClientResponseError as error: |
|
if error.status == 401: self.authorized = False |
|
self.errors += 1 |
|
log.error(f"{self.session_name} | Claim http error: {error}") |
|
await asyncio.sleep(delay=3) |
|
return False |
|
except Exception as error: |
|
log.error(f"{self.session_name} | Claim error: {error}") |
|
await asyncio.sleep(delay=3) |
|
return False |
|
|
|
async def farming_start(self) -> bool: |
|
url = 'https://game-domain.blum.codes/api/v1/farming/start' |
|
try: |
|
await self.http_client.options(url) |
|
response = await self.http_client.post(url) |
|
response.raise_for_status() |
|
return True |
|
except aiohttp.ClientResponseError as error: |
|
if error.status == 401: self.authorized = False |
|
self.errors += 1 |
|
log.error(f"{self.session_name} | Starting farm http error: {error}") |
|
await asyncio.sleep(delay=3) |
|
return False |
|
except Exception as error: |
|
log.error(f"{self.session_name} | Starting farm error: {error}") |
|
await asyncio.sleep(delay=3) |
|
return False |
|
|
|
async def perform_friend_rewards(self) -> None: |
|
balance_url = 'https://user-domain.blum.codes/api/v1/friends/balance' |
|
claim_url = 'https://user-domain.blum.codes/api/v1/friends/claim' |
|
try: |
|
await self.http_client.options(balance_url) |
|
response = await self.http_client.get(balance_url) |
|
response_json = await response.json() |
|
claim_amount = float(response_json.get('amountForClaim', 0)) |
|
can_claim = response_json.get('canClaim', False) |
|
if claim_amount > 0 and can_claim is not False: |
|
log.info(f"{self.session_name} | Reward for friends available") |
|
await asyncio.sleep(3) |
|
await self.http_client.options(claim_url) |
|
response = await self.http_client.post(claim_url) |
|
response_json = await response.json() |
|
claim_amount = float(response_json.get('claimBalance', 0)) |
|
if claim_amount > 0: |
|
log.success(f"{self.session_name} | Claimed {str(claim_amount)} points") |
|
self.errors = 0 |
|
else: |
|
log.warning(f"{self.session_name} | Unable to claim friend reward") |
|
else: |
|
log.info(f"{self.session_name} | Reward for friends not available") |
|
except aiohttp.ClientResponseError as error: |
|
if error.status == 401: self.authorized = False |
|
self.errors += 1 |
|
log.error(f"{self.session_name} | Friend reward http error: {error}") |
|
await asyncio.sleep(delay=3) |
|
except Exception as error: |
|
log.error(f"{self.session_name} | Friend reward error: {error}") |
|
await asyncio.sleep(delay=3) |
|
|
|
async def perform_games(self, games: int) -> None: |
|
start_url = 'https://game-domain.blum.codes/api/v1/game/play' |
|
finish_url = 'https://game-domain.blum.codes/api/v1/game/claim' |
|
|
|
if games > 4: games = 4 |
|
while games > 0: |
|
try: |
|
await self.http_client.options(start_url) |
|
response = await self.http_client.post(start_url) |
|
response.raise_for_status() |
|
response_json = await response.json() |
|
game_id = response_json.get('gameId', False) |
|
|
|
if game_id is not False: |
|
log.info(f"{self.session_name} | Game started") |
|
await asyncio.sleep(30) |
|
points = random.randint(*config.GAME_POINTS) |
|
json_data = {'gameId': game_id, "points": points} |
|
await self.http_client.options(finish_url) |
|
response = await self.http_client.post(finish_url, json=json_data) |
|
response.raise_for_status() |
|
response_text = await response.text() |
|
if response_text == 'OK': |
|
log.success(f"{self.session_name} | Game completed (+{points} points)") |
|
self.errors = 0 |
|
games -= 1 |
|
await asyncio.sleep(random.randint(*config.SLEEP_BETWEEN_GAME)) |
|
except aiohttp.ClientResponseError as error: |
|
if error.status == 401: self.authorized = False |
|
self.errors += 1 |
|
log.error(f"{self.session_name} | Games http error: {error}") |
|
await asyncio.sleep(delay=3) |
|
except Exception as error: |
|
log.error(f"{self.session_name} | Games error: {error}") |
|
await asyncio.sleep(delay=3) |
|
|
|
async def perform_tasks(self) -> None: |
|
url = 'https://earn-domain.blum.codes/api/v1/tasks' |
|
try: |
|
await self.http_client.options(url) |
|
response = await self.http_client.get(url) |
|
response.raise_for_status() |
|
response_json = await response.json() |
|
started = 0 |
|
completed = 0 |
|
for category in response_json: |
|
tasks = category.get('tasks', []) |
|
for task in tasks: |
|
if started == 2 or completed == 2: break |
|
if task['status'] == 'FINISHED'or task.get('isHidden', False): continue |
|
if 'socialSubscription' not in task or task.get('socialSubscription', {}).get('openInTelegram', False): continue |
|
log.info(f"{self.session_name} | Processing task {task['id']}") |
|
if task['status'] == 'NOT_STARTED': |
|
await self.http_client.post(f"https://earn-domain.blum.codes/api/v1/tasks/{task['id']}/start") |
|
await asyncio.sleep(random.randint(4, 8)) |
|
started += 1 |
|
elif task['status'] == 'READY_FOR_CLAIM': |
|
await self.http_client.post(f"https://earn-domain.blum.codes/api/v1/tasks/{task['id']}/claim") |
|
await asyncio.sleep(1) |
|
log.success(f"{self.session_name} | Task {task['id']} completed and reward claimed") |
|
self.errors = 0 |
|
await asyncio.sleep(random.randint(2, 4)) |
|
completed += 1 |
|
sub_sections = category.get('subSections', []) |
|
for sub_section in sub_sections: |
|
tasks = sub_section.get('tasks', []) |
|
for task in tasks: |
|
if started == 2 or completed == 2: break |
|
if task['status'] == 'FINISHED' or task.get('isHidden', False): continue |
|
if 'socialSubscription' not in task or task.get('socialSubscription', {}).get('openInTelegram', False): continue |
|
log.info(f"{self.session_name} | Processing task {task['id']}") |
|
if task['status'] == 'NOT_STARTED': |
|
await self.http_client.post(f"https://earn-domain.blum.codes/api/v1/tasks/{task['id']}/start") |
|
await asyncio.sleep(random.randint(4, 8)) |
|
started += 1 |
|
elif task['status'] == 'READY_FOR_CLAIM': |
|
await self.http_client.post(f"https://earn-domain.blum.codes/api/v1/tasks/{task['id']}/claim") |
|
await asyncio.sleep(1) |
|
log.success(f"{self.session_name} | Task {task['id']} completed and reward claimed") |
|
self.errors = 0 |
|
await asyncio.sleep(random.randint(2, 4)) |
|
completed += 1 |
|
except aiohttp.ClientResponseError as error: |
|
if error.status == 401: self.authorized = False |
|
self.errors += 1 |
|
log.error(f"{self.session_name} | Tasks http error: {error}") |
|
await asyncio.sleep(delay=3) |
|
except Exception as error: |
|
log.error(f"{self.session_name} | Tasks error: {error}") |
|
self.errors += 1 |
|
await asyncio.sleep(delay=3) |
|
|
|
async def refresh_tokens(self) -> str | bool: |
|
url = 'https://user-domain.blum.codes/api/v1/auth/refresh' |
|
try: |
|
await self.http_client.options(url) |
|
json_data = {'refresh': self.refresh_token} |
|
response = await self.http_client.post(url, json=json_data) |
|
response.raise_for_status() |
|
response_json = await response.json() |
|
self.access_token = response_json.get('access', '') |
|
self.refresh_token = response_json.get('refresh', '') |
|
return True if self.access_token != '' else False |
|
except aiohttp.ClientResponseError as error: |
|
if error.status == 401: self.authorized = False |
|
self.errors += 1 |
|
log.warning(f"{self.session_name} | Refresh auth tokens http error") |
|
return False |
|
except Exception: |
|
log.warning(f"{self.session_name} | Refresh auth tokens error") |
|
return False |
|
|
|
async def check_proxy(self, proxy: Proxy) -> None: |
|
try: |
|
response = await self.http_client.get(url='https://httpbin.org/ip', timeout=aiohttp.ClientTimeout(5)) |
|
ip = (await response.json()).get('origin') |
|
log.info(f"{self.session_name} | Proxy IP: {ip}") |
|
except Exception as error: |
|
log.error(f"{self.session_name} | Proxy: {proxy} | Error: {error}") |
|
|
|
async def run(self, proxy: str | None) -> None: |
|
access_token_created_time = 0 |
|
proxy_conn = ProxyConnector().from_url(proxy) if proxy else None |
|
|
|
async with aiohttp.ClientSession(headers=headers, connector=proxy_conn) as http_client: |
|
self.http_client = http_client |
|
if proxy: |
|
await self.check_proxy(proxy=proxy) |
|
|
|
self.authorized = False |
|
while True: |
|
if self.errors >= config.ERRORS_BEFORE_STOP: |
|
log.error(f"{self.session_name} | Bot stopped (too many errors)") |
|
break |
|
try: |
|
if not self.authorized: |
|
tg_web_data = await self.get_tg_web_data(proxy=proxy) |
|
access_token, refresh_token = await self.login(init_data=tg_web_data) |
|
if access_token is not False and refresh_token != '': |
|
self.authorized = True |
|
self.access_token = access_token |
|
self.refresh_token = refresh_token |
|
self.http_client.headers['Authorization'] = 'Bearer ' + access_token |
|
access_token_created_time = time() |
|
else: continue |
|
|
|
if time() - access_token_created_time >= 3600: |
|
refresh_success = await self.refresh_tokens() |
|
if refresh_success: |
|
self.http_client.headers['Authorization'] = 'Bearer ' + self.access_token |
|
access_token_created_time = time() |
|
else: |
|
self.authorized = False |
|
continue |
|
|
|
daily_claimed = await self.daily_reward() |
|
if daily_claimed: |
|
log.success(f"{self.session_name} | Daily reward claimed") |
|
self.errors = 0 |
|
else: |
|
log.info(f"{self.session_name} | Daily reward not available") |
|
|
|
profile = await self.get_profile() |
|
self.balance = profile['availableBalance'] |
|
log.info(f"{self.session_name} | Balance: {self.balance}") |
|
|
|
await asyncio.sleep(random.randint(2, 4)) |
|
await self.perform_friend_rewards() |
|
|
|
games_left = profile['playPasses'] |
|
system_time = profile['timestamp'] // 1000 |
|
farming = profile.get('farming', {}) |
|
farm_start = farming.get('startTime', 0) // 1000 |
|
farm_end = farming.get('endTime', 0) // 1000 |
|
|
|
await asyncio.sleep(random.randint(2, 4)) |
|
if farm_start == 0 or farm_end == 0: |
|
log.info(f"{self.session_name} | Start farming...") |
|
if await self.farming_start(): |
|
log.success(f"{self.session_name} | Farming started successfully") |
|
self.errors = 0 |
|
elif system_time > farm_end: |
|
log.info(f"{self.session_name} | Time to claim and restart farming") |
|
if await self.farming_claim(): |
|
log.success(f"{self.session_name} | Claim successful") |
|
self.errors = 0 |
|
else: continue |
|
if await self.farming_start(): |
|
log.success(f"{self.session_name} | Farming restarted successfully") |
|
self.errors = 0 |
|
else: continue |
|
|
|
await asyncio.sleep(random.randint(2, 4)) |
|
await self.perform_tasks() |
|
|
|
if config.GAMES_ENABLED and games_left > 0: |
|
await asyncio.sleep(random.randint(2, 4)) |
|
await self.perform_games(games=games_left) |
|
|
|
|
|
profile = await self.get_profile() |
|
self.balance = profile['availableBalance'] |
|
log.info(f"{self.session_name} | Balance: {self.balance}") |
|
|
|
if system_time < farm_end: |
|
claim_wait = farm_end - system_time |
|
hours = claim_wait // 3600 |
|
minutes = (claim_wait % 3600) // 60 |
|
log.info(f"{self.session_name} | Waiting for {hours} hours and {minutes} minutes before claiming and restarting") |
|
await asyncio.sleep(claim_wait) |
|
|
|
except RuntimeError as error: |
|
raise error |
|
except Exception as error: |
|
log.error(f"{self.session_name} | Unknown error: {error}") |
|
await asyncio.sleep(delay=3) |
|
else: |
|
log.info(f"Sleep 1 min") |
|
await asyncio.sleep(delay=60) |
|
|
|
async def run_bot(tg_client: Client, proxy: str | None): |
|
try: |
|
await CryptoBot(tg_client=tg_client).run(proxy=proxy) |
|
except RuntimeError as error: |
|
log.error(f"{tg_client.name} | Session error: {str(error)}") |
|
|