Доброго времени суток всем!
Продолжу изгаляться над 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/
Добавить комментарий