Чат-боты стали уже очень распространенным явлением, и появляются во всех мессенджерах ежедневно.
В этой статье по шагам разберем создание бота с набором простых команд и узнаем, как в дальнейшем можно расширить его функционал. Статья будет полезна для самых новичков, которые никогда не пробовали создавать чат-ботов.
Когда мне захотелось создать бота, я изучил доступные примеры ботов для ВКонтакте и постарался достигнуть максимального упрощения их структуры.
Для создания бота я использовал Python 3.5 (вероятно, подойдут и другие версии 3-го питона) и дополнительные библиотеки Flask и VK. Их надо будет установить. По установке Flask есть много статей на русском. Если у вас стоит Pycharm, то он, скорее всего, установился вместе с ним.
Начнем с самого API. Для нашего бота будем использовать Callback API, доступный для сообщений групп. Прежде всего, нам нужно создать или уже иметь группу ВКонтакте с подключенными сообщениями.
В разделе управление сообществом -> работа с API необходимо создать ключ с доступом к сообщениям сообщества.
Настройка Callback API Бот для групп ВК
Для работы с Callback нужно иметь веб-сервер, который будет принимать запросы о каких-либо событиях от API, обрабатывать их и посылать ответные запросы. То есть мы напишем «сайт», который будет только отвечать на посылаемые ему запросы и посылать свои.
Поскольку пишем на питоне, самое простое, что можно использовать, — это хостинг для питона. Я пользовался бесплатным хостингом для Python. Там нужно зарегистрироваться, а затем создать приложение для питона 3.5 на Flask (создать можно в разделе Web). Будет создан начальный файл:
Единственная функция, которая сейчас есть в файле, отвечает за наполнение страницы по адресу, выданному при регистрации. Если перейти в браузере по адресу username.pythonanywhere.com (со своим ником), то можно увидеть только текст «Hello from Flask!».
Для обработки запросов, посылаемых сайту, добавим в конце документа следующий код:
Где вместо иксов подставляем «строку, которую должен вернуть сервер». Она указана в управлении группой в разделе Callback API.
Эта функция позволит нам подключить наш сайт для уведомлений к группе.
Теперь можем проверить работу. Только необходимо перезагрузить приложение. На хостинге после того, как файлы были изменены и сохранены, чтобы сайт стал работать с новыми данными, нужно его перезагрузить во вкладке Web. После добавления этого кода можем ввести соответствующий адрес username.pythonanywhere.com в строку адреса сервера в группе ВКонтакте и нажать «Подтвердить».
Должно появиться зеленое уведомление о том, что адрес сервера успешно подключен.
При нажатии «Подтвердить» ВКонтакте пытается связаться с нашим сервером и убедиться, что он действительно принадлежит владельцу группы, и «ждет», что сервер вернет код подтверждения в ответ на запрос.
Можем переходить к следующему шагу. Добавим возможность писать сообщения от имени сообщества. Пришло время установить на хостинге библиотеку VK. В разделе Consoles запускаем bash-консоль и исполняем команду:
Настраиваем Callback API Бот. Подробная инструкция. Очень лёгкая😁
pip3.5 install —user vk
Как устанавливать модули описано здесь.
Изменим код нашей функции по обработке входящих запросов:
Сообщение о том, что обработка прошла успешно, нужно серверу ВКонтакте. Если произойдет ошибка, или придет какой-то иной ответ, сервер будет с некоторыми промежутками продолжать посылать уведомление о входящем сообщении (пока мы его не обработаем).
Структура входящего запроса, оповещающего о новом сообщении, такова:
, «group_id»:xxxxxxxxxxx>
Вконтакте передает нашему сайту три объекта: «type», «object», «group_id», а внутри «object» хранится информация о самом сообщении.
Все запросы можно посмотреть в документации ВКонтакте.
Также добавляем новые «import» в начало файла:
from flask import Flask, request, json from settings import * import vk
У нас появился новый файл в этой же папке settings.py, в котором сохранены необходимые данные для входа:
token = ‘xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx’ confirmation_token = ‘xxxxxxxx’
Их надо заменить на ваши токены. Первый мы создали в начале статьи, второй – код подтверждения, чтобы соединить группу с сервером.
Теперь наш бот может на входящие сообщения здороваться и подтверждать свою принадлежность группе, код которой мы ему дали.
Можем его проверить и написать ему какое-нибудь сообщение, только надо подключить в настройках группы в разделе Callback API уведомления о входящих сообщениях.
Чтобы бот стал слать сообщения, нужно перезагрузить приложение. После этого снова пишем боту и, если все в порядке, переходим к следующему шагу.
Если все прошло хорошо, и бот с вами поздоровался в ответ на ваше сообщение, переходим к следующему шагу. Вынесем все взаимодействие с библиотекой vk в другой файл, я назвал его vkapi:
import vk session = vk.Session() api = vk.API(session, v=5.0) def send_message(user_id, token, message, attachment=»»): api.messages.send(access_token=token, user_id=str(user_id), message=message, attachment=attachment)
Пока там только одна функция и инициализация сессии ВКонтакте, потом добавим другие. Потенциально функция может также отправлять и вложения. Позже мы воспользуемся этой возможностью.
Дальше заведем файл — обработчик сообщений. Он будет обрабатывать входящие сообщения, определять соответствующие команды, когда они появятся, и выдавать нужные ответы.
import vkapi def get_answer(body): message = «Привет, я новый бот!» return message def create_answer(data, token): user_id = data[‘user_id’] message = get_answer(data[‘body’].lower()) vkapi.send_message(user_id, token, message)
Осталось подключить наши новые файлы к основному. Изменяем функцию обработки запросов в главном файле:
И добавляем соответствующий импорт в начало файла:
import messageHandler
Можем проверить, что у нас получилось, перезагрузив приложение.
Приступим к созданию команд. Создадим класс команд.
У класса есть свойство keys, где будут храниться ключи, по которым можно обратиться к данной команде. Все ключи сохраняются в строчных буквах при задании свойства, и сравнивать их нужно с переведенными в строчную форму сообщениями пользователя, чтобы регистр не влиял на успех вызова команды.
Поле description будем использовать для выдачи информации по командам бота. Функция process будет исполняться для формирования ответного сообщения.
Есть общий список, в который сохраняются все команды при их инициализации. Он находится снаружи класса. Этот список будем использовать для поиска команды, которую запросил пользователь своим сообщением.
Теперь создадим несколько команд для нашего бота. Для удобства загрузки будем помещать файлы, в которых инициализируем команды, в папку «commands».
Я создам несколько файлов, но можно и разместить команды и в одном файле
import command_system def hello(): message = ‘Привет, друг!nЯ новый чат-бот.’ return message, » hello_command = command_system.Command() hello_command.keys = [‘привет’, ‘hello’, ‘дратути’, ‘здравствуй’, ‘здравствуйте’] hello_command.description = ‘Поприветствую тебя’ hello_command.process = hello
import command_system import vkapi def cat(): # Получаем случайную картинку из паблика attachment = vkapi.get_random_wall_picture(-32015300) message = ‘Вот тебе котик :)nВ следующий раз я пришлю другого котика.’ return message, attachment cat_command = command_system.Command() cat_command.keys = [‘котик’, ‘кошка’, ‘кот’, ‘котенок’, ‘котяра’, ‘cat’] cat_command.description = ‘Пришлю картинку с котиком’ cat_command.process = cat
Для команды, отправляющей котика, используем новую функцию, которую написали в файле «vkapi», она возвращает случайную картинку со стены группы или пользователя. В данном случае получаем случайную фотографию со стены паблика с котами. Здесь немного расширяем спектр используемых методов API.
Этот метод выглядит так:
def get_random_wall_picture(group_id): max_num = api.photos.get(owner_id=group_id, album_id=’wall’, count=0)[‘count’] num = random.randint(1, max_num) photo = api.photos.get(owner_id=str(group_id), album_id=’wall’, count=1, offset=num)[‘items’][0][‘id’] attachment = ‘photo’ + str(group_id) + ‘_’ + str(photo) return attachment
Дописываем его в файл «vkapi». Также в начало файла «vkapi» надо добавить необходимый импорт:
import random
И последняя команда
import command_system def info(): message = » for c in command_system.command_list: message += c.keys[0] + ‘ — ‘ + c.description + ‘n’ return message, » info_command = command_system.Command() info_command.keys = [‘помощь’, ‘помоги’, ‘help’] info_command.desciption = ‘Покажу список команд’ info_command.process = info
Окончательная иерархия файлов:
botFlask — главный файл, который принимает входящие запросы.
Теперь, когда мы описали команды, нужно позаботиться о том, чтобы наш лист команд был наполнен, и мы могли понять, к какой из команд обращался пользователь, так как список “command_list” заполняется только в момент запуска файлов с конкретными командами.
Мы будем автоматически запускать на исполнение все файлы из папки «commands» при запуске нашего бота.
Для этого в файле «messageHandler.py» дописываем функцию:
def load_modules(): # путь от рабочей директории, ее можно изменить в настройках приложения files = os.listdir(«mysite/commands») modules = filter(lambda x: x.endswith(‘.py’), files) for m in modules: importlib.import_module(«commands.» + m[0:-3])
В этой функции мы загружаем список файлов из директории с командами, отфильтровываем только файлы питона и импортируем их в нашу программу, что обеспечивает заполнение списка командами.
Вызов этой функции добавляем в «create_answer». Теперь изменим функцию «get_answer» так, чтобы она вызывала соответствующий ответ.
Итоговый вид файла:
import vkapi import os import importlib from command_system import command_list def load_modules(): # путь от рабочей директории, ее можно изменить в настройках приложения files = os.listdir(«mysite/commands») modules = filter(lambda x: x.endswith(‘.py’), files) for m in modules: importlib.import_module(«commands.» + m[0:-3]) def get_answer(body): # Сообщение по умолчанию если распознать не удастся message = «Прости, не понимаю тебя. Напиши ‘помощь’, чтобы узнать мои команды» attachment = » for c in command_list: if body in c.keys: message, attachment = c.process() return message, attachment def create_answer(data, token): load_modules() user_id = data[‘user_id’] message, attachment = get_answer(data[‘body’].lower()) vkapi.send_message(user_id, token, message, attachment)
Все, наш бот готов! Теперь вы знаете, как создать основу для бота и добавить для него новые команды.
Дальнейшая часть статьи будет про одно улучшение, которое я считаю необходимым. Однако бот будет работать и без этого улучшения.
Приблизительное распознавание команд
Если пользователь допустил ошибку в одном символе, скорее всего, он имел в виду максимально похожую команду. Поэтому было бы хорошо, если бы наш бот все равно давал ответ, а не говорил «не понимаю тебя».
Для приблизительного распознавания будем использовать расстояние Дамерау-Левенштейна. Оно показывает, за сколько операций удаления, вставки, замены и перемещения символов можно перейти от одной строки к другой.
Алгоритм нахождения этого расстояния изложен, например, в Википедии.
Добавляем в файл “messageHandler.py” функцию:
def damerau_levenshtein_distance(s1, s2): d = <> lenstr1 = len(s1) lenstr2 = len(s2) for i in range(-1, lenstr1 + 1): d[(i, -1)] = i + 1 for j in range(-1, lenstr2 + 1): d[(-1, j)] = j + 1 for i in range(lenstr1): for j in range(lenstr2): if s1[i] == s2[j]: cost = 0 else: cost = 1 d[(i, j)] = min( d[(i — 1, j)] + 1, # deletion d[(i, j — 1)] + 1, # insertion d[(i — 1, j — 1)] + cost, # substitution ) if i and j and s1[i] == s2[j — 1] and s1[i — 1] == s2[j]: d[(i, j)] = min(d[(i, j)], d[i — 2, j — 2] + cost) # transposition return d[lenstr1 — 1, lenstr2 — 1]
Она реализует алгоритм нахождения этого расстояния, при желании вы можете его изменить или улучшить.
По данным строкам она будет выдавать количество операций для преобразования одной в другую. Теперь изменим метод «get_answer»:
def get_answer(body): message = «Прости, не понимаю тебя. Напиши ‘помощь’, чтобы узнать мои команды» attachment = » distance = len(body) command = None key = » for c in command_list: for k in c.keys: d = damerau_levenshtein_distance(body, k) if d < distance: distance = d command = c key = k if distance == 0: message, attachment = c.process() return message, attachment if distance < len(body)*0.4: message, attachment = command.process() message = ‘Я понял ваш запрос как «%s»nn’ % key + message return message, attachment
В этой функции мы вычисляем расстояние для сообщения и каждого из ключей. Если совпадение неточное, пишем, как бот распознал каждую из тех команд, которые ему отправили. В случае, если расстояние превысило 40% от длины поданного сообщения, считаем, что пользователь ошибся слишком сильно и возвращаем сообщение по умолчанию, где предлагаем обратиться к помощи.
На этом все, рабочий (на момент написания статьи) код выложен на гитхабе.
Надеюсь, эта статья облегчит жизнь вам жизнь, если вы решили создать своего бота для VK.
Источник: www.liveinternet.ru
iSa1vatore/vk-callback-bot
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Switch branches/tags
Branches Tags
Could not load branches
Nothing to show
Could not load tags
Nothing to show
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Cancel Create
- Local
- Codespaces
HTTPS GitHub CLI
Use Git or checkout with SVN using the web URL.
Work fast with our official CLI. Learn more about the CLI.
Sign In Required
Теперь включим обработку сообщений от пользователя, для этого добавим обработку типа «message_new», так же не забудьте поставить обработку входящих сообщений во вкладке «Типы событий». Обязательно добавляем в конец файла функцию, которая отправит строку «OK», так VK поймет, что мы смогли ответить на событие и не будет отправлять его повторно.
switch ($bot->event) < case ‘message_new’: //Обрабатывает сообщение пользователя и вводит нужные переменные. $bot->MessageProcessing(); switch ($bot->command) < case ‘start’: case ‘начать’: $bot->message ->text(«Добро пожаловать!») ->send(); break; default: $bot->message ->text(«Команда не найдена.») ->send(); break; > break; case ‘confirmation’: exit($bot->confirmation_key); break; > $bot->sendOK();
Команда пользователя находится в переменной $bot->command она формируется из первого слова в сообщении, остальные слова помещаются по одному в массив $bot->commandOptions.
Далее мы видим обращение к объекту $bot->message он нужен для отправки/удаления/редактирования сообщений. Чтобы отправить простое текстовое сообщение нужно выполнить след. команду:
$bot->message ->text(«Текст») ->send();
Также можно прикрепить вложения:
$bot->message ->addPhoto(‘photo137371466_325103360’) //1 метод ->addPhoto(137371466, 456239044) // 2 метод ->text(«Текст») ->send();
$bot->message ->addDoc(‘doc-166966945_472727937’) //1 метод ->addDoc(-166966945, 472727937) // 2 метод ->text(«Текст») ->send();
$bot->message ->addVideo(‘video277941697_456239037’) //1 метод ->addVideo(277941697, 456239037) // 2 метод ->text(«Текст») ->send();
$bot->message ->addAudio(‘audio137371466_456239655’) //1 метод ->addAudio(137371466, 456239655) // 2 метод ->text(«Текст») ->send();
$bot->message ->addWall(‘wall137371466_2811’) //1 метод ->addWall(137371466, 2811) // 2 метод ->text(«Текст») ->send();
$bot->message ->keyboard() ->row() ->button(‘Кнопка 1 в первом ряду’, ‘positive’, [‘command’ => ‘start’]) ->button(‘Кнопка 2 в первом ряду’, ‘positive’, [‘iSa1vatore’ => ‘Sexy boy’]) ->row() ->button(‘Кнопка 1 во втором ряду’, ‘positive’, []) ->button(‘Кнопка 2 во втором ряду’, ‘positive’, []) ->row() ->button(‘Кнопка 1 третьем ряду’, ‘positive’, [‘command’ => ‘sendAttach’, ‘parametr’ => 5]) ->one_time() //false by default ->text(«Клацни на кнопошку») ->send();
Для создания массива клавиатуры вызовите ->keyboard()
Для создания ряда используйте ->row()
Для создания кнопки используйте ->button(Название, Цвет, массив который будет передан в payload)
У кнопок может быть один из 4 цветов:
- primary — синяя кнопка, обозначает основное действие. #5181B8
- default — обычная белая кнопка. #FFFFFF
- negative — опасное действие, или отрицательное действие (отклонить, удалить и тд). #E64646
- positive — согласиться, подтвердить. #4BB34B Если передать «command» в payload, то именно эта команда попадет в переменную $bot->command. (Кнопка 1 в первом ряду)
После того как вы сформировали клавиатуру добавьте ->one_time() эта функция сформирует клавиатуру. В неё также можно передать параметр true, тогда клавиатура удалится у пользователя как только он ей воспользуется.
Если вам нужно передавать одинаковую клавиатуру сразу в нескольких условиях, то вы можете поместить её в переменную.
Помещение клавиатуры в переменную и отправка:
$defaultKeyboard = $bot->message ->keyboard() ->row() ->button(‘Кнопка 1’, ‘primary’, [‘command’ => ‘clickbutton’, ‘parametr’ => 1]) ->button(‘Кнопка 2’, ‘primary’, [‘command’ => ‘clickbutton’, ‘parametr’ => 2]) ->row() ->button(‘Кнопка 3’, ‘positive’, [‘thisbutton’ => 3]) ->button(‘Кнопка 4’, ‘positive’, [‘thisbutton’ => 4]) ->one_time() //false by default ->getKeyboard(); //getKeyboard возвращает строку. $bot->message ->text(«Добро пожаловать!») ->setKeyboard($defaultKeyboard) //setKeyboard добавляет клавиатуру из переменной. ->send();
Чтобы удалить сообщение вызовите метод delete у объекта message передав в параметр ID сообщения.
$mid = $bot->message->text(«Меня удалят, вот же («)->send(); $bot->message->delete($mid);
Для редактирования сообщения используйте метод edit вместо send, но передав ID сообщения. Также добавить вложения.
$mid = $bot->message->text(«Ghbdtn!»)->send(); $bot->message->text(«Привет!»)->edit($mid);
Загрузка файлов (Думаю, примера кода будет достаточно):
$bot->message->text(«Началась загрузка файлов!»)->send(); //Передаю параметр false, тем самым не прекращая работу скрипта, а VK получает ответ «ok». //Рекомендую использовать когда пользователю нужно отправить большой фаил и нужно чтобы VK не выбил TimeOut.
$bot->sendOK(false); //Загрузка фото $uploadPhoto = $bot->api->uploadPhoto(__DIR__.’/assets/image.jpg’); if(isset($uploadPhoto[‘id’])) < $bot->message ->addPhoto($uploadPhoto[‘oid’], $uploadPhoto[‘id’]) ->send(); > else < $bot->message->text(«Возникла ошибка при загрузке.
«)->send(); > //Загрузка документа $uploadDoc = $bot->api->uploadDoc(__DIR__.’/assets/file.txt’, ‘Название’); if(isset($uploadDoc[‘id’])) < $bot->message ->addDoc($uploadDoc[‘oid’], $uploadDoc[‘id’]) ->send(); > else < $bot->message->text(«Возникла ошибка при загрузке.»)->send(); > //Загрузка голосового сообщения $uploadVoice = $bot->api->uploadVoice(__DIR__.’/assets/click_audio.wav’); if(isset($uploadVoice[‘id’])) < $bot->message ->addDoc($uploadVoice[‘oid’], $uploadVoice[‘id’]) ->send(); > else < $bot->message->text(«Возникла ошибка при загрузке.»)->send(); >
Источник: github.com
Ответы бота Вк в беседах (callback api python)
У меня есть бот для ВК на callback api(python), в ЛС он отвечает, а вот в беседы нет!
import vk import random session = vk.Session() api = vk.API(session, v=5.0) def get_random_wall_picture(group_id, token): max_num = api.photos.get(owner_id=group_id, album_id=’260175787′, count=0, access_token=token)[‘count’] num = random.randint(1, max_num) photo = api.photos.get(owner_id=str(group_id), album_id=’260175787ll’, count=1, offset=num, access_token=token)[‘items’][0][‘id’] attachment = ‘photo’ + str(group_id) + ‘_’ + str(photo) return attachment def send_message(user_id, token, message, attachment=»»): api.messages.send(access_token=token, user_id=str(user_id), message=message, attachment=attachment)
import vkapi import os import importlib from command_system import command_list def damerau_levenshtein_distance(s1, s2): d = <> lenstr1 = len(s1) lenstr2 = len(s2) for i in range(-1, lenstr1 + 1): d[(i, -1)] = i + 1 for j in range(-1, lenstr2 + 1): d[(-1, j)] = j + 1 for i in range(lenstr1): for j in range(lenstr2): if s1[i] == s2[j]: cost = 0 else: cost = 1 d[(i, j)] = min( d[(i — 1, j)] + 1, # deletion d[(i, j — 1)] + 1, # insertion d[(i — 1, j — 1)] + cost, # substitution ) if i and j and s1[i] == s2[j — 1] and s1[i — 1] == s2[j]: d[(i, j)] = min(d[(i, j)], d[i — 2, j — 2] + cost) # transposition return d[lenstr1 — 1, lenstr2 — 1] def load_modules(): files = os.listdir(«mysite/commands») modules = filter(lambda x: x.endswith(‘.py’), files) for m in modules: importlib.import_module(«commands.» + m[0:-3]) def get_answer(body): message = «Прости, не понимаю тебя. ПЛАКИЧ! Напиши ‘?команды’, чтобы узнать как не странно мои команды НЯ» attachment = » distance = len(body) command = None key = » for c in command_list: for k in c.keys: d = damerau_levenshtein_distance(body, k) if d < distance: distance = d command = c key = k if distance == 0: message, attachment = c.process() return message, attachment if distance < len(body)*0.4: message, attachment = command.process() message = ‘Я поняла твой запрос как «%s»nn’ % key + message return message, attachment def create_answer(data, token): load_modules() user_id = data[‘user_id’] message, attachment = get_answer(data[‘body’].lower()) vkapi.send_message(user_id, token, message, attachment)
Источник: ru.stackoverflow.com