Как написать веб бота

Предисловие.

Веб-бот — это программа, которая автоматизирует ваши действия в интернете.

В этой статье, я объясню общий принцип создания ботов на Python, применив полученные знания, вы сможете создать бота который:

  • Получает актуальную информацию о стоимости товара.
  • Автоматически участвует в раздачах.
  • Пишет сообщения от вашего имени (ссылка на урок).
  • Скачивает картинки с сайта (ссылка на урок).

Приступим.

Создаем первого бота на Selenium.

Selenium — это библиотека для автоматизации действий в браузере.

Данный способ подойдет для любого сайта, однако, за все нужно платить. Selenium запускает браузер, отъедая огромный запас оперативной памяти. Используйте его только тогда, когда нужно выполнить JS код на странице.

Первым делом нужно установить библиотеку, для этого введите в консоли:

pip install selenium

Далее, установите веб-драйвер под браузер Firefox отсюда. Также, необходимо установить браузер Mozilla Firefox, если еще не установлен.

Теперь напишем простейшего бота. Для этого, напишите следующий python скрипт.

from selenium import webdriver #Ипортируем библиотеку
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary

binary = FirefoxBinary('C:/Program Files/Mozilla Firefox/firefox.exe') #Прописываем путь до firefox.exe
browser = webdriver.Firefox(firefox_binary=binary) #создаем объект Firefox браузера
browser.get('https://under-prog.ru') #посредством метода get, переходим по указаному URL

Код скрипта описан в комментариях.

Далее, переместите файл скрипта, в одну папку с веб-драйвером geckodriver.exe

И запустите python скрипт. У вас должен открыться браузер.

В адресной строке видна иконка робота, это значит, что браузером управляет программа.

Хорошо, бот создан, но он бесполезен. Единственное на что он способен, это заходить на сайт. Давайте добавим ему новых функций. Например, сделаем так, чтобы бот лайкал посты на сайте.

Бот лайкающий посты на сайте.

Последовательность действий у нас следующая.

  1. Зайти на сайт under-prog.ru (открыть браузер)
  2. Пройтись по каждому из постов.
  3. Нажать кнопку лайк, если она не нажата.
  4. Закрыть браузер.

Первый пункт мы уже сделали, перейдем ко второму.

Пройтись по каждому из постов.

На этом этапе, нужно понимать разметку HTML.

Зайдите на сайт, и нажмите кнопку F12.

У вас откроются инструменты разработчика. Изучив разметку, мы понимаем, что все посты находятся в теге article.

Сейчас нам нужно получить ссылку, на каждый пост. Для этого будем использовать этот css селектор.

article div div.blog-entry-content header.entry-header h2.entry-title a

Данный селектор указывает:

  • На элемент с тегом a
  • который находится находится внутри тега h2 с классом entry-title
  • тот, в свою очередь, находится внутри тега header с классом entry-header
  • тег header находится внутри тега div с классом blog-entry-content
  • тот, находится в теге div
  • тег div находится внутри тега article

Теперь, дополним бота.

link = browser.find_elements(By.CSS_SELECTOR, 'article div div.blog-entry-content header.entry-header h2.entry-title a')[0].get_attribute('href')
print(link)

Разберем новую функцию.

browser.find_elements(By.CSS_SELECTOR, css_селектор)

Данная функция ищет элементы по css селектору. В результате своей работы, она возвращает массив элементов.

link = browser.find_elements(By.CSS_SELECTOR, 'article div div.blog-entry-content header.entry-header h2.entry-title a')[0].get_attribute('href')

В-общем, мы из этого массива, достали первый элемент, и при помощи функции get_attribute(), получили значение атрибута href (ссылка на пост).

print(link)

И вывели его на экран.

Запустите скрипт, в консоли должна появится ссылка на первый пост.

Если закинуть массив элементов в цикл, то получится извлечь ссылки на все посты.

from selenium import webdriver #Ипортируем библиотеку
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary

binary = FirefoxBinary('C:/Program Files/Mozilla Firefox/firefox.exe') #Прописываем путь до firefox.exe
browser = webdriver.Firefox(firefox_binary=binary) #создаем объект Firefox браузера
browser.get('https://under-prog.ru') #посредством метода get, переходим по указаному URL

elements = browser.find_elements(By.CSS_SELECTOR, 'article div div.blog-entry-content header.entry-header h2.entry-title a')
for elem in elements:
    print(elem.get_attribute('href'))

Отлично, ссылки на все посты получены, осталось всем этим постам, поставить лайк.

Нажать кнопку лайк, если она не нажата

Сначала перекопируем наши ссылки в отдельный массив. Замените это:

elements = browser.find_elements(By.CSS_SELECTOR, 'article div div.blog-entry-content header.entry-header h2.entry-title a')
for elem in elements:
    print(elem.get_attribute('href'))

На это:

elements = browser.find_elements(By.CSS_SELECTOR, 'article div div.blog-entry-content header.entry-header h2.entry-title a')
links_arr = []
for elem in elements:
    links_arr.append(elem.get_attribute('href'))

Далее напишем код, отвечающий за нажатие кнопки лайк.

for link in links_arr: #Проходим по каждому элементу массива ссылок
    browser.get(link) #Переходим по ссылке
    if browser.find_element(By.CSS_SELECTOR, 'button.wp_ulike_btn').get_attribute('class').find('wp_ulike_btn_is_active') == -1: #Если кнопка 'лайк' не нажата, тогда
        browser.find_element(By.CSS_SELECTOR, 'button.wp_ulike_btn').click() #кликаем по кнопке 'лайк'

Разберем данные строки.

if browser.find_elements(By.CSS_SELECTOR, 'button.wp_ulike_btn').get_attribute('class').find('wp_ulike_btn_is_active') == -1:

Данная строка ищет кнопку с помощью css_селектора, и получает строку с названиями классов нашей кнопки.

Далее, при помощи функции find (стандартная функция python), мы получаем индекс подстроки ‘wp_ulike_btn_is_active‘, если не удалось найти подстроку, функция find возвращает -1, этим мы и воспользовались в нашем условии. Т.е. если атрибут ‘class‘ не содержит подстроку ‘wp_ulike_btn_is_active‘, то.

browser.find_elements(By.CSS_SELECTOR, 'button.wp_ulike_btn').click()

Кликаем по кнопке лайк.

Осталось закрыть браузер, делается это с помощью функции quit().

browser.quit()

Бот завершен, запустите скрипт, и наслаждайтесь автоматизацией.

Делаем браузер невидимым

Бот работает и все-бы ничего, но своим окном бразуера, он перекрывает все остальные окна. К счастью, у Firefox есть headless режим, позволяющий пользоваться функциями бразура, не открывая окно браузера.

Добавьте следующий код перед инициализацией браузера:

option = webdriver.FirefoxOptions()
option.headless = True

Здесь, мы переопредили настройки браузера, осталось передать их, нашему браузеру.

browser = webdriver.Firefox(options=option)

Теперь, порядок.

Полный код бота:

from selenium import webdriver #Ипортируем библиотеку
from selenium.webdriver.common.by import By
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary

binary = FirefoxBinary('C:/Program Files/Mozilla Firefox/firefox.exe') #Прописываем путь до firefox.exe
browser = webdriver.Firefox(firefox_binary=binary) #создаем объект Firefox браузера
browser.get('https://under-prog.ru') #посредством метода get, переходим по указаному URL

elements = browser.find_elements(By.CSS_SELECTOR, 'article div div.blog-entry-content header.entry-header h2.entry-title a')
links_arr = []
for elem in elements:
    links_arr.append(elem.get_attribute('href'))
    
for link in links_arr:
    browser.get(link)
    if browser.find_element(By.CSS_SELECTOR, 'button.wp_ulike_btn').get_attribute('class').find('wp_ulike_btn_is_active') == -1:
        browser.find_element(By.CSS_SELECTOR, 'button.wp_ulike_btn').click()
    
browser.quit()

Итоговый результат.

avatar

Будущее веб-технологий: создаём интеллектуального чат-бота, который может слышать и говорить

Время на прочтение
10 мин

Количество просмотров 17K

Голосовые интерфейсы в наши дни вездесущи. Во-первых — всё больше пользователей мобильных телефонов используют голосовых помощников, таких как Siri и Cortana. Во-вторых — устройства, вроде Amazon Echo и Google Home, становятся привычным элементом интерьера. Эти системы построены на базе программного обеспечения для распознавания речи, которое позволяет пользователям общаться с машинами с помощью голосовых команд. Теперь же эстафета, в обличье Web Speech API, переходит к браузерам.

image

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

Web Speech API позволяет веб-сайтам и веб-приложениям не только говорить с пользователем, но и воспринимать его речь. Взгляните хотя бы на несколько отличных примеров того, как это может быть использовано для расширения возможностей взаимодействия программ с человеком.

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

Возможности, использованные в этом материале, и распознавание, и синтез речи, сейчас поддерживают только браузеры, основанные на Chromium, в том числе — Chrome 25+ и Opera 27+, в то время как Firefox, Edge и Safari поддерживают только синтез речи.


Поддержка синтеза и распознавания речи в различных браузерах

Вот видео, в котором показано, как то, что мы собираемся создать, работает в Chrome.

Работа над веб-приложением состоит из трёх основных шагов:

  1. Использование интерфейса SpeechRecognition из Web Speech API для организации восприятия программой голоса пользователя.
  2. Преобразование того, что сказал пользователь, в текст, и отправка запроса коммерческому API обработки естественного языка, в нашем случае — API.AI.
  3. Получение ответа от API.AI и озвучивание его с использованием интерфейса SpeechSynthesis.


Схема взаимодействия приложения с пользователем и внешними службами

Полный исходный код того, что мы тут создадим, можно найти на GitHub.

Данное учебное руководство основано на Node.js. Для того, чтобы успешно с ним разобраться, вы должны знать JavaScript и иметь базовое понимание Node.js. Проверьте, установлен ли у вас Node и можете приступать к работе.

Подготовка Node.js-приложения

Для начала подготовим каркас Node.js-приложения. Создайте папку и разместите в ней следующее:

.
├── index.js
├── public
│   ├── css
│   │   └── style.css
│   └── js
│       └── script.js
└── views
    └── index.html

Затем выполните такую команду для инициализации приложения:

$ npm init -f

Ключ -f позволяет применить настройки по умолчанию, но вы можете настроить приложение и вручную, не используя этот ключ. В ходе инициализации будет создан файл package.json, который содержит основные сведения о приложении.

Теперь нужно установить зависимости, необходимые для сборки программы:

$ npm install express socket.io apiai --save

Использование флага --save ведёт к автоматическому обновлению файла package.json. В него будут добавлены сведения о зависимостях.

Мы собираемся использовать Express — серверный фреймворк для Node.js-приложений, который будет работать на локальном сервере. Для того, чтобы организовать двунаправленный обмен данными между сервером и клиентом в реальном времени, применим Socket.IO. Кроме того, мы установим средство для взаимодействия со службой обработки естественного языка API.AI. Это позволит нам создать интеллектуального чат-бота, способного поддерживать беседу с человеком.

Socket.IO — это библиотека, упрощающая использование технологии WebSockets в Node.js. После установки соединения между клиентом и сервером, основанном на сокетах, сообщения между ними будут передаваться очень быстро. А именно, происходить это будет, со стороны клиента, в тот момент, когда Web Speech API преобразует фразу пользователя в текстовое сообщение, и, со стороны сервера, когда текст ответа искусственного интеллекта придёт от API.AI.

Создадим файл index.js, подготовим экземпляр Express и будем ожидать соединений.

const express = require('express');
const app = express();

app.use(express.static(__dirname + '/views')); // html
app.use(express.static(__dirname + '/public')); // js, css, images

const server = app.listen(5000);
app.get('/', (req, res) => {
  res.sendFile('index.html');
});

Теперь займёмся работой над клиентской частью системы. На следующем этапе работы мы интегрируем Web Speech API в интерфейс приложения.

Распознавание речи с помощью SpeechRecognition

В Web Speech API есть основной интерфейс контроллера, называемый SpeechRecognition. Он позволяет преобразовывать в текст то, что пользователь сказал в микрофон.

Пользовательский интерфейс этого приложения очень прост. Его главный элемент — кнопка, нажатие на которую запускает распознавание голоса. Настроим файл index.html, подключим к нему файл с клиентскими скриптами (script.js) и библиотеку Socket.IO, которую позже будем использовать для обмена данными с сервером:

<html lang="en">
  <head>…</head>
  <body>
    …
    <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.1/socket.io.js"></script>
    <script src="js/script.js"></script>
  </body>
</html>

Добавим в тело HTML-документа кнопку:

<button>Talk</button>

Для стилизации кнопки используется файл style.css, его можно найти в коде примера.

Захват голоса с использованием JavaScript

В script.js, для распознавания голоса, создадим экземпляр интерфейса контроллера сервисов распознавания речи SpeechRecognition из Web Speech API:

const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();

Тут мы используем имена объектов с префиксом и без него, так как Chrome сейчас поддерживает нужное нам API только в варианте с префиксом webkit.

Кроме того, мы применяем синтаксис ECMAScript6. В него входит поддержка ключевого слова const и стрелочных функций. Всё это доступно в браузерах, которые поддерживают оба интерфейса Speech API — SpeechRecognition и SpeechSynthesis.

При желании распознавание речи можно настроить, устанавливая различные свойства соответствующего объекта. Мы применяем следующие настройки:

recognition.lang = 'en-US';
recognition.interimResults = false;

Затем нам нужна ссылка на кнопку. Мы будем прослушивать событие нажатия на неё, которое используется для запуска процесса распознавания речи.

После запуска распознавания воспользуемся событием result для того, чтобы получить текстовое представление того, что сказал пользователь:

recognition.addEventListener('result', (e) => {
  let last = e.results.length - 1;
  let text = e.results[last][0].transcript;

  console.log('Confidence: ' + e.results[0][0].confidence);

  // Позже здесь мы воспользуемся Socket.IO
});

Здесь нас интересует объект SpeechRecognitionResultList, который содержит результат распознавания речи. Текст можно извлечь из соответствующего массива. Кроме того, как вы можете видеть, мы тут выводим в консоль показатель confidence для полученного текста.

Сейчас воспользуемся Socket.IO для передачи данных серверному коду.

Обмен данными в реальном времени с использованием Socket.IO

Socket.IO — это библиотека, предназначенная для веб-приложений реального времени. Она позволяет организовать двунаправленный обмен данными между клиентской и серверной частями системы. Мы собираемся использовать эту библиотеку для передачи текстового представления распознанной речи коду, работающему на Node.js-сервере, а затем — для передачи ответа сервера в браузер.

Возможно, вы зададитесь вопросом о том, почему мы не используем простые HTTP или AJAX-запросы. Данные можно было бы отправить на сервер, например, используя POST-запрос. Однако, мы используем WebSockets посредством Socket.IO, так как это — наиболее удачное решение для организации двунаправленного обмена, в особенности для тех случаев, когда события передаются с сервера в браузер. Благодаря постоянному соединению на базе сокета, нам не нужно перезагружать данные в браузере или часто отправлять AJAX-запросы.


Схема взаимодействия клиента, сервера и стороннего сервиса обработки естественного языка

На клиенте, в файле script.js, создадим экземпляр Socket.IO:

const socket = io();

Затем добавим следующий код туда, где мы обрабатываем событие result от SpeechRecognition:

socket.emit('chat message', text);

Теперь перейдём к серверной части приложения. Она должна получить текст, переданный клиентом и передать его стороннему сервису, реализующему функции искусственного интеллекта. После получения ответа от сервиса, сервер должен передать его клиенту.

Получение ответа от внешнего сервиса

Множество платформ и сервисов позволяют интегрировать приложение с системой искусственного интеллекта с использованием преобразования речи в текст и с обработкой естественного языка. Среди них — IBM Watson, Microsoft LUIS, и Wit.ai. Для быстрого построения голосового интерфейса мы будем использовать API.AI. Здесь можно получить бесплатную учётную запись разработчика и быстро настроить ядро простого чат-бота, используя веб-интерфейс сервиса и библиотеку для Node.js.

После того, как вы создали учётную запись, создайте агента. Почитать об этом можно в начале руководства по началу работы. Полная настройка подразумевает создание сущностей и настройку связей между фразами, сказанными пользователем, и действиями, выполняемыми системой. Мы не будем этим заниматься, воспользовавшись предустановкой Small Talk. Этот пункт надо выбрать в левом меню, а затем, с помощью переключателя, включить сервис.


Настройка Small Talk в API.AI

Тут же, используя интерфейс API.AI, настроим агента. Перейдите к странице General Settings, щёлкнув по значку шестерёнки рядом с именем агента и получите ключ API. Вам понадобится то, что называется здесь «client access token» — токен доступа к сервису. Этот токен мы будем использовать в Node.js SDK.

Использование SDK API.AI для Node.js

Подключим наше Node.js-приложение к API.AI с использованием соответствующего SDK. В файле index.js инициализируем API.AI, используя токен доступа.

const apiai = require('apiai')(APIAI_TOKEN);

Если вы собираетесь запускать код только локально, можете просто ввести тут свой токен API. В противном случае лучше хранить его в переменной окружения. Есть множество способов настройки переменных окружения, обычно я записываю соответствующие данные в файл .env. В коде, который выложен на GitHub, этого файла нет, так как он включён в .gitignore. Если вы хотите узнать, как устроены подобные файлы, взгляните на файл .env-test.

Итак, мы, на сервере, используем Socket.IO для получения из браузера результата распознавания речи. Как только соединение будет установлено и сообщение будет доставлено на сервер, воспользуемся возможностями API.AI для получения ответа бота на высказывание пользователя:

io.on('connection', function(socket) {
  socket.on('chat message', (text) => {

    // Получим ответ от API.AI

    let apiaiReq = apiai.textRequest(text, {
      sessionId: APIAI_SESSION_ID
    });

    apiaiReq.on('response', (response) => {
      let aiText = response.result.fulfillment.speech;
      socket.emit('bot reply', aiText); // Send the result back to the browser!
    });

    apiaiReq.on('error', (error) => {
      console.log(error);
    });

    apiaiReq.end();

  });
});

Когда API.AI вернёт результат, используем метод socket.emit() Socket.IO для отправки данных в браузер.

SpeechSynthesis — голос для бота

Для того, чтобы завершить работу над приложением, вернёмся к клиентскому файлу script.js.

Создадим функцию для синтеза голоса. Тут мы будем использовать интерфейс контроллера SpeechSynthesis из Web Speech API.

Функция принимает, в качестве аргумента, строку, после чего система произносит текст из этой строки:

function synthVoice(text) {
  const synth = window.speechSynthesis;
  const utterance = new SpeechSynthesisUtterance();
  utterance.text = text;
  synth.speak(utterance);
}

В этой функции мы сначала создаём ссылку на входную точку API window.speechSynthesis. Можно заметить, что в этот раз мы не используем свойства с префиксами. Дело в том, что поддержка этого API шире, нежели у SpeechRecognition, и все браузеры, которые это API поддерживают, уже убрали префикс для SpeechSynthesis.

Затем, с помощью конструктора, мы создаём новый объект SpeechSynthesisUtterance(). Далее, устанавливаем свойство text объекта utterance. Именно то, что мы запишем в это свойство, и произнесёт машина. Тут можно устанавливать и другие свойства, например — свойство voice для выбора типа голоса.

И, наконец, мы используем вызов SpeechSynthesis.speak(), благодаря которой компьютер произносит фразу.

Теперь получим ответ от сервера, снова воспользовавшись Socket.IO. Как только сообщение будет получено, вызовем вышеописанную функцию:

socket.on('bot reply', function(replyText) {
  synthVoice(replyText);
});

Теперь с нашим интеллектуальным чат-ботом можно поболтать!

Общение с чат-ботом

Обратите внимание на то, что при первой попытке общения с программой браузер запросит у вас разрешение на использование микрофона. Как и в случае с другими API (Geolocation, Notification), браузер, перед работой с конфиденциальными данными, запрашивает разрешение. В результате ваш голос не будет записываться без вашего ведома.

Если вы пообщаетесь с этим ботом какое-то время, вам наскучит его болтовня, так как использованный здесь ИИ очень прост. Однако, боты, созданные на базе API.AI, поддаются настройке и обучению. Вы можете сделать вашего бота умнее, почитав документацию к API.AI и поработав над его интеллектом.

Надо отметить, что в этом руководстве освещены лишь основные возможности нового API, однако оно, на самом деле, очень гибко и поддерживает множество настроек. Можно менять язык распознавания и синтеза речи, синтезированный голос, включая акцент (например — американский и британский английский), высоту голоса и скорость произнесения слов. Вот несколько полезных ссылок, которые помогут вам узнать больше о Web Speech API:

  • Web Speech API (Mozilla Developer Network)
  • Web Speech API Specification (W3C)
  • Web Speech API: Speech Synthesis (Microsoft)

Вот — полезные материалы по Node.js и по библиотекам, которыми мы здесь пользовались:

  • Руководство по Node.js.
  • Документация к npm.
  • «Hello World» на Express.
  • Начало работы в Socket.IO.

И, наконец, взгляните на различные инструменты для обработки естественного языка и на платформы для организации общения с компьютерами:

  • API.AI (Google)
  • Wit.ai (Facebook)
  • LUIS (Microsoft)
  • Watson (IBM)
  • Lex (Amazon)

Итоги: шаг в будущее веба

Полагаем, в списке разработчиков платформ для обработки естественного языка далеко не случайно оказались Google, Facebook, Microsoft, IBM и Amazon. Значит ли это, что будущее веба однозначно за голосовыми интерфейсами? Нет, с полной уверенностью этого утверждать нельзя. Однако, это, а также то, что такие интерфейсы пользуются определённой популярностью на мобильных платформах, говорит о том, что им найдётся место и в веб-приложениях. Голосовые технологии и искусственный интеллект дополнят привычные способы работы с веб-содержимым, сделав интернет удобнее и доступнее.

Надеюсь, вам понравился наш рассказ о голосовых веб-технологиях и вы создали интересного виртуального собеседника.

Уважаемые читатели! Какие способы применения технологий распознавания и синтеза речи в веб-приложениях кажутся вам самыми интересными и перспективными?

Пишем своего бота для любого сайта на C#

DarkD Сливы

Всем привет. По просьюам трудящихся, решил показать как сделать своего бота практически для любого сайта на C# и библиотеки для управления браузером Selenium Web Driver.

Писать я буду очень простой код и стараться рассказать его так, чтобы даже далёкий от программирования человек мог всё понять, поэтому нехуй меня обсирать. Делайте свой мануал и там слушайте истории про свои кривые руки

В чем плюсы написания бота именно на C#

[*] С# (далее шарп) даёт очень понятный для понимания код, даже тем, кто в программировании полный ноль

[*] На выходе бот будет именно приложением EXE, а не каким-то скриптом

[*] Да и этого думаю хватит, а то больше голова не варит)

В качестве среды разработки я использую бесплатную официальную версию Visual Studio Community 2015

https://www.microsoft.com/ru-ru/SoftMicrosoft/vs2015Community.aspx

Так же понадобится сама библиотека Selenium Web Driver + Chrome Driver т.к. я буду показывать пример именно для браузера хром

В Visual Studio нажимаем «Проект — Управление пакетами NuGet»

На вкладке «Обзор» пишем Selenium и устанавливаем его

Чуть ниже качаем Selenium.Chrome.WebDriver

Теперь можно приступить к написанию самого бота, запускаем нашу Vusual Studio и нажимаем «Файл — создать — проект»

Язык выбираем C#, затем «Приложение Windows Forms (.NET Framework)», внизу пишем как будет называться наш проект и выбираем место сохранения проекта, затем жмём ОК

Теперь стоит вопрос, какой бот для примера написать? Так как в голову после вчерашней синьки ничего толкового не пришло, я решил показать пример написания бота для VLMI.su (реклама хули, а то всёравно копипастом разойдется статейка)

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

Так как мы только учимся, то нагружать бот большим функционалом мы не будем.

ТЗ будет выглядеть примерно так:

1. Зайти на сайт
2. Авторизоваться на форуме используя свой логин и пароль
3. Зайти в раздел "Флудилка"
4. Нажать там кнопку "Создать тему"
5. Ввести заголовок темы
6. Ввести текст темы
7. Нажать кнопку "Создать тему"

Думаю для понимания принципа работы бота этого будет достаточно.

Дальше будет на время написания статьи стоять большой хайд, т.к. время у меня не резиновое.

После написания всей статьи хайд будет снят.

Для того, чтобы окно нашей программы нельзя было растягивать по высоте и ширине, нужно кликнуть по окну программы и в свойствах установить Form Border Style в Fixed Dialog. Там же можно и изменить текст, который будет писаться в окне нашей программы

Там можно и другие настройки выставить, внизу будет писать, что делает тот или иной пункт

Какие элементы нужно добавить на форму я буду писать в процессе написания.

Сейчас нам нужно добавить

Два Label (Это обычный текст, чтобы было понятно куда писать логин, а куда пароль для авторизации)

Делаем это

Теперь нажимаем на каждый label и пишем ему нормальное имя всё в тех же свойствах, про которые я писал выше

Тепрь на форму нужно кинуть 2 текстбокса и всё это говницо более менее нормально выровнять, чтобы кровь из глаз не пошла

Теперь нужно нажать на каждом текстбоксе и изменить его имя, чтобы потом было легче писать код, я сделал текстбоксам имена LOGINBOX и PASSBOX

Теперь нужно по такому же принципу добавить на форму пару label и Textbox, в которые мы будем писать желаемый заголовок темы и сам текст темы. После добавления получаем такую картину

Добавленным текстбоксам я дал имена THREADBOX и TEXTBOX

Так же я поменял цвет фона приложения (BackColor), чтобы было лучше всё видно

Теперь еще хотелось бы добавить небольшой лог работы программы, чтобы в случае проблемы, мы видели где накосячили.

Дабавляем ниже еще label и Textbox, Textbox я назову LOG.

Кстати забыл сказать, текстбоксу с текстом темы и текстбоксу с логом, ножно добавить мультистрочность, чтобы всё в одну строку не писалось, для этого просто нажимаем по текстбоксу, потом нажимаем по стрелочке в правом верхнем углу и ставим галочку MultiLine

Получилось вот так:

Один хрен чего-то не хватает… Бля точно, нужно же сделать кнопку, которая всё это будет запускать

Перекидываем с панели элементов элемент Button на нашу форму и растягиваем кнопку как нам хочется, называем кнопку как угодно, а в свойствах Разработка — Name пишем START

Вот мы и накидали всё необходимое на нашу форму, теперь пора писать наш первый говнобот. Вообще нужно работу выводить в отдельный поток, чтобы небыло глюков и зависаний, но это наш первый бот, поэтому обойдемся без потоков, а то половина до этого момента дочитавших уже пошла за бухлом, т.к. новичкам тут без 100 грамм не разобраться.

Побухали, поматерили меня — приступим.

Клакаем 2 раза по кнопке СТАРТ и начинаем «кодить»…

Если вы попали сюда, то уже всё заебись)

Нужно теперь подключить необходимые директивы, поднимаемся в самый верх кода и пишем туда следующее

using System;
using System.Windows.Forms;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.PhantomJS;
using OpenQA.Selenium;
using System.Threading;

Теперь нужно инициализировать веб драйвер и в программу прочитать введенные логин, пароль, название темы и текст темы

private void START_Click(object sender, EventArgs e)
        {
            IWebDriver web; //Это мы... Как бы проще сказать... Короче инициализируем наш веб драйвер

            string LOGIN = LOGINBOX.Text;   //Читаем в переменную LOGIN введенный логин
            string PASS = PASSBOX.Text;     //Читаем в переменную PASS введенный логин
            string THREAD = THREADBOX.Text; //Читаем в переменную THREAD введенное название темы
            string TEXT = TEXTBOX.Text;     //Читаем в переменную TEXT введенный текст для темы

        }

Selenium Web Driver выполняет следующее действие, только когда страница загрузится на 100% иногда загрузка может занять очень долгое время из-за плохого интернета или просто из-за глюка, а например нужная вам кнопка уже давно появилась.

Чтобы избавиться от глюков и максимально ускорить быстродействие программы, нужно добавить следующую строку в код:

  web.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(20);

Эта команда ждет в течении 20 секунд, пока не появится нужный вам элемент (кнопка на сайте, поле для ввода или что-то другое) и выполняет указанное действие.

Как только нужный вам элемент появляется на странице, программа сразу приступит к следующему действию и не будет ждать пока страница загрузится на 100%

Осталось совсем не много, всё основное мы сделали.

Обернём весь код в try, чтобы не вылетали исключения

private void START_Click(object sender, EventArgs e)
        {
            IWebDriver web; //Это мы... Как бы проще сказать... Короче инициализируем наш веб драйвер

            string LOGIN = LOGINBOX.Text;   //Читаем в переменную LOGIN введенный логин
            string PASS = PASSBOX.Text;     //Читаем в переменную PASS введенный логин
            string THREAD = THREADBOX.Text; //Читаем в переменную THREAD введенное название темы
            string TEXT = TEXTBOX.Text;     //Читаем в переменную TEXT введенный текст для темы

            web = new ChromeDriver();       //В каком браузере будет работать наш бот, если лиса,
                                            //то нужно будет в NuGet скачать FireFox.WebDriver
                                            //и заменить эту строку на web = new FireFoxDriver();

            web.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(20);

            try
            {
                //Теперь весь код пишем только тут



            }
            catch (Exception)
            {

  
            }

        }

Вспомним своё ТЗ, первым делом нам нужно зайти на сайт

web.Navigate().GoToUrl("https://vlmi.su");  //Переходим по URL

Дальше будет сложнее, наш бот должен понимать, на какой элемент в коде страницы нужно нажать, для этого нужно установить расширение в хром.

Топаем сюда

https://chrome.google.com/webstore/category/extensions?hl=ru

В поиске вбиваем XPath и устанавливаем первое приложение XPath Helper.

Когда установилось расщирение, пора узнать какой «код» соответствует кнопке «Войти» на сайте VLMI.su

Переходим на VLMI.su -> Нажимаем кнопки CTRL + SHIFT + X и откроется откно хелпера. Теперь зажимаем правый шифт и мышкой наводим на нашу кнопку «Войти»

Нам хелпер даёт такой код:

/html[@id='XenForo']/body/div[@class='xbOffCanvasContainer']/div[@class='xbBoxed']/div[@id='headerMover']/header/fieldset[@id='moderatorBar']/div[@class='pageWidth']/div[@class='pageContent']/div/div[@id='right_nav']/ul[@class='publicTabs loginBar']/li[@class='login Popup PopupControl PopupClosed PopupContainerControl']/a[@class='logbt']

Но это что-то дохуя длинный, давайте сократим его методом тыка:

Я оставлю только самую последнюю часть кода и напишу её в хелпер через двойной слеш

//a[@class='logbt']

Как видим мне повезло и найден только один элемент с этим кодом

Это и есть код кнопки войти, вот по этому коду и нужно нажать, делается в Selenium это так

web.FindElement(By.XPath("//a[@class='logbt']")).Click();   //Нажать по кнопке ВОЙТИ

Давайте немного разберем этот код:

FindElement — это командой мы говорим программе, что нужно найти элемент

ByXpath — это по какому локатору мы ищем элемент, в данном случаем мы ищем по XPath. Существует 10 разных локаторов. Например локатор By.LinkText ищет ссылку, в которой есть указанный текст. Еще очень популярен CssSelector, тут в помощь идёт гугл.

.Click() указывает на то, что по найденому элементу нужно нажать

Дальше всё делаем по логике. Нажали кнопку, смотри что появилось. Ага, появились поля для ввода логина и пароля. Нужно значит ввести логин и пароль. Делается это точно так же как предыдущая команда:

находим XPath поля логина

//input[@id='ctrl_pageLogin_login']

и XPath поля пароля

//input[@id='ctrl_pageLogin_password']

Вводим логин:

web.FindElement(By.XPath("//input[@id='ctrl_pageLogin_login']")).SendKeys(LOGIN);   //Вводим логин из переменной LOGIN, а переменная LOGIN считывает то, что мы вручную ввели в программе

Так же вводим пароль:

web.FindElement(By.XPath("//input[@id='ctrl_pageLogin_password']")).SendKeys(PASS); //По такому же принципу вводим пароль

А теперь немного пиздец, я забыл про то, что мы еще собирались вести логи, но ничего страшного, это всё исправляется очень быстро. Нужно каждую команду обернуть в try и если будет ошибка, то писать это в лог. В итоге должно получиться вот так:

private void START_Click(object sender, EventArgs e)
        {
            IWebDriver web; //Это мы... Как бы проще сказать... Короче инициализируем наш веб драйвер

            string LOGIN = LOGINBOX.Text;   //Читаем в переменную LOGIN введенный логин
            string PASS = PASSBOX.Text;     //Читаем в переменную PASS введенный логин
            string THREAD = THREADBOX.Text; //Читаем в переменную THREAD введенное название темы
            string TEXT = TEXTBOX.Text;     //Читаем в переменную TEXT введенный текст для темы

            web = new ChromeDriver();       //В каком браузере будет работать наш бот, если лиса,
                                            //то нужно будет в NuGet скачать FireFox.WebDriver
                                            //и заменить эту строку на web = new FireFoxDriver();

            web.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(20);

            try
            {
                //Теперь весь код пишем только тут
                // + Environment.NewLine это означает перевести строку,
                //чтобы не писать всё в одну строчку

                try
                {
                    web.Navigate().GoToUrl("https://vlmi.su");  //Переходим по URL
                }
                catch (Exception)
                {
                    //Эта команда сработает только в том случае, если будет ошибка в блоке try
                    //и запишет текст ошибки в окно программы
                    LOG.AppendText("Ошибка. Не удалось перейти по URL" + Environment.NewLine);
                }

                try
                {
                    web.FindElement(By.XPath("//a[@class='logbt']")).Click();   //Нажать по кнопке ВОЙТИ
                }
                catch (Exception)
                {
                    //Эта команда сработает только в том случае, если будет ошибка в блоке try
                    //и запишет текст ошибки в окно программы
                    LOG.AppendText("Ошибка. Не удалось нажать на кнопку ВОЙТИ" + Environment.NewLine);
                }

                try
                {
                    web.FindElement(By.XPath("//input[@id='ctrl_pageLogin_login']")).SendKeys(LOGIN);   //Вводим логин из переменной LOGIN, а переменная LOGIN считывает то, что мы вручную ввели в программе
                }
                catch (Exception)
                {
                    //Эта команда сработает только в том случае, если будет ошибка в блоке try
                    //и запишет текст ошибки в окно программы
                    LOG.AppendText("Ошибка. Не удалось ввести логин" + Environment.NewLine);
                }

                try
                {
                    web.FindElement(By.XPath("//input[@id='ctrl_pageLogin_password']")).SendKeys(PASS); //По такому же принципу вводим пароль
                }
                catch (Exception)
                {
                    //Эта команда сработает только в том случае, если будет ошибка в блоке try
                    //и запишет текст ошибки в окно программы
                    LOG.AppendText("Ошибка. Не удалось ввести пароль" + Environment.NewLine);
                }



  
            }
            catch (Exception)
            {

  
            }

Теперь ошибок постараемся не делать и будем каждую команду обёртывать в try.

После ввода логина и пароля, хорошо бы нажать кнопку ВХОД, как это сделать вы уже должны уметь

try
                {
                    web.FindElement(By.XPath("//input[@class='button primary']")).Click();
                }
                catch (Exception)
                {
                    //Эта команда сработает только в том случае, если будет ошибка в блоке try
                    //и запишет текст ошибки в окно программы
                    LOG.AppendText("Ошибка. Не удалось нажать кнопку ВОЙТИ" + Environment.NewLine);
                }
                }

После попадания на форум видим что XPath мягко говоря хуёво справляется со своей задачей и при помощи него нам вываливает аж 40 вариантов…

Хуйня вопрос, вместо XPath будем использовать By.LinkText (Текст ссылки) в этом случает это годится лучше всего

try
                {
                    web.FindElement(By.LinkText("Флудилка"));
                }
                catch (Exception)
                {
                    //Эта команда сработает только в том случае, если будет ошибка в блоке try
                    //и запишет текст ошибки в окно программы
                    LOG.AppendText("Ошибка. Не нашли ФЛУДИЛКУ" + Environment.NewLine);

Теперь «охотимся» за кнопкой СОЗДАТЬ ТЕМУ. Как видим XPath и LinkText находят несколько вариантов. Тут можно пробовать использовать другие селекторы или как я ленивая жопа просто удлинить XPath

try
                {
                    web.FindElement(By.XPath("//div[@class='nodeListNewDiscussionButton']/a[@class='callToAction']")).Click();
                }
                catch (Exception)
                {
                    //Эта команда сработает только в том случае, если будет ошибка в блоке try
                    //и запишет текст ошибки в окно программы
                    LOG.AppendText("Ошибка. Не нашли кнопку СОЗДАТЬ ТЕМУ" + Environment.NewLine);
                }

Вводим название темы:

try
                {
                    web.FindElement(By.XPath("//iframe[@class='redactor_textCtrl redactor_MessageEditor redactor_BbCodeWysiwygEditor redactor_']")).SendKeys(TEXT);
                }
                catch (Exception)
                {
                    //Эта команда сработает только в том случае, если будет ошибка в блоке try
                    //и запишет текст ошибки в окно программы
                    LOG.AppendText("Ошибка. Не удалось ввести текст темы" + Environment.NewLine);
                }

Нажимаем кнопку СОЗДАТЬ ТЕМУ:

try
                {
                    web.FindElement(By.XPath("//dl[@class='ctrlUnit submitUnit'][1]/dd/input[@class='button primary']")).Click();
                }
                catch (Exception)
                {
                    //Эта команда сработает только в том случае, если будет ошибка в блоке try
                    //и запишет текст ошибки в окно программы
                    LOG.AppendText("Ошибка. Не удалось нажать кнопку создания темы" + Environment.NewLine);
                }

Вот и всё, самый простой бот готов, дальше можно использовать полученные знания для создания чего-то более сложного и под свои нужды

Бот работает немного криво, видимо пивко дало о себе знать.

Ниже прикреплю весь исходник, но думаю основное вы поняли. Если есть вопросы, то задавайте, попробую на них ответить.

=====================

Подписывайтесь на канал DarkD Сливы : https://telete.in/private_manual

Базы данных ФСБ/МВД (Сливы) : https://telete.in/joinchat/AAAAAFGLZjknYfJKHJRewA

Наш проект по накрутке в соц. сетях : https://telete.in/fastnakrutkaru

Подписывайте на канал DarkD-Shop : https://telete.in/dark_shop39

Уровень: начинающий

Материал рассчитан на тех, кто в жизни не написал ни строчки кода. Если вы уже в курсе основ программирования, прочитайте лучше о чистых функциях.

Многие слышали про чат-ботов и роботов для общения: им пишешь, они отвечают, получается диалог с машиной. Чат-бот может рассказать анекдот, поискать за вас в интернете, забронировать столик в ресторане и что угодно ещё, чему его обучат создатели.

Иногда такое общение выглядит как общение с человеком. Может даже показаться, что там работает искусственный интеллект — и иногда так действительно бывает. Но часто всё проще: это алгоритм, который умеет распознавать некоторые ваши слова и давать ответы по заранее заготовленным шаблонам. Чем алгоритм более разветвлённый, тем естественнее и полезнее бот.

Давайте сделаем собственного чат-бота с очень простым алгоритмом. Позже вы сможете усложнить его, как захотите. Но сначала — самая база для тех, кто никогда не писал код.

Обычно, чтобы создать какую-то программу, нужно выполнить несколько действий: например, скачать программу-обработчик языка, завести проект, написать задуманную программу, скомпилировать. И только потом ей можно пользоваться. Но мы пойдём по более простому пути: напишем программу, работающую прямо в браузере, через который вы читаете эту статью. Сделать это можно лишь на компьютере, на телефоне придётся пользоваться ботом.

Мы будем решать задачу на языке JavaScript — это язык программирования, который встроен в ваш браузер и на котором написать код можно прямо сейчас, ничего не устанавливая.

Чтобы сделать что-то на JavaScript, нужно открыть консоль. Почти во всех современных браузерах это делается сочетанием клавиш Shift + Ctrl + J или Cmd + Alt + J. Справа или снизу появится дополнительное окно, в котором уже будет что-то происходить:

Чат-бот будет создан через консоль

Если у вас не открылась консоль, зайдите в верхнее меню и поищите слово «Консоль». Обычно этот пункт прячется в разделе «Инструменты разработчика».

Когда вы открываете консоль, она сразу готова выполнять ваши команды. Если в неё вставить программу, написанную на JavaScript, и нажать Enter, ваш браузер её реализует. Если в коде есть ошибки, консоль сама подсветит их. Можно отправлять в неё программу кусками или даже построчно: браузер будет помнить всё, что происходило в вашей программе, пока вы не перезагрузите страницу.

Первая строка

В консоли можно не только писать код, но и выводить туда результаты. Давайте для начала сделаем самую простую программу, которая отобразит в консоли слово «Привет!». Для этого используем команду console.log('Привет!');

Вставим её в консоль и нажмём Enter:

Чат-бот: пишем первую строку кода

Поздравляем, вы только что написали свою первую программу для компьютера! Она очень простая: компьютер всего лишь говорит «Привет!». Но оцените момент: это вы его научили так говорить. Попробуйте научить его и другим словам.

Если написать несколько команд, получим сообщение из нескольких строк:

console.log('Привет!');
console.log('Я — ваш компьютер.');

Вот мы и начали создавать своего чат-бота, который нас уже поприветствовал в консоли. Теперь сделаем так, чтобы мы тоже могли ему что-нибудь ответить. Для этого нам понадобятся переменные.

Переменные

Переменная — это ячейка в памяти компьютера, где можно что-то хранить и менять. Дело в том, что компьютеру для вычислений нужно сказать: «Вот тут данные у нас меняться не будут, а вот тут будут, выдели память». И система выделит достаточно памяти, чтобы хранить всё, что будет лежать внутри переменной. В последнюю можно записать новое значение, а можно узнать, что уже лежит внутри неё.

Чтобы дать понять компьютеру, что у нас сейчас будет переменная, нужно сказать ему слово var, после которого вписать название переменной — так нам проще к ней обращаться. Например, следующая строка создаст переменную name и положит в неё слово «Код»:

var name = 'Код';

Название тут может быть практически любым, главное, чтобы оно начиналось с буквы. По-русски переменные называть нельзя, только буквами латинского алфавита. Можно было бы использовать вариант imya или zovut, но программисты считают, что чем проще название переменной, тем лучше.

Теперь посмотрим содержимое элемента. Следующая команда выведет то, что сейчас записано в переменной name:

console.log(name);

Можно посмотреть, какое сегодня число. Это внутренняя системная переменная. Строго говоря, это не совсем переменная, но для начала давайте считать так:

console.log(Date());

Но это мы всё смотрим во внутренности компьютера. А нам нужно спросить что-то у пользователя. Чтобы мы могли ввести новые данные в нашу программу, используем команду prompt()

var name = prompt('Как вас зовут?');

Вставьте в консоль команду var name = prompt('Как вас зовут?'); и посмотрите, что произойдёт. Компьютер выведет окно и будет ждать, пока вы внесёте туда своё имя. Интерфейс выглядит красиво: давайте в диалоге общаться с компьютером не через консоль, а через такие появляющиеся окошки. Для этого напишем новые команды:

alert('Привет! Я — ваш компьютер.');
var name = prompt('Как вас зовут?');

Пусть компьютер проявит вежливость и скажет, что ему приятно с нами познакомиться. Чтобы он смог обратиться к нам по имени, используем переменную name — в ней как раз хранится то, что мы ответили компьютеру:

alert('Привет, ' + name + ', приятно познакомиться!');

Чтобы вывести осмысленную фразу, мы взяли начало ‘Привет, ‘, затем с помощью плюсика соединили со значением переменной name, которая хранит наше имя, а потом ещё одним плюсиком добавили к фразе концовку. Чтобы компьютер знал, что мы хотим вывести на экран текст, а не числа, используются кавычки. Компьютер воспринимает как текст то, что внутри кавычек. И выводит точно в том виде, в котором написано.

Расчёт дня рождения

Давайте соединим все наши команды в одну программу и допишем несколько новых фраз:

alert('Привет! Я — ваш компьютер.');
var name = prompt('Как вас зовут?');
alert('Отлично, ' + name + ', приятно познакомиться!');
var hobby = prompt('Скажите, ' + name + ', а какое у вас хобби?');
alert('Действительно, ' + hobby + ' — интересное занятие! А я больше всего люблю вычисления и алгоритмы.');
var d = prompt(name + ', у меня для вас сюрприз! Напишите свой день рождения в формате месяц-день (например, 05-23)');
//Началась магия, где мы берём текущую дату и сравниваем с тем, что вы ввели.
//Кстати, это — комментарий, он не влияет на ход программы. 🙂
var current_date = new Date();
var my_date = new Date((d + '-' + (current_date.getFullYear() + 1)));
var dayZ = Math.abs(((Date.parse(my_date) - Date.parse(current_date)) / (1000 * 3600 * 24)) % 365);
var result = dayZ.toFixed(0);
//Магия закончилась, в переменной result у нас теперь хранится количество дней.
alert('Знаете, сколько дней осталось до вашего дня рождения? ' + result + '!');
alert('Спасибо за общение, ' + name + ', ещё увидимся!');

Обратите внимание: у нас появился новый вопрос и новая переменная hobby, в которой хранится информация об увлечении. А ещё — комментарии, которых можно добавлять сколько угодно. JavaScript не обращает внимания на то, что написано после двух косых черт:

//это комментарий

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

Что ещё посмотреть

Вот кое-что, что может вам пригодиться при создании первого чат-бота.

performance.now() — эта команда возвращает время в миллисекундах с момента открытия текущей страницы. Можно поделить на 1 000, и вы узнаете, сколько секунд вы сидите на какой-то странице. Если поделить на 60 000 — сколько минут.

setTimeout() — позволяет выполнить любой код через определённое время. Например, вы можете задать вопрос и предоставить ровно минуту на размышление, после чего появится окно для ответа.

setInterval() —  то же самое, что и предыдущее, но выполнение кода повторяется с равномерным интервалом, например раз в 5 минут. Если вы хотите научить чат-бота, чтобы он раз в час напоминал попить воды, эта команда — то, что нужно.

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

Подготовка

Этот туториал, и код в нем, требует установки нескольких дополнительных библиотек для Python. Они обеспечивают обертку Python’а в кусок низкоуровневого C-кода, который значительно упрощает создание и скорость исполнения.

Некоторые библиотеки существуют только под Windows. У них могут быть эквиваленты под Mac или linux, но мы не будем их рассматривать.

Вам нужно скачать и установить следующие библиотеки:

  • Python Imaging Library (PYL)
  • Numpy
  • PyWin

Все представленные библиотеки комплектуются установщиками. Запуск их автоматически установит модуль в директорию libsite-packages и, теоретически, добавит соответствующий pythonPath. Однако, на практике это происходит не всегда. Если Вы получите сообщение об ошибке после установки, добавьте их вручную в переменные Path.

Последний инструмент это графический редактор. Я предлагаю использовать Paint.Net как лучший из бесплатных, но подойдет любая программа с линейками и измерениями в пикеслях.

Мы будем использовать несколько игр в качестве примеров.

Введение

Это руководство написано с целью дать базовое понимание основы разработки ботов для браузерных игр. Подход, который мы собираемся дать, вероятно немного отличается от того, что многие ожидают услышать говоря о ботах. Вместо того, чтобы сделать программу, вставляющую код между клиентом и сервером (как боты для Quake или CS), наш бот будет находиться чисто снаружи. Мы будем опираться на методы Компьютерного зрения и вызовы Windows API для сбора необходимой информации и выполнения движений.

С этим подходом мы теряем часть деталей и контроля, но сокращаем время разработки и получим простоту в использовании. Автоматизирование специфичных игровых функций может быть создано в несколько строк кода, и полноценный бот, от начала до конца (для простой игры) может быть собран за несколько часов.

Когда вы привыкните к тому, что компьютер может видеть, начнете смотреть на игры по-другому. Хороший пример это поиск в играх-пазлах. Обычное решение основывается на ограничении скорости игрока, что заставляет принимать не оптимальные решения. Интересно (и довольно легко) «взломать» их скриптами движений которые не повторить человеку.

Эти боты могут быть также очень полезны для тестирования простых игр — в отличии от реальных игроков, боту не надоест играть один сценарий снова и снова.

Исходники примеров из курса, а также одного законченного бота можно найти здесь.

Шаг 1: Создание проекта

В папке с проектом создайте текстовый файл quickGrab, измените расширение на ‘py’ и откройте его в редакторе кода.

Шаг 2: Создаем приложение, которое делает скриншот экрана

Начнем работу с изучения базовой функции, которая делает скриншот экрана. Один раз создав и запустив, мы будем строчка за строчкой, как эту функцию, создавать каркас нашего кода.

Вставим в наш файл с проектом quickGrab.py следующий код:

ImageGrab
import os
import time
 
def screenGrab():
    box = ()
    im = ImageGrab.grab()
    im.save(os.getcwd() + '\full_snap__' + str(int(time.time())) + '.png', 'PNG')
 
def main():
    screenGrab()
 
if __name__ == '__main__':
    main()

Запустив этот код, вы получите скриншот экрана:

screenshot

Данный код забирает всю ширину и высоту области экрана и сохраняет в PNG файл в директорию проекта.

Давайте пошагово разберем код, чтобы понять как это работает. Первые три строки:

import ImageGrab
import os
import time

…называются ‘import statements’. Они говорят Pytjon’у какие модули загружать во время выполнения. Это дает доступ к методам этих модулей через синтаксис module.attribute .

Первый модуль Python Image Library мы установили ранее. Как следует из названия, он дает нам функциональность взаимодействия с экраном на которую ссылается бот.

Вторая строка импортирует модуль операционной системы (OS — operating system). Он дает возможность простой навигации по директориям в операционной системе. Это пригодится, когда мы начинаем размещать файлы в разных папках.

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

Следующие четыре строки определяют функцию screenGrab().

def screenGrab():
      box = ()
      im = ImageGrab.grab()
      im.save(os.getcwd() + '\full_snap__' + str(int(time.time())) + '.png', 'PNG')

Первая строка def screenGrab() определяет имя функции. Пустые скобки означают, что она не принимает аргументов.

Строка 2, box = () присваивает пустое значение переменной «box». Мы заполним это значение дальше.

Строка 3, im = ImageGrab.grab() создает полный скриншот экрана и возвращает RGB изображение в переменную im .

Строка 4, может быть немного сложнее если вы не очень хорошо знакомы с тем как работает Time module. Первая часть im.save( вызывает метод «save». Он принимает два аргумента. Первый это директория в которую нужно сохранить файл, а второй это формат файла.

Здесь мы устанавливаем директорию вызовом метода os.getcwd() . Функция получает текущую директорию в которой выполняется код и возвращает её как строку. Далее мы добавим «+». Сложение нужно использовать между каждым новым аргументом для соединения всех строк вместе.

Следующая часть '\full_snap__ дает нам простое описание в имени файла. Обратный слеш является экранирующим символом в Python, и мы добавили два, чтобы избежать отмены одного из символов.

Далее идет эта сложная конструкция: str(int(time.time())). Она использует встроенные функции Питона. Мы рассмотрим работу этого куска кода изнутри:

time.time() возвращает количество секунд с начала Эпохи, тип данных Float (число с плавающей точкой). Так как мы используем дату для именования файлов, мы не можем использовать десятичное число, поэтому мы обернем выражение в int(), чтобы конвертировать в целое число (Integer). Это делает нас ближе к решению, но Python не может соединить тип Integer с типом String, поэтому следующим шагом мы обернем все в функцию str(). Далее остается только добавить расширение как часть строки + '.png' и добавить вторым аргументом функции снова расширение: «PNG».

Последняя часть кода определяет функцию main(), которая вызывает функцию screenGrab(), когда исполняется.

И, наконец, условное обозначение, которое позволяет интерпретатору определить, какую функцию запускать в случае, если файл запущен как основной (а не просто импортирован в другой скрипт). В противном случае, если он загружен как модуль другого скрипта — он только передает доступ к этому методу, вместо выполнения.

def main():
      screenGrab()
   
  if __name__ == '__main__':
      main()

Шаг 3: Область видимости

Функция ImageGrab.grab() принимает один аргумент, который определяет область видимости. Это набор координат по шаблону (x,y,x,y), где

  1. Первая пара значение (x,y… определяет левый верхний угол рамки;
  2. Вторая пара …x,y) определяет правый нижний.

Это дает нам возможность скопировать только часть экрана, которая нам нужна.

Рассмотрим это на практике.

Для примера рассмотрим игру Sushi Go Round (Довольно увлекательная. Я Вас предупредил). Откройте игру в новой вкладке и сделайте скриншот использую существующий код screenGrab():

screenshot

Шаг 4: Задание координат

Пришло время задания координат для нашей области видимости.

Откройте скриншот в редакторе картинок.

Координаты (0,0) это всегда левый верхний угол изображения. Мы хотим заполнить X и Y таким образом, чтобы нашему новому скриншоту функция установила координаты (0,0) в крайний левый угол игровой области.

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

координаты экрана

Если вы это еще не сделали, включите линейки в вашем графическом редакторе и приблизьте верхний угол игровой области до той степени, пока не увидите рамки пикселей.

Наведите курсор на первый пиксель игровой области и запишите координаты на линейках. Это будут первые два значения для нашей функции. У меня получились значения (305, 243).

Затем следуйте к нижнему краю и запишите вторую пару координат. У меня получилось (945, 723). Вместе эти пары дают область с координатами (305,243,945,723).

Давайте добавим координаты в код:

import ImageGrab
  import os
  import time
   
  def screenGrab():
      box = (305,243,945,723)
      im = ImageGrab.grab(box)
      im.save(os.getcwd() + '\full_snap__' + str(int(time.time())) +
  '.png', 'PNG')
   
  def main():
      screenGrab()
   
  if __name__ == '__main__':
      main()

На строке 6 мы обновили массив для хранения координат игровой области.

Сохраните и запустите код. Откройте новое сохраненное изображение и вы увидите следующее:

Отлично! Это идеальный снимок игровой области. Нам не всегда будет требоваться эта напряженная охота за координатами. После того, как мы узнаем о win32api, мы рассмотрим более быстрые методы для установки координат, когда нам нужна идеальная точность.

Шаг 5: Перспективное планирование для гибкости

На данный момент, мы жестко прописали координаты по отношению к текущей настройке, исходя из нашего браузера и нашего разрешения монитора. Вообще, это плохая идея — жестко прописывать координаты таким образом. Если, например, мы хотим запустить код на другом компьютере, или, скажем, что-то на сайте немного сдвинет позицию игровой области — нам придется вручную скрупулезно фиксировать все наши координаты заново.

Давайте создадим две новые переменные: x_pad и y_pad. В них будет храниться расстояние между игровой областью и остальным экраном. Это поможет легко портировать код с места на место, так как каждая новая координата будет задаваться относительно двух глобальных переменных, которые мы создадим. Чтобы настроить изменения экрана нужно сбросить эти две переменные.

Так как мы уже сделали измерения, установить отступы для нашей текущей системы достаточно просто. Мы собираемся установить отступы, чтобы хранить положение первого пикселя за пределами игровой площадки. От первой пары координат нашего кортежа вычесть по 1. Получается 304 и 242.

Давайте добавим это в наш код:

# Globals
# ------------------
 
x_pad = 304
y_pad = 242

Теперь, когда они установлены, мы скорректируем координаты игровой области относительно них.

def screenGrab():
    box = (x_pad+1, y_pad+1,945,723)
    im = ImageGrab.grab(box)
    im.save(os.getcwd() + '\full_snap__' + str(int(time.time())) + '.png', 'PNG')

Для второй пары значений мы собираемся сначала найти разницу между первой и второй парой координат, чтобы получить размер игрового окна, а затем использовать эти значения с нашими переменными.

def screenGrab():
    box = (x_pad+1, y_pad+1, x_pad+641, y_pad+481)
    im = ImageGrab.grab(box)
    im.save(os.getcwd() + '\full_snap__' + str(int(time.time())) + '.png', 'PNG')

Для координаты x значение стало 945 — 304 = 641, а для y стало 723 — 242 = 481.

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

Шаг 6: Создание документации

Перед тем как перейти дальше, создадим документацию в начале нашего проекта. Так как большая часть нашего кода будет базироваться на особых координатах экрана и отношении к этим координатам, важно понимать окружение в котором всё будет работать правильно. Например, таких условия как разрешение монитора, браузер, включенная панель инструментов (так как это меняет размер окна браузера), и другие настройки необходимые для центровки игровой области на экране, все это влияет на относительные позиции координат. Документирование всего этого сильно помогает в решении проблем, когда код запускается на разных браузерах и компьютерах.

Напоследок, нужно следить за постоянно меняющимся рекламным пространством на популярных игровых сайтах. Если функция захвата экрана перестает себя вести, как ожидалось, стоит добавить координатам немного смещения.

Для примера, я обычно добавляю подобный комментарий в начало моего кода:

"""
 
All coordinates assume a screen resolution of 1280x1024, and Chrome 
maximized with the Bookmarks Toolbar enabled.
Down key has been hit 4 times to center play area in browser.
x_pad = 156
y_pad = 345
Play area =  x_pad+1, y_pad+1, 796, 825
"""

Добавление всей этой информации в начало файла позволяет быстро и легко перепроверить все настройки и выравнивание экрана без необходимости корпеть над кодом, пытаясь вспомнить, где вы сохранили конкретную x-координату.

Шаг 7: Делаем quickGrab.py удобным инструментом

Мы собираемся клонировать наш проект в этой точке, создавая два файла: один, чтобы писать код нашего бота, другой чтобы хранить функцию снимка экрана. Мы будем делать много снимков, поэтому с отдельным модулем работа пойдет быстрее.

Сохраните и закройте текущий проект.

Создайте копию проекта в этой же папке и переименуйте файл в code.py. Теперь добавление и редактирование всех изменений мы будем производить в code.py, а quickGrab.py оставим исключительно для скриншотов. Только добавим одно финальное изменение: изменим расширение на .pyw

Это расширение сообщает Питону, что нужно запускать скрипт без открытия консоли. Двойной клик по файлу и он быстро выполнится в фоне и сохранит скриншот в рабочую директорию.

Держите игру открытой в фоне (не забудьте отключить зацикленную музыку, иначе она сведет вас с ума); мы скоро к ней вернемся. У нас еще есть несколько инструментов для внедрения, прежде чем начать контролировать вещи на экране.

Шаг 8: win32api — краткий обзор

Работать с win32api может быть немного сложно на начальном этапе. Это обертка в низкоуровневый Windows C, которых хорошо задокументирован здесь, но навигация похожа на лабиринт, так что пару раз придется пройти по кругу.

Если при выполнении Вы видите ошибку «ImportError: No module named win32api», значит не установлен этот модуль. Выполните в консоли команду pip install pypiwin32

Прежде чем мы начнем писать код, давайте поближе познакомимся с некоторыми функциями API на которые далее мы будем опираться. После того как у нас появится четкое понимание каждого параметра, мы сможем легко настроить их для наших целей в игре.

win32api.mouse_event():

win32api.mouse_event(
    dwFlags,
    dx,
    dy,
    dwData  
    )

Первый параметр dwFlags определяет «действия» мыши. Такие как перемещение, клик, скроллинг и т.п. Следующий список показывает распространенные параметры, используемые для программирования движений.

  • win32con.MOUSEEVENTF_LEFTDOWN
  • win32con.MOUSEEVENTF_LEFTUP
  • win32con.MOUSEEVENTF_MIDDLEDOWN
  • win32con.MOUSEEVENTF_MIDDLEUP
  • win32con.MOUSEEVENTF_RIGHTDOWN
  • win32con.MOUSEEVENTF_RIGHTUP
  • win32con.MOUSEEVENTF_WHEEL

Имена говорят сами за себя. Если вы хотите выполнить виртуальный правый клик, нужно отправить параметр win32con.MOUSEEVENTF_RIGHTDOWN в dwFlags.

Следующие два параметра, dx и dy, описывают абсолютную позицию вдоль осей x и y. Пока мы будем использовать эти параметры для программирования движения мыши, они будут использовать систему координат отличную от той, которую мы использовали до этого. Мы зададим нули и будем опираться на другую часть API для движения мыши.

Четвертый параметр это dwData. Эта функция используется тогда и только тогда, когда dwFlags содержит MOUSEEVENTF_WHEEL. В других случаях она может быть опущена или установлена в 0. dwData скорость прокрутки колеса мыши.

Простой пример для закрепления

Если мы представим игру с переключением оружия как в Half-Life 2 (где оружие может быть выбрано вращением колеса) — мы можем использовать эту функцию для выбора оружия из списка:

def browseWeapons():
    weaponList = ['crowbar','gravity gun','pistol'...]
    for i in weaponList:    
        win32api.mouse_event(win32con.MOUSEEVENTF_MOUSEEVENTF_WHEEL,0,0,120)

Здесь мы хотим симулировать скроллинг колеса мыши для навигации по нашему теоретическому списку оружия, поэтому мы вносим ...MOUSEEVENTF_WHEEL в dwFlag. Не нужно указывать позиционирование dx и dy, оставим эти значения 0, и нам нужен один скрол вперед для каждого оружия в списке, поэтому устанавливаем значение 120 для dwData, что соответствует одному скролу мыши.

Как вы видите, работа с mouse_event это просто вопрос подключения правильных аргументов в правильном порядке. Давайте перейдем к более полезным функциям.

Шаг 9: Клики мыши

Мы переходим к созданию трех новых функций. Одна общая функция нажатия левой кнопки мыши, и два обработчика состояний нажатия и отпускания.

Откройте code.py в редакторе и добавьте следующее выражение к списку импортов

import win32api, win32con

Как и ранее, это дает нам доступ к содержимому модуля через синтаксис module.attribute

Далее создадим первую функцию клика мыши

def leftClick():
      win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
      time.sleep(.1)
      win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
      print "Click."          #completely optional. But nice for debugging purposes.

Напомню, что все, что мы делаем здесь это назначаем действие первому аргументу mouse_event. Мы не должны указывать никакую информацию о позиционировании, поэтому мы опускаем параметры координат (0,0), и мы не должны указывать дополнительную информацию, такую как dwData. Функция time.sleep(.1) говорит Питону приостановить выполнение на время указанное в скобках. Добавим это в наш код. Обычно это очень короткий промежуток времени. Без этого клик может получиться до того, как меню обновится.

Мы создали функцию для левого клика. Один раз нажать, один раз отпустить. Мы потратили много времени на нее, но давайте создадим еще две вариации. Это тоже самое, но теперь каждый шаг мы разобьем на отдельную функцию. Это можно использовать когда нам надо удерживать нажатой мышь в течение продолжительного времени (например, для перетаскивания предметов или стрельбы.)

def leftDown():
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
    time.sleep(.1)
    print 'left Down'
         
def leftUp():
    win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0)
    time.sleep(.1)
    print 'left release'

Шаг 10: Простые движения мышью

Все, что остается это движение мыши по экрану. Добавим следующие функции в файл code.py

def mousePos(cord):
    win32api.SetCursorPos((x_pad + cord[0], y_pad + cord[1])
     
def get_cords():
    x,y = win32api.GetCursorPos()
    x = x - x_pad
    y = y - y_pad
    print x,y

Эти две функции служат совершенно разным целям. Первая будет использоваться для задания движения в программе. Благодаря соглашению об именовании, тело функции делает именно то, что обозначает название SetCursorPos(). Вызов этой функции устанавливает координаты мыши по заданным (x,y). Обратите внимание, что мы добавили поправки x_pad и y_pad к координатам; это важно делать там, где координаты объявляются.

Вторая функция это простой инструмент который мы будем использовать в интерактивном режиме. Он выводит в консоль координаты текущей позиции мыши. Это сильно ускоряет процесс навигации в меню без необходимости делать скриншот и пользоваться линейками. Мы не хотим постоянно использовать эту функцию, так как некоторая активность мыши будет требовать точного позиционирования, но там где это возможно, это хорошо сэкономит нам время.

На следующем шаге мы применим эти техники для начала навигации в игровом меню. Но сначала надо удалить текущий контент у main() в сode.py и заменить его на тот, который мы написали. На следующем шаге мы будем работать с интерактивным режимом , поэтому функция screenGrab() нам пока не понадобиться.

Шаг 11: Навигация в меню

В этом и в следующих нескольких этапах мы попытаемся собрать координаты для разных событий используя метод get_cords(). Используя его мы сможем быстро построить код для таких вещей как навигация по меню, очистка столов, приготовление еды. После сбора мы встроим их в логику бота.

Давайте начнем. Сохраните и запустите код в Python Shell. С тех пор как на прошлом шаге мы заменили тело функции main()на pass, вы должны увидеть пустое окно после запуска Shell.

Теперь прежде чем перейти к игровой части, нужно пройти 4 начальных меню.

  1. Кнопка «Play»
  2. Кнопка «continue»
  3. Пропустить обучение «skip»
  4. Кнопка «continue»

Мы должны получить координаты каждой кнопки и добавить их в новую функцию startGame(). Расположите на экране Shell так, чтобы была видна игровая область. Нужно поставить мышь на кнопку, координаты которой нужно получить и выполнить в Shell функцию get_cords(). Убедитесь, что активным окном является Shell. Мы получим в Shell’е координаты текущей позиции мыши. Повторите это для остальных трех окон.

Оставьте Shell открытым и настройте экран так, чтобы видеть IDLE редактор. Добавим функцию startGame() и заполним новыми координатами.

def startGame():
    #location of first menu
    mousePos((182, 225))
    leftClick()
    time.sleep(.1)
     
    #location of second menu
    mousePos((193, 410))
    leftClick()
    time.sleep(.1)
     
    #location of third menu
    mousePos((435, 470))
    leftClick()
    time.sleep(.1)
     
    #location of fourth menu
    mousePos((167, 403))
    leftClick()
    time.sleep(.1)

Теперь у нас есть компактная функция, которую можно вызывать на старте каждой игры. Она устанавливает курсор на каждую позицию в меню, которую мы заранее определили и кликает. time.sleep(.1) говорит Питону остановить выполнение на 1/10 секунды между каждым кликом, чтобы меню успевало обновляться между кликами. Сохраните и запустите код.

У меня, как у медленного человека, прохождение меню вручную занимает больше секунды, тогда как наш бот может сделать это в течение примерно 0,4 секунд. Совсем неплохо!

Шаг 12: Зададим координаты еды

Давайте повторим процесс для каждой кнопки.

меню с едой

С помощью get_cords(), соберите координаты еды из меню. Еще раз в Python Shell напишите get_cords(), наведите мышь на еду и выполните команду.

Как вариант, для ускорения работы, если у вас есть второй монитор или вы можете организовать расположение таким образом, чтобы видеть браузер и окно Shell, можно не вводить каждый раз get_cords(), а сделать простой цикл for. Используйте метод time.sleep() чтобы успевать перемещать мышь между итерациями цикла.

for i in range(6):
    time.sleep(1.2)
    print get_cords()

Нам нужно создать новый класс Cord, чтобы хранить в нем собранные координаты. Возможность вызова через Cord.f_rice дает большие преимущества, так как можно передававть координаты прямо в mousePos(). Как вариант, можно хранить всё в словарях, но я нахожу синтаксис классов более удобным.

class Cord:
     
    f_shrimp = (40,340)
    f_rice = (90, 340)
    f_nori = (40, 387)
    f_roe = (90, 387)
    f_salmon = (40, 440)
    f_unagi = (90, 440)

Мы будем хранить много наших координат в этом классе, там будет некоторое дублирование, поэтому добавив префикс ‘f_’ мы будем знать, что это ссылка на еду, а не, скажем, на заказ еды по телефону.

Продолжим добавлять координаты.

Шаг 13: Координаты пустых мест

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

Повторите действия из предыдущих шагов, чтобы получить координаты тарелок. Сохраните их пока в закомментированной строке.

"""
 
Plate cords:
 
    87, 208
    194, 208
    288, 208
    395, 208
    491, 208
    591, 208

Осталось всего несколько шагов до действительно интересных штук.

Шаг 14: Координаты телефона

Итак, это будет последняя часть координат. Здесь понадобиться намного больше действий, поэтому вы можете сделать это вручную функцией get_cords(), вместо цикла. Мы собираемся прокликать всё меню телефона, чтобы получить координаты каждого пункта.

Здесь есть сложности, так как нам нужно иметь достаточно денег, чтобы купить что-то. Поэтому нам нужно сделать несколько порций суши, прежде чем вбивать координаты телефонных сделок. Нам придется сделать два суши рола , чтобы купить немного риса.

Есть шесть меню, через которые нам надо пройти.

  1. Телефон

  2. Начальное меню

  3. Начинки

  4. Рис

  5. Доставка

Нам нужно получить координаты всех, кроме Саке (можете её тоже добавить, если хотите. На мой взгляд бот отлично работает и без нее).

class Cord:
     
    f_shrimp = (40,340)
    f_rice = (90, 340)
    f_nori = (40, 387)
    f_roe = (90, 387)
    f_salmon = (40, 440)
    f_unagi = (90, 440)
     
#-----------------------------------    
     
    phone = (574, 358)
 
    menu_toppings = (528, 273)
     
    t_shrimp = (495, 224)
    t_nori = (490, 287)
    t_roe = (574, 279)
    t_salmon = (494, 340)
    t_unagi = (576, 218)
    t_exit = (591, 342)
 
    menu_rice = (532, 293)
    buy_rice = (548, 281)
     
    delivery_norm = (490, 292)

Окей! Мы наконец собрали все необходимые координаты. Давайте создадим что-нибудь полезное!

Шаг 15: Убираем со стола

Мы используем координаты собранные ранее для создания функции clear_tables().

def clear_tables():
    mousePos((87, 208))
    leftClick()
 
    mousePos((194, 208))
    leftClick()
 
    mousePos((288, 208))
    leftClick()
 
    mousePos((395, 208))
    leftClick()
 
    mousePos((491, 208))
    leftClick()
 
    mousePos((591, 208))
    leftClick()
    time.sleep(1)

Как вы можете видеть, это выглядит более менее похоже на нашу прошлую функцию startGame(). С одним небольшим отличием: нет функции time.sleep() между кликами. Нам не нужно ждать обновления меню, поэтому не нужно ждать задержку между кликами.

Однако, у нас есть функция time.sleep() в самом конце. Хотя это и не обязательно, лучше добавить паузы в выполнение кода, чтобы была возможность вручную завершить цикл, если это потребуется. В противном случае скрипт будет менять позицию мыши снова и снова и вы не будете в состоянии переместить фокус на Shell, чтобы остановить сценарий. Это прикольно первые два или три раза, но быстро теряет свое очарование.

Шаг 16: Создание суши

Сначала нужно понять как сделать суши. Кликните на книгу рецептов, чтобы открыть инструкции. Все виды суши, встречающиеся в игре, могут быть найдены на страницах этой книги. Оставлю первые три ниже, остальные вы можете найти в книге по ходу игры.

'''
Recipes:
 
    onigiri
        2 rice, 1 nori
     
    caliroll:
        1 rice, 1 nori, 1 roe
         
    gunkan:
        1 rice, 1 nori, 2 roe
'''

Теперь давайте создадим функцию, которая будет принимать в виде аргумента тип суши и затем собирать требуемые ингредиенты для переданного значения.

def makeFood(food):
    if food == 'caliroll':
        print 'Making a caliroll'
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_nori)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_roe)
        leftClick()
        time.sleep(.1)
        foldMat()
        time.sleep(1.5)
     
    elif food == 'onigiri':
        print 'Making a onigiri'
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_nori)
        leftClick()
        time.sleep(.1)
        foldMat()
        time.sleep(.05)
         
        time.sleep(1.5)
 
    elif food == 'gunkan':
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_nori)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_roe)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_roe)
        leftClick()
        time.sleep(.1)
        foldMat()
        time.sleep(1.5)

Эта функция такая же как и все остальные, но с одним небольшим отличием: мы не передаем координаты непосредственно, а вызываем их в качестве атрибутов класса Cord.

Функция foldMat() вызывается в конце каждого приготовления. Она кликает по циновке, чтобы завернуть суши, которое мы приготовили. Давайте зададим её:

def foldMat():
    mousePos((Cord.f_rice[0]+40,Cord.f_rice[1])) 
    leftClick()
    time.sleep(.1)

Давайте кратко пройдемся по функции mousePos(). Мы обращаемся к первому значению f_rice через добавление [0] в конце атрибута. Напомню, что это координата x. Для клика по циновке нам необходимо добавить небольшое смещение по оси х, и мы прибавим 40 к значению координаты x и передадим f_price[1] в y. Это сдвинет нашу позицию по x достаточно, чтобы активировать циновку.

Обратите внимание, что после вызова foldMat() у нас идет длинный time.sleep(). Циновка будет закручиваться и ингредиенты будут не доступны для клика, поэтому придется подождать конца анимации.

Шаг 17: Навигация в телефонном меню

В этом шаге мы зададим все точки mousePos() для подходящий пунктов меню, координаты которых были оставлены для этого момента. Эта часть программы которая будет обернута и контролируема логикой бота. Мы вернемся к этой функции, после того как обзаведемся несколькими новыми техниками.

def buyFood(food):
     
    mousePos(Cord.phone)
     
    mousePos(Cord.menu_toppings)
     
    mousePos(Cord.t_shrimp)
    mousePos(Cord.t_nori)
    mousePos(Cord.t_roe)
    mousePos(Cord.t_salmon)
    mousePos(Cord.t_unagi)
    mousePos(Cord.t_exit)
     
    mousePos(Cord.menu_rice)
    mousePos(Cord.buy_rice)
     
    mousePos(Cord.delivery_norm)

Краткое введение в компьютерное зрение

Сейчас в нашем распоряжении имеются очень интересные куски кода. Давайте рассмотрим как научить компьютер «видеть» события. Это очень увлекательная часть процесса.

Еще одна сторона построения бота заключается в том, что в конечном итоге бот может предоставить нам достаточное количество информации, что дальнейшем облегчит работу. Например, в случае суши-бота, как только мы пройдем первый уровень, бот будет предоставлять нам достаточное количество данных о том, что происходит на экране, и все что нам останется сделать это объяснить ему как реагировать на эти новые данные.

Другая большая часть в написании бота это обучение игре. Понимание какие значения нужно отслеживать в игре, а какие можно игнорировать. Например, можно не отслеживать деньги в кассе. Это то, что в конечном счете не имеет отношения к боту. Все, что ему надо знать это достаточно ли еды, чтобы продолжать работать. Таким образом, вместо того, чтобы мониторить количество денег, он просто проверяет, может ли бот что-то купить независимо от цены, потому что в игре это вопрос лишь нескольких секунд. Поэтому если бот не может что-то купить, он просто ждет несколько секунд.

Это подводит нас к финальной точке. Это брутфорс против элегантного решения. Алгоритмы зрения требуют значительного процессорного времени. Проверка нескольких точек во многих разных областях игровой области могут сильно снижать производительность. Таким образом все сводится к вопросу «должен бот узнать о том что что-то случилось или нет?»

Например, посетитель может проходить через четыре состояния: отсутствует, ожидает, ест и закончил есть. Когда закончил есть, он оставляет пустую тарелку. Мы могли бы затратить ресурсы на проверку всех мест просмотрев их, а затем кликнуть напротив ожидаемой тарелки (что может приводить к ошибкам, так как тарелки мигают, давая ложный сигнал). Или можно убирать их простым перебором, прокликивая все места через каждые несколько секунд. На практике прокликивание оказывается таким же эффективным, как и элегантное решение, позволяющее определить состояние клиента. Прокликать шесть мест занимает доли секунды, в то время как захват и обработка шести изображений сравнительно медленный способ. Мы можем использовать сэкономленное время для других более важных задач, связанных с обработкой изображений.

Шаг 18: Импорт библиотек Numpy и ImageOps

Добавим следующие выражения импорта

import ImageOps
from numpy import *

ImageOps это еще одна библиотека для работы с изображениями Python’а. Она используется для выполнения операций над изображениями (таких как перевод в черно-белый формат).

Я кратко поясню вторую строку для тех, кто близко не знаком с Питоном. Стандартное выражение импорта загружает пространство имен модуля (коллекцию имен переменных и функций). Таким образом, для доступа к элементам из области видимости мы используем синтаксис module.attribute. Однако, используя выражение from _ import мы наследуем имена в локальную область видимости. То есть синтаксис module.attribute больше не нужен. Это не верхний уровень, поэтому мы можем использовать их как встроенные функции Питона, как str() или list(). Импорт Numpy таким способом дает нам возможность просто вызвать array() вместо numpy.array().

Символ * означает импорт всего из модуля.

Шаг 19: Создаем компьютерное зрение

Первый метод сравнивает значения RGB у пикселей с ожидаемым значением. Этот метод отлично подходит для статичных объектов, таких как меню. Так как нужно иметь дело с конкретными пикселями, то метод не слишком надежен для движущихся объектов. Однако, это варьируется от случая к случаю. Иногда это отличная техника, а в другой раз приходится искать другой метод.

Запустите Sushi Go Round в браузере и начните новую игру. Откройте телефонное меню. Можете пока не обращать внимание на посетителей. Вы начинаете игру без денег, поэтому все пункты меню будут серыми как показано ниже. Это и будут те значения RGB, которые мы будем проверять.

В code.py перейдите к функции screenGrub(). Нужно внести следующие изменения:

def screenGrab():
    b1 = (x_pad + 1,y_pad+1,x_pad+640,y_pad+480)
    im = ImageGrab.grab(b1)
 
    ##im.save(os.getcwd() + '\Snap__' + str(int(time.time())) +'.png', 'PNG')
    return im

Мы сделали два небольших изменения. Во—первых, мы закомментировали строку, которая сохраняет скриншот. Во—вторых, на шестой строке мы возвращаем объект с изображением после выполнения функции.

Сохраните и запустите код.

Во время того как открыто телефонное меню и все пункты серые, выполните следующий код:

>>>im = screenGrab()
>>>

Это сохранит скриншот, который мы сделали с помощью функции screenGrub() в переменную im. Теперь мы можем вызвать функцию getpixel(xy) чтобы получить данные о заданных пикселях.

Теперь нам надо получить RGB значения для каждого серого элемента меню. Они нужны для сравнения со значениями, которые бот будет сравнивать с теми, которые он будет получать вызывая getpixel().

Мы же имеем необходимые координаты после выполнения предыдущих шагов, поэтому просто передадим их в качестве аргументов в функцию getpixel() и запишем результаты.

>>> im = screenGrab()
  >>> im.getpixel(Cord.t_nori)
  (109, 123, 127)
  >>> im.getpixel(Cord.t_roe)
  (127, 61, 0)
  >>> im.getpixel(Cord.t_salmon)
  (127, 71, 47)
  >>> im.getpixel(Cord.t_shrimp)
  (127, 102, 90)
  >>> im.getpixel(Cord.t_unagi)
  (94, 49, 8)
  >>> im = screenGrab()
  >>> im.getpixel(Cord.buy_rice)
  (127, 127, 127)
  >>>

Если мы добавим эти значения в функцию buyFood(), чтобы знать доступна покупка в данный момент или нет.

def buyFood(food):
     
    if food == 'rice':
        mousePos(Cord.phone)
        time.sleep(.1)
        leftClick()
        mousePos(Cord.menu_rice)
        time.sleep(.05)
        leftClick()
        s = screenGrab()
        if s.getpixel(Cord.buy_rice) != (109, 123, 127):
            print 'rice is available'
            mousePos(Cord.buy_rice)
            time.sleep(.1)
            leftClick()
            mousePos(Cord.delivery_norm)
            time.sleep(.1)
            leftClick()
            time.sleep(2.5)
        else:
            print 'rice is NOT available'
            mousePos(Cord.t_exit)
            leftClick()
            time.sleep(1)
            buyFood(food)
             
 
             
    if food == 'nori':
        mousePos(Cord.phone)
        time.sleep(.1)
        leftClick()
        mousePos(Cord.menu_toppings)
        time.sleep(.05)
        leftClick()
        s = screenGrab()
        print 'test'
        time.sleep(.1)
        if s.getpixel(Cord.t_nori) != (109, 123, 127):
            print 'nori is available'
            mousePos(Cord.t_nori)
            time.sleep(.1)
            leftClick()
            mousePos(Cord.delivery_norm)
            time.sleep(.1)
            leftClick()
            time.sleep(2.5)
        else:
            print 'nori is NOT available'
            mousePos(Cord.t_exit)
            leftClick()
            time.sleep(1)
            buyFood(food)
 
    if food == 'roe':
        mousePos(Cord.phone)
        time.sleep(.1)
        leftClick()
        mousePos(Cord.menu_toppings)
        time.sleep(.05)
        leftClick()
        s = screenGrab()
         
        time.sleep(.1)
        if s.getpixel(Cord.t_roe) != (127, 61, 0):
            print 'roe is available'
            mousePos(Cord.t_roe)
            time.sleep(.1)
            leftClick()
            mousePos(Cord.delivery_norm)
            time.sleep(.1)
            leftClick()
            time.sleep(2.5)
        else:
            print 'roe is NOT available'
            mousePos(Cord.t_exit)
            leftClick()
            time.sleep(1)
            buyFood(food)

Мы передаем имя ингредиента в функцию buyFood(). Серия выражений if/else получает переданный параметр и дает соответствующий ответ. Каждая ветка следует одинаковой логике, поэтому мы рассмотрим только одну.

if food == 'rice':
     mousePos(Cord.phone)
     time.sleep(.1)
     leftClick()
     mousePos(Cord.menu_rice)
     time.sleep(.05)
     leftClick()
     s = screenGrab()
     time.sleep(.1)

Первое, что мы должны сделать это кликнуть на телефон и открыть нужное меню. В этом случае меню с рисом.

s = screenGrab()
if s.getpixel(Cord.buy_rice) != (109, 123, 127):

Далее мы делаем скриншот области и вызываем getpixel(), чтобы получить RGB-значение пикселя у координат Cord.buy_rice. Мы сравниваем их с RGB-значением пикселя, полученного до этого на неактивном элементе. Если мы получаем в результате сравнения значение True, значит кнопка больше не серая и у нас достаточно денег для покупки. Соответственно, если получаем False, значит не можем себе это позволить.

print 'rice is available'
mousePos(Cord.buy_rice)
time.sleep(.1)
leftClick()
mousePos(Cord.delivery_norm)
time.sleep(.1)
leftClick()
time.sleep(2.5)

Если у нас достаточно денег, мы просто проходим через оставшиеся этапы, необходимые для покупки.

else:
    print 'rice is NOT available'
    mousePos(Cord.t_exit)
    leftClick()
    time.sleep(1)
    buyFood(food)

Наконец, если нам не хватает денег, то мы говорим боту закрыть меню, подождать секунду и повторить все сначала. Обычно это вопрос секунд, когда нам снова становится доступна покупка. Довольно просто добавить дополнительную логику, чтобы бот мог решить, нужно ли продолжать ждать или заняться в это время чем-то полезным и вернуться позже.

Шаг 20: Следим за ингредиентами

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

Мы должны придумать способ хранить информацию как много ингредиентов у нас сейчас на руках. Мы будем запрашивать экран в определенной области, или усредняя каждую ячейку ингредиента (к этой технике мы вернемся позже), но в дальнейшем, простой и наиболее быстрый способ просто хранить все элементы в словаре.

Количество каждого ингредиента остается постоянным на старте каждого уровня. Всегда начинаем с 10 обычных ингредиентов (рис, нори, икра), и по 5 дефицитных ингредиентов (креветки, лосось, угорь).

Начальные ингредиенты

Давайте добавим информацию о них в массив.

foodOnHand = {'shrimp':5,
              'rice':10,
              'nori':10,
              'roe':10,
              'salmon':5,
              'unagi':5}

Ключи массива содержат имена ингредиентов, по ним мы получаем доступ к значениям.

Шаг 21: Добавим отслеживание продуктов в код

Каждый раз, когда мы готовим, мы расходуем ингредиенты. И также пополняем их, когда делаем покупки. Давайте немного расширим нашу функцию makeFood()

def makeFood(food):
    if food == 'caliroll':
        print 'Making a caliroll'
        foodOnHand['rice'] -= 1
        foodOnHand['nori'] -= 1
        foodOnHand['roe'] -= 1 
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_nori)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_roe)
        leftClick()
        time.sleep(.1)
        foldMat()
        time.sleep(1.5)
     
    elif food == 'onigiri':
        print 'Making a onigiri'
        foodOnHand['rice'] -= 2 
        foodOnHand['nori'] -= 1 
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_nori)
        leftClick()
        time.sleep(.1)
        foldMat()
        time.sleep(.05)
         
        time.sleep(1.5)
 
    elif food == 'gunkan':
        print 'Making a gunkan'
        foodOnHand['rice'] -= 1 
        foodOnHand['nori'] -= 1 
        foodOnHand['roe'] -= 2 
        mousePos(Cord.f_rice)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_nori)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_roe)
        leftClick()
        time.sleep(.05)
        mousePos(Cord.f_roe)
        leftClick()
        time.sleep(.1)
        foldMat()
        time.sleep(1.5)

Теперь каждый раз при приготовлении Суши, мы уменьшаем значения наших ингредиентов на соответствующие значения. Теперь дополним код в функции buyFood()

def buyFood(food):
     
    if food == 'rice':
        mousePos(Cord.phone)
        time.sleep(.1)
        leftClick()
        mousePos(Cord.menu_rice)
        time.sleep(.05)
        leftClick()
        s = screenGrab()
        print 'test'
        time.sleep(.1)
        if s.getpixel(Cord.buy_rice) != (127, 127, 127):
            print 'rice is available'
            mousePos(Cord.buy_rice)
            time.sleep(.1)
            leftClick()
            mousePos(Cord.delivery_norm)
            foodOnHand['rice'] += 10     
            time.sleep(.1)
            leftClick()
            time.sleep(2.5)
        else:
            print 'rice is NOT available'
            mousePos(Cord.t_exit)
            leftClick()
            time.sleep(1)
            buyFood(food)
             
    if food == 'nori':
        mousePos(Cord.phone)
        time.sleep(.1)
        leftClick()
        mousePos(Cord.menu_toppings)
        time.sleep(.05)
        leftClick()
        s = screenGrab()
        print 'test'
        time.sleep(.1)
        if s.getpixel(Cord.t_nori) != (33, 30, 11):
            print 'nori is available'
            mousePos(Cord.t_nori)
            time.sleep(.1)
            leftClick()
            mousePos(Cord.delivery_norm)
            foodOnHand['nori'] += 10         
            time.sleep(.1)
            leftClick()
            time.sleep(2.5)
        else:
            print 'nori is NOT available'
            mousePos(Cord.t_exit)
            leftClick()
            time.sleep(1)
            buyFood(food)
 
    if food == 'roe':
        mousePos(Cord.phone)
        time.sleep(.1)
        leftClick()
        mousePos(Cord.menu_toppings)
        time.sleep(.05)
        leftClick()
        s = screenGrab()
         
        time.sleep(.1)
        if s.getpixel(Cord.t_roe) != (127, 61, 0):
            print 'roe is available'
            mousePos(Cord.t_roe)
            time.sleep(.1)
            leftClick()
            mousePos(Cord.delivery_norm)
            foodOnHand['roe'] += 10                
            time.sleep(.1)
            leftClick()
            time.sleep(2.5)
        else:
            print 'roe is NOT available'
            mousePos(Cord.t_exit)
            leftClick()
            time.sleep(1)
            buyFood(food)

Шаг 22: Проверка запасов еды

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

def checkFood():
    for i, j in foodOnHand.items():
        if i == 'nori' or i == 'rice' or i == 'roe':
            if j <= 3:
                print '%s is low and needs to be replenished' % i
                buyFood(i)

Мы будем циклом обходить пары ключ:значение в массиве с запасами ингредиентов. Если нори, риса или икры останется меньше 4, вызывается функция buyFood(), параметром в которую передается имя ингредиента.

Шаг 23: Перевод RGB значений — Установка

Для того, чтобы двигаться дальше, мы должны получать информацию о том, какой тип суши запрашивает клиент. Сделать это с помощью функции getpixel() было бы достаточно тяжело, так как нам пришлось бы искать область с уникальным значением RGB для каждого вида суши для каждого посетителя. Кроме того, для каждого нового типа суши, вам придется вручную осмотреть его, чтобы увидеть, есть ли у него уникальный RGB, который не найден ни в одном из других типов суши. Это значит, что нам пришлось бы хранить 6 мест, по 8 типов суши. Это 48 уникальных координат.

Очевидно, нам нужен метод попроще.

Метод номер два: усреднение изображения. Этот способ работает с набором RGB-значений, вместо конкретного пикселя. Для каждого скриншота, переведенного в оттенки серого, и загруженного в массив, мы просуммируем все пиксели. Эта сумма обрабатывается также как значение RGB в методе getpixel().

Гибкость этого метода в том, что, когда он настроен, от нас больше ничего не требуется. По мере ввода новых суши, их значения RGB суммируются и выводятся для нашего использования. Нет необходимости искать конкретные координаты с помощью getpixel().

Тем не менее, для этого метода требуется настройка. Нам нужно получать скрин только той области, в которой отображается желаемое суши конкретного клиента, а не всего игрового окна.

Найдите уже написанную функцию screenGrab() и скопируйте ее рядом. Переименуйте копию в grab(), и внесите следующие изменения:

def grab():
    box = (x_pad + 1,y_pad+1,x_pad+641,y_pad+481)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    return a

На второй строке мы переводим скриншот в оттенки серого и записываем в переменную im. Такой скриншот позволяет работать с ним намного быстрее, так как вместо 3 значений Красного, Зеленого и Голубого, каждый пиксель имеет только одно значение от 0 до 255.

На третьей строчке мы создаем массив значений цветов использую PIL метод getcolors() и записываем в переменную im.

На четвертой строке мы суммируем все точки и выводим их в консоль. Это число по которому мы будем сравнивать изображения.

Шаг 24: Зададим области для скринов заказов

Начните новую игру и дождитесь когда все посетители рассядутся по своим местам. Сделайте скрин двойным кликом по quickGrab.py.

Области с заказами

Нам нужно задать ограничивающие области внутри каждого из этих спич-баблов (белые облака с рисунком суши). Приблизьте из в редакторе так, чтобы было видно пиксели:

Области с заказами

Для каждого спич-бабла мы должны быть уверены что верхний левый край начинается в одном и том же месте. Для этого отступим два пикселя от внутреннего края спич-бабла. Первый белый пиксель на второй ступеньке будет началом отсчета.

Области с заказами

Для создания пары координат, отсчитайте 63 по оси x и 16 по оси y. Это даст подобный прямоугольник:

Области с заказами

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

Приступим к созданию 6 новых функций, каждая будет уточнением функции grab() и будет передавать аргументом координаты спич-баблов. Как только закончим, создадим функцию, запускающую их все сразу для тестирования.

def get_seat_one():
    box = (45,427,45+63,427+16)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    im.save(os.getcwd() + '\seat_one__' + str(int(time.time())) + '.png', 'PNG')    
    return a
 
def get_seat_two():
    box = (146,427,146+63,427+16)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    im.save(os.getcwd() + '\seat_two__' + str(int(time.time())) + '.png', 'PNG')    
    return a
 
def get_seat_three():
    box = (247,427,247+63,427+16)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    im.save(os.getcwd() + '\seat_three__' + str(int(time.time())) + '.png', 'PNG')    
    return a
 
def get_seat_four():
    box = (348,427,348+63,427+16)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    im.save(os.getcwd() + '\seat_four__' + str(int(time.time())) + '.png', 'PNG')    
    return a
 
def get_seat_five():
    box = (449,427,449+63,427+16)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    im.save(os.getcwd() + '\seat_five__' + str(int(time.time())) + '.png', 'PNG')    
    return a
 
def get_seat_six():
    box = (550,427,550+63,427+16)
    im = ImageOps.grayscale(ImageGrab.grab(box))
    a = array(im.getcolors())
    a = a.sum()
    print a
    im.save(os.getcwd() + '\seat_six__' + str(int(time.time())) + '.png', 'PNG')    
    return a
 
def get_all_seats():
    get_seat_one()
    get_seat_two()
    get_seat_three()
    get_seat_four()
    get_seat_five()
    get_seat_six()

Отлично! Много кода, но это только вариации функции ImageGrab.Grab. Теперь нужно перейти в игру и протестировать. Нужно убедиться, что не зависимо от того в каком спич-бабле находится суши, один и тот же заказ отображает одну и ту же сумму.

Шаг 25: Создаем массив с типами Суши

Как только Вы убедились, что каждый тип суши дает одинаковое значение суммы пикселей, запишите эти суммы в массив:

sushiTypes = {1893:'onigiri', 
    2537:'caliroll',
    1900:'gunkan',}

Здесь значение стоит на месте ключа потому, что по нему будет осуществляться поиск.

Шаг 26: Создаем массив мест без спич-баблов

Для работы нам нужны значения сумм скриншотов мест под спич-баблами, чтобы понимать что посетитель отсутствует в данном месте.

Нужно начать игру и выполнить функцию get_all_seats() до того как придет первый посетитель. Полученные значения запишем в массив Blank.

class Blank:
    seat_1 = 8041
    seat_2 = 5908
    seat_3 = 10984
    seat_4 = 10304
    seat_5 = 6519
    seat_6 = 8963

Шаг 27: Соединяем все вместе

Время окончательно передать контроль нашему боту. Напишем скрипт, который позволит ему реагировать на клиентов, готовить еду и пополнять запасы.

Основа логики будет следующая: Проверка мест > Проверка заказов > Если не хватает ингредиентов, то купить > почистить столы > Повторить сначала

def check_bubs():

  checkFood()
  s1 = get_seat_one()
  if s1 != Blank.seat_1:
      if sushiTypes.has_key(s1):
          print 'table 1 is occupied and needs %s' % sushiTypes[s1]
          makeFood(sushiTypes[s1])
      else:
          print 'sushi not found!n sushiType = %i' % s1

  else:
      print 'Table 1 unoccupied'

  clear_tables()
  checkFood()
  s2 = get_seat_two()
  if s2 != Blank.seat_2:
      if sushiTypes.has_key(s2):
          print 'table 2 is occupied and needs %s' % sushiTypes[s2]
          makeFood(sushiTypes[s2])
      else:
          print 'sushi not found!n sushiType = %i' % s2

  else:
      print 'Table 2 unoccupied'

  checkFood()
  s3 = get_seat_three()
  if s3 != Blank.seat_3:
      if sushiTypes.has_key(s3):
          print 'table 3 is occupied and needs %s' % sushiTypes[s3]
          makeFood(sushiTypes[s3])
      else:
          print 'sushi not found!n sushiType = %i' % s3

  else:
      print 'Table 3 unoccupied'

  checkFood()
  s4 = get_seat_four()
  if s4 != Blank.seat_4:
      if sushiTypes.has_key(s4):
          print 'table 4 is occupied and needs %s' % sushiTypes[s4]
          makeFood(sushiTypes[s4])
      else:
          print 'sushi not found!n sushiType = %i' % s4

  else:
      print 'Table 4 unoccupied'

  clear_tables()
  checkFood()
  s5 = get_seat_five()
  if s5 != Blank.seat_5:
      if sushiTypes.has_key(s5):
          print 'table 5 is occupied and needs %s' % sushiTypes[s5]
          makeFood(sushiTypes[s5])
      else:
          print 'sushi not found!n sushiType = %i' % s5

  else:
      print 'Table 5 unoccupied'

  checkFood()
  s6 = get_seat_six()
  if s6 != Blank.seat_6:
      if sushiTypes.has_key(s6):
          print 'table 1 is occupied and needs %s' % sushiTypes[s6]
          makeFood(sushiTypes[s6])
      else:
          print 'sushi not found!n sushiType = %i' % s6

  else:
      print 'Table 6 unoccupied'

  clear_tables()

Первое что мы делаем — это проверяем запасы. Далее делаем скрин позиции заказа первого посетителя и записываем в s1. После этого проверяем скрин на неравенство пустому месту. Если они не равны, значит у нас есть посетитель. Далее проверяем массив sushiTypes чтобы определить какой заказ сделал посетитель и передаем этот аргумент в функцию makeFood().

Clear_tables() выполняется через проверку каждых двух мест.

Теперь надо это зациклить.

Шаг 28: Главный цикл

Мы создаем цикл. Так как мы не задаем никакого условия выхода из цикла, то чтобы завершить игру, нужно в консоли нажать CTRL + C.

def main():
    startGame()
    while True:
        check_bubs()

Вот и все! Обновите страницу, дождитесь загрузки игры и запустите бота!

Бот немного неуклюжий и нуждается в доработках. Но это отличный скелет для того чтобы продолжить экспериментировать!

Наличие лэндинга на вашем сайте ставит перед вами очевидную задачу – необходимость своевременно отвечать потенциальным клиентам на вопросы, задаваемые через встроенные в лэндинг формы.

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

Сегодня я начинаю цикл статей, в которых мы рассмотрим написание простого Телеграм-бота, который будет присылать сообщение в специально созданный канал, после того, как пользователь заполнил форму на вашем сайте.

Разработку мы будем осуществлять в Astra Linux, которую настроили в предыдущей статье

А чтобы не изобретать велосипед мы будем использовать готовую библиотеку:

https://github.com/irazasyed/telegram-bot-sdk

Но для её установки нам потребуется Composer!

Установка Composer

Установим Composer, это популярный установщик библиотек для PHP. Данный метод рекомендуется в документации для использования при установке.

Актуальную команду для установки вы можете найти на странице проекта –

https://getcomposer.org/doc/faqs/how-to-install-composer-programmatically.md

Создадим папку для будущего бота:

sudo mkdir -p /var/phpbots/landing-bot

Перейдем в папку

sudo cd /var/phpbots/landing-bot

Для упрощения разработки установим права доступа на папку:

sudo chmod -R 777 /var/phpbots/landing-bot

Обратите внимание! При установке на продакшн-сервер права 777 на папку ставить строго не рекомендуется!

Запустим

sudo wget https://raw.githubusercontent.com/composer/getcomposer.org/76a7060ccb93902cd7576b67264ad91c8a2700e2/web/installer -O - -q | php –

Перенесем скрипт в папку

/usr/local/bin/

Таким образом он будет глобально:

sudo mv composer.phar /usr/local/bin/composer

Проверим корректность установки:

sudo composer self-update

You are already using the latest available Composer version 2.1.5 (stable channel).

Установка библиотеки telegram-bot-sdk

Обратите внимание строго не рекомендуется запускать composer из под пользователя root.

Используйте учетную запись обычного пользователя! 

Запустим установку.

composer require irazasyed/telegram-bot-sdk ^2.0

./composer.json has been created

Running composer update irazasyed/telegram-bot-sdk

Loading composer repositories with package information

Updating dependencies

Lock file operations: 20 installs, 0 updates, 0 removals

  - Locking doctrine/inflector (2.0.3)

…

  - Locking symfony/translation-contracts (v2.4.0)

Writing lock file

Installing dependencies from lock file (including require-dev)

Package operations: 20 installs, 0 updates, 0 removals

  - Downloading doctrine/inflector (2.0.3)

…

  - Downloading irazasyed/telegram-bot-sdk (v2.3.0)

  - Installing doctrine/inflector (2.0.3): Extracting archive

…

  - Installing irazasyed/telegram-bot-sdk (v2.3.0): Extracting archive

15 package suggestions were added by new dependencies, use `composer suggest` to see details.

Generating autoload files

10 packages you are using are looking for funding.

Use the `composer fund` command to find out more!

Я сильно сократил вывод команды, для экономии места!

После окончания установки будет создана папка

vendor

и два файла

composer.json 

composer.lock

На этом установка библиотеки закончена!

Создание бота в Телеграм

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

Откройте в Телеграм раздел Все чаты и введите в строку поиска:

@BotFather 

2021-08-09_19-29-35.png

Отец всех ботов выглядит ка как показано на рисунке. Разными «умниками» созданы поддельные боты, для продвижения всяческой чепухи, но нам нужен только главный!

2021-08-09_19-33-02.png

В чат боту напишем

 /newbot

Alright, a new bot. How are we going to call it? Please choose a name for your bot.

Напишем имя бота. Так как я создаю тестового бота, то и назову его:

Ru-Test9999-bot

Good. Now let's choose a username for your bot. It must end in `bot`. Like this, for example: TetrisBot or tetris_bot.

Теперь нам нужно придумать внешнее имя, это имя, по которому его можно будет найти в Телеграм.

Обратите внимание, что многие имена уже зарегистрированы и даже имя Ru-Test9999-bot занято! Будем импровизировать:

Ru_Test9999_bot

Если всё прошло успешно, вы получите сообщение:

Done! Congratulations on your new bot. You will find it at t.me/Ru_Test9999_bot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.

Use this token to access the HTTP API:

1234567890:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Keep your token secure and store it safely, it can be used by anyone to control your bot.


For a description of the Bot API, see this page: https://core.telegram.org/bots/api

 Нас интересует строчка вида:

1234567890:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Это токен вашего бота и вы должны бережно его хранить в недоступном месте! Никому не сообщать и не забывать удалять его из исходного кода, если размещаете его на github.com!

Для теста я создал группу:

altuninvv-dev

и при создании ввел имя бота

Ru_Test9999_bot

После создания группы зайдите в Настройки группы — Разрешения и уберите все возможности. Это будет канал для бота, соответственно нам не нужно, чтобы сюда писали всякую чушь и приглашали левых людей.

Так же откроем Управление группой и сделаем Ru_Test9999_bot администратором.

На этом настройка бота завершена.

Чтобы бот из Телеграм имел доступ к программному коду на вашем сервере нам потребуется доменное имя и доступ к серверу по протоколу https://

Настройка Nginx

Бот должен быть доступен по https://. Давайте настроим Nginx для предоставления доступа к файлу с ботом.

Чтобы защититься от разных «хацкеров» я рекомендую создать на веб-сервере папку с длинным именем. Для этого сгенерируем UUID:

python -c 'import uuid; print(str(uuid.uuid4()))'

d2549d33-255a-40a6-8cce-36c8f7efe780

Каждый раз при запуске результат будет разным!

Создадим папку для веб-сервера

sudo mkdir /var/www/phpbots

В папке создадим симлинк до папки /var/phpbots/landing-bot

cd /var/www/phpbots

sudo ln -s /var/phpbots/landing-bot d2549d33-255a-40a6-8cce-36c8f7efe780

Теперь, если мы напишем:

ls /var/www/phpbots/d2549d33-255a-40a6-8cce-36c8f7efe780

composer.json  composer.lock  vendor

То увидим содержимое папки /var/phpbots/landing-bot

 Создадим файл конфигурации для nginx

server {
        listen 9966 default_server;
        listen [::]:9966 default_server;


        root /var/www/phpbots;

        # Add index.php to the list if you are using PHP
        index index.php index.html;

        server_name _;


        location / {
                try_files $uri $uri/ /index.php;
        }


        location ~ .php$ {
                fastcgi_pass 127.0.0.1:9000;
                fastcgi_index  index.php;
                fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
                include        fastcgi_params;
                include /etc/nginx/fastcgi.conf;
        }
}

Откроем 

http://192.168.1.42:9966

Получим

403 Forbidden

nginx/1.14.1

Создадим в папке

/var/www/phpbots/d2549d33-255a-40a6-8cce-36c8f7efe780

файл test.php с содержимым:

<?php echo phpinfo(); ?>

Откроем

http://192.168.1.42:9966/d2549d33-255a-40a6-8cce-36c8f7efe780/test.php

Откроется страница с phpinfo()

Настройка туннеля с помощью ngrok.com

Еще одной проблемой при разработке бота является то, что нам придется вносить изменения сразу на сервере. Не хотелось и не рекомендуется использовать продакшн-сервер для опытов.

Чтобы решить эту проблемы мы воспользуемся бесплатным планом сервиса https://ngrok.com

Установим клиент ngrok на наш локальный сервер:

Зарегистрируйтесь на сайте и в разделе

https://dashboard.ngrok.com/get-started/setup

Скачайте файл ngrok-stable-linux-amd64.zip

На момент написания статьи ссылка была такой:

https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip

Но лучше скопировать свежую!

Скачаем в папку обычного пользователя, опять же, не запускайте ничего от root без необходимости!

cd ~

wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip

Обратите внимание мы будем запускать ngrok от обычного пользователя!

Распакуем архив:

unzip ngrok-stable-linux-amd64.zip

Archive:  ngrok-stable-linux-amd64.zip

  inflating: ngrok

На странице

https://dashboard.ngrok.com/get-started/setup

вы найдете команду для добавления вашего токена в локальное хранилище ngrok

скопируйте и запустите строчку вида:

./ngrok authtoken XXXXXXXXXXваштокенXXXXXXXXXXXXXXXXXXXXXXX

Authtoken saved to configuration file: /home/user/.ngrok2/ngrok.yml

Запустим ngrok

./ngrok https 9966

ngrok by @inconshreveable                                                                                                          (Ctrl+C to quit)

Session Status                online
Account                       Vasiliy Altunin (Plan: Free)
Version                       2.3.40
Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http:// bc8ab1191806.ngrok.io -> http://localhost:9966
Forwarding                    https:// bc8ab1191806.ngrok.io -> http://localhost:9966

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

Откроем

https://bc8ab1191806.ngrok.io/d2549d33-255a-40a6-8cce-36c8f7efe780/test.php

Обратите внимание, bc8ab1191806 меняется каждый раз, когда вы перезапускаете ngrok. Чтобы получить постоянное имя придется купить подписку!

Но нам для написания и отладки бота хватит и бесплатного функционала.

Откроем ссылку 

https://bc8ab1191806.ngrok.io/d2549d33-255a-40a6-8cce-36c8f7efe780/test.php

И увидим свою страницу phpinfo().

Привязываем Вебхук (Webhook)

Теперь, когда у нас есть внешний домен, пришла пора привязать к боту наш сайт через Вебхук.

Каждый раз, когда пользователь взаимодействует с ботом, тот отсылает сообщение на наш сервер, чтобы указать адрес, на который бот должен направлять запросы используется Вебхук.

Создадим файл для будущего бота:

python -c 'import uuid; print(str(uuid.uuid4()))'

6a98fbdb-9fc1-4681-878a-4e4292b79332

sudo touch /var/phpbots/landing-bot/6a98fbdb-9fc1-4681-878a-4e4292b79332.php

Откроем в браузере:

https://bc8ab1191806.ngrok.io/d2549d33-255a-40a6-8cce-36c8f7efe780/6a98fbdb-9fc1-4681-878a-4e4292b79332.php

Получим пустую страницу – это хорошо, значит файл доступен.

Далее не забудьте заменить

1234567890:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

На ваш токен!

Обратите внимание, мы добавили bot перед токеном ЭТО ВАЖНО!

Привяжем URL

Для этого укажем токен и адрес файла с ботом!

curl https://api.telegram.org/bot1234567890:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/setWebhook?url=  https://bc8ab1191806.ngrok.io/d2549d33-255a-40a6-8cce-36c8f7efe780/6a98fbdb-9fc1-4681-878a-4e4292b79332.php

{"ok":true,"result":true,"description":"Webhook was set"}

Таким образом мы установили Вебхук!

Пишем код для бота

 Напишем простой код для бота: 

<?php
include('vendor/autoload.php');

$token = "1234567890:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ";

use TelegramBotApi;

$telegram = new Api($token);
$result = $telegram->getWebhookUpdates();

$text = $result["message"]["text"];
$chat_id = $result["message"]["chat"]["id"];

if($text == "/start"){
    $r = "Hello user!";
    
    $telegram->sendMessage(['chat_id' => $chat_id, 'text' => $r]);
}

Откроем ссылку:

https://bc8ab1191806.ngrok.io/d2549d33-255a-40a6-8cce-36c8f7efe780/6a98fbdb-9fc1-4681-878a-4e4292b79332.php
Notice: Undefined index: message in /var/phpbots/landing-bot/vendor/illuminate/support/Collection.php on line 1290

Notice: Trying to access array offset on value of type null in /var/phpbots/landing-bot/6a98fbdb-9fc1-4681-878a-4e4292b79332.php on line 12

Notice: Undefined index: message in /var/phpbots/landing-bot/vendor/illuminate/support/Collection.php on line 1290

Notice: Trying to access array offset on value of type null in /var/phpbots/landing-bot/6a98fbdb-9fc1-4681-878a-4e4292b79332.php on line 13

Notice: Trying to access array offset on value of type null in /var/phpbots/landing-bot/6a98fbdb-9fc1-4681-878a-4e4292b79332.php on line 13 

Так ка мы вызываем бот напрямую, он выдает множество нотисов, это нормально!

В будущем мы добавим проверку и в случае вызова напрямую, он будет отдавать страницу с кодом 403.

Откроем телеграм и найдем бот Ru_Test9999_bot нажмем на кнопку Запустить

2021-08-09_21-53-45.png

И получим ответ:

 2021-08-09_21-54-54.png

Поздравляю! Мы с вами написали простой бот для Телеграм.

Заключение

Сегодня мы рассмотрели создание простого Телеграм-бота.

Был установлен Composer PHP.

С его помощью мы установили библиотеку для разработки Телеграм-ботов — telegram-bot-sdk.

Мы создали бот в Телеграм и добавили его в специально созданную группу и получили токен.

Настроили Nginx и скрипт для бота.

Установили ngrok для организации доступа к боту, размещенному на нашем локальном сервере, из интернета.

Привязали Вебхук к публичному адресу ngrok.

Написали и протестировали простой Телеграм-бот.

В следующей части мы рассмотрим отправку сообщения с формы сайта в группу Телеграм.

В этой статье разберём, как добавить на сайт простой чат-бот SimpleChatbot и создать для него необходимый сценарий диалога.

О чат-боте SimpleChatbot

SimpleChatbot — это простой чат-бот для сайта, сценарий диалога которого определяется JSON-файлом или данными, расположенными непосредственно в скрипте. Состоит он из 2 частей: клиентской и северной. Первая написана на чистом JavaScript, вторая — на PHP.

SimpleChatbot очень прост в настройке и установке на сайт. Он не зависит от сторонних сервисов и библиотек. Для его работы (серверной стороны) нужен веб-сервер Nginx, Apache или любой другой, умеющий выполнять php-скрипты.

Демо чат-бота

На клиентской стороне кроме самого скрипта чат-бота («chatbot.js») используется ещё FingerPrint JS. Он применяется для создания отпечатка браузера (выполнение анонимной идентификации клиента). Сам чат-бот непосредственно от него не зависит. Его действие заключается в формировании некоторого идентификатора, который затем сохраняется в LocalStorage и используется чат-ботом для передачи его на сервер. А серверу он нужен только для создания файла с соответствующим именем, в который он будет сохранять всю переписку бота с этим пользователем. По умолчанию файлы сохраняются в папку chats.

Пример содержимого каталога chats:

Пример содержимого папки chats

В этом примере имеются 2 файла. Каждый из них – это некий диалог бота с определённым клиентом.

Если его открыть, то мы увидим весь контент «разговора».

Например:

Пример разговора чат-бота с клиентом

Если обратно вернуться к FingerPrint JS и немного углубиться в эту тему, то увидим, что значение, которое он генерирует сохраняется в LocalStorage. При этом в качестве имени ключа используется значение configChatbot.key. По сути, здесь не важно, что использовать, FingerPrint JS , или какую-то другую библиотеку, или свой код, главное, чтобы он создавал некий идентификатор клиента и помещал его в хранилище.

Исходный код этого чат-бота является открытым и находится на GitHub. Текущая версия 1.3.2. В качестве лицензии, используется MIT. Эта лицензия позволяет данное программное обеспечение использовать как в бесплатных, так и в коммерческих проектах (при условии, что текст лицензии поставляется вместе с этим программным обеспечением).

Установка чат-бота на сайт

Порядок действий:

1. Скачать последний релиз SimpleCatbot (v. 1.3.2) с GitHub и распаковать его.

2. Загрузить папку «chatbot» в корень вашего сайта или любое другое место. Если вы выбрали последнее, то в этом случае необходимо будет ещё в определённых местах изменить отредактировать пути к файлам.

3. Добавить стили (файл «chatbot.css») к странице:

<link rel="stylesheet" href="/chatbot/chatbot.css">

4. Подключить скрипты («fp2.js» и «chatbot.js»):

<!-- FingerPrint JS -->
<script src="/chatbot/fp2.js"></script>
<!-- ChatBot JS -->
<script src="/chatbot/chatbot.js"></script>

Код «chatbot.js» написан на синтаксисе, который поддерживается всеми используемые в данное время браузерами включая IE11.

<!-- FingerPrint JS -->
<script src="/chatbot/fp2.js"></script>
<!-- ChatBot JS -->
<script src="/chatbot/chatbot.js"></script>

5. Поместить в HTML документ кнопку, посредством которой будем вызывать окно чат-бота:

<!-- Кнопка для вызова окна чат-бота -->
<div class="chatbot__btn">
  <div class="chatbot__tooltip d-none">Есть вопрос?</div>
</div>

Кнопка для открытия окна чат-бота

Далее необходимо поместить на страницу скрипт после подключения этих файлов.

1 вариант скрипта – с загрузкой сценария диалога из JSON файла («/data/data-1.json») посредством AJAX.

// конфигурация чат-бота
const configChatbot = {};
// CSS-селектор кнопки, посредством которой будем вызывать окно диалога с чат-ботом
configChatbot.btn = '.chatbot__btn';
// ключ для хранения отпечатка браузера
configChatbot.key = 'fingerprint';
// реплики чат-бота
configChatbot.replicas = '/data/data-1.json';
// корневой элемент
configChatbot.root = SimpleChatbot.createTemplate();
// URL chatbot.php
configChatbot.url = '/chatbot/chatbot.php';
// переменная для хранения экземпляра
let chatbot = null;
// добавление ключа для хранения отпечатка браузера в LocalStorage
let fingerprint = localStorage.getItem(configChatbot.key);
if (!fingerprint) {
  Fingerprint2.get(function (components) {
    fingerprint = Fingerprint2.x64hash128(components.map(function (pair) {
      return pair.value
    }).join(), 31)
    localStorage.setItem(configChatbot.key, fingerprint)
  });
}
// при клике по кнопке configChatbot.btn
document.querySelector(configChatbot.btn).onclick = function (e) {
  this.classList.add('d-none');
  const $tooltip = this.querySelector('.chatbot__tooltip');
  if ($tooltip) {
    $tooltip.classList.add('d-none');
  }
  configChatbot.root.classList.toggle('chatbot_hidden');
  if (chatbot) {
    return;
  }
  // получение json-файла, содержащего сценарий диалога для чат-бота через AJAX
  const request = new XMLHttpRequest();
  request.open('GET', configChatbot.replicas, true);
  request.responseType = 'json';
  request.onload = function () {
    const status = request.status;
    if (status === 200) {
      const data = request.response;
      // для поддержки IE11
      if (typeof data === 'string') {
        configChatbot.replicas = JSON.parse(data);
      } else {
        configChatbot.replicas = data;
      }
      // инициализация SimpleChatbot
      chatbot = new SimpleChatbot(configChatbot);
      chatbot.init();
    } else {
      console.log(status, request.response);
    }
  };
  request.send();
};

Настройка чат-бота выполняется посредством установки определённых ключей configChatbot:

  • configChatbot.btn — селектор кнопки, посредством которой будем вызывать окно диалога с чат-ботом;
  • configChatbot.key — имя ключа LocalStorage, в котором будет храниться отпечаток браузера;
  • configChatbot.replicas — URL json-файла, содержащего сценарий диалога для чат-бота;
  • configChatbot.root — ссылка, на корневой элемент чат-бота;
  • configChatbot.url — путь к файлу chatbot.php.

Инициализация чат-бота в коде выполняется так:

let chatbot = new SimpleChatbot(configChatbot);
chatbot.init();

2 вариант скрипта – с помещением данных для чат-бота непосредственно в сценарий.

// конфигурация чат-бота
const configChatbot = {};
// CSS-селектор кнопки, посредством которой будем вызывать окно диалога с чат-ботом
configChatbot.btn = '.chatbot__btn';
// ключ для хранения отпечатка браузера
configChatbot.key = 'fingerprint';
// реплики чат-бота
configChatbot.replicas = { /*...*/ };
// корневой элемент
configChatbot.root = SimpleChatbot.createTemplate();
// URL chatbot.php
configChatbot.url = '/chatbot/chatbot.php';
// создание SimpleChatbot
let chatbot = new SimpleChatbot(configChatbot);
// при клике по кнопке configChatbot.btn
document.querySelector(configChatbot.btn).onclick = function (e) {
  this.classList.add('d-none');
  const $tooltip = this.querySelector('.chatbot__tooltip');
  if ($tooltip) {
    $tooltip.classList.add('d-none');
  }
  configChatbot.root.classList.toggle('chatbot_hidden');
  chatbot.init();
};
// добавление ключа для хранения отпечатка браузера в LocalStorage
let fingerprint = localStorage.getItem(configChatbot.key);
if (!fingerprint) {
  Fingerprint2.get(function (components) {
    fingerprint = Fingerprint2.x64hash128(components.map(function (pair) {
      return pair.value
    }).join(), 31)
    localStorage.setItem(configChatbot.key, fingerprint)
  });
}

Настройка чат-бота в этом варианте практически ничем отличается от способа с JSON за исключением того, что данные здесь уже находятся непосредственно в скрипте (в configChatbot.replicas), и их не нужно дополнительно загружать через AJAX.

Составление диалога для чат-бота

Написание реплик для чат-бота выполняется в формате объекта. При создании объекта необходимо определить 2 ключа. Первый ключ (bot) отвечает за сообщения бота, а второй (human) — пользователя.

configChatbot.replicas = {
  bot: { /* ... */ },
  human: { /* ... */ }
}

Далее в bot необходимо поместить сами реплики. Описание их также выполняется в формате объекта. В качестве ключа используется число, а в роли значения – объект, состоящий из свойств content и human. В content помещаем сам контент, а в human – массив ответов для пользователя.

configChatbot.replicas = {
  bot: {
    0: { content: 'Привет! Я Инфинити - бот поддержки сайта <a href="https://itchief.ru" target="_blank">itchief.ru</a>', human: [0, 1, 2] },
    /* ... */
  },
  human: { /* ... */ }
}

Кстати, работа чат-бота всегда стартует с приветственного сообщения бота, заданного в 0. После показа этой реплики и некоторой паузы будет выведены варианты ответов для пользователя, указанные в human.

Приветственная реплика бота

Создание ответов для пользователя выполняется аналогичным образом. Т.е. в content помещаем ответ пользователя, который будет выведен в виде кнопки, а в bot – номер следующей реплики бота при выборе этого ответа пользователем.

configChatbot.replicas = {
  bot: {
    0: { content: 'Привет! Я Инфинити - бот поддержки сайта <a href="https://itchief.ru" target="_blank">itchief.ru</a>', human: [0, 1, 2] },
    /* ... */
  },
  human: {
    0: { content: 'Привет! Я рад с тобой познакомиться', bot: 1 },
    1: { content: 'Салют!', bot: 2 },
    2: { content: 'Приветик, Инфинити!', bot: 2 },
    /* ... */
  }
}

Ответы для пользователя в чат-боте

Но если нужно, чтобы пользователь не просто выбрал один из предоставленных ему ответов, а ввёл некоторые данные. То в этом случае в качестве значения content нужно указать пустую строку. В этом сценарии пользователю станет доступно поле для ввода (input) информации и кнопка, с помощью которой её можно будет отправить.

Если данные, которые ввёл пользователь вы хотите использовать в репликах, то им необходимо задать имя. Осуществляется это с помощью свойства name.

configChatbot.replicas = {
  bot: {
    0: { content: 'Привет! Я Инфинити - бот поддержки сайта <a href="https://itchief.ru" target="_blank">itchief.ru</a>', human: [0, 1, 2] },
    1: { content: 'Я тоже рад, как мне к Вам обращаться', human: [3] },
    2: { content: 'Как мне к Вам обращаться?', human: [3] },
    /* ... */
  },
  human: {
    0: { content: 'Привет! Я рад с тобой познакомиться', bot: 1 },
    1: { content: 'Салют!', bot: 2 },
    2: { content: 'Приветик, Инфинити!', bot: 2 },
    3: { content: '', bot: 3, name: 'name' },
    /* ... */
  }
}

Поле чат-бота для ввода запрашиваемых данных

Например, в этом примере, мы установили данным, которые введёт пользователь, имя name. Далее эти данные мы можем использовать в репликах бота используя это имя, заключенное в двойные фигурные скобки (например, {{name}}).

configChatbot.replicas = {
  bot: {
    0: { content: 'Привет! Я Инфинити - бот поддержки сайта <a href="https://itchief.ru" target="_blank">itchief.ru</a>', human: [0, 1, 2] },
    1: { content: 'Я тоже рад, как мне к Вам обращаться', human: [3] },
    2: { content: 'Как мне к Вам обращаться?', human: [3] },
    3: { content: '{{name}}, что Вас интересует?', human: [4, 5] },
    /* ... */
  },
  human: {
    0: { content: 'Привет! Я рад с тобой познакомиться', bot: 1 },
    1: { content: 'Салют!', bot: 2 },
    2: { content: 'Приветик, Инфинити!', bot: 2 },
    3: { content: '', bot: 3, name: 'name' },
    4: { content: 'Меня интересует, как я могу использовать этот чат-бот у себя на сайте', bot: 4 },
    5: { content: 'Хочу оставить запрос разработчику чат-бота', bot: 5 },
    /* ... */
  }
}

Использование имени пользователя в реплики чат-бота

Далее, придерживаясь этих правил при написании реплик составляем нужный диалоговый сценарий чат-бота для своего сайта.

Пример сценария:

configChatbot.replicas = {
  bot: {
    0: {
      content: 'Привет! Я Инфинити - бот поддержки сайта <a href="https://itchief.ru" target="_blank">itchief.ru</a>', human: [0, 1, 2]
    },
    1: { content: 'Я тоже рад, как мне к Вам обращаться?', human: [3] },
    2: { content: 'Как мне к Вам обращаться?', human: [3] },
    3: { content: '{{name}}, что Вас интересует?', human: [4, 5] },
    4: { content: '{{name}}, для этого перейдите на <a href="https://itchief.ru/javascript/chatbot-for-website" target="_blank">эту страницу</a>. Она содержит подробную инструкцию по использованию этого чат-бота.', human: [6] },
    5: { content: "{{name}}, какой у Вас вопрос?", human: [7] },
    6: { content: '{{name}}, мы получили Ваш вопрос! Скажите, как с Вами удобнее будет связаться?', human: [8, 9] },
    7: { content: '{{name}}, укажите пожалуйста ваш телефон', human: [10] },
    8: { content: '{{name}}, укажите пожалуйста ваш Email ниже', human: [10] },
    9: { content: 'Готово! {{name}}, мы свяжемся с вами в ближайшее время по {{contact}}. Всего хорошего!', human: [6] },
  },
  human: {
    0: { content: 'Привет! Я рад с тобой познакомиться', bot: 1 },
    1: { content: 'Салют!', bot: 2 },
    2: { content: 'Приветик, Инфинити!', bot: 2 },
    3: { content: '', bot: 3, name: 'name' },
    4: { content: 'Меня интересует, как я могу использовать этот чат-бот у себя на сайте', bot: 4 },
    5: { content: 'Хочу оставить запрос разработчику чат-бота', bot: 5 },
    6: { content: 'В начало', bot: 3 },
    7: { content: '', bot: 6, name: '' },
    8: { content: 'по телефону', bot: 7 },
    9: { content: 'по email', bot: 8 },
    10: { content: '', bot: 9, name: 'contact' },
  }
}

Когда сценарий у вас готов, его следует, если вы используете 2 вариант скрипта, поместить вместо этой строчки:

configChatbot.replicas = { /*...*/ };

При использовании 1 варианта скрипта, его необходимо перевести в строку JSON. Для этого можно воспользоваться каким-нибудь онлайн сервисом или стандартным JavaScript методом stringify:

configChatbot.replicas = { /* ... */ };
const json = JSON.stringify(configChatbot.replicas);
console.log(json);

Если вы воспользовались последним советом, то после открытия страницы из консоли браузера нужно скопировать полученную строку (без обрамляющих её кавычек). После этого вставить её в файл с расширением json и загрузить его на сервер.

Новые возможности чат-бота

В SimpleChatbot начиная с версии 1.2.0 для бота можно указывать сразу несколько сообщений. Для этого их необходимо передать в формате массива. Такие сообщения будут выведены последовательно друга за другом в виде отдельных реплик.

Например:

bot: {
  0: { content: ['Привет!', 'Я Инфинити - бот поддержки сайта <a href="https://itchief.ru" target="_blank">itchief.ru</a>'], human: [0, 1, 2] },
  ...

Задание нескольких сообщений в чат-боте одно за другим

В версии 1.3.0 добавлена возможность отправки сообщения клавишей Enter, метод reset для очистки (сброса) чата и кнопка для вызова этого метода.

Понравилась статья? Поделить с друзьями:
  • Как написать вводную часть анкеты
  • Как написать введение эссе
  • Как написать введение проекта презентация
  • Как написать введение на курсовую работу
  • Как написать введение на курсовой проект