Spaces:
Paused
Paused
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: <y>+{num_prettier(earned_money)}</y> | Energy left: <blue>{num_prettier(energy)}</blue>" | |
) | |
except TapsError as e: | |
self.logger.warning(f"Taps stopped (<red>{e.message}</red>)") | |
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 <g>{desc}</g> claimed") | |
elif desc: | |
self.logger.info(f"Quest not executed: \n<r>{desc}</r>") | |
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 <g>{key}</g> 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 <g>{i["key"]}</g> 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: <blue>{league['key']}</blue> | Strategy: <g>{strategy}</g>" | |
) | |
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: <y>+{num_prettier(money)}</y>" | |
if money >= 0 | |
else f"Loss: <red>-{num_prettier(money)}</red>" | |
) | |
self.logger.info(f"PvP negotiations stopped (<red>not enough money</red>). 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 <g>WIN</g> (<y>+{num_prettier(fight.moneyProfit)})</y>" | |
else: | |
money -= fight.moneyContract | |
log_part = f"You <red>LOSE</red> (<y>-{num_prettier(fight.moneyProfit)}</y>)" | |
self.logger.success( | |
f"Contract sum: <y>{num_prettier(fight.moneyContract)}</y> | " | |
f"Your strategy: <c>{current_strategy}</c> | " | |
f"Opponent strategy: <blue>{opponent_strategy}</blue> | " | |
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"<i><g>+{num_prettier(money)}</g></i>" if money >= 0 else f"<i><red>{num_prettier(money)}</red></i>") | |
) | |
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 <g>{friend['name']}</g> claimed money <y>{num_prettier(friend['bonusToTake'])}</y>" | |
) | |
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 <g>{quest["title"]}</g> Reward: <y>+{num_prettier(quest["rewardMoney"])}</y>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 <g>{quest['title']}</g>") | |
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 <y>{num_prettier(amount)}</y> to fund <blue>{fund}</blue>") | |
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 <g>{key}</g> Was looted: <y>{res['loot']}</y>") | |
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: <blue>{skill.title}</blue> upgraded to level: <c>{skill.next_level}</c> " | |
f"Profit: <y>{num_prettier(skill.skill_profit)}</y> " | |
f"Costs: <blue>{num_prettier(skill.skill_price)}</blue> " | |
f"Money stay: <y>{num_prettier(self.balance)}</y> " | |
f"Skill weight <magenta>{skill.weight:.5f}</magenta>" | |
) | |
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: <y>{num_prettier(config.MONEY_TO_SAVE)}</y>") | |
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") | |