Простейший бот «на движке» WEB WhatsApp

от автора

Доброго времени суток всем!

Продолжу изгаляться над Web версией Whatsapp и попробую превратить его в подобие телеграмм-бота. Тем более, что все это бесплатно и не требует никаких внешних сервисов.

Я не буду повторять все действия, которые нужны для для запуска python c необходимыми пакетами, они описаны в моей предыдущей статье.

Только упомяну, что в данном боте (его код будет ниже) не требуется подключение autoit, так как он не предусматривает отправку файлов в ответ на запрос.

Теперь опишу суть задумки. Так как нет специализированного сервера, то придётся использовать выделенный компьютер (виртуальную машину, docker-образ & etc). На нём в цикле запускать робота, который будет просматривать группу, где подключены все пользователи бота, и отвечать на их запросы. Данный робот ничего умного не умеет, кроме как отправить назад, в группу текст сообщения с префиксом «Echo:». Но с учетом предыдущего поста, думаю, что можно превратить его в полноценного «ответчика».

А сейчас, подробнее по структуре бота. Он реализован в классе, который называется whatapp(). В нём есть следующие методы:

  • startBrowser — запускает браузер;

  • finish — закрывает браузер;

  • searchGroup — ищет группу, в которой будут обрабатывать сообщения;

  • sendMessage — отсылает сообщение в текущую группу или в текущий чат пользователя;

  • searchLastMessageAndProcessMessages — осуществляет поиск последнего обработанного сообщения и затем приступает к обработке новых.

Отмечу встреченные проблемы при построении бота. Первое, для просмотра сообщений нужно блокировать работу JavaScript, чтобы не появлялись новые сообщения и не обновлялся DOM, а для отправки сообщений необходимо разрешить JavaScript. Второе, нужно где-то хранить маркер для последнего обработанного сообщения, я для простоты сохраняю его в файле. Конечно же лучше хранить эти маркеры в базе данных и для каждого пользователя, но я не стал загромождать логику бота. Третье, пришлось отбрасывать сообщения, которые содержат смайлики, так в этом случае пришлось бы еще как-то обрабатывать эти картинки.

И еще одно замечание, ответы отправляются в ту же самую группу, где просматриваются запросы. Лучше бы было поискать чат запросившего пользователя и отправить ответ только ему, но я, опять же, не стал загромождать логику работы бота.

Подчеркну, что я не рассматриваю данное приложение как коммерческий проект, но для небольшой компании, в которой фаворитом среди мессенджеров является Whatsapp, это вполне приемлемое решение.

Теперь текст бота. Комментарии, как обычно, по ходу текста и на двух языках.

from selenium import webdriver from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.support.ui import WebDriverWait from time import sleep import argparse from datetime import datetime   # Константы для Selenium # Constants for Selenium url = f"https://web.whatsapp.com/" xpath = "//button[@data-testid='compose-btn-send']" xpathAttach = "//div[@data-testid='conversation-clip']" cssIdOfDocument = "[aria-label='Документ']" cssIdOfSendButton = "[aria-label='Отправить']" xpathSearchField = "//div[@data-testid='chat-list-search']" xpathFoundItem = "//span[contains(@class,'matched-text')]" xpathInputLineForText = "//div[@data-testid='conversation-compose-box-input']" xpathMessage = "//div[@role = 'row']" xpathAuthorTime = "span[contains(@class,'_3FuDI')]" xpathText1 = "span[contains(@class,'selectable-text')]" xpathText2 = "span[contains(@class,'copyable-text')]"  commandEnableOrDisableJavaScript = "Emulation.setScriptExecutionDisabled"  # Константы для обработки сообщений # Constants for message process messageIsOutgoing = "message-out" messageIsText = "data-pre-plain-text" messageClassOfBody = "selectable-text copyable-text" messageBodyStart = "<span>" messageBodyStop = "</span>" messageIsPicture = "img" messageWarrningAboutPicture = "Смайлики не обрабатываю!  I don't process emoticons !" fileNameWithLastMessage = "lastmessage.txt"  class whatapp():     """ Это класс бота для Whatsapp        This class is bot for Whatsapp"""       def startBrowser(self):         options = webdriver.ChromeOptions() # если у вас другой браузер, например FireFox, мы просто пишем options = webdriver.FirefoxOptions() . # if you have the Mozilla Fifefox just write                   options = webdriver.FirefoxOptions()         options.add_argument('--allow-profiles-outside-user-dir')         options.add_argument('--enable-profile-shortcut-manager') # УКАЖИТЕ ПУТЬ ГДЕ ЛЕЖИТ ВАШ python ФАЙЛ. Советую создать отдельную папку для него # Specify the path to where your python file is located. I suggest you create a separate folder for it         options.add_argument(r'user-data-dir=X:\\YYYYYY\\ZZZZZZ\\')         options.add_argument('--profile-directory=Profile 1')         options.add_argument('--profiling-flush=n')         options.add_argument('--enable-aggressive-domstorage-flushing')  # эти опции нужны чтобы подавить любые сообщения об ошибках  SSL, сертификатов и т.п. Но работает только последняя :( # these options need to disabled any messages about bad ssl, certification & etc         options.add_argument('--ignore-certificate-errors-spki-list')         options.add_argument('--ignore-certificate-errors')         options.add_argument('--ignore-ssl-errors')         options.add_argument('log-level=3') # INFO = 0, # WARNING = 1, # LOG_ERROR = 2, # LOG_FATAL = 3. # default is 0. # Мы запускаем браузер # We are starting a browser         self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options) # ждём его загрузки # it takes some time to load it         wait = WebDriverWait(self.driver, 30) # идём туда # go to there         self.driver.get(url) # ждём загрузки страницы Whatsapp # we are waiting for Whatsapp page to load         wait.until(EC.element_to_be_clickable((By.XPATH, xpathSearchField)))       def finish(self): # закрыть все # close all         self.driver.quit()      def searchGroup(self, group_name): # Найти группу для обработки # В поле поиска вводим название группы и нажимаем на кнопку "Искать" # Find a group to process # In the search field, enter the name of the group and click "Search".         self.driver.find_element(By.XPATH, xpathSearchField).send_keys(group_name)         self.driver.find_element(By.XPATH, xpathFoundItem).click()         sleep(5)       def sendMessage(self, message): # Разрешаем JavaSctipt # Enable JavaSctipt         self.driver.execute_cdp_cmd(commandEnableOrDisableJavaScript, {'value': False}) # Ищем строку ввода и отправляем туда текст сообщения # Find the input line and send the message text there         self.driver.find_element(By.XPATH, xpathInputLineForText).send_keys(message) # теперь ищем кнопку "Отправить" и нажимаем на нее # now we look for the Send Button and click on it         self.driver.find_element(By.XPATH, xpath).click()         sleep(5)       def searchLastMessageAndProcessMessages(self, dt): # Запрещаем JavaSctipt # Disable   JavaSctipt         self.driver.execute_cdp_cmd(commandEnableOrDisableJavaScript, {'value': True}) # Выберем все сообщения # Select all messages         messages = self.driver.find_elements(By.XPATH, xpathMessage)         print(str(dt) + '. I found %s messages' % len(messages)) # Получим последнеe обработанное сообщение из файла # Get the last processed message from file         f = open(fileNameWithLastMessage, "r")         last_message = f.read()         f.close()         need_process = False # просмотрим все сообщения # review all messages         for item in messages: # получим тело сообщения # get body of the message             line = str(item.get_attribute('innerHTML')) # это исходящее сообщение. пропускаем # this is outgoing message... skip it             if (messageIsOutgoing in line):                  continue # в сообщении нет текста. пропускаем # this message without text... skip it             if not (messageIsText in line):                  continue # извлечем дату, время и автора сообщения # extract date, time and author from the message             index1 = line.index(messageIsText)             index2 = line.index("]", index1 + len(messageIsText))             index3 = line.index(":", index2)             author_datetime = line[index1 + len(messageIsText) + 2 : index3] # извлечем текст сообщения # extract text from the message             index1 = line.index(messageClassOfBody)             index2 = line.index(messageBodyStart, index1 + len(messageClassOfBody))             index3 = line.index(messageBodyStop, index2)             message = line[index2 + len(messageBodyStart): index3] # сформируем полное сообщение для сравнения с последним обработанным # create a complete message for comparison with the last processed message             full_message = author_datetime + " " + message # если сообшение последнее обработанное, то начинаем обработку, пропустив это # if the message was last processed, we start processing by skipping this             if (full_message == last_message):                 need_process = True                 continue # обработка сообщений. Посылаем "эхо" # process messages. Send echo             if need_process: # не будем отвечать на сообщения со смайликами # block a response for any pictures                 if (messageIsPicture in message):  ## smile                     message = messageWarrningAboutPicture                 self.sendMessage("Echo: " + message) # Запрещаем JavaSctipt # Disable   JavaSctipt                 self.driver.execute_cdp_cmd(commandEnableOrDisableJavaScript, {'value': True}) # запишем последнее обработанное сообщение # write last processed message to file         if not (author_datetime is None):             f = open(fileNameWithLastMessage, "w")             f.write(full_message)             f.close() # Разрешаем JavaSctipt # Enable    JavaSctipt         self.driver.execute_cdp_cmd(commandEnableOrDisableJavaScript, {'value': False})   def main(args): # основной бесконечный цикл. Прервать его - Ctrl+C # the main infinite loop. interrupt it with Ctrl+C     try:         while True:             wa = whatapp()             wa.startBrowser()             wa.searchGroup(args.group)             dt = datetime.now()             wa.searchLastMessageAndProcessMessages(dt);             wa.finish()     except KeyboardInterrupt:         wa.finish()     return   if __name__ == '__main__': # мы разбираем параметры командной строки # we are parsing command line parameters     parser = argparse.ArgumentParser(description='Process messages by Whatsapp')     parser.add_argument('--group', help='Text for send', required=True)     args = parser.parse_args() # начать обработку # start  processing     main(args)     

Запуск бота осуществляется следующим образом

python whatsapp.py --group "Тест"

Всем удачи!


ссылка на оригинал статьи https://habr.com/ru/articles/744774/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *