diff --git a/.env-example b/.env-example index b6df8df2..81f2e4d8 100644 --- a/.env-example +++ b/.env-example @@ -8,11 +8,14 @@ AUTO_UPGRADE= MAX_LEVEL= MIN_PROFIT= MAX_PRICE= +MAX_PRICE_LIMIT= +BEST_CARD_ONLY= BALANCE_TO_SAVE= UPGRADES_COUNT= MAX_COMBO_PRICE= +BEST_COMBO_ONLY= APPLY_COMBO= APPLY_PROMO_CODES= diff --git a/bot/config/config.py b/bot/config/config.py index 70e955a3..05446a24 100644 --- a/bot/config/config.py +++ b/bot/config/config.py @@ -15,11 +15,14 @@ class Settings(BaseSettings): MAX_LEVEL: int = 20 MIN_PROFIT: int = 1000 MAX_PRICE: int = 50000000 + MAX_PRICE_LIMIT: bool = False + BEST_CARD_ONLY: bool = False BALANCE_TO_SAVE: int = 1000000 UPGRADES_COUNT: int = 10 MAX_COMBO_PRICE: int = 10000000 + BEST_COMBO_ONLY: bool = False APPLY_COMBO: bool = True APPLY_PROMO_CODES: bool = True diff --git a/bot/core/tapper.py b/bot/core/tapper.py index d4b072fc..ba17944f 100644 --- a/bot/core/tapper.py +++ b/bot/core/tapper.py @@ -2,7 +2,7 @@ import asyncio from time import time from random import randint -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import Any, Union, Dict, List, Optional, Tuple import aiohttp @@ -126,25 +126,34 @@ async def run(self, proxy: Optional[str]) -> None: last_passive_earn = int(profile_data.get('lastPassiveEarn', 0)) earn_on_hour = int(profile_data.get('earnPassivePerHour', 0)) total_keys = profile_data.get('totalKeys', 0) - + balance = int(profile_data.get('balanceCoins', 0)) logger.info(f"{self.session_name} | Last passive earn: +{last_passive_earn:,} | " - f"Earn every hour: {earn_on_hour:,} | Total keys: {total_keys}") + f"Earn every hour: {earn_on_hour:,} | Total keys: {total_keys} | " + f"Total balance: {balance:,}") available_energy = profile_data.get('availableTaps', 0) - balance = int(profile_data.get('balanceCoins', 0)) upgrades = upgrades_data['upgradesForBuy'] daily_combo = upgrades_data.get('dailyCombo') if daily_combo and settings.APPLY_COMBO: bonus = daily_combo['bonusCoins'] - is_claimed = daily_combo['isClaimed'] + is_combo_claimed = daily_combo['isClaimed'] upgraded_list = daily_combo['upgradeIds'] - if not is_claimed: + date = "" + cards = [] + available_combo_cards = [] + if not is_combo_claimed: combo_cards = await get_combo_cards(http_client=http_client) - cards = combo_cards['combo'] date = combo_cards['date'] + expires = combo_cards['expires'] + if int(time()) < combo_cards['expires']: + logger.info(f"{self.session_name} | " + f"Daily Combo for Today | Cards: {cards}") + else: + logger.info(f"{self.session_name} | " + f"Daily Combo for {date} | Cards: {cards}") available_combo_cards = [ data for data in upgrades @@ -156,56 +165,60 @@ async def run(self, proxy: Optional[str]) -> None: and data.get('maxLevel', data['level']) >= data['level'] ] - start_bonus_round = datetime.strptime(date, "%d-%m-%y").replace(hour=15) - end_bonus_round = start_bonus_round + timedelta(days=1) + if not settings.BEST_COMBO_ONLY: + start_bonus_round = datetime.strptime(date, "%d-%m-%y").replace(hour=15) + end_bonus_round = start_bonus_round + timedelta(days=1) - if start_bonus_round <= datetime.now() < end_bonus_round: - common_price = sum([upgrade['price'] for upgrade in available_combo_cards]) - need_cards_count = 3 - len(upgraded_list) - possible_cards_count = len(available_combo_cards) - is_combo_accessible = need_cards_count == possible_cards_count + if start_bonus_round <= datetime.now() < end_bonus_round: + common_price = sum([upgrade['price'] for upgrade in available_combo_cards]) + need_cards_count = 3 - len(upgraded_list) + possible_cards_count = len(available_combo_cards) + is_combo_accessible = need_cards_count == possible_cards_count - if not is_combo_accessible: - logger.info(f"{self.session_name} | " - f"Daily combo is not applicable, you can only purchase {possible_cards_count} of {need_cards_count} cards") + if not is_combo_accessible: + logger.info(f"{self.session_name} | " + f"Daily combo is not applicable, you can only purchase {possible_cards_count} of {need_cards_count} cards") - if balance < common_price: - logger.info(f"{self.session_name} | " - f"Daily combo is not applicable, you don't have enough coins. Need {common_price:,} coins, but your balance is {balance:,} coins") + elif balance < common_price: + logger.info(f"{self.session_name} | " + f"Daily combo is not applicable, you don't have enough coins. Need {common_price:,} coins, but your balance is {balance:,} coins") - if common_price < settings.MAX_COMBO_PRICE and balance > common_price and is_combo_accessible: - for upgrade in available_combo_cards: - upgrade_id = upgrade['id'] - level = upgrade['level'] - price = upgrade['price'] - profit = upgrade['profitPerHourDelta'] + if common_price < settings.MAX_COMBO_PRICE and balance > common_price and is_combo_accessible: + for upgrade in available_combo_cards: + upgrade_id = upgrade['id'] + upgrade_name = upgrade['name'] + level = upgrade['level'] + price = upgrade['price'] + profit = upgrade['profitPerHourDelta'] - logger.info(f"{self.session_name} | " - f"Sleep 5s before upgrade combo card {upgrade_id}") + logger.info(f"{self.session_name} | " + f"Sleep 5s before upgrade combo card {upgrade_name}") - await asyncio.sleep(delay=5) + await asyncio.sleep(delay=5) - status, upgrades = await buy_upgrade(http_client=http_client, - upgrade_id=upgrade_id) + status, upgrades = await buy_upgrade(http_client=http_client, + upgrade_id=upgrade_id) - if status is True: - earn_on_hour += profit - balance -= price - logger.success(f"{self.session_name} | " - f"Successfully upgraded {upgrade_id} with price {price:,} to {level} lvl | " - f"Earn every hour: {earn_on_hour:,} (+{profit:,}) | " - f"Money left: {balance:,}") + if status is True: + earn_on_hour += profit + balance -= price + logger.success(f"{self.session_name} | " + f"Successfully upgraded {upgrade_name} with price {price:,} to {level} lvl | " + f"Earn every hour: {earn_on_hour:,} (+{profit:,}) | " + f"Money left: {balance:,}") - await asyncio.sleep(delay=1) + await asyncio.sleep(delay=1) + + await asyncio.sleep(delay=2) + + status = await claim_daily_combo(http_client=http_client) + if status is True: + logger.success(f"{self.session_name} | Successfully claimed daily combo | " + f"Bonus: +{bonus:,}") - await asyncio.sleep(delay=2) - status = await claim_daily_combo(http_client=http_client) - if status is True: - logger.success(f"{self.session_name} | Successfully claimed daily combo | " - f"Bonus: +{bonus:,}") - await asyncio.sleep(delay=randint(2, 4)) + await asyncio.sleep(delay=randint(2, 4)) if settings.APPLY_DAILY_REWARD: daily_task = tasks[-1] @@ -242,7 +255,7 @@ async def run(self, proxy: Optional[str]) -> None: else: logger.info(f"{self.session_name} | Daily Reward already claimed today") - await asyncio.sleep(delay=randint(2, 4)) + await asyncio.sleep(delay=randint(2, 4)) daily_cipher = game_config.get('dailyCipher') if daily_cipher and settings.APPLY_DAILY_CIPHER: @@ -255,13 +268,15 @@ async def run(self, proxy: Optional[str]) -> None: status = await claim_daily_cipher(http_client=http_client, cipher=decoded_cipher) if status is True: + balance += 1000000 logger.success(f"{self.session_name} | " f"Successfully claim daily cipher: {decoded_cipher} | " f"Bonus: +{bonus:,}") + await asyncio.sleep(delay=2) - await asyncio.sleep(delay=randint(2, 4)) + await asyncio.sleep(delay=randint(2, 4)) daily_mini_game = game_config.get('dailyKeysMiniGames') if daily_mini_game and settings.APPLY_DAILY_MINI_GAME: @@ -315,75 +330,76 @@ async def run(self, proxy: Optional[str]) -> None: logger.info( f"{self.session_name} | Key for Mini Game {mini_game_id} is not found") - await asyncio.sleep(delay=randint(2, 4)) - - for _ in range(randint(a=settings.GAMES_COUNT[0], b=settings.GAMES_COUNT[1])): - game_config = await get_game_config(http_client=http_client) - daily_mini_game = game_config.get('dailyKeysMiniGames') - if daily_mini_game and settings.APPLY_DAILY_MINI_GAME: - tiles_mini_game = daily_mini_game.get('Tiles') - if tiles_mini_game: - is_claimed = tiles_mini_game['isClaimed'] - seconds_to_next_attempt = tiles_mini_game['remainSecondsToNextAttempt'] - start_date = tiles_mini_game['startDate'] - mini_game_id = tiles_mini_game['id'] - remain_points = tiles_mini_game['remainPoints'] - max_points = tiles_mini_game['maxPoints'] - - if not is_claimed and remain_points > 0: - game_sleep_time = randint(a=settings.SLEEP_MINI_GAME_TILES[0], - b=settings.SLEEP_MINI_GAME_TILES[1]) - game_score = randint(a=settings.SCORE_MINI_GAME_TILES[0], - b=settings.SCORE_MINI_GAME_TILES[1]) - - if game_score > remain_points: - game_score = remain_points + await asyncio.sleep(delay=randint(2, 4)) + + # We shouldn't be in this for loop if I don't want mini games + for _ in range(randint(a=settings.GAMES_COUNT[0], b=settings.GAMES_COUNT[1])): + game_config = await get_game_config(http_client=http_client) + daily_mini_game = game_config.get('dailyKeysMiniGames') + if daily_mini_game and settings.APPLY_DAILY_MINI_GAME: + tiles_mini_game = daily_mini_game.get('Tiles') + if tiles_mini_game: + is_claimed = tiles_mini_game['isClaimed'] + seconds_to_next_attempt = tiles_mini_game['remainSecondsToNextAttempt'] + start_date = tiles_mini_game['startDate'] + mini_game_id = tiles_mini_game['id'] + remain_points = tiles_mini_game['remainPoints'] + max_points = tiles_mini_game['maxPoints'] + + if not is_claimed and remain_points > 0: + game_sleep_time = randint(a=settings.SLEEP_MINI_GAME_TILES[0], + b=settings.SLEEP_MINI_GAME_TILES[1]) + game_score = randint(a=settings.SCORE_MINI_GAME_TILES[0], + b=settings.SCORE_MINI_GAME_TILES[1]) + + if game_score > remain_points: + game_score = remain_points - logger.info(f"{self.session_name} | " - f"Remain points {remain_points}/{max_points} in {mini_game_id} | " - f"Sending score {game_score}") + logger.info(f"{self.session_name} | " + f"Remain points {remain_points}/{max_points} in {mini_game_id} | " + f"Sending score {game_score}") - encoded_body = await get_mini_game_cipher( - user_id=user_id, - start_date=start_date, - mini_game_id=mini_game_id, - score=game_score - ) + encoded_body = await get_mini_game_cipher( + user_id=user_id, + start_date=start_date, + mini_game_id=mini_game_id, + score=game_score + ) - if encoded_body: - await start_daily_mini_game(http_client=http_client, mini_game_id=mini_game_id) + if encoded_body: + await start_daily_mini_game(http_client=http_client, mini_game_id=mini_game_id) - logger.info(f"{self.session_name} | " - f"Sleep {game_sleep_time}s in Mini Game {mini_game_id}") - await asyncio.sleep(delay=game_sleep_time) + logger.info(f"{self.session_name} | " + f"Sleep {game_sleep_time}s in Mini Game {mini_game_id}") + await asyncio.sleep(delay=game_sleep_time) - profile_data, daily_mini_game, bonus = await claim_daily_mini_game( - http_client=http_client, cipher=encoded_body, mini_game_id=mini_game_id) + profile_data, daily_mini_game, bonus = await claim_daily_mini_game( + http_client=http_client, cipher=encoded_body, mini_game_id=mini_game_id) - await asyncio.sleep(delay=2) + await asyncio.sleep(delay=2) - if bonus: - new_balance = int(profile_data.get('balanceCoins', 0)) - balance = new_balance + if bonus: + new_balance = int(profile_data.get('balanceCoins', 0)) + balance = new_balance - logger.success(f"{self.session_name} | " - f"Successfully claimed Mini Game {mini_game_id} | " - f"Balance {balance:,} (+{bonus:,})") - else: - if is_claimed or remain_points == 0: - logger.info(f"{self.session_name} | " - f"Daily Mini Game {mini_game_id} already claimed today") - break - elif seconds_to_next_attempt > 0: - logger.info(f"{self.session_name} | " - f"Need {seconds_to_next_attempt}s to next attempt in Mini Game {mini_game_id}") - break - elif not encoded_body: - logger.info(f"{self.session_name} | " - f"Key for Mini Game {mini_game_id} is not found") - break + logger.success(f"{self.session_name} | " + f"Successfully claimed Mini Game {mini_game_id} | " + f"Balance {balance:,} (+{bonus:,})") + else: + if is_claimed or remain_points == 0: + logger.info(f"{self.session_name} | " + f"Daily Mini Game {mini_game_id} already claimed today") + break + elif seconds_to_next_attempt > 0: + logger.info(f"{self.session_name} | " + f"Need {seconds_to_next_attempt}s to next attempt in Mini Game {mini_game_id}") + break + elif not encoded_body: + logger.info(f"{self.session_name} | " + f"Key for Mini Game {mini_game_id} is not found") + break - await asyncio.sleep(delay=randint(2, 4)) + await asyncio.sleep(delay=randint(2, 4)) if settings.APPLY_PROMO_CODES: promos_data = await get_promos(http_client=http_client) @@ -460,14 +476,14 @@ async def run(self, proxy: Optional[str]) -> None: await asyncio.sleep(delay=2) - await asyncio.sleep(delay=randint(2, 4)) + await asyncio.sleep(delay=randint(2, 4)) if settings.AUTO_COMPLETE_TASKS: tasks = await get_tasks(http_client=http_client) for task in tasks: task_id = task['id'] is_completed = task['isCompleted'] - + tasks_config = version_config['tasks'] for task_config in tasks_config: if task_config['id'] == task_id: amount_reward = int(task_config.get('rewardCoins', 0)) @@ -495,7 +511,7 @@ async def run(self, proxy: Optional[str]) -> None: await get_upgrades(http_client=http_client) - await asyncio.sleep(delay=randint(2, 4)) + await asyncio.sleep(delay=randint(2, 4)) exchange_id = profile_data.get('exchangeId') if not exchange_id: @@ -503,7 +519,7 @@ async def run(self, proxy: Optional[str]) -> None: if status is True: logger.success(f"{self.session_name} | Successfully selected exchange Bybit") - await asyncio.sleep(delay=randint(2, 4)) + await asyncio.sleep(delay=randint(2, 4)) if settings.USE_TAPS: taps = randint(a=settings.RANDOM_TAPS_COUNT[0], b=settings.RANDOM_TAPS_COUNT[1]) @@ -527,15 +543,23 @@ async def run(self, proxy: Optional[str]) -> None: logger.success(f"{self.session_name} | Successful tapped! | " f"Balance: {balance:,} (+{calc_taps:,}) | Total: {total:,}") - if settings.AUTO_UPGRADE is True: + if settings.AUTO_UPGRADE or settings.BEST_COMBO_ONLY: for _ in range(settings.UPGRADES_COUNT): - available_upgrades = [ - data for data in upgrades - if data['isAvailable'] is True - and data['isExpired'] is False - and data.get('cooldownSeconds', 0) == 0 - and data.get('maxLevel', data['level']) >= data['level'] - ] + if settings.BEST_CARD_ONLY or settings.BEST_COMBO_ONLY: + available_upgrades = [ + data for data in upgrades + if data['isAvailable'] is True + and data['isExpired'] is False + and data.get('maxLevel', data['level']) >= data['level'] + ] + else: + available_upgrades = [ + data for data in upgrades + if data['isAvailable'] is True + and data['isExpired'] is False + and data.get('cooldownSeconds', 0) == 0 + and data.get('maxLevel', data['level']) >= data['level'] + ] queue = [] @@ -544,47 +568,154 @@ async def run(self, proxy: Optional[str]) -> None: level = upgrade['level'] price = upgrade['price'] profit = upgrade['profitPerHourDelta'] + expiry_date = datetime.fromisoformat(upgrade.get('expiresAt', "2100-12-12T00:00:00.000Z")) + cooldown_seconds = upgrade.get('cooldownSeconds', 0) significance = profit / max(price, 1) free_money = balance - settings.BALANCE_TO_SAVE - max_price_limit = max(earn_on_hour, 50000) * 24 - - if ((free_money * 0.8) >= price + if settings.MAX_PRICE_LIMIT: + max_price_limit = max(earn_on_hour, 50000) * 24 + else: + max_price_limit = float('inf') + if settings.BEST_CARD_ONLY: + if (expiry_date > (datetime.now(timezone.utc) + timedelta(seconds=cooldown_seconds)) + and profit > settings.MIN_PROFIT + and level <= settings.MAX_LEVEL + and price <= settings.MAX_PRICE + and price < max_price_limit): + heapq.heappush(queue, (-significance, upgrade_id, upgrade)) + else: + if ((free_money) >= price and profit > settings.MIN_PROFIT and level <= settings.MAX_LEVEL and price <= settings.MAX_PRICE - and price < max_price_limit): - heapq.heappush(queue, (-significance, upgrade_id, upgrade)) + and price < max_price_limit + and cooldown_seconds == 0): + heapq.heappush(queue, (-significance, upgrade_id, upgrade)) if not queue: continue - top_card = heapq.nsmallest(1, queue)[0] - + heapq.heapify(queue) + top_card = queue[0] upgrade = top_card[2] - upgrade_id = upgrade['id'] - level = upgrade['level'] - price = upgrade['price'] - profit = upgrade['profitPerHourDelta'] + if settings.APPLY_COMBO and settings.BEST_COMBO_ONLY and not is_combo_claimed: - logger.info(f"{self.session_name} | Sleep 5s before upgrade {upgrade_id}") - await asyncio.sleep(delay=5) + start_bonus_round = datetime.strptime(date, "%d-%m-%y").replace(hour=15) + end_bonus_round = start_bonus_round + timedelta(days=1) - status, upgrades = await buy_upgrade(http_client=http_client, upgrade_id=upgrade_id) + if start_bonus_round <= datetime.now() < end_bonus_round: + common_price = sum([upgrade['price'] for upgrade in available_combo_cards]) + daily_combo_roi = sum([upgrade['profitPerHourDelta'] for upgrade in available_combo_cards]) / (common_price - bonus) + need_cards_count = 3 - len(upgraded_list) + possible_cards_count = len(available_combo_cards) + is_combo_accessible = need_cards_count == possible_cards_count - if status is True: - earn_on_hour += profit - balance -= price - logger.success(f"{self.session_name} | " - f"Successfully upgraded {upgrade_id} with price {price:,} to {level} lvl | " - f"Earn every hour: {earn_on_hour:,} (+{profit:,}) | " - f"Money left: {balance:,}") + if not is_combo_accessible: + logger.info(f"{self.session_name} | " + f"Daily combo is not applicable, you can only purchase {possible_cards_count} of {need_cards_count} cards") - await asyncio.sleep(delay=1) + elif balance < common_price: + logger.info(f"{self.session_name} | " + f"Daily combo is not applicable, you don't have enough coins. Need {common_price:,} coins, but your balance is {balance:,} coins") + + elif not daily_combo_roi > -queue[5][0]: + logger.info(f"{self.session_name} | " + f"Daily combo is not profitable. Your coins are better spent on upgrading the {top_card[2]['name']} card.") + + else: + logger.info(f"{self.session_name} | " + f"Daily Combo is profitable. Applying Daily Combo." + ) + + if common_price < settings.MAX_COMBO_PRICE and balance > common_price and is_combo_accessible and daily_combo_roi > -queue[5][0]: + for combo_upgrade in available_combo_cards: + combo_upgrade_id = combo_upgrade['id'] + combo_upgrade_name = combo_upgrade['name'] + combo_level = combo_upgrade['level'] + combo_price = combo_upgrade['price'] + combo_profit = combo_upgrade['profitPerHourDelta'] + + logger.info(f"{self.session_name} | " + f"Sleep 5s before upgrade combo card {combo_upgrade_name}") + + await asyncio.sleep(delay=5) + + status, upgrades = await buy_upgrade(http_client=http_client, + upgrade_id=combo_upgrade_id) + + if status is True: + earn_on_hour += combo_profit + balance -= combo_price + logger.success(f"{self.session_name} | " + f"Successfully upgraded {combo_upgrade_name} with price {combo_price:,} to {combo_level} lvl | " + f"Earn every hour: {earn_on_hour:,} (+{combo_profit:,}) | " + f"Money left: {balance:,}") + + await asyncio.sleep(delay=1) + + await asyncio.sleep(delay=2) + + status = await claim_daily_combo(http_client=http_client) + if status is True: + logger.success(f"{self.session_name} | Successfully claimed daily combo | " + f"Bonus: +{bonus:,}") + is_combo_claimed = True + + continue + + if settings.AUTO_UPGRADE: + upgrade_id = upgrade['id'] + upgrade_name = upgrade['name'] + level = upgrade['level'] + price = upgrade['price'] + profit = upgrade['profitPerHourDelta'] + cooldown_seconds = upgrade.get('cooldownSeconds', 0) + + status = False + if settings.BEST_CARD_ONLY: + if (cooldown_seconds == 0) and (free_money >= price): + logger.info(f"{self.session_name} | Sleep 5s before upgrade {upgrade_name}") + await asyncio.sleep(delay=5) + + status, upgrades = await buy_upgrade(http_client=http_client, upgrade_id=upgrade_id) + + elif (cooldown_seconds == 0) and (free_money < price): + best_card_sleep_seconds = (price - free_money) / earn_on_hour * 3600 + logger.info(f"{self.session_name} | " + f"Not enough money for best card {upgrade_name} | " + f"Sleep {best_card_sleep_seconds:,.0f}s for enough Money") + await asyncio.sleep(delay=best_card_sleep_seconds + 10) + balance += (price - free_money) + status, upgrades = await buy_upgrade(http_client=http_client, upgrade_id=upgrade_id) + + else: + logger.info(f"{self.session_name} | " + f"Sleep till best card {upgrade_name} off cooldown | {cooldown_seconds:,}s") + await asyncio.sleep(delay=cooldown_seconds) + balance += (cooldown_seconds / 3600 * earn_on_hour) + cooldown_seconds = 0 + + status, upgrades = await buy_upgrade(http_client=http_client, upgrade_id=upgrade_id) + + else: + logger.info(f"{self.session_name} | Sleep 5s before upgrade {upgrade_name}") + await asyncio.sleep(delay=5) + + status, upgrades = await buy_upgrade(http_client=http_client, upgrade_id=upgrade_id) + + if status is True: + earn_on_hour += profit + balance -= price + logger.success(f"{self.session_name} | " + f"Successfully upgraded {upgrade_name} with price {price:,} to {level} lvl | " + f"Earn every hour: {earn_on_hour:,} (+{profit:,}) | " + f"Money left: {balance:,}") + + await asyncio.sleep(delay=1) - continue if available_energy < settings.MIN_AVAILABLE_ENERGY or not settings.USE_TAPS: if settings.USE_TAPS: @@ -614,8 +745,8 @@ async def run(self, proxy: Optional[str]) -> None: if settings.USE_TAPS: logger.info(f"{self.session_name} | Minimum energy reached: {available_energy:.0f}") - logger.info(f"{self.session_name} | Sleep {random_sleep:,}s") + logger.info(f"{self.session_name} | Sleep {random_sleep:,}s") await asyncio.sleep(delay=random_sleep) access_token_created_time = 0