⬆️ ⬇️

Creating and integrating a VK bot into a group via VkBotLongPoll [Python]

In this article we will create a bot and integrate it into the VK group in Python 3.x



Who is this article for?



For those who want to write a simple bot for their community, who knows how to define commands and display the appropriate answer



Main stages





Create bot group



We begin with the creation of a bot, namely a group in the VC.

')

For this you need to go to "groups" → "create a community."



Select any type of community and enter the name, subject of the group.



On the settings page that opens, select "Work with API".



Next, you need to create an API key.



Then select the parameters you need with access for your API key.



Most likely, you will have to confirm the action in the VC using a mobile phone. Then copy the resulting API key somewhere in the file. We still need it.



Then you need to enable messages. To do this, go to the "message" and turn them on. We also enable the “Bots features” in “Messages” -> “Settings for bot”.



In the same place, let's allow the community to be added to groups if we want the bot to be able to receive messages from the group.



Long Poll Setup



To work with the Long Poll API, we use the vk_api library. You can install it through pip.



Before work we will save our API token in the config.py file from there we will load our key.



Let's create our first script. Let's call server.py, which will be the main server script.



We import the modules we need:



import vk_api.vk_api from vk_api.bot_longpoll import VkBotLongPoll from vk_api.bot_longpoll import VkBotEventType 


Create a class server:



 class Server: def __init__(self, api_token, group_id, server_name: str="Empty"): #    self.server_name = server_name #  Long Poll self.vk = vk_api.VkApi(token=api_token) #   Long Poll API self.long_poll = VkBotLongPoll(self.vk, group_id) #    vk_api self.vk_api = self.vk.get_api() def send_msg(self, send_id, message): """     messages.send :param send_id: vk id ,    :param message:    :return: None """ self.vk_api.messages.send(peer_id=send_id, message=message) def test(self): #      ID self.send_msg(255396611, "-!") 


Now we will create a server_manager.py file in which it will manage different servers. For now, for tests, we will only write a call to the Server class:



 #     Server from server import Server #   config.py  api-token from config import vk_api_token server1 = Server(vk_api_token, 172998024, "server1") # vk_api_token - API ,     # 172998024 - id - # "server1" -   server1.test() 


Important!

A bot can write messages only to those users who have allowed the bot to send messages. This can be done on the community page or first write a bot


If everything is done correctly, the bot will send us a personal message.



Now we will add a bot to the group and teach it to process messages.

To add a bot to a group, click “Invite to conversation” in the right-hand menu of the community.



Add the start function to the bot, after calling which it will start “listening” to the message via Long Poll (do not forget to add permissions to the event types in the “Working with API” -> “Long Poll API” section and install the latest version):



 def start(self): for event in self.long_poll.listen(): print(event) 


Run it through server_manager.py:



 server1.start() 


Now, if we write a message to the group, we can see the event object:

<< class 'vk_api.bot_longpoll.VkBotMessageEvent'> ({'type': 'message_new', 'object': {'date': 1541273151, 'from_id': 25599999999, 'id': 0, 'out': 0, 'peer_id': 2000000001, 'text': '[club172998024 | bot in da Vk] this is a text!', 'conversation_message_id': 187, 'fwd_messages': [], 'important': False, 'random_id': 0 , 'attachments': [], 'is_hidden': False}, 'group_id': 172998024})>


Also, if we write in private messages:

<< class 'vk_api.bot_longpoll.VkBotMessageEvent'> ({'type': 'message_new', 'object': {'date': 1541273238, 'from_id': 25599999999, 'id': 47, 'out': 0, 'peer_id': 255396611, 'text': 'this is a private message', 'conversation_message_id': 47, 'fwd_messages': [],' important ': False,' random_id ': 0,' attachments': [], 'is_hidden ': False},' group_id ': 172998024})>




From this data we should pay attention to type, object.from_id, object.id, object.peer_id, object.text . The data received from the messages and from the group is not much different, except for object.peer_id and object.id .



If you take a closer look, object.id for all messages from the group is 0, and there are no messages from personal ones. Thus, it is possible to separate the messages received from the group and from personal ones.



We process the received data inside the Server class:



 def start(self): for event in self.long_poll.listen(): #   #    if event.type == VkBotEventType.MESSAGE_NEW: print("Username: " + self.get_user_name(event.object.from_id)) print("From: " + self.get_user_city(event.object.from_id)) print("Text: " + event.object.text) print("Type: ", end="") if event.object.id > 0: print("private message") else: print("group message") print(" --- ") def get_user_name(self, user_id): """   """ return self.vk_api.users.get(user_id=user_id)[0]['first_name'] def get_user_city(self, user_id): """   """ return self.vk_api.users.get(user_id=user_id, fields="city")[0]["city"]['title'] 




We will write two messages to the bot: one from the group, one in lichku. Then we get:

Username: Arthur

From: St. Petersburg

Text: [club172998024 | @ club172998024] this is a message from group

Type: group message

-

Username: Arthur

From: St. Petersburg

Text: this is a private message

Type: private message

-


Note



As you can see before the message in the group there is [club172998024 | @ club172998024], to correctly process the command, you should get rid of all the content in square brackets, or allow the bot access to all correspondence


As we see, vk_api allows us to easily use VK API methods. For example, now we used the users.get method



The list of all methods is available at the link: vk.com/dev/methods



I advise you to study and experiment with methods that interest you. Fortunately, VK has provided us with very good documentation, also in Russian.



To consolidate the material, let's add the function of sending a message via the messages.send method:



 def send_message(self, peer_id, message): self.vk_api.messages.send(peer_id=peer_id, message=message) 


<peer_id> is the destination identifier. To respond to someone's message, we specify event.object.peer_id as the peer_id parameter. That is, send a message to where the request came from.



Change the start method:



 def start(self): for event in self.long_poll.listen(): #   #    if event.type == VkBotEventType.MESSAGE_NEW: username = self.get_user_name(event.object.from_id) print("Username: " + username) print("From: " + self.get_user_city(event.object.from_id)) print("Text: " + event.object.text) print("Type: ", end="") if event.object.id > 0: print("private message") else: print("group message") print(" --- ") self.send_message(event.object.peer_id, f"{username},    !") 


Now, if the bot accepts the message, it will respond to us in this style:

Arthur, I received your message!


All code

server.py



 import vk_api.vk_api from vk_api.bot_longpoll import VkBotLongPoll from vk_api.bot_longpoll import VkBotEventType class Server: def __init__(self, api_token, group_id, server_name: str="Empty"): #    self.server_name = server_name #  Long Poll self.vk = vk_api.VkApi(token=api_token) #   Long Poll API self.long_poll = VkBotLongPoll(self.vk, group_id, wait=20) #    vk_api self.vk_api = self.vk.get_api() def send_msg(self, send_id, message): """     messages.send :param send_id: vk id ,    :param message:    :return: None """ self.vk_api.messages.send(peer_id=send_id, message=message) def test(self): self.send_msg(255396611, "-!") def start(self): for event in self.long_poll.listen(): #   #    if event.type == VkBotEventType.MESSAGE_NEW: username = self.get_user_name(event.object.from_id) print("Username: " + username) print("From: " + self.get_user_city(event.object.from_id)) print("Text: " + event.object.text) print("Type: ", end="") if event.object.id > 0: print("private message") else: print("group message") print(" --- ") self.send_message(event.object.peer_id, f"{username},    !") def get_user_name(self, user_id): """   """ return self.vk_api.users.get(user_id=user_id)[0]['first_name'] def get_user_city(self, user_id): """   """ return self.vk_api.users.get(user_id=user_id, fields="city")[0]["city"]['title'] def send_message(self, peer_id, message): self.vk_api.messages.send(peer_id=peer_id, message=message) 


server_manager.py



 #     Server from server import Server #   config.py  api-token from config import vk_api_token server1 = Server(vk_api_token, 172998024, "server1") server1.start() 




Task for securing the material:



Create a function that takes a peer_id parameter and sends the user a photo uploaded to the community. Useful dock: vk.com/dev/messages.send



Decision
First, upload a photo to the group and open it in the VC, consider the link:

vkcom / club172998024? z = photo-172998024_456239017 % 2Falbum-172998024_256250731


We are only interested in the highlighted part: photo-172998024_456239017 . Let's pass it as an argument to the messages.send method:



 def send_img(self, peer_id): self.vk_api.messages.send(peer_id=peer_id, attachment="photo-172998024_456239017") 


Add it to the start method and get:







That's all the basics. The main thing is to learn how to use vk_api using various methods, their entire list: vk.com/dev/methods . If you learn how to work with VK API documentation, you can create bots of varying complexity and assignments. An example of my bot for the study group: github.com/AppLoidx/GroupAssistant/tree/master



Now let's start creating the logic of the bot.



Let's create commander.py, which will receive commands and return the response sent to the Vk user:



 class Commander: def __init__(self, vk_api, user_id): self.vk_api = vk_api self.user_id = user_id def input(self, msg): """     :param msg:  :return:  ,   """ pass 


Let's build the architecture of our program:



We “listen” to the Long Poll server and get the user's message ->

We transfer the message to Commander.input () -> We define the mode -> We define the command ->

Return the answer -> Pass to the user



To determine the mode and command, create two files command_enum.py and mode_enum.py. Using them, we will define modes and commands through the methods of the Enum class:



command_enum.py:



 from enum import Enum class Command(Enum): """ weather """ weather = ["weather", ""] """ myanimelist """ anime_top = ["top anime", " "] 




mode_enum.py:



 from enum import Enum class Mode(Enum): default = [" ", "default"] translate = [" ", "translate"] get_ans = 0 




To change modes, use [slash ("/") + <mode_name>], and accept all other commands as commands.



Implement it in Commander.py:



 #  ,  from command_enum import Command from mode_enum import Mode #   from translate.yandex_translate import Translator from weather import Weather from myanimelist import Myanimelist # Config from config import yandex_translate_api class Commander: def __init__(self): # ,   self.now_mode = Mode.default self.last_mode = Mode.default self.last_command = None #     self.last_ans = None #    self.translator = Translator(yandex_translate_api) def change_mode(self, to_mode): """     :param to_mode:   """ self.last_mode = self.now_mode self.now_mode = to_mode self.last_ans = None def input(self, msg): """     :param msg:  :return:  ,   """ #      if msg.startswith("/"): for mode in Mode: if msg[1::] in mode.value: self.change_mode(mode) return "   " + self.now_mode.value[0] return "  " + msg[1::] #    if self.now_mode == Mode.get_ans: self.last_ans = msg self.now_mode = self.last_mode return "Ok!" if self.now_mode == Mode.default: #  if msg in Command.weather.value: return Weather.get_weather_today() #   if msg in Command.anime_top.value: res = "" top = Myanimelist.get_top() for anime in top: res += anime + " : " + top[anime] + "\n" return res if self.now_mode == Mode.translate: if self.last_ans is None: #    ,    self.change_mode(Mode.get_ans) self.last_command = msg return "     " elif self.last_ans == "change": #    self.last_ans = None self.change_mode(Mode.default) else: #  return self.translator.translate_to(msg, self.last_ans) return "  !" 


weather.py
 import requests from bs4 import BeautifulSoup class Weather: @staticmethod def get_weather_today(city: str = "-") -> list: http = "https://sinoptik.com.ru/-" + city b = BeautifulSoup(requests.get(http).text, "html.parser") p3 = b.select('.temperature .p3') weather1 = p3[0].getText() p4 = b.select('.temperature .p4') weather2 = p4[0].getText() p5 = b.select('.temperature .p5') weather3 = p5[0].getText() p6 = b.select('.temperature .p6') weather4 = p6[0].getText() result = '' result = result + (' :' + weather1 + ' ' + weather2) + '\n' result = result + (' :' + weather3 + ' ' + weather4) + '\n' temp = b.select('.rSide .description') weather = temp[0].getText() result = result + weather.strip() return result 




myanimelist.py
 import requests import bs4 class Myanimelist: @staticmethod def get_top(count: int=5, by: str="") -> dict: types = ["", "airing", "upcoming", "tv", "movie", "ova", "special", "bypopularity", "favorite"] if by not in types: return {"error: ": " !"} html = requests.get("https://myanimelist.net/topanime.php?type="+by) soup = bs4.BeautifulSoup(html.text, "html.parser") res = {} for anime in soup.select(".ranking-list", limit=count): url = anime.select(".hoverinfo_trigger")[0]['href'] anime = anime.select(".hoverinfo_trigger")[0].findAll("img")[0] name = anime['alt'].split(":")[1].strip(" ") res[name] = url return res 




yandex_translate.py
 import requests from config import yandex_translate_api class Translator: """ -  API Yandex Translate : _key --   API Yandex.Translate _yandex_comment --       API Yandex.Translate """ def __init__(self, key, comment=None): """ :param key:   API Yandex.Translate :param comment:     """ self._key = key if comment is None: self._yandex_comment = "\n  «.» http://translate.yandex.ru/" else: self._yandex_comment = comment def translate(self, text, lang, to_lang=None): """         :param text: ,    :param lang:   :param to_lang:   :return:   """ if to_lang is not None: lang = f"{lang}-{to_lang}" main_url = "https://translate.yandex.net/api/v1.5/tr.json/translate" response = requests.get(f"{main_url}?" f"key={self._key}&" f"lang={lang}&" f"text={text}") return response.json()['text'][0] + self._yandex_comment def lang_identify(self, text, hint="ru,en"): """   :param text:  :param hint:     :return:   """ main_url = "https://translate.yandex.net/api/v1.5/tr.json/detect" response = requests.get(f"{main_url}?" f"key={self._key}&" f"hint={hint}&" f"text={text}") return response.json()['lang'] def translate_ru_en(self, text): """       :param text: ,    :return:      """ if self.lang_identify(text) == "ru": to_lang = "en" from_lang = "ru" else: to_lang = "ru" from_lang = "en" return self.translate(text, from_lang, to_lang) def translate_to_ru(self, text, hint=None): """     :param text: ,    :param hint:     :return:      """ if hint is None: hint = "ru,en" from_lang = self.lang_identify(text, hint) return self.translate(text, from_lang, "ru") def translate_to(self, text, to_lang, hint=None): """      :param text: ,    :param to_lang:    :param hint:     :return:   """ if hint is None: hint = "ru,en" from_lang = self.lang_identify(text, hint) return self.translate(text, from_lang, to_lang) 




All code is available on github: github.com/AppLoidx/VkLongPollBot



Add a keyboard:



This is a very easy process, complications can be caused when we change the keyboard to a specific command signature, which is different for each mode.



To add a keyboard in the dialog box, you need to specify in the messages.send method the keyboard parameter that accepts json. It looks like this:



 vk_api.messages.send(...,keyboard=keyboard_json,...) 


Alternatively, you can transfer the keyboard directly from a .json file:



 vk_api.messages.send(...,keyboard=open(filename,"r",encoding="UTF-8").read() 


Documentation: vk.com/dev/bots_docs_3?f=4.%2BKeyboards%2B for%2Bbots



Consider the example of our program by adding a keyboard.



First, create a keyboard.json file:



 { "one_time": false, "buttons": [ [{ "action": { "type": "text", "label": "top anime" }, "color": "positive" }, { "action": { "type": "text", "label": "weather" }, "color": "positive" }], [{ "action": { "type": "text", "label": "translate" }, "color": "default" }] ] } 


To remove the keyboard you need to pass json with empty buttons:



 {"buttons":[],"one_time":true} 


Override send_message in server.py:



 def send_msg(self, send_id, message): """     messages.send :param send_id: vk id ,    :param message:    :return: None """ return self.vk_api.messages.send(peer_id=send_id, message=message, keyboard=open("keyboards/default.json", "r", encoding="UTF-8").read()) 


And also in the start method:



 def start(self): for event in self.long_poll.listen(): #   if event.type == VkBotEventType.MESSAGE_NEW: if event.object.from_id not in self.users: self.users[event.object.from_id] = Commander() #    if event.type == VkBotEventType.MESSAGE_NEW: self.send_msg(event.object.peer_id, self.users[event.object.from_id].input(event.object.text)) 


As a result, we get:







The last word



You should not use the bare listing of source codes presented here, they are used only for you to better understand what is happening under the hood. Of course, they are all usable and can be used in parts.



Personally, I used such a bot for a group assistant who could:





Project on githaba

Sources presented here

Source: https://habr.com/ru/post/428680/



All Articles