Как стать автором
Поиск
Написать публикацию
Обновить

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

Уровень сложностиСредний
Время на прочтение6 мин
Количество просмотров15K

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

Продолжу изгаляться над 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 "Тест"

Всем удачи!

Теги:
Хабы:
Всего голосов 4: ↑3 и ↓1+3
Комментарии1

Публикации

Ближайшие события