إنشاء ودمج VK bot في مجموعة من خلال VkBotLongPoll [Python]

في هذه المقالة ، سنقوم بإنشاء روبوت ودمجها في مجموعة VK في Python 3.x

لمن هذه المقالة؟


بالنسبة لأولئك الذين يرغبون في كتابة روبوت بسيط لمجتمعهم ، قادرون على تحديد الفرق وعرض الإجابة المناسبة

المراحل الرئيسية



إنشاء مجموعة بوت


سنبدأ بإنشاء روبوت ، أي مجموعة في VK.

للقيام بذلك ، انتقل إلى "مجموعات" → "إنشاء مجتمع".

حدد أي نوع من المجتمع وأدخل اسمًا أو موضوع مجموعة.

في صفحة الإعدادات التي تفتح ، حدد "العمل مع واجهة برمجة التطبيقات".

بعد ذلك ، تحتاج إلى إنشاء مفتاح API.

ثم حدد المعلمات التي تحتاج إليها مع الوصول إلى مفتاح API الخاص بك.

على الأرجح ، سيكون عليك تأكيد الإجراء في VK باستخدام هاتفك المحمول. ثم انسخ مفتاح واجهة برمجة التطبيقات الناتج في مكان ما إلى ملف. ما زلنا بحاجة إليها.

ثم تحتاج إلى السماح بالرسائل. للقيام بذلك ، انتقل إلى "الرسالة" وقم بتشغيلها. قم أيضًا بتمكين "ميزات البوت" في "الرسائل" -> "إعدادات البوت".

سنسمح هنا بإضافة منتدى إلى المجموعات إذا أردنا أن يتمكن الروبوت من تلقي رسائل من مجموعة.

إعداد استطلاع طويل


للعمل مع Long Poll API ، نستخدم مكتبة vk_api . يمكنك تثبيته من خلال النقطة.

قبل العمل ، سنحفظ رمز API المميز الخاص بنا في ملف config.py ، ومن هناك سنقوم بتحميل مفتاحنا.

لنقم بإنشاء أول سيناريو لدينا. دعنا نطلق اسم 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) #    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, "-!") 

الآن قم بإنشاء ملف 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") # vk_api_token - API ,     # 172998024 - id - # "server1" -   server1.test() 

هام!

يمكن للبوت فقط كتابة الرسائل للمستخدمين الذين سمحوا للبوت بإرسال الرسائل. يمكنك القيام بذلك على صفحة المجتمع أو كتابة البوت أولاً

إذا تم كل شيء بشكل صحيح ، فسيرسل لنا البوت رسالة شخصية.

الآن أضف الروبوت إلى المجموعة وعلّمه كيفية معالجة الرسائل.
لإضافة روبوت إلى مجموعة ، انقر فوق "دعوة إلى محادثة" في قائمة المجتمع اليمنى.

أضف وظيفة البدء إلى برنامج التتبُّع ، وبعد ذلك يبدأ في "الاستماع" إلى الرسائل عبر Long Poll (لا تنس إضافة أذونات إلى أنواع الأحداث في قسم "العمل مع واجهة برمجة التطبيقات" -> "Long Poll API" ووضع أحدث إصدار):

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

قم بتشغيله من خلال server_manager.py:

 server1.start() 

الآن ، إذا كتبنا رسالة إلى المجموعة ، يمكننا رؤية كائن الحدث:
<< 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] هذا نص!'، 'chat_message_id': 187، 'fwd_messages': []، 'important': False، 'random_id': 0 ، 'attachments': []، 'is_hidden': False}، 'group_id': 172998024})>

أيضًا ، إذا كتبنا في رسائل خاصة:
<< 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 private message'، 'chat_message_id': 47، 'fwd_messages': []، 'important': False، 'random_id': 0، 'attachments': []، 'is_hidden ': False}،' group_id ': 172998024})>


من هذه البيانات ، يجب الانتباه إلى النوع ، object.from_id ، object.id ، object.peer_id ، object.text . البيانات المستلمة من الرسائل ومن المجموعة لا تختلف كثيرًا باستثناء object.peer_id و object.id .

إذا نظرت عن كثب ، فإن object.id لجميع الرسائل من المجموعة يساوي 0 ، ولكن لا توجد رسائل من الرسائل الشخصية. وبالتالي ، يمكن فصل الرسائل الواردة من المجموعة ومن الرسائل الشخصية.

نعالج البيانات المستلمة داخل فئة الخادم:

 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'] 


سنكتب رسالتين إلى البوت: واحدة من المجموعة ، وواحدة مساءً. ثم نحصل على:
اسم المستخدم: Arthur
من: سانت بطرسبرغ
نص: [club172998024 | @ club172998024] هذه رسالة من مجموعة
النوع: رسالة جماعية
-
اسم المستخدم: Arthur
من: سانت بطرسبرغ
النص: هذه رسالة خاصة
النوع: رسالة خاصة
-

ملاحظة


كما ترى قبل الرسالة في المجموعة يوجد [club172998024 | @ club172998024] ، لكي يتعامل الفريق بشكل صحيح ، يجب التخلص من جميع المحتويات بين قوسين معقوفين ، أو السماح للبوت بالوصول إلى جميع المراسلات

كما نرى ، يسمح لنا vk_api باستخدام طرق VK API بسهولة. على سبيل المثال ، استخدمنا الآن طريقة users.get

تتوفر قائمة بجميع الطرق على: vk.com/dev/methods

أنصحك بدراسة وتجربة الأساليب التي تهمك. لحسن الحظ ، زودتنا VK بوثائق جيدة جدًا ، باللغة الروسية أيضًا.

لدمج المادة ، دعنا نضيف وظيفة لإرسال رسالة من خلال طريقة Messages.send:

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

<peer_id> هو معرف الوجهة. للرد على رسالة شخص ما ، سنحدد event.object.peer_id كمعلمة peer_id . أي أننا سنرسل رسالة إلى المكان الذي جاء منه الطلب.

قم بتغيير طريقة البدء:

 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},    !") 

الآن ، إذا تلقى البوت الرسالة ، فسيستجيب لنا بهذا الأسلوب:
آرثر ، لقد تلقيت رسالتك!

كود كامل

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() 


مهمة إصلاح المادة:


أنشئ وظيفة تأخذ المعلمة peer_id وترسل للمستخدم صورة تم تحميلها إلى المجتمع. منصة مفيدة: vk.com/dev/messages.send

الحل
أولاً ، قم بتحميل الصورة إلى المجموعة وافتحها في VK ، ضع في اعتبارك الرابط:
vkcom / club172998024؟ z = photo-172998024_456239017 ٪ 2Falbum-172998024_256250731

نحن مهتمون فقط بالجزء المميز: photo-172998024_456239017 . قم بتمريرها كوسيطة إلى أسلوب Messages.send:

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

أضفه إلى طريقة البدء واحصل على:



هذا كل الأساسيات. الشيء الرئيسي هو تعلم كيفية استخدام vk_api باستخدام طرق مختلفة ، والقائمة الكاملة هي: vk.com/dev/methods . إذا تعلمت كيفية العمل مع وثائق VK API ، يمكنك إنشاء روبوتات ذات تعقيد وأغراض مختلفة. مثال على برنامج الروبوت الخاص بي لمجموعة دراسة: github.com/AppLoidx/GroupAssistant/tree/master

لنبدأ الآن في إنشاء منطق الروبوت.


قم بإنشاء commander.py ، الذي سيقبل الأوامر ويعيد الاستجابة التي تم تمريرها إلى مستخدم Vk:

 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 

دعونا نبني بنية برنامجنا:


نستمع إلى خادم الاستطلاع الطويل ونستقبل رسالة مستخدم ->
نقوم بتمرير الرسالة إلى Commander.input () -> تحديد الوضع -> تحديد الأمر ->
نعود الجواب -> ننتقل إلى المستخدم

لتحديد الوضع والأمر ، نقوم بإنشاء ملفين command_enum.py و mode_enum.py. باستخدامها ، سنحدد الأوضاع والأوامر من خلال طرق فئة Enum:

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 


لتغيير الأوضاع ، استخدم [slash ("/") + <mode_name>] ، وسوف نقبل جميع الأوامر الأخرى كأوامر.

نقوم بتنفيذ هذا في 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) 


كل الكود متاح على github: github.com/AppLoidx/VkLongPollBot

أضف لوحة مفاتيح:


هذه عملية سهلة للغاية ، يمكن أن تحدث مضاعفات عندما نغير لوحة المفاتيح إلى توقيع محدد للأوامر ، والذي يختلف لكل وضع.

لإضافة لوحة مفاتيح إلى مربع الحوار ، يجب تحديد معلمة لوحة المفاتيح التي تقبل json في أسلوب Messages.send. يبدو هذا:

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

أو يمكنك تمرير لوحة المفاتيح مباشرة من ملف json:

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

التوثيق: vk.com/dev/bots_docs_3؟f=4.٪2BKeyboard٪2Bfor٪2Bworks

ضع في اعتبارك مثال برنامجنا بإضافة لوحة مفاتيح.

أولاً ، قم بإنشاء ملف keyboard.json:

 { "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" }] ] } 

لإزالة لوحة المفاتيح ، تحتاج إلى تمرير json بأزرار فارغة:

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

تجاوز send_message في 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()) 

وأيضًا في طريقة البدء:

 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)) 

نتيجة لذلك ، نحصل على:



الكلمة الأخيرة


يجب ألا تستخدم القائمة المكشوفة لرموز المصدر المعروضة هنا ، فهي تُستخدم فقط حتى تفهم بشكل أفضل ما يحدث تحت غطاء المحرك. بالطبع ، كلها قابلة للاستخدام ويمكنك استخدامها في أجزاء.

أنا شخصياً استخدمت مثل هذا الروبوت لمساعد مجموعة يعرف كيف:

  • إنشاء قوائم انتظار من أعضاء المجموعة ، بما في ذلك العديد من الفرق التي قامت بتحرير قائمة الانتظار ، مثل إضافة أو حذف أو إنشاء ، إلخ.
  • إرسال رسائل لجميع المشاركين
  • الأسئلة المطروحة (على سبيل المثال ، في Java)
  • جعل من الممكن إنشاء تطبيقات لتبادل الأماكن ، إلخ.

مشروع جيثب
المصادر المقدمة هنا

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


All Articles