Beispiel #1
0
    def farm_quests(self, quests: List[Quest] = None):
        """
        Собирает награды из заданий.
        """
        if quests is not None and not quests:
            logger.info('No quests to farm.')  # provided but empty
            return

        logger.info('Farming quests…')
        self.log(f'✅ *{self.user.name}* выполняет задачи…')
        if quests is None:
            quests = self.api.get_all_quests()
        for quest in quests:
            if not quest.is_reward_available:
                continue
            if self.settings.bot.no_experience and quest.reward.experience:
                logger.warning(
                    f'Ignoring {quest.reward.experience} experience reward for quest #{quest.id}.'
                )
                continue
            with self.logger:
                self.logger.append(f'✅ *{self.user.name}* получает за задачу:',
                                   '')
                self.api.farm_quest(quest.id).log(self.logger)
        self.log(f'✅ *{self.user.name}* выполнил задачи.')
Beispiel #2
0
    def get_arena_replays(self):
        """
        Читает и сохраняет журналы арен.
        """
        self.log(f'📒️ *{self.user.name}* читает журнал арены…')

        replays: List[Replay] = [
            *self.api.get_battle_by_type(BattleType.ARENA),
            *self.api.get_battle_by_type(BattleType.GRAND),
        ]
        for replay in replays:
            if f'replays:{replay.id}' in self.db:
                continue
            self.db[f'replays:{replay.id}'] = {
                'start_time':
                replay.start_time.timestamp(),
                'win':
                replay.result.win,
                'attackers':
                [hero.dict() for hero in replay.attackers.values()],
                'defenders': [
                    hero.dict() for defenders in replay.defenders
                    for hero in defenders.values()
                ],
            }
            logger.info(f'Saved #{replay.id}.')

        self.log(f'📒️ *{self.user.name}* прочитал журнал арены.')
Beispiel #3
0
 def quack(self):
     """
     Отладочная задача.
     """
     logger.info('About to quack…')
     self.log(f'🐤 *{self.user.name}* собирается крякать…')
     sleep(5)
     logger.info('Quack!')
     self.log(f'🐤 Бот *{self.user.name}* сказал: «Кря!»')
     return now() + timedelta(seconds=15)
Beispiel #4
0
    def attack_any_arena(
        self,
        *,
        n_heroes: int,
        make_solver: Callable[[Model, List[Hero]], ArenaSolver],
        attack: Callable[[ArenaSolution], Tuple[ArenaResult, Quests]],
        finalise: Callable[[], Any],
    ):
        self.log(f'⚔️ *{self.user.name}* идет на арену…')

        # Load arena model.
        logger.info('Loading model…')
        try:
            model: Model = pickle.loads(b85decode(self.db['bot:model']))
        except KeyError:
            logger.warning('Model is not ready yet.')
            return
        logger.trace('Model: {}.', model)

        # Get all heroes.
        heroes = self.api.get_all_heroes()
        if len(heroes) < n_heroes:
            logger.warning('Not enough heroes: {} needed, you have {}.',
                           n_heroes, len(heroes))
            return

        # Refresh clan ID.
        self.user = self.api.get_user_info()

        # Pick an enemy and select attackers.
        solution = make_solver(model, heroes).solve()
        with self.logger:
            self.logger.append(f'⚔️ *{self.user.name}* атакует арену:', '')
            solution.log(self.logger)

        # Retry if win probability is too low.
        if solution.probability < constants.ARENA_MIN_PROBABILITY:
            logger.warning('Win probability is too low.')
            self.log(f'⚔️ *{self.user.name}* отменил атаку.')
            return now() + constants.ARENA_RETRY_INTERVAL

        # Attack!
        result, quests = attack(solution)

        # Collect results.
        with self.logger:
            self.logger.append(f'⚔️ *{self.user.name}* закончил арену:\n')
            result.log(self.logger)
        finalise()
        self.farm_quests(quests)
Beispiel #5
0
 def farm_mail(self):
     """
     Собирает награды из почты.
     """
     logger.info('Farming mail…')
     self.log(f'📩 *{self.user.name}* читает почту…')
     if letters := self.api.get_all_mail():
         logger.info(f'{len(letters)} letters.')
         with self.logger:
             self.logger.append(f'📩 *{self.user.name}* получил из почты:\n')
             log_rewards(
                 self.api.farm_mail(letter.id
                                    for letter in letters).values(),
                 self.logger)
Beispiel #6
0
def main(settings: Settings, verbosity: int, shell: bool):
    """
    Hero Wars game bot ­ЪЈє
    """
    install_logging(verbosity)
    logger.info('Bot is starting.')

    with Session() as session, Database(constants.DATABASE_NAME) as db:
        session.mount('https://', HTTPAdapter(max_retries=5))
        session.headers['User-Agent'] = constants.USER_AGENT

        telegram = Telegram(session,
                            settings.telegram) if settings.telegram else None
        api = API(session, db, settings)
        bot = Bot(db, api, VK(session, settings), telegram, settings)

        bot.log('­ЪјЅ лЉлЙЛѓ лил░л┐ЛЃЛЂл║л░лхЛѓЛЂЛЈРђд')
        api.prepare()
        bot.prepare()

        logger.info('Welcome ┬Ф{}┬╗!', bot.user.name)
        logger.info('Game time: {:%H:%M:%S %Z}', datetime.now(bot.user.tz))
        logger.info('Next day: {:%H:%M:%S %Z}.',
                    bot.user.next_day.astimezone(bot.user.tz))
        bot.log(f'­ЪјЅ *{bot.user.name}* лил░л┐ЛЃЛЂЛѓлИл╗ЛЂЛЈ!')

        if not shell:
            bot.run()
        else:
            IPython.embed()
Beispiel #7
0
    def get_raid_mission_ids(self) -> Iterable[str]:
        missions: Dict[str, Mission] = {
            mission.id: mission
            for mission in self.api.get_all_missions()
            if mission.is_raid_available and mission_name(mission.id).lower()
            in self.settings.bot.raid_missions
        }

        # Get heroic mission IDs.
        heroic_mission_ids = get_heroic_mission_ids()

        # First, yield heroic missions.
        raided_heroic_mission_ids = list(missions.keys() & heroic_mission_ids)
        shuffle(raided_heroic_mission_ids
                )  # shuffle in order to distribute stamina evenly
        logger.info(f'Raided heroic missions: {raided_heroic_mission_ids}.')
        for mission_id in raided_heroic_mission_ids:
            tries_left = constants.RAID_N_HEROIC_TRIES - missions[
                mission_id].tries_spent
            logger.info(f'Mission #{mission_id}: {tries_left} tries left.')
            for _ in range(tries_left):
                yield mission_id

        # Then, randomly choose non-heroic missions infinitely.
        non_heroic_mission_ids = list(missions.keys() - heroic_mission_ids)
        logger.info(f'Raided non-heroic missions: {non_heroic_mission_ids}.')
        if not non_heroic_mission_ids:
            logger.info('No raided non-heroic missions.')
            return
        while True:
            yield choice(non_heroic_mission_ids)
Beispiel #8
0
    def level_up_titan_hero_gift(self):
        """
        Вложить и сбросить искры самому слабому герою.
        """
        self.log(f'⚡️ *{self.user.name}* вкладывает и сбрасывает искры мощи…')

        hero = min(self.api.get_all_heroes(), key=attrgetter('power'))
        logger.info('Hero: {}.', hero)
        self.farm_quests(self.api.level_up_titan_hero_gift(hero.id))
        reward, quests = self.api.drop_titan_hero_gift(hero.id)
        with self.logger:
            self.logger.append(
                f'⚡️ *{self.user.name}* получил за вложение искр мощи:', '')
            reward.log(self.logger)
        self.farm_quests(quests)

        self.log(f'⚡️ *{self.user.name}* вложил и сбросил искры мощи.')
Beispiel #9
0
    def raid_bosses(self):
        """
        Рейдит боссов Запределья.
        """
        self.log(f'🔴 *{self.user.name}* рейдит боссов Запределья…')

        for i, boss in enumerate(self.api.get_all_bosses(), 1):
            self.log(f'🔴 *{self.user.name}* рейдит боссов Запределья: {i}-й…')
            if boss.may_raid:
                logger.info(f'Raid boss #{boss.id}…')
                self.api.raid_boss(boss.id).log()
                rewards, quests = self.api.open_boss_chest(boss.id)
                with self.logger:
                    self.logger.append(
                        f'🔴 *{self.user.name}* получил в Запределье:', '')
                    log_rewards(rewards, self.logger)
                self.farm_quests(quests)
            else:
                logger.info(f'May not raid boss #{boss.id}.')

        self.log(f'🔴 *{self.user.name}* закончил рейд боссов Запределья.')
Beispiel #10
0
    def open_titan_artifact_chest(self):
        """
        Открывает сферы артефактов титанов.
        """
        self.log(f'⚫️ *{self.user.name}* открывает сферы артефактов титанов…')

        for amount in [10, 1]:
            try:
                rewards, quests = self.api.open_titan_artifact_chest(amount)
            except NotEnoughError:
                logger.info(f'Not enough resources to open {amount} chests.')
            else:
                with self.logger:
                    self.logger.append(
                        f'⚫️ *{self.user.name}* получил из сферы артефактов титанов:',
                        '')
                    log_rewards(rewards, self.logger)
                self.farm_quests(quests)
                break

        self.log(f'⚫️ *{self.user.name}* открыл сферы артефактов титанов.')
Beispiel #11
0
    def raid_missions(self):
        """
        Ходит в рейды в миссиях в кампании за предметами.
        """
        self.log(f'🔥 *{self.user.name}* идет в рейды…')

        for mission_id in self.get_raid_mission_ids():
            logger.info(
                f'Raid mission #{mission_id} «{mission_name(mission_id)}»…')
            try:
                with self.logger:
                    rewards = self.api.raid_mission(mission_id)
                    self.logger.append(
                        f'🔥 *{self.user.name}* получил из рейда «{mission_name(mission_id)}»:',
                        '')
                    log_rewards(rewards, self.logger)
            except NotEnoughError as e:
                logger.info(f'Not enough: {e}.')
                break

        self.log(f'🔥 *{self.user.name}* сходил в рейды.')
Beispiel #12
0
    def check_freebie(self):
        """
        Собирает подарки на странице игры ВКонтакте.
        """
        self.log(f'🎁 *{self.user.name}* проверяет подарки на VK.com…')
        should_farm_mail = False

        for gift_id in self.vk.find_gifts():
            if f'gifts:{self.api.user_id}:{gift_id}' in self.db:
                continue
            logger.info(f'Checking {gift_id}…')
            reward = self.api.check_freebie(gift_id)
            if reward is not None:
                reward.log()
                should_farm_mail = True
            self.db[f'gifts:{self.api.user_id}:{gift_id}'] = True

        self.log(f'🎁 *{self.user.name}* проверил подарки на VK.com.')

        if should_farm_mail:
            self.farm_mail()
Beispiel #13
0
    def shop(self):
        """
        Покупает в магазине вещи.
        """
        self.log(f'🛍 *{self.user.name}* идет в магазин…')

        logger.info('Requesting shops…')
        slots: List[Tuple[str, str]] = [
            (shop_id, slot.id) for shop_id in constants.SHOP_IDS
            for slot in self.api.get_shop(shop_id)
            if (not slot.is_bought) and (not slot.cost.star_money) and (
                slot.reward.keywords & self.settings.bot.shops)
        ]

        logger.info(f'Going to buy {len(slots)} slots.')
        for shop_id, slot_id in slots:
            logger.info(
                f'Buying slot #{slot_id} in shop «{shop_name(shop_id)}»…')
            try:
                reward = self.api.shop(shop_id=shop_id, slot_id=slot_id)
            except NotEnoughError as e:
                logger.warning(f'Not enough: {e}')
            except AlreadyError as e:
                logger.warning(f'Already: {e}')
            else:
                with self.logger:
                    self.logger.append(f'🛍 *{self.user.name}* купил:', '')
                    reward.log(self.logger)

        self.log(f'🛍 *{self.user.name}* сходил в магазин.')
Beispiel #14
0
    def farm_zeppelin_gift(self):
        """
        Собирает ключ у валькирии и открывает артефактные сундуки.
        """
        self.log(f'🔑 *{self.user.name}* открывает артефактные сундуки…')

        self.api.farm_zeppelin_gift().log()
        for _ in range(constants.MAX_OPEN_ARTIFACT_CHESTS):
            try:
                rewards = self.api.open_artifact_chest()
            except NotEnoughError:
                logger.info('All keys are spent.')
                break
            with self.logger:
                self.logger.append(
                    f'🔑 *{self.user.name}* получил из артефактного сундука:',
                    '')
                log_rewards(rewards, self.logger)
        else:
            logger.warning('Maximum number of chests opened.')

        self.log(f'🔑 *{self.user.name}* открыл артефактные сундуки.')
Beispiel #15
0
    def hall_of_fame(self):
        """
        Турнир Стихий.
        """
        self.log(f'💨 *{self.user.name}* идет в Турнир Стихий…')

        weekday = now().weekday()

        if weekday == calendar.SATURDAY:
            logger.info('Farming reward today…')
            trophy = self.api.get_hall_of_fame().trophy
            if trophy:
                reward = self.api.farm_hall_of_fame_trophy_reward(trophy.week)
                with self.logger:
                    self.logger.append(
                        f'💨 *{self.user.name}* получил в Турнире Стихий:\n')
                    reward.log(self.logger)
            else:
                logger.warning('No trophy.')
        else:
            logger.info('Doing nothing today.')

        self.log(f'💨 *{self.user.name}* закончил Турнир Стихий.')
Beispiel #16
0
    def skip_tower(self):
        """
        Зачистка башни.
        """
        self.log(f'🗼 *{self.user.name}* проходит башню…')

        tower = self.api.get_tower_info()

        # Yeah, it's a bit complicated…
        while tower.floor_number <= 50:
            logger.info(f'Floor #{tower.floor_number}: {tower.floor_type}.')
            self.log(
                f'🗼 *{self.user.name}* на {tower.floor_number}-м этаже башни…')

            if tower.floor_type == TowerFloorType.BATTLE:
                # If we have the topmost level, then we can skip the tower entirely.
                # But we need to go chest by chest. So go to the next chest.
                if tower.may_full_skip:
                    tower = self.api.next_tower_chest()
                # Maybe we can skip the floor, because of the yesterday progress.
                elif tower.floor_number <= tower.may_skip_floor:
                    tower, reward = self.api.skip_tower_floor()
                    reward.log()
                # Otherwise, we're not able to continue.
                else:
                    break
            elif tower.floor_type == TowerFloorType.CHEST:
                # The simplest one. Just open a random chest.
                reward, _ = self.api.open_tower_chest(choice([0, 1, 2]))
                with self.logger:
                    self.logger.append(
                        f'🗼 *{self.user.name}* получил на {tower.floor_number}-м этаже башни:\n'
                    )
                    reward.log(self.logger)
                # If it was the topmost floor, we have to stop.
                if tower.floor_number == 50:
                    logger.success('Finished. It was the topmost floor.')
                    break
                # If we can skip the tower entirely, then go to the next chest.
                if tower.may_full_skip:
                    tower = self.api.next_tower_chest()
                # Otherwise, just proceed to the next floor.
                else:
                    tower = self.api.next_tower_floor()
            elif tower.floor_type == TowerFloorType.BUFF:
                # Buffs go from the cheapest to the most expensive.
                # So try to buy the most expensive ones first.
                for buff in reversed(tower.floor):
                    buff_id = int(buff['id'])
                    # Some buffs require to choose a hero. We ignore these.
                    if buff_id not in constants.TOWER_IGNORED_BUFF_IDS:
                        try:
                            self.api.buy_tower_buff(buff_id)
                        except NotEnoughError:
                            logger.info(
                                f'Not enough resources for buff #{buff_id}.')
                        except AlreadyError:
                            logger.info(f'Already bought buff #{buff_id}.')
                        except NotFoundError as e:
                            logger.warning(
                                f'Not found for buff #{buff_id}: {e}.')
                    else:
                        logger.debug(f'Skip buff #{buff_id}.')
                # Then normally proceed to the next floor.
                tower = self.api.next_tower_floor()

        self.log(
            f'🗼 *{self.user.name}* закончил башню на *{tower.floor_number}-м* этаже.'
        )
Beispiel #17
0
    def send_expeditions(self) -> Optional[datetime]:
        logger.info('Sending expeditions…')
        self.log(f'⛺️ *{self.user.name}* пробует отправить экспедиции…')

        # Need to know which expeditions are already started.
        expeditions = self.api.list_expeditions()
        started_expeditions = [
            expedition for expedition in expeditions if expedition.is_started
        ]
        logger.info('{} expeditions in progress.', len(started_expeditions))
        next_run_at = min(
            [expedition.end_time for expedition in started_expeditions],
            default=None)
        if next_run_at:
            logger.info('The earliest expedition finishes at {}.',
                        next_run_at.astimezone(self.user.tz))

        # Select available heroes.
        busy_ids = {
            hero_id
            for expedition in started_expeditions
            for hero_id in expedition.hero_ids
        }
        logger.info('Busy heroes: {}.', busy_ids)
        heroes: Dict[str, Hero] = {
            hero.id: hero
            for hero in self.api.get_all_heroes() if hero.id not in busy_ids
        }
        logger.info('{} heroes are still available.', len(heroes))

        # Let's see which expeditions are available.
        available_expeditions = [
            expedition for expedition in expeditions if expedition.is_available
        ]
        logger.info('{} expeditions are still available.',
                    len(available_expeditions))

        while available_expeditions:
            # Choose the least powerful expedition.
            expedition, *available_expeditions = sorted(
                available_expeditions, key=attrgetter('power'))
            logger.info('The optimal expedition power is {}.',
                        expedition.power)

            # Choose the least powerful appropriate team.
            team = find_expedition_team(heroes.values(), expedition.power)
            if team is None:
                logger.info('Could not find powerful enough team.')
                break

            # Send the expedition.
            end_time, quests = self.api.send_expedition_heroes(
                expedition.id, get_unit_ids(team))
            self.log(
                f'⛺️ *{self.user.name}* отправил экспедицию #{expedition.id}.')
            self.farm_quests(quests)

            # Exclude the busy heroes.
            for hero in team:
                del heroes[hero.id]

            # We should farm the earliest finished expedition.
            if next_run_at is None or end_time < next_run_at:
                next_run_at = end_time

        self.log(f'⛺️ *{self.user.name}* закончил с отправкой экспедиций.')
        return next_run_at