import asyncio import math import random import time from collections.abc import Generator from datetime import datetime from enum import Enum import aiohttp from aiohttp_proxy import ProxyConnector from aiohttp_socks import ProxyConnector as SocksProxyConnector from pyrogram import Client from pytz import UTC from bot.config.headers import headers from bot.config.logger import log from bot.config.settings import Strategy, config from bot.core.api_js_helpers.bet_counter import BetCounter from .api import CryptoBotApi from .errors import TapsError from .models import DbSkill, DbSkills, Profile, ProfileData, SessionData, SkillLevel from .utils import load_codes_from_files, num_prettier class CryptoBot(CryptoBotApi): def __init__(self, tg_client: Client, additional_data: dict) -> None: super().__init__(tg_client) self.temporary_stop_taps_time = 0 self.bet_calculator = BetCounter(self) self.pvp_count = config.PVP_COUNT self.authorized = False self.settings_was_set = False self.sleep_time = config.BOT_SLEEP_TIME self.additional_data: SessionData = SessionData.model_validate( {k: v for d in additional_data for k, v in d.items()} ) async def claim_daily_reward(self) -> None: for day, status in self.data_after.daily_rewards.items(): if status == "canTake": await self.daily_reward(json_body={"data": str(day)}) self.logger.success("Daily reward claimed") return async def perform_taps(self, profile: Profile) -> None: self.logger.info("Taps started") energy = profile.energy while True: taps_per_second = random.randint(*config.TAPS_PER_SECOND) seconds = random.randint(5, 8) earned_money = profile.money_per_tap * taps_per_second * seconds energy_spent = math.ceil(earned_money / 2) energy -= energy_spent if energy < 0: self.logger.info("Taps stopped (not enough energy)") break await asyncio.sleep(delay=seconds) try: json_data = { "data": { "data": {"task": {"amount": earned_money, "currentEnergy": energy}}, "seconds": seconds, } } energy = await self.api_perform_taps(json_body=json_data) self.logger.success( f"Earned money: +{num_prettier(earned_money)} | Energy left: {num_prettier(energy)}" ) except TapsError as e: self.logger.warning(f"Taps stopped ({e.message})") self.temporary_stop_taps_time = time.monotonic() + 60 * 60 * 3 break async def execute_and_claim_daily_quest(self) -> None: helper_data = await self.get_helper() helper_data.youtube.update(load_codes_from_files()) all_daily_quests = await self.all_daily_quests() for key, value in all_daily_quests.items(): desc = value["description"] if ( value["type"] == "youtube" and not value["isRewarded"] and (code := helper_data.youtube.get(value["description"])) is not None ): await self.daily_quest_reward(json_body={"data": {"quest": key, "code": str(code)}}) self.logger.info(f"Quest {desc} claimed") elif desc: self.logger.info(f"Quest not executed: \n{desc}") if not value["isRewarded"] and value["isComplete"] and not value["url"]: await self.daily_quest_reward(json_body={"data": {"quest": key, "code": None}}) self.logger.info(f"Quest {key} claimed") async def claim_all_executed_quest(self) -> None: for i in self.data_after.quests: if not i["isRewarded"]: if config.SKIP_IMPROVE_DISCIPLINE_BUG and i["key"] == "improve_discipline": continue await self.quest_reward_claim(json_body={"data": [i["key"], None]}) self.logger.info(f'Quest {i["key"]} claimed ') def random_pvp_count(self) -> int: return random.randint(config.PVP_COUNT, config.PVP_COUNT * 2) async def _perform_pvp(self, league: dict, strategy: str) -> None: self.pvp_count = self.random_pvp_count() self.logger.info( f"PvP negotiations started | League: {league['key']} | Strategy: {strategy}" ) res = await self.get_pvp_info() await self.sleeper() if res.get("fight"): await self.get_pvp_claim() await self.sleeper() current_strategy = strategy money = 0 while self.pvp_count > 0: if self.balance < int(league["maxContract"]): money_str = ( f"Profit: +{num_prettier(money)}" if money >= 0 else f"Loss: -{num_prettier(money)}" ) self.logger.info(f"PvP negotiations stopped (not enough money). Pvp profit: {money_str}") break if strategy == "random": current_strategy = random.choice(self.strategies) self.logger.info("Searching opponent...") current_strategy = current_strategy.value if isinstance(current_strategy, Enum) else current_strategy json_data = {"data": {"league": league["key"], "strategy": current_strategy}} response_json = await self.get_pvp_fight(json_body=json_data) if response_json is None: await self.sleeper(delay=10, additional_delay=5) continue fight = response_json.fight opponent_strategy = ( fight.player2Strategy if fight.player1 == self.user_profile.user_id else fight.player1Strategy ) if fight.winner == self.user_profile.user_id: money += fight.moneyProfit log_part = f"You WIN (+{num_prettier(fight.moneyProfit)})" else: money -= fight.moneyContract log_part = f"You LOSE (-{num_prettier(fight.moneyProfit)})" self.logger.success( f"Contract sum: {num_prettier(fight.moneyContract)} | " f"Your strategy: {current_strategy} | " f"Opponent strategy: {opponent_strategy} | " f"{log_part}" ) await self.sleeper(additional_delay=10) await self.get_pvp_claim() self.pvp_count -= 1 await self.sleeper() self.logger.info( "Total money after all pvp:" + (f"+{num_prettier(money)}" if money >= 0 else f"{num_prettier(money)}") ) self.pvp_count = config.PVP_COUNT async def get_friend_reward(self) -> None: for friend in [friend for friend in self.data_after.friends if friend["bonusToTake"] > 0]: await self.friend_reward(json_body={"data": friend["id"]}) self.logger.info( f"Friend {friend['name']} claimed money {num_prettier(friend['bonusToTake'])}" ) await self.sleeper() async def solve_quiz_and_rebus(self) -> None: for quest in self.dbs["dbQuests"]: quest_key = quest["key"] if quest["requiredLevel"] > self.user_profile.level: continue if "t.me" in (link := quest.get("actionUrl")) and not self._is_event_solved(quest_key): if len(link.split("/")) > 4 or "muskempire" in link: continue if quest["checkType"] != "fakeCheck": link = link if "/+" in link else link.split("/")[-1] await self.join_and_archive_channel(link) await self.quest_check(json_body={"data": [quest_key]}) self.logger.info( f'Claimed {quest["title"]} Reward: +{num_prettier(quest["rewardMoney"])}quest' ) if any(i in quest_key for i in ("riddle", "rebus", "tg_story")) and not self._is_event_solved(quest_key): await self.quest_check(json_body={"data": [quest_key, quest["checkData"]]}) self.logger.info(f"Was solved {quest['title']}") def _is_event_solved(self, quest_key: str) -> bool: return self.data_after.quests and any(i["key"] == quest_key for i in self.data_after.quests) async def set_funds(self) -> None: helper_data = await self.get_helper() if helper_data.funds: current_invest = await self.get_funds_info() already_funded = {i["fundKey"] for i in current_invest["funds"]} for fund in list(helper_data.funds - already_funded)[: 3 - len(already_funded)]: if self.balance > (amount := self.bet_calculator.calculate_bet()): self.logger.info(f"Investing {num_prettier(amount)} to fund {fund}") await self.invest(json_body={"data": {"fund": fund, "money": amount}}) else: self.logger.info("Not enough money for invest") async def starting_pvp(self) -> None: if self.dbs: league_data = None for league in self.dbs["dbNegotiationsLeague"]: if league["key"] == config.PVP_LEAGUE: league_data = league break if league_data is not None: if self.level >= int(league_data["requiredLevel"]): self.strategies = [strategy["key"] for strategy in self.dbs["dbNegotiationsStrategy"]] if Strategy.random == config.PVP_STRATEGY or config.PVP_STRATEGY in self.strategies: await self._perform_pvp( league=league_data, strategy=config.PVP_STRATEGY.value, ) else: config.PVP_ENABLED = False self.logger.warning("PVP_STRATEGY param is invalid. PvP negotiations disabled.") else: config.PVP_ENABLED = False self.logger.warning( f"Your level is too low for the {config.PVP_LEAGUE} league. PvP negotiations disabled." ) else: config.PVP_ENABLED = False self.logger.warning("PVP_LEAGUE param is invalid. PvP negotiations disabled.") else: self.logger.warning("Database is missing. PvP negotiations will be skipped this time.") async def upgrade_hero(self) -> None: available_skill = list(self._get_available_skills()) if config.AUTO_UPGRADE_HERO: await self._upgrade_hero_skill(available_skill) if config.AUTO_UPGRADE_MINING: await self._upgrade_mining_skill(available_skill) async def get_box_rewards(self) -> None: boxes = await self.get_box_list() for key, box_count in boxes.items(): for _ in range(box_count): res = await self.box_open(json_body={"data": key}) self.logger.info(f"Box {key} Was looted: {res['loot']}") async def _upgrade_mining_skill(self, available_skill: list[DbSkill]) -> None: for skill in [skill for skill in available_skill if skill.category == "mining"]: if ( skill.key in config.MINING_ENERGY_SKILLS and skill.next_level <= config.MAX_MINING_ENERGY_RECOVERY_UPGRADE_LEVEL or ( skill.next_level <= config.MAX_MINING_UPGRADE_LEVEL or skill.skill_price <= config.MAX_MINING_UPGRADE_COSTS ) ): await self._upgrade_skill(skill) def _is_enough_money_for_upgrade(self, skill: DbSkill) -> bool: return (self.balance - skill.skill_price) >= config.MONEY_TO_SAVE async def _upgrade_hero_skill(self, available_skill: list[DbSkill]) -> None: for skill in sorted( [skill for skill in available_skill if skill.weight], key=lambda x: x.weight, reverse=True, ): if skill.title in config.SKIP_TO_UPGRADE_SKILLS: continue # if skill.weight >= config.SKILL_WEIGHT or skill.skill_price <= config.MAX_SKILL_UPGRADE_COSTS: if skill.weight >= config.SKILL_WEIGHT: await self._upgrade_skill(skill) async def _upgrade_skill(self, skill: DbSkill) -> None: if self._is_enough_money_for_upgrade(skill): try: await self.skills_improve(json_body={"data": skill.key}) self.logger.info( f"Skill: {skill.title} upgraded to level: {skill.next_level} " f"Profit: {num_prettier(skill.skill_profit)} " f"Costs: {num_prettier(skill.skill_price)} " f"Money stay: {num_prettier(self.balance)} " f"Skill weight {skill.weight:.5f}" ) await self.sleeper() except ValueError: self.logger.exception(f"Failed to upgrade skill: {skill}") raise def _get_available_skills(self) -> Generator[DbSkill, None, None]: for skill in DbSkills(**self.dbs).dbSkills: self._calkulate_skill_requirements(skill) if self._is_available_to_upgrade_skills(skill): yield skill def _calkulate_skill_requirements(self, skill: DbSkill) -> None: skill.next_level = ( self.data_after.skills[skill.key]["level"] + 1 if self.data_after.skills.get(skill.key) else 1 ) skill.skill_profit = skill.calculate_profit(skill.next_level) skill.skill_price = skill.price_for_level(skill.next_level) skill.weight = skill.skill_profit / skill.skill_price skill.progress_time = skill.get_skill_time(self.data_after) def _is_available_to_upgrade_skills(self, skill: DbSkill) -> bool: # check the current skill is still in the process of improvement if skill.progress_time and skill.progress_time.timestamp() + 60 > datetime.now(UTC).timestamp(): return False if skill.next_level > skill.maxLevel: return False skill_requirements = skill.get_level_by_skill_level(skill.next_level) if not skill_requirements: return True return ( len(self.data_after.friends) >= skill_requirements.requiredFriends and self.user_profile.level >= skill_requirements.requiredHeroLevel and self._is_can_learn_skill(skill_requirements) ) def _is_can_learn_skill(self, level: SkillLevel) -> bool: if not level.requiredSkills: return True for skill, level in level.requiredSkills.items(): if skill not in self.data_after.skills: return False if self.data_after.skills[skill]["level"] >= level: return True return False async def login_to_app(self, proxy: str | None) -> bool: if self.authorized: return True tg_web_data = await self.get_tg_web_data(proxy=proxy) self.http_client.headers["Api-Key"] = tg_web_data.hash if await self.login(json_body=tg_web_data.request_data): self.authorized = True return True return False async def run(self, proxy: str | None) -> None: proxy = proxy or self.additional_data.proxy if proxy and "socks" in proxy: proxy_conn = SocksProxyConnector.from_url(proxy) elif proxy: proxy_conn = ProxyConnector.from_url(proxy) else: proxy_conn = None async with aiohttp.ClientSession( headers=headers, connector=proxy_conn, timeout=aiohttp.ClientTimeout(total=60), ) as http_client: self.http_client = http_client if proxy: await self.check_proxy(proxy=proxy) while True: if self.errors >= config.ERRORS_BEFORE_STOP: self.logger.error("Bot stopped (too many errors)") break try: if await self.login_to_app(proxy): # if not self.settings_was_set: # await self.sent_eng_settings() data = await self.get_profile_full() self.dbs = data["dbData"] await self.get_box_rewards() self.user_profile: ProfileData = ProfileData(**data) if self.user_profile.offline_bonus > 0: await self.get_offline_bonus() profile = await self.syn_hero_balance() config.MONEY_TO_SAVE = self.bet_calculator.max_bet() self.logger.info(f"Max bet for funds saved: {num_prettier(config.MONEY_TO_SAVE)}") self.data_after = await self.user_data_after() await self.claim_daily_reward() await self.execute_and_claim_daily_quest() await self.syn_hero_balance() await self.get_friend_reward() if config.TAPS_ENABLED and profile.energy and time.monotonic() > self.temporary_stop_taps_time: await self.perform_taps(profile) await self.set_funds() await self.solve_quiz_and_rebus() await self.claim_all_executed_quest() await self.syn_hero_balance() await self.upgrade_hero() if config.PVP_ENABLED: await self.starting_pvp() await self.syn_hero_balance() sleep_time = random.randint(*config.BOT_SLEEP_TIME) self.logger.info(f"Sleep minutes {sleep_time // 60} minutes") await asyncio.sleep(sleep_time) except RuntimeError as error: raise error from error except Exception: self.errors += 1 self.authorized = False self.logger.exception("Unknown error") await self.sleeper(additional_delay=self.errors * 8) else: self.errors = 0 self.authorized = False async def run_bot(tg_client: Client, proxy: str | None, additional_data: dict) -> None: try: await CryptoBot(tg_client=tg_client, additional_data=additional_data).run(proxy=proxy) except RuntimeError: log.bind(session_name=tg_client.name).exception("Session error")