Чат-боты: введение от разработчика

В последнее время Telegram у всех на слуху. Нужно отдать должное отделу маркетинга этого приложения, шумиху подняли на славу. Одной из основных "фишек" Telegram является его якобы защищённость - по словам Павла Дурова вся переписка между пользователями шифруется. Более того, ни одна спец.служба мира не будет иметь доступ к вашим сообщениям. Но в данной статье речь не об этом. Сегодня хотелось бы поговорить о не менее крутой фишке в Telegram, а именно о ботах. Помимо того, что в сети уже полно информации о различного рода Telegram ботах (github бот, например), мессенджер открыл своё API для разработчиков, и теперь каждый может создать своего собственного бота с блэкджеком и плюшками.

В статье я приведу пример написания онлайн бота с использованием Python и Django фреймворка. То есть мы "запилим" полноценное веб-приложение, которое будет крутиться на удалённом хосте и принимать команды от пользователей. Весь исходный текст доступен в моём github репозитории .

Документация, описывающая процесс взаимодействия с ботами Telegram находится . Чтобы не изобретать велосипед, я нашел неплохую Python библиотеку, реализующую все основные функции ботов - telepot . Как я уже упоминал ранее, для того, чтобы обслуживать пользователей нашего бота мы будет разрабатывать веб-приложение, используя Django фреймворк.

Как создать Telegram бота?

Для начала нам необходимо зарегистрировать в Telegram нашего будущего бота. Это делается следующим образом:

  • Необходимо установить приложение Telegram на телефон или компьютер. Скачать приложение можно
  • Добавляем к себе в контакт-лист бота с именем BotFather
  • Запускаем процедуру "общения" с ботом нажатием кнопки Start . Далее перед нами предстанет список команд точно как на скриншоте.
  • Для того, чтобы создать нового бота необходимо выполнить команду /newbot и следовать инструкциям. Обратите внимание, что username для бота должен всегда содержать в конце слово bot . Например, DjangoBot или Django_bot.

  • Для нашего бота я выбрал имя PythonPlanetBot, так как его основная функция заключается в парсинге RSS feed сайта Python Planet и выдача информации о последних постах пользователю:)

После создания бота, обратите внимание на строку с текстом:

Use this token to access the HTTP API:

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

  • Присвоить боту описание
  • Установить аватар
  • Поменять token

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

Как я ранее уже упоминал, мы будем писать веб-приложение на Django . Но стоит отметить, что это делать необязательно. Можно обойтись и обычным Python скриптом, правда в этом случае необходимо будет периодически опрашивать Telegram на предмет новых запросов от пользователей бота (используя метод getUpdates ) и увеличивая offset для получения самых последних данных без повторений. В Telegram существует два взаимоисключающих метода получения команд/сообщений для вашего бота.

  • Использование вызова API метода getUpdates
  • Установка Webhook

Установка Webhook заключается в передаче боту специального URL адреса на который будет поступать POST запрос каждый раз, когда кто-то начнёт посылать сообщения боту. Именно этот вариант мы и будем использовать для взаимодействия между ботом и его пользователем. Для того, чтобы задать URL, необходимо использовать API метод setWebhook . Отмечу, что URL должен начинаться с https, то есть иметь защищённое SSL соединение с валидным сертификатом. Telegram разрешает использовать самоподписанный сертификат, правда для этого необходимо в методе setWebhook передавать также публичный ключ в PEM формате (ASCII base64). Либо же можно получить от Let"s Encrypt.

Подробнее о getUpdates и setWebhook можно почитать соответственно и .

Итак, вернёмся к python библиотеке для работы с Telegram - telepot . На текущий момент самой последней её версий является 6.7. Устанавливаем её в виртуальное окружение python virtualenv:

Pip install telepot

Самый простой вариант взаимодействия с Telegram ботом на Python выглядит следующим образом:

Import telepot token = "123456" TelegramBot = telepot.Bot(token) print TelegramBot.getMe()

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

{u"username": u"PythonPlanetBot", u"first_name": u"Python Planet Bot", u"id": 199266571}

Поздравляю! Мы вызывали самый простой API запрос getMe, который возвращает информацию о боте: username, id, first_name.

Добавим нашего бота к себе в контакт-лист и пошлём ему первую стандартную команду /start

Выполняем код:

TelegramBot.getUpdates() [{u"message": {u"date": 1459927254, u"text": u"/start", u"from": {u"username": u"adilkhash", u"first_name": u"Adil", u"id": 31337}, u"message_id": 1, u"chat": {u"username": u"adilkhash", u"first_name": u"Adil", u"type": u"private", u"id": 7350}}, u"update_id": 649179764}]

Процесс общения с telegram ботом происходит по HTTPS; для передачи данных используется JSON. Метод getUpdates возвращает список/массив из объектов типа Update . Внутри Update находится объект Message . Для стандартного взаимодействия с ботом нас фактически интересует именно объект Message, у которого мы считываем атрибут text, хранящий в себе текст, переданный боту и объект chat, в котором лежит информация о пользователе, инициировавшем общение с нашим Telegram ботом. Также имеется параметр update_id, который служит в качестве offset параметра при вызове метода getUpdates. То есть update_id+1 вернёт все сообщения, поступившие после последнего update_id, при этом все предыдущие сообщения будут удалены.

TelegramBot.getUpdates(649179764+1) [{u"message": {u"date": 1459928527, u"text": u"hello bro", u"from": {u"username": u"adilkhash", u"first_name": u"Adil", u"id": 31337}, u"message_id": 13, u"chat": {u"username": u"adilkhash", u"first_name": u"Adil", u"type": u"private", u"id": 7350}}, u"update_id": 649179765}]

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

Простая функция парсинга RSS фида Planet Python выглядит вот так:

# -*- coding: utf8 -*- from xml.etree import cElementTree import requests def parse_planetpy_rss(): """Parses first 10 items from http://planetpython.org/rss20.xml """ response = requests.get("http://planetpython.org/rss20.xml") parsed_xml = cElementTree.fromstring(response.content) items = for node in parsed_xml.iter(): if node.tag == "item": item = {} for item_node in list(node): if item_node.tag == "title": item["title"] = item_node.text if item_node.tag == "link": item["link"] = item_node.text items.append(item) return items[:10]

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


Статья написана для новичков, чтобы показать, что ничего сложного в написании ботов на Python нет.

Авторизация

Нам понадобится библиотека vk_api . Авторизоваться в вк можно двумя способами:
- Как пользователь
- Как сообщество


В первом случае надо будет ввести логин и пароль. Во втором случае в группе надо включить "Сообщения сообщества" и создать ключ доступа к API:





import time import vk_api vk = vk_api.VkApi(login = "login", password = "password") #vk_api.VkApi(token = "a02d...e83fd") #Авторизоваться как сообщество vk.auth()

Отправка сообщений

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


P.S. Сообщество может отправлять сообщения только ранее писавшим пользователям.


def write_msg(user_id, s): vk.method("messages.send", {"user_id":user_id,"message":s})

В vk.method мы можем вызывать любой метод из VK API и передавать параметры в виде словаря.


В данном случае мы вызываем метод messages.send и в качестве параметров передаем id пользователя и текст сообщения.

Прием сообщений

Отлично! Отправлять сообщения мы научились, осталось научиться их принимать. Для этого нам нужен метод messages.get .


Несколько параметров, на которые стоит обратить внимание:


1) out - если этот параметр равен 1, сервер вернет исходящие сообщения.
2) count - количество сообщений, которое необходимо получить.
3) time_offset - максимальное время, прошедшее с момента отправки сообщения до текущего момента в секундах.
4) last_message_id - идентификатор сообщения, полученного перед тем, которое нужно вернуть последним (при условии, что после него было получено не более count сообщений)


values = {"out": 0,"count": 100,"time_offset": 60} vk.method("messages.get", values)

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


В итоге мы получаем список items:


{u"count": 3441, u"items": [{u"body": u"\u041f\u0438\u0448\u0435\u043c \u0431\u043e\u0442\u0430 \u0434\u043b\u044f \u0432\u043a!", u"date": 1491934484, u"id": 7387, u"out": 0, u"read_state": 0, u"title": u" ... ", u"user_id": 23107592}, {u"body": u"\u041f\u0440\u0438\u0432\u0435\u0442 \u0425\u0430\u0431\u0440!", u"date": 1491934479, u"id": 7386, u"out": 0, u"read_state": 0, u"title": u" ... ", u"user_id": 23107592}]}

Если объяснять простыми словами, то items - это то, что можно выделить в диалоге.



Финальный аккорд, делаем вечный цикл, где на каждое сообщение будем отвечать "Привет, Хабр!".


while True: response = vk.method("messages.get", values) if response["items"]: values["last_message_id"] = response["items"]["id"] for item in response["items"]: write_msg(item,u"Привет, Хабр!") time.sleep(1)

Чат-бот готов.


P.S. Мы запоминаем параметр last_message_id, чтобы в следующий раз обрабатывать только новые сообщения.


Полный код

# -*- coding: utf-8 -*- import time import vk_api vk = vk_api.VkApi(login = "login", password = "password") #vk_api.VkApi(token = "a02d...e83fd") #Авторизоваться как сообщество vk.auth() values = {"out": 0,"count": 100,"time_offset": 60} def write_msg(user_id, s): vk.method("messages.send", {"user_id":user_id,"message":s}) while True: response = vk.method("messages.get", values) if response["items"]: values["last_message_id"] = response["items"]["id"] for item in response["items"]: write_msg(item,u"Привет, Хабр!") time.sleep(1)


Получилось 17 строк кода. Успехов!


UPD 17.09.18:
К сожалению в новой версии (5.80) VK API был убран метод "messages.get" и данная статья потеряла актуальность. Теперь для создания ботов используйте систему longpoll. Пример на модуле vk_api для Python вы можете найти .

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


Статья написана для новичков, чтобы показать, что ничего сложного в написании ботов на Python нет.

Авторизация

Нам понадобится библиотека vk_api . Авторизоваться в вк можно двумя способами:
- Как пользователь
- Как сообщество


В первом случае надо будет ввести логин и пароль. Во втором случае в группе надо включить "Сообщения сообщества" и создать ключ доступа к API:





import time import vk_api vk = vk_api.VkApi(login = "login", password = "password") #vk_api.VkApi(token = "a02d...e83fd") #Авторизоваться как сообщество vk.auth()

Отправка сообщений

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


P.S. Сообщество может отправлять сообщения только ранее писавшим пользователям.


def write_msg(user_id, s): vk.method("messages.send", {"user_id":user_id,"message":s})

В vk.method мы можем вызывать любой метод из VK API и передавать параметры в виде словаря.


В данном случае мы вызываем метод messages.send и в качестве параметров передаем id пользователя и текст сообщения.

Прием сообщений

Отлично! Отправлять сообщения мы научились, осталось научиться их принимать. Для этого нам нужен метод messages.get .


Несколько параметров, на которые стоит обратить внимание:


1) out - если этот параметр равен 1, сервер вернет исходящие сообщения.
2) count - количество сообщений, которое необходимо получить.
3) time_offset - максимальное время, прошедшее с момента отправки сообщения до текущего момента в секундах.
4) last_message_id - идентификатор сообщения, полученного перед тем, которое нужно вернуть последним (при условии, что после него было получено не более count сообщений)


values = {"out": 0,"count": 100,"time_offset": 60} vk.method("messages.get", values)

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


В итоге мы получаем список items:


{u"count": 3441, u"items": [{u"body": u"\u041f\u0438\u0448\u0435\u043c \u0431\u043e\u0442\u0430 \u0434\u043b\u044f \u0432\u043a!", u"date": 1491934484, u"id": 7387, u"out": 0, u"read_state": 0, u"title": u" ... ", u"user_id": 23107592}, {u"body": u"\u041f\u0440\u0438\u0432\u0435\u0442 \u0425\u0430\u0431\u0440!", u"date": 1491934479, u"id": 7386, u"out": 0, u"read_state": 0, u"title": u" ... ", u"user_id": 23107592}]}

Если объяснять простыми словами, то items - это то, что можно выделить в диалоге.



Финальный аккорд, делаем вечный цикл, где на каждое сообщение будем отвечать "Привет, Хабр!".


while True: response = vk.method("messages.get", values) if response["items"]: values["last_message_id"] = response["items"]["id"] for item in response["items"]: write_msg(item,u"Привет, Хабр!") time.sleep(1)

Чат-бот готов.


P.S. Мы запоминаем параметр last_message_id, чтобы в следующий раз обрабатывать только новые сообщения.


Полный код

# -*- coding: utf-8 -*- import time import vk_api vk = vk_api.VkApi(login = "login", password = "password") #vk_api.VkApi(token = "a02d...e83fd") #Авторизоваться как сообщество vk.auth() values = {"out": 0,"count": 100,"time_offset": 60} def write_msg(user_id, s): vk.method("messages.send", {"user_id":user_id,"message":s}) while True: response = vk.method("messages.get", values) if response["items"]: values["last_message_id"] = response["items"]["id"] for item in response["items"]: write_msg(item,u"Привет, Хабр!") time.sleep(1)


Получилось 17 строк кода. Успехов!


UPD 17.09.18:
К сожалению в новой версии (5.80) VK API был убран метод "messages.get" и данная статья потеряла актуальность. Теперь для создания ботов используйте систему longpoll. Пример на модуле vk_api для Python вы можете найти .

Итак в этой статье я продемонстрирую как создать простого бота основанного на визуальном поиске объектов на экране. Преимуществом данного метода является «незаметность» для различных систем защиты от ботов. Для написания бота мы естественно будем использовать специализированную среду для разработки ботов . Если боты создаются для того чтобы сэкономить ваше время на совершение однообразных рутинных действий, то NOMAD создан для того, чтобы сэкономить ваше время при написании бота. В сравнении с написанием ботов на неспециализированных средах программирования или на не очень специализированных разработка на NOMAD может происходить в десятки, а то и в сотни раз быстрее. (т.к. для начала вам еще придется разработать инструменты обнаружения объектов, которые в NOMAD уже встроены готовые). Скачать дистрибутив NOMAD с уже установленным ботом из этой статьи вы можете по этой ссылке (внимание! настройки программы в дистрибутиве рассчитаны на установку его в папку по умолчанию, если вы устанавливаете его в другое место, то измените пути к базе и конструктору с проигрывателем!)

adminbot
Дата: 22.07.2015

Управлять многими - то же, что управлять немногими. Дело в организации.

Сунь Цзы

Нашей задачей сейчас является демонстрация того, как делается бот программа, а потому для автоматизации мы выберем что-нибудь простое и в то же время всем доступное, например, Калькулятор. Конечно это не онлайн игра, но поверьте, создание бота для онлайн игры ничем не отличается. (внимание пример работает в Windows 8 и Windows 7, причем калькулятор должен выглядеть как на скриншоте, который есть ниже по тексту, т.е. например, в Windows XP калькулятор называется и выглядит иначе, и потому там бот работать не будет)

Для того чтобы начать играть «автоматически» в что нибудь нам необходимо пройти следующие этапы:

  1. Сформулировать для себя задачу. Каких целей должен достигать наш бот.
  2. Какие действия он должен выполнять для этого?
  3. Исходя из задачи определить какие объекты он должен видеть, и каким образом, и на какие события реагировать для достижения целей описанных в пункте 1.
  4. Создание алгоритма.
  5. Программирование.

Задачей нашего бота будет бесконечно складывать на калькуляторе 2 числа 59 и 3.

Для достижения нашей цели нам необходимо, чтобы наша программа нажимала на кнопки 5 и 9 калькулятора, а в случае успеха на кнопку + и кнопку 3 и затем кнопку =

Следовательно эти объекты мы и должны будем искать на изображении, а затем тыкать в них мышью. Также мы будем анализировать число 59 и число 0 в поле результата, а также кнопку CE, для того чтобы сбросить вычисления на 0 перед началом очередной итерации.

Итак, наш алгоритм: В бесконечном цикле сначала ищем и жмем мышью на кнопку 5, и на кнопку 9. В случае успеха делаем паузу 3 секунды (чтобы успеть насладиться результатом). Затем проверяем есть ли у нас изображение числа 59, если есть то находим и кликаем мышью на кнопки + и 3 калькулятора, если эти операции также выполнены успешно, то находим и кликаем по кнопке = калькулятора. После чего спим 5 секунд. Затем проверяем, если в поле результата нет картинки 0, значит необходимо нажать кнопку CE, чтобы сбросить результаты предыдущих вычислений. После чего итерация повторяется. При нажатии кнопки Esc программа останавливается.

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

Delphi/Pascal

Procedure StartOnClick(); begin while (getasynckeystate($1B) = 0) do //цикл продолжается пока не нажмут Esc++ begin if five.MouseClick("l") and nine.MouseClick("l") then //если успешно нажали пять и 9 то begin sleep(3000); //спим 3 секунды if fiftynine.FindPicture then //если нашли 59, то прибавим к ним 3: begin if plus.MouseClick("l") and three.MouseClick("l") then //если получилось нажать плюс 3, то жмем на равно equal.MouseClick("l"); end; end; sleep(5000); //спим 5 секунд if not zero.FindPicture then //если на табло нет нуля CE.MouseClick("l"); //то жмем CE Application.ProcessMessages;//принудительно обрабатывает //сообщения пришедшие в окно за время работы, например //сообщение о нажатии клавиши Esc. Если не вызывать то //программа может "подвисать", плохо реагировать на нажатия Esc и т.п. end; //цикл продолжается пока не нажмут Esc -- ShowMessage("Программа остановлена!"); end;

Procedure StartOnClick () ;

begin

while (getasynckeystate ($ 1B ) =0 ) do //цикл продолжается пока не нажмут Esc++

begin

if five . MouseClick ("l" ) and nine . MouseClick ("l" ) then //если успешно нажали пять и 9 то

begin

sleep (3000 ) ; //спим 3 секунды

if fiftynine . FindPicture then //если нашли 59, то прибавим к ним 3:

begin

if plus . MouseClick ("l" ) and three . MouseClick ("l" ) then //если получилось нажать плюс 3, то жмем на равно

equal . MouseClick ("l" ) ;

end ;

end ;

sleep (5000 ) ; //спим 5 секунд

if not zero . FindPicture then //если на табло нет нуля

CE . MouseClick ("l" ) ; //то жмем CE

Application . ProcessMessages ; //принудительно обрабатывает

//сообщения пришедшие в окно за время работы, например

//сообщение о нажатии клавиши Esc. Если не вызывать то

//программа может "подвисать", плохо реагировать на нажатия Esc и т.п.

end ; //цикл продолжается пока не нажмут Esc --

ShowMessage ("Программа остановлена!" ) ;

end ;

Тут мы забежали немного вперед, написав сразу код, т.к. в NOMAD сначала необходимо создать шаблоны изображений которые мы будем искать. Так если посмотреть в код, то можно увидеть следующие записи: five,nine,fiftynine,plus,three,equal,zero,CE — что это? а это собственно и есть те шаблоны, в NOMAD с ними можно работать из кода как с объектами, т.е. пишем имя объекта в коде и вызываем его метод, например: FindPicture — найти изображение шаблона, MouseClick(‘l’) — найти изображение шаблона и кликнуть левой кнопкой мыши по нему. Для того чтобы подробно изучить возможности и функции NOMAD рекомендую обратиться к . А сейчас я в кратце опишу как нам создать недостающие шаблоны:

Во первых создадим базу данных, для этого в Launcher нажмем кнопку ADD, в открывшимся окне заполним в первом поле имя базы данных (например Калькулятор), и во втором поле укажем каталог где мы хотим хранить все что мы напрограммировали. Т.е. собственно исходник нашей программы, или иными словами базу данных. Далее жмем кнопку Save и в списке у нас должна появится запись. Выбираем ее и жмем кнопку Constructor.

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

На пункте выделенном на картинке голубым цветом жмем правой кнопкой мыши и выбираем пункт меню «добавить конвеер скриншотов». В появившемся окошке указываем его имя — «Calc». И нажимаем кнопку Input. После чего объект с таким именем должен появиться в дереве объектов в разделе ScreenShotPipe. Найдем его там и кликнем на нем левой кнопкой мыши. В левой части программы появится окошко следующего вида (свойства могут идти в другом порядке):

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

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

Нажимаем кнопку Borders (примечание. с момента написания этой статьи в программе появился режим FixBorders (кнопка черный мониторчик с луной), рекомендую использовать именно его. Используется он точно также как Borders, но работает на динамических изображениях гораздо стабильнее ) и начинаем делать шаблоны. Объясню как это делается на примере шаблона для кнопки «+» — мы должны выбрать на изображении самый характерный его участок, который не повторяется в других местах. Собственно и выбираем сам символ +, нажимаем на кнопку Select, и выделяем этот знак в квадратик:

Кликаем правой кнопкой мыши внутри пунктирного квадратика и у нас появляется окно:

в поле имя которого мы пишем «plus» (так как этот объект у нас в коде был написан) и жмем Input. В разделе Templates дерева объектов у нас появился объект с таким именем. Нажмем на него и слева появится уже знакомое нам окошко:

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

Очень важно, чтобы наименования в точности включая регистр ввода соответствовали тому, что вы видите на картинке. В примере шаблоны five и nine сделаны другим способом, в режиме ColMap, за подробностями того как это делается предлагаю обратиться к документации. На работу кода программы это не окажет особого влияния и вы можете сделать все шаблоны тем же образом как и шаблон кнопки +. А можете ничего не делать, и скачать готовый дистрибутив с примером

Для того чтобы создать шаблоны fiftynine и zero необходимо делать шаблон участка калькулятора выделенного на картинке:

По шаблонам все.

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

Откроется окно с формой, на которой нужно дважды щелкнуть и из открывшегося окна добавить объект Button в обоих полях формы пишем Start. Жмем ОК и выходим из формы, а на главной форме у нас появилась кнопка Start, нажимаем на нее и появляется опять меню свойств, жмем на закладку Events и видим следующее:

В колонке Value на против события OnClick дважды щелкаем мышью, и в Value автоматически заполняется значение на картинке выше. А в центральном окне появляется текстовый редактор:

Вот этот текст мы и должны заменить кодом, который я привел в начале статьи. Все теперь программа готова. Для запуска необходимо сохранить программу нажав кнопку с изображением двух дискет, затем откомпилировать программу нажав кнопку с картинкой перекрещенных отвертки и ключа, а затем нажать кнопку Launch, или запустить ее из лаунчера. Из конструктора программа всегда запускается в отладочном режиме, поэтому будут показываться отладочные сообщения. Если мы все сделали верно то должны увидеть окно с кнопкой которое мы создали:

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

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

Другие примеры ботов можно поискать на форуме например,

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

Но, как известно, в последнее время крупнейшие покер-румы начали активно закручивать гайки, выкидывая любителей нечестной игры из-за столов. В такой ситуации разработка покер-ботов превращается в весьма неблагодарное занятие, а порой и просто в пустую трату времени. Казалось бы, овчинка не стоит выделки, и тему можно смело закрывать. Однако не стоит опускать руки раньше времени. Устраивайся поудобнее, сейчас я покажу тебе, как научить собственного бота играть на partypoker.com :).

Flop aka вливаемся в игру

Как ты уже понял, речь в статье пойдет о написании покерного бота. Он рассчитан на работу во время твоего сна/отдыха/etc. Представь, что для улучшения своего благосостояния тебе достаточно лишь запустить софтину на забугорном дедике. Прогуливаешься в парке - бот работает, спишь - бот наигрывает кэш. Бот полностью имитирует действия игрока мышью, а всю информацию со стола собирает снимками необходимых областей.

Мы не будем лезть в код клиентского софта покер-рума. Это понижа ет удобство работы взамен на гарантии безопасности и стабильности функционирования бота. Однако все не так сложно, как тебе кажется:).

Turn - реализуем бота

Итак, перед запуском бота необходимо произвести следующие действия:

  1. Сливаем официальный бесплатный клиент с partypoker.com .
  2. Запускаем его и регистрируемся в покер-руме.
  3. В настройках клиента ставим четырехцветную колоду карт.
  4. Открываем четыре любых игровых стола и устанавливаем автоматическое расположение окон.

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

Подготовка завершена, но запускать бота пока рано, для начала разберемся в начинке софтины. Логика работы бота станет понятна после просмотра сорца Unit3.cpp. Для экономии места похожие строчки кода мы заменим на «…».

Для удобства хранения инфы создадим четыре объекта TABLE, хранящие данные по каждому столу:

TABLE table1;
...

Позиция игрока за столом и предыдущие карты выставляются по-дефолту:

table1.position = "1";
...
table1.last_cards = "start";
...

Забираем из боксов и присваиваем позиции для каждого стола:

table1.position = Form1->Edit1->Text.c_str();
...

Запускаем основной цикл. Задержка в начале цикла выставлена не случайно. Дело в том, что работа с нашими снимками занимает приличное количество времени. Данная задержка оптимальна для рабочей лошадки P4 2800MHZ, 1ГБ ОЗУ.

while(true)
{
Sleep(2000);

check_situation(table1.situation, table2.situation, table3.situation, table4.situation);
Form1->Label34->Caption = table1.situation.c_str();
Form1->Label35->Caption = table2.situation.c_str();
Form1->Label36->Caption = table3.situation.c_str();
Form1->Label37->Caption = table4.situation.c_str();

И, наконец, обработка каждого стола. Рассмотрим на примере первого. Для начала проверим, требуется ли от бота игра на этом столе. Это удобно, поскольку можно отключать бота от стола и играть вручную:

if (table_1_start == "go") {

Проверяем ситуацию - требуется ли от бота принятие каких-либо решений, или сейчас ходят другие игроки:

if (table1.situation=="check" ||
table1.situation == "call_0.10" ||
table1.situation=="call_0.05" ||
table1.situation=="call_many" ||
table1.situation=="allin") {
Обнуляем параметры стола:
table1.combination = "--";
table1.action = "--";

Проверяем и прорисовываем карты игрока и карты на столе:

check_p_cards(1, table1.p_card_1, table1.p_card_2);
Form1->Label26->Caption = table1.p_card_1.c_str();
Form1->Label27->Caption = table1.p_card_2.c_str();
check_t_cards(1,table1.t_card_1,table1.t_card_2, table1.t_card_3,table1.t_card_4, table1.t_card_5);
Form1->Label11->Caption = table1.t_card_1.c_str();
Form1->Label12->Caption = table1.t_card_2.c_str();
Form1->Label13->Caption = table1.t_card_3.c_str();
Form1->Label14->Caption = table1.t_card_4.c_str();
Form1->Label15->Caption = table1.t_card_5.c_str();

Определяем место игрока в данной раздаче (большой блайнд/малый блайнд/etc):

check_position(1, table1);
Form1->Label62->Caption = table1.position.c_str();

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

shortstack(1, table1);

В результате мы имеем конкретное решение в свойстве table1.action:

Form1->Label38->Caption = table1.action.c_str();
Form1->Label58->Caption = table1.combination.c_str();

Решение - это хорошо, но от нас клиентская программа покеррума все еще ждет действий. Действуем согласно решению:

mouse_click(1, table1);

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

write_stat(1, table1);

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

table1.last_cards = table1.p_card_1 + table1.p_card_2;

На этом основной цикл заканчивается. Рассмотрим используемые функции более подробно. Создадим функцию для получения нужной нам картинки. В переменной outfile_name получим название файла, куда будет необходимо сохранить снимок. Здесь startX и startY - координаты верхней левой точки прямоугольника с высотой height и шириной width.

void PRINT_RECT_SVV (char* outfi le_name, int startX, int startY, int width, int height)
// Функция GetDC извлекает дескриптор
// дисплейного контекста устройства. 0 - экран
{
HDC hdc = GetDC(0);
if (hdc) //если дескриптор успешно получен
{
Graphics::TBitmap* bmp = new Graphics::TBitmap();
__try {
bmp->Width = width;
bmp->Height = height;
// Копиpует каpту бит из hdc в bmp, выполняя
// указанную pастpовую операцию, в данном случае SRCCOPY
BitBlt(bmp->Canvas->Handle, 0, 0, width, height, hdc, startX, startY, SRCCOPY);
bmp->SaveToFile(outfi le_name);
//сэйв BMP
}
__finally {
delete bmp; //освобождаем память
}
}
}

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

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

bool CHECK_MD5_SVV (char* ET_file, char* newfile)
{
md5wrapper md5;
// Получим хэш сравниваемого файла
std::string hash1 = md5.getHashFromFile(newfile);
// Получим хэш файла, содержащего шаблон
std::string hash2 = md5.getHashFromFile(ET_file);
// Сравним хэши
if (hash1==hash2) return true;
else return false;
}

Сравниваем контрольные суммы:

void check_this_card (char* new_path, string &card) {
// A
if (CHECK_MD5_SVV(".\ET\ET_A_p.bmp", new_path))
{card = "Ap"; }
else if (CHECK_MD5_SVV(".\ET\ET_A_k.bmp", new_path))
{card = "Ak"; }
else if (CHECK_MD5_SVV(".\ET\ET_A_ch.bmp", new_path))
{card = "Ach"; }
else if (CHECK_MD5_SVV(".\ET\ET_A_b.bmp", new_path))
{card = "Ab"; }
// К
...
else { card = "--"; }
}

void check_p_cards(int table, string &card1, string &card2) {
if (table==1) {
//скринить первую карту игрока
PRINT_RECT_SVV(".\ET\ch_card1_t1.bmp", 37,150,12,22);
//скринить вторую карту игрока
PRINT_RECT_SVV(".\ET\ch_card2_t1.bmp", 55,150,12,22);
//распознать первую
check_this_card(".\ET\ch_card1_t1.bmp", card1);
//распознать вторую
check_this_card(".\ET\ch_card2_t1.bmp", card2);
}
if (table==2) {
...
}
void check_t_cards (int table, string &card1, string &card2, string &card3, string &card4, string &card5) {
if (table==1) {
PRINT_RECT_SVV(".\ET\t1c1.bmp",198,154,12,22);
PRINT_RECT_SVV(".\ET\t1c2.bmp",249,154,12,22);
PRINT_RECT_SVV(".\ET\t1c3.bmp",300,154,12,22);
PRINT_RECT_SVV(".\ET\t1c4.bmp",351,154,12,22);
PRINT_RECT_SVV(".\ET\t1c5.bmp",402,154,12,22);
//распознать
check_this_card(".\ET\t1c1.bmp", card1);
check_this_card(".\ET\t1c2.bmp", card2);
check_this_card(".\ET\t1c3.bmp", card3);
check_this_card(".\ET\t1c4.bmp", card4);
check_this_card(".\ET\t1c5.bmp", card5);
}
if (table==2) {
...
}

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

bool is_a_table (int table_number) {
if (table_number==1) {
PRINT_RECT_SVV(".\ET\is_a_table_1.bmp",5,5,95,25);
if (CHECK_MD5_SVV(".\ET\ET_is_table.bmp", ".\ET\is_a_table_1.bmp")) return true;
else return false;
}
if (table_number==2) {
...

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

void mouse_click (int table_number, TABLE &this_table) {
...
if (this_table.action == "fold") {
SetCursorPos(x+380, y+410);
mouse_event(MOUSEEVENTF_LEFTDOWN, x+380, y+410,0,0);
Sleep(100);
mouse_event(MOUSEEVENTF_LEFTUP, x+380, y+410, 0, 0);
}
...
}

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

Теперь: mov ah,86h; mov dx,cx; int 15h .

«Бред!» - скажешь ты, и будешь абсолютно прав! Если ты читаешь эти строки – значит, ты прошел почти весь долгий путь создания бота и можешь сделать передышку:). Но расслабляться все еще рано, впереди нас ждет самый ответственный этап - анализ и разработка стратегии игры. Все начинающие игроки, как правило, изучают стратегию коротких стеков (shortstack). Рассмотрим ситуацию на префлопе (карты розданы игрокам, но на столе все еще пусто), когда у нас на руках «карманка» (пара карт одинакового ранга).

// Итак, удостоверяемся что на столе нет карт
if (this_table.t_card_1 == "--") {
// А на руках у нас карты одинакового ранга:
if (card_rank(this_table.p_card_1)== card_rank(this_table.p_card_2)) {
//Если кто-то до нас повысил ставки
// или нас заставляют пойти ва-банк
if ((this_table.situation == "call_many") || (this_table.situation == "allin")) {
//Если карманка выше восьмерок и это уже
// второй круг торговли, идем ва-банк (all in)
if ((card_rank(this_table.p_card_1)>=9) && (this_table.trade_cycle>=2))
{this_table.action = "allin";}
// Если карманка начиная с десяток -
// не обращаем внимания на круг торговли
// и сразу идём all-in
else if (card_rank(this_table.p_card_1)>=10)
{ this_table.action = "allin"; }
else { this_table.action = "fold"; }
//Если до нас никто внятно не рейзил
// (ставка была не больше размера большого блайнда)
} else if ((this_table.situation == "check") || (this_table.situation == "call_0.05")||
(this_table.situation == "call_0.10")) {
// Если мы находимся в ранней
// позиции (с нас начинаются торги)
...

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

River: all-in

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

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

Tricks & Tips

  1. Тестируй технические детали действий бота в играх на фантики, а стратегии - в играх на реальные деньги. В первом случае ты сэкономишь деньги, а во втором - время. Дело в том, что в играх без реальных денежных затрат срабатывает психологический эффект aka «а и ладно, не корову проигрываю». Игроки действуют хаотично, не сбрасывают слабые руки, чаще блефуют и так далее.
  2. Регулярно просматривай статистику бота не только на предмет технических ошибок или неправильного следования стратегии, но и на предмет тенденций. Если игроки распознают шаблонное поведение спустя полчаса игры, они смогут использовать это в своих целях, и денежный счет твоего бота будет планомерно уменьшаться.
  3. Если есть возможность, используй несколько разных аккаунтов для покер-рума. Несмотря на то, что на многих ресурсах это запрещено правилами, никто не мешает сделать пару запасных акков - разумеется, исключительно ради спортивного интереса:).
  4. Не забывай, что администрация покер-рума может распознать работу бота по статистике, которая ведется для каждого игрока.
  5. Выбирай столы с новичками на низких ставках. Пусть мал выигрыш, зато част:).

Info

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

    какой то “куцый” обзор… как будто спешили куда то