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