コード例 #1
0
ファイル: mainwindow.py プロジェクト: gil9red/moswar_bot
    def __init__(self, parent=None):
        super().__init__(parent)

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # Все действия к прикрепляемым окнам поместим в меню
        for dock in self.findChildren(QDockWidget):
            self.ui.menuDockWindow.addAction(dock.toggleViewAction())

        # Все действия к toolbar'ам окнам поместим в меню
        for tool in self.findChildren(QToolBar):
            self.ui.menuTools.addAction(tool.toggleViewAction())

        self.progress_bar = QProgressBar()
        self.progress_bar_timer = QTimer()
        self.progress_bar_timer.setInterval(1000)
        self.progress_bar_timer.timeout.connect(lambda x=None: self.progress_bar.setValue(self.progress_bar.value() - 1))
        self.progress_bar.valueChanged.connect(lambda value: self.progress_bar_timer.stop() if self.progress_bar.value() <= 0 else None)
        self.ui.statusbar.addWidget(self.progress_bar)

        # TODO: показывать историю бота: self.view.history()

        self.moswar_url = 'http://www.moswar.ru/'

        # Чтобы не было проблем запуска компов с прокси:
        QNetworkProxyFactory.setUseSystemConfiguration(True)

        QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True)

        self.ui.view.urlChanged.connect(lambda x: self.ui.url_le.setText(x.toString()))
        self.ui.view.linkClicked.connect(lambda x: self.ui.url_le.setText(x.toString()))
        self.ui.pushButtonBackWebPage.clicked.connect(self.ui.view.back)

        # При клике на кнопку, мы получаем значение data текущего элемента и вызываем функцию, хранящуюся там
        self.ui.run_pb.clicked.connect(lambda: self.ui.commands_cb.itemData(self.ui.commands_cb.currentIndex())())

        self.thimblerig = Thimblerig(self)
        self.fight = Fight(self)
        self.restore_hp = RestoreHP(self)
        self.factory_petric = FactoryPetric(self)
        self.shaurburgers = Shaurburgers(self)
        self.patrol = Patrol(self)

        # Список действий бота
        self.name_action_dict = {
            'Закоулки': self.alley,
            'Площадь': self.square,
            'Метро': self.metro,
            'Завод': self.factory,
            'Задания': self.jobs,
            'Персонаж': self.player,
            'Хата': self.home,
            'Игра в наперстки': self.thimblerig.run,
            'Напасть': self.fight.run,
            'Ищем следующего противника': self.fight._next_enemy,
            'Восстановление жизней': self.restore_hp.run,
            'Варка нано-петриков': self.factory_petric.run,
            'Убрать таймаут Тонусом': self.fight.use_tonus,
            'Шаурбургерс': self.shaurburgers.go,
            'Работать в Шаурбургерсе': self.shaurburgers.run,
            'Патрулировать': self.patrol.run,
        }

        # Добавляем команды
        for command in sorted(self.name_action_dict):
            self.ui.commands_cb.addItem(command, self.name_action_dict[command])

        # Выполнение кода в окне "Выполнение скрипта"
        self.ui.button_exec.clicked.connect(lambda x=None: exec(self.ui.code.toPlainText()))

        # Таймер используемый для вызова функции для запуска задач
        self._task_timer = QTimer()
        self._task_timer.setSingleShot(True)
        self._task_timer.timeout.connect(self._task_tick)

        self.ui.actionStartTimer.triggered.connect(self._task_tick)
        self.ui.actionStopTimer.triggered.connect(self._task_timer.stop)
        self.ui.actionStopTimer.triggered.connect(self.progress_bar_timer.stop)

        # Если стоит True -- происходит выполнение задачи и функция _task_tick прерывается
        self._used = False

        # Название процесса, из-за которого в данный момент  _task_tick не может выполниться
        self._used_process = None

        # Минимальная сумма для игры в Наперстки
        self.min_money_for_thimblerig = 200000
コード例 #2
0
ファイル: mainwindow.py プロジェクト: gil9red/moswar_bot
class MainWindow(QMainWindow, QObject):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # Все действия к прикрепляемым окнам поместим в меню
        for dock in self.findChildren(QDockWidget):
            self.ui.menuDockWindow.addAction(dock.toggleViewAction())

        # Все действия к toolbar'ам окнам поместим в меню
        for tool in self.findChildren(QToolBar):
            self.ui.menuTools.addAction(tool.toggleViewAction())

        self.progress_bar = QProgressBar()
        self.progress_bar_timer = QTimer()
        self.progress_bar_timer.setInterval(1000)
        self.progress_bar_timer.timeout.connect(lambda x=None: self.progress_bar.setValue(self.progress_bar.value() - 1))
        self.progress_bar.valueChanged.connect(lambda value: self.progress_bar_timer.stop() if self.progress_bar.value() <= 0 else None)
        self.ui.statusbar.addWidget(self.progress_bar)

        # TODO: показывать историю бота: self.view.history()

        self.moswar_url = 'http://www.moswar.ru/'

        # Чтобы не было проблем запуска компов с прокси:
        QNetworkProxyFactory.setUseSystemConfiguration(True)

        QWebSettings.globalSettings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True)

        self.ui.view.urlChanged.connect(lambda x: self.ui.url_le.setText(x.toString()))
        self.ui.view.linkClicked.connect(lambda x: self.ui.url_le.setText(x.toString()))
        self.ui.pushButtonBackWebPage.clicked.connect(self.ui.view.back)

        # При клике на кнопку, мы получаем значение data текущего элемента и вызываем функцию, хранящуюся там
        self.ui.run_pb.clicked.connect(lambda: self.ui.commands_cb.itemData(self.ui.commands_cb.currentIndex())())

        self.thimblerig = Thimblerig(self)
        self.fight = Fight(self)
        self.restore_hp = RestoreHP(self)
        self.factory_petric = FactoryPetric(self)
        self.shaurburgers = Shaurburgers(self)
        self.patrol = Patrol(self)

        # Список действий бота
        self.name_action_dict = {
            'Закоулки': self.alley,
            'Площадь': self.square,
            'Метро': self.metro,
            'Завод': self.factory,
            'Задания': self.jobs,
            'Персонаж': self.player,
            'Хата': self.home,
            'Игра в наперстки': self.thimblerig.run,
            'Напасть': self.fight.run,
            'Ищем следующего противника': self.fight._next_enemy,
            'Восстановление жизней': self.restore_hp.run,
            'Варка нано-петриков': self.factory_petric.run,
            'Убрать таймаут Тонусом': self.fight.use_tonus,
            'Шаурбургерс': self.shaurburgers.go,
            'Работать в Шаурбургерсе': self.shaurburgers.run,
            'Патрулировать': self.patrol.run,
        }

        # Добавляем команды
        for command in sorted(self.name_action_dict):
            self.ui.commands_cb.addItem(command, self.name_action_dict[command])

        # Выполнение кода в окне "Выполнение скрипта"
        self.ui.button_exec.clicked.connect(lambda x=None: exec(self.ui.code.toPlainText()))

        # Таймер используемый для вызова функции для запуска задач
        self._task_timer = QTimer()
        self._task_timer.setSingleShot(True)
        self._task_timer.timeout.connect(self._task_tick)

        self.ui.actionStartTimer.triggered.connect(self._task_tick)
        self.ui.actionStopTimer.triggered.connect(self._task_timer.stop)
        self.ui.actionStopTimer.triggered.connect(self.progress_bar_timer.stop)

        # Если стоит True -- происходит выполнение задачи и функция _task_tick прерывается
        self._used = False

        # Название процесса, из-за которого в данный момент  _task_tick не может выполниться
        self._used_process = None

        # Минимальная сумма для игры в Наперстки
        self.min_money_for_thimblerig = 200000

    def _task_tick(self):
        """Функция для запуска задач."""

        if self._used:
            logger.debug('Запуск задач отменяется -- процесс занят "%s".', self._used_process)
        else:
            logger.debug('Запуск задач.')

            try:
                # Если уже играем в Наперстки или набрали нужную сумму для игры в Наперстки
                if 'thimble' in self.current_url() or self.money() >= self.min_money_for_thimblerig:
                    self.thimblerig.run()

                elif self.shaurburgers.is_ready():
                    self.shaurburgers.run()

                elif self.patrol.is_ready():
                    self.patrol.run()

                elif self.factory_petric.is_ready():
                    self.factory_petric.run()

                elif self.fight.is_ready():
                    self.fight.run()

            except MoswarClosedError as e:
                logger.warn(e)

                # В случаи закрытия сайт, каждый час проверяем
                interval = 60 * 60 * 1000

            except MoswarBotError as e:
                logger.error(e)

                # Возможно, в следующий раз ошибки не будет
                interval = 1 * 1000

            except Exception as e:
                logger.error(e)

                # Возможно, в следующий раз ошибки не будет
                interval = 1 * 1000

                import traceback
                traceback.print_exc()

            else:
                # TODO: настраивать interval: спрашивать у другиз модулей их таймауты (если есть) и выбирать
                # наименьший, он и будет interval. Если же interval не был изменен, то задавать рандомное время
                # Это позволит увеличить эффективность бота

                # Запускаем таймер выполнение задач
                # Следующий вызов будет случайным от 3 до 10 минут + немного случайных секунд
                interval = int((randint(3, 10) + random()) * 60 * 1000)

        self._start_task_timer(interval)

    def _start_task_timer(self, interval):
        secs = interval / 1000
        logger.debug('Повторный запуск задач через %s секунд.', secs)
        self._task_timer.start(interval)

        self.progress_bar.setRange(0, secs)
        self.progress_bar.setValue(secs)
        self.progress_bar_timer.start()

    def _get_doc(self):
        return self.ui.view.page().mainFrame().documentElement()

    doc = property(_get_doc)

    def current_url(self):
        """Функция возвращает текущий адрес страницы."""

        return self.ui.view.url().toString()

    def wait_loading(self):
        """Функция ожидания загрузки страницы. Использовать только при изменении url."""

        logger.debug('Начинаю ожидание загрузки страницы.')

        # Ждем пока прогрузится страница
        loop = QEventLoop()
        self.ui.view.loadFinished.connect(loop.quit)
        loop.exec_()

        logger.debug('Закончено ожидание загрузки страницы.')

    def go(self, relative_url):
        """Функция для загрузки страниц.

        Если вызывать без параметров, то загрузит основную страницу.
        Если указывать relative_url, то он будет присоединен к адресу мосвара.
        Функция ожидание окончания загрузки страницы.

        Выбрасывает исключение MoswarClosedError, когда сайта закрыт (closed.html)

        """

        url = urljoin(self.moswar_url, relative_url)
        logger.debug('Перехожу по адресу "%s"', url)

        self.ui.view.load(url)
        self.wait_loading()

        # TODO: вынести обработку переадресаций в отдельную функцию
        # Проверяем, что не случилось переадресации. Она возможна, например, при игре
        # в наперстки или попадании в милицию
        current_url = self.current_url()

        # Сравниваем url'ы между собой. Такая проверка для обхода ситуации, когда QWebView отдает url
        # со слешем на конце. Если сравнить их обычной проверкой (== или !=), то это будет неправильно.
        # http://www.moswar.ru/shaurburgers/
        #
        # А сравниваем с:
        # http://www.moswar.ru/shaurburgers
        equal = url in current_url or current_url in url

        # Если адреса не равны
        if not equal:
            self.slog(url + " -> " + current_url)
            self.slog('Текущий заголовок: "{}"'.format(self.title()))
            logger.warn('Похоже, случилась переадресация: шли на %s, а попали на %s.', url, current_url)

            # TODO: Для http://www.moswar.ru/closed.html описывать причину -- брать из auth()
            # Проверка на временное отсутствие доступа к сайту
            if 'closed.html' in current_url:
                reason = self.doc.toPlainText().strip()
                logger.warn('Закрыто, причина:\n%s', reason)

                raise MoswarClosedError(reason)

            # TODO: руды может не хватать, поэтому предусмотреть ситуацию, когда придется платить деньгами
            # Обработка ситуации: Задержка за бои
            # url полиции police, но url'ы иногда неправильно возвращаются, поэтому надежнее смотреть
            # на заголовок страницы
            if self.title() == 'Милиция':
                logger.debug('Задержаны в милиции.')

                # Ищем кнопку для налаживания связей рудой
                button = self.doc.findFirst('.police-relations .button')
                if not button.isNull():
                    logger.debug('Плачу взятку рудой.')

                    # Нажать на кнопку что-то не получается, поэтому просто шлем запрос,
                    # который и так бы отослался при клике на кнопку
                    self.go('police/relations')

            # TODO: если новый уровень выпал в момент выполнения задания, то возможна такая неприятная
            # ситуация: попадаем на is_ready таски, делается переход к локации такси, перенапрявляет нас
            # на страницу поздравления, мы это определяем, кликаем на кнопку, в этот момент is_ready
            # возвращает True, и мы попадаем в функцию выполнения, которая снова переходит на страницу локации
            # и снова нас перенапрявляет, мы это определяем, кликаем и это так может случится несколько раз
            # TODO: возвращать признак перенаправления и по нему таска сама решает -- отменить или нет свое
            # выполнение
            #
            # Проверка на новый уровень
            if 'quest' in current_url:
                level_up = self.doc.findFirst('.levelup')
                if not level_up.isNull():
                    # Показать столько побед / награблено
                    for td in level_up.findAll('td'):
                        logger.debug('Получен новый уровень! Результат:\n' + ' '.join(td.toPlainText().split()))

                    # Ищем кнопку 'Вперед, к новым победам!' и кликаем на нее
                    button = self.doc.findFirst('.levelup .button')
                    if button.isNull():
                        raise MoswarButtonIsMissError('Вперед, к новым победам!')

                    button.evaluateJavaScript('this.click()')

    def auth(self):
        """Функция загружает страницу мосвара, заполняет поля логина и пароля и нажимает на кнопку Войти.
        После нажатия на Войти происходит ожидание загрузки страницы.

        """

        logger.debug('Авторизуюсь.')

        # Открываем страницу мосвара
        url = self.moswar_url
        logger.debug('Перехожу по адресу "%s"', url)

        self.ui.view.load(url)
        self.wait_loading()

        # Если закрыт доступ к сайту
        if 'closed.html' in self.current_url():
            logger.warn('Закрыто, причина:\n%s', self.doc.toPlainText().strip())

            # Попробуем снова авторизоваться через 1 час
            QTimer.singleShot(60 * 60 * 1000, self.auth)
            return

        login = self.doc.findFirst('#login-email')
        password = self.doc.findFirst('#login-password')

        if login.isNull() or password.isNull():
            raise MoswarAuthError('Не найдены поля логина и пароля.')

        login.setAttribute("value", LOGIN)
        password.setAttribute("value", PASSWORD)

        submit = self.doc.findFirst("input[type=submit]")
        if submit.isNull():
            raise MoswarButtonIsMissError('Войти')

        logger.debug('Захожу в игру.')
        submit.evaluateJavaScript("this.click()")

        self.wait_loading()

        logger.debug('Запуск таймера выполнения задач.')

        # Выполнение первых задач
        self._task_tick()

    def alley(self):
        self.go('alley')

    def square(self):
        self.go('square')

    def factory(self):
        self.go('factory')

    def metro(self):
        self.go('metro')

    def jobs(self):
        self.go('nightclub/jobs')

    def player(self):
        self.go('player')

    def home(self):
        self.go('home')

    def name(self):
        """Функция возвращает имя текущего персонажа."""

        try:
            css_path = '#personal .name'
            name = self.doc.findFirst(css_path).toPlainText()
            name = name[:name.rindex('[')]
            return name.strip()
        except Exception as e:
            raise MoswarElementIsMissError(e)

    def money(self):
        """Функция возвращает количество денег персонажа."""

        try:
            css_path = '.tugriki-block'
            tugriki = self.doc.findFirst(css_path)
            tugriki = tugriki.attribute('title')
            tugriki = tugriki.split(': ')[-1]
            return int(tugriki)

        except Exception as e:
            raise MoswarElementIsMissError(e)

    def current_hp(self):
        """Функция возвращает текущее количество жизней персонажа."""

        try:
            css_path = '#personal #currenthp'
            hp = self.doc.findFirst(css_path)
            hp = hp.toPlainText()
            return int(hp)

        except Exception as e:
            raise MoswarElementIsMissError(e)

    def max_hp(self):
        """Функция возвращает текущее количество жизней персонажа."""

        try:
            hp = self.doc.findFirst('#personal #maxhp')
            hp = hp.toPlainText()
            return int(hp)

        except Exception as e:
            raise MoswarElementIsMissError(e)

    def level(self):
        """Функция возвращает уровень персонажа."""

        try:
            level = self.doc.findFirst('#personal b').toPlainText()
            level = level.split()[-1]
            level = level.replace('[', '').replace(']', '')
            return int(level)

        except Exception as e:
            raise MoswarElementIsMissError(e)

    def title(self):
        """Функция возвращает заголовок текущей страницы."""

        title = self.doc.findFirst('head title')
        if title.isNull():
            logger.warn('Не найден заголовок текущей страницы (%s).', self.current_url())

        return title.toPlainText()

    # TODO: добавить возможность выбрать область поиска элемента для клика, а то она все время вся страница -- self.doc
    def click_tag(self, css_path):
        """Функция находит html тег по указанному пути и эмулирует клик на него.

        Строки в css_path нужно оборачивать в апострофы.
        Пример:
            # Кликаем на кнопку "Отнять у слабого"
            self.click_tag("div[class='button-big btn f1']")

            # Кликаем на кнопку "Искать другого"
            self.click_tag(".button-search a")
        """

        logger.debug('Выполняю клик по тегу: %s', css_path)

        # Используем для клика jQuery
        code = """
        tag = $("{}");
        tag.click();""".format(css_path)

        ok = self.doc.evaluateJavaScript(code)
        if ok is None:
            logger.warn('Выполнение js скрипта неудачно. Code:\n' + code)

    def alert(self, text):
        """Функция показывает окно сообщений в браузере, используя javascript функцию alert."""

        self.doc.evaluateJavaScript('alert("{}")'.format(text))

    def slog(self, *args, **kwargs):
        """Функция для добавления текста в виджет-лог, находящегося на форме."""

        # Используем стандартный print для печати в строку
        str_io = io.StringIO()
        kwargs['file'] = str_io
        kwargs['end'] = ''

        print(*args, **kwargs)

        text = str_io.getvalue()
        self.ui.simple_log.appendPlainText(text)

    def read_settings(self):
        # TODO: при сложных настройках, лучше перейти на json или yaml
        config = QSettings(CONFIG_FILE, QSettings.IniFormat)
        self.restoreState(config.value('MainWindow_State'))
        self.restoreGeometry(config.value('MainWindow_Geometry'))

    def write_settings(self):
        config = QSettings(CONFIG_FILE, QSettings.IniFormat)
        config.setValue('MainWindow_State', self.saveState())
        config.setValue('MainWindow_Geometry', self.saveGeometry())

    def closeEvent(self, *args, **kwargs):
        self.write_settings()
        super().closeEvent(*args, **kwargs)