Телеграм-канал Программист и бизнес. Каждый день интересные истории, как программист строит бизнес.
Эта статья не для программистов (они и так уже всё это знают), а для тех, кто только подходит к теме программирования. Или для тех, кто просто услышал термин VK API, и хочет понимать, что это такое.
Что такое API
Сайт Вконтакте с технической стороны представляет собой сложный комплекс программ, которые работают на серверах компании. Результат их работы отображается в браузерах или мобильных приложениях в виде знакомого всем сайта социальной сети. Пользователи могут взаимодействовать с сайтом – публиковать статьи, проводить поиск, подписываться на сообщества и так далее. Все эти действия обеспечиваются программами, которые работают совершенно незаметно для посетителей.
Этих программ (скриптов, как их называют программисты) может быть очень много, и они должны взаимодействовать друг с другом. Для этого разработаны специальные правила и протоколы, следуя которым, отдельные скрипты имеют возможность отвечать на вызовы и получать ответы от других. В результате всё огромное множество разнообразных скриптов работает как единое целое.
Совокупность этих правил и составляет единый интерфейс взаимодействия со сложной программной системой. Его называют API – Application Programming Interface.
Взаимодействие программных модулей именно через библиотеку API, это общепринятая норма. Без такого механизма строить сложные программные системы было бы просто невозможно. Ведь тогда каждый скрипт имел бы собственные правила приёма внешних команд и выдачи результатов. Взаимодействие каждых двух отдельных скриптов нужно было бы программировать отдельно, и весь проект был бы похоронен под огромной кучей разнородных правил. Разобраться в них не было бы никакой возможности. Если же в проект изначально закладывается принцип работы модулей только через API, тогда всё многообразие взаимодействий сводится к нескольким несложным правилам, которые универсальны и подходят для любого скрипта. Это очень удобно.
Чем же этот механизм может быть полезен простому программисту, который не состоит в штате компании Вконтакте и хочет просто написать какой-нибудь полезный скрипт для себя?
Базовый алгоритм
Основная идея алгоритма в том, что основной тред будет занят слушанием longpoll, который при появлении нового сообщения будет создавать новый тред. В этот новый тред, как аргументы, будут отсылаться айди пользователя и текст сообщения и далее обрабатываться.
В новосозданном треде бот сразу берет текущее положение пользователя в БД, в зависимости от этого положения происходит обработка сообщения и после обновляется положение пользователя в БД на новое и выполняются нужные инструкции.
Для начала представим этот алгоритм для основной функции, которая и будет создавать эти треды (код составлен для сообщений в личку сообщества):
import vk_api from vk_api import VkUpload from vk_api.utils import get_random_id from vk_api.bot_longpoll import VkBotLongPoll, VkBotEventType import threading import requests import random import pymysql import pymysql.cursors if __name__ == ‘__main__’: while True: session = requests.Session() vk_session = vk_api.VkApi(token=»%Токен сообщества VK%») vk = vk_session.get_api() upload = VkUpload(vk_session) longpoll = VkBotLongPoll(vk_session, «%ID сообщества VK%») try: for event in longpoll.listen(): if event.type == VkBotEventType.MESSAGE_NEW and event.from_user: threading.Thread(target=processing_message, args=(event.obj.from_id, event.obj.text)).start() except Exception: pass
Весь код в основной функции завернут в while True из-за того, что раньше каждую ночь (возможно также и сейчас) примерно в 4:30 по МСК переставал отвечать VK на запросы и бот падал (видимо перезагружали серверы).
Основная идея тренировочного бота — это сделать три меню, где Основное меню 1 будет иметь возможность попасть в Меню 2 и в Меню 3, а они, в свою очередь, могли вернуться обратно в Основное меню 1:
В формате меню в самом ВКонтакте хочется представить это так:
У нас 3 клавиатуры, которые нужно создать в виде файлов с расширением .json в папке с main.py. Первый файл keyboard_main.json будет с кодом:
keyboard_main.json { «one_time»: false, «buttons»: [ [{ «action»: { «type»: «text», «label»: «Цитаты Дурова» }, «color»: «default» }], [ { «action»: { «type»: «text», «label»: «Цитаты Цукерберга» }, «color»: «default» }] ] }
Второй файл keyboard_durov.json с кодом:
keyboard_durov.json
{ «one_time»: false, «buttons»: [ [{ «action»: { «type»: «text», «label»: «Хочу ещё Дурова» }, «color»: «positive» }], [ { «action»: { «type»: «text», «label»: «Выйти в главное меню» }, «color»: «default» }] ] }
Третий, практически идентичный — keyboard_zuckerberg.json:
keyboard_zuckerberg.json
{ «one_time»: false, «buttons»: [ [{ «action»: { «type»: «text», «label»: «Хочу ещё Цукерберга» }, «color»: «positive» }], [ { «action»: { «type»: «text», «label»: «Выйти в главное меню» }, «color»: «default» }] ] }
Значение target у нас processing_message, поэтому создаём функцию в main.py с именем processing_message и c аргументами в виде id юзера и его сообщения:
processing_message
def processing_message(id_user, message_text): number_position = take_position(id_user) if number_position == 0: send_message(id_user, «keyboard_main.json», «Тебя приветствует бот!») add_new_line(id_user) elif number_position == 1: if message_text == «Цитаты Дурова»: update_position(id_user, «2») send_message(id_user, «keyboard_durov.json», durov_quote()) elif message_text == «Цитаты Цукерберга»: update_position(id_user, «3») send_message(id_user, «keyboard_zuckerberg.json», zuckerberg_quote()) else: send_message(id_user, «keyboard_main.json», «Непонятная команда») elif number_position == 2: if message_text == «Хочу ещё Дурова»: send_message(id_user, «keyboard_durov.json», durov_quote()) elif message_text == «Выйти в главное меню»: update_position(id_user, «1») send_message(id_user, «keyboard_main.json», «Мы в главном меню») else: send_message(id_user, «keyboard_durov.json», «Непонятная команда») elif number_position == 3: if message_text == «Хочу ещё Цукерберга»: send_message(id_user, «keyboard_zuckerberg.json», zuckerberg_quote()) elif message_text == «Выйти в главное меню»: update_position(id_user, «1») send_message(id_user, «keyboard_main.json», «Мы в главном меню») else: send_message(id_user, «keyboard_zuckerberg.json», «Непонятная команда») else: send_message(id_user, «keyboard_main.json», «Произошла какая-то ошибка»)
Весь алгоритм этой функции построен на том, что мы сразу берем текущую позицию пользователя и в зависимости от неё обрабатываем сообщение пользователя. Если number_position — это 0 (функция take_position(id_user) вернула 0, то есть в базе данных она не нашла пользователя), то она сразу добавляет его в БД с позицией 1 через функцию add_new_line(id_user), перемещая таким образом его в главное меню и открывая ему клавиатуру keyboard_main.json.
Если number_position от 1 до 3 — то бот выполняет код в соответствующем для него ветвлении if-elif-else.
Функция take_position(id_user) выглядит таким образом:
def take_position(id_user): connection = get_connection() try: with connection.cursor() as cursor: sql = «SELECT position FROM user WHERE iduser = %s» cursor.execute(sql, (id_user)) line = cursor.fetchone() if line is None: return_count = 0 else: return_count = line[«position»] finally: connection.close() return return_count
В свою очередь add_new_line(id_user) выглядит так:
def add_new_line(id_user): connection = get_connection() try: with connection.cursor() as cursor: sql = «INSERT INTO user (iduser, position) VALUES (%s, %s)» cursor.execute(sql, (id_user, «1»)) connection.commit() finally: connection.close() return
Когда пользователь имеет свою строку в БД — он уже может приступать к взаимодействию с ботом. И обновление его положения — это функция update_position(id_user, new_position), которая выглядит таким образом:
def update_position(id_user, new_position): connection = get_connection() try: with connection.cursor() as cursor: sql = «UPDATE user SET position = %s WHERE iduser = %s» cursor.execute(sql, (new_position, id_user)) connection.commit() finally: connection.close() return
Эти три функции не будут работать без основной функции для БД (которую необходимо добавить практически в самое начало, сразу после импорта библиотек) и вписать ваши данные от БД. Я изменяю только название БД и кодировку на те, которые мы создали в самом начале:
def get_connection(): connection = pymysql.connect(host=»%name_host%», user=»%name_user%», password=»%password_user%», db=»vktest», charset=»utf8mb4″, cursorclass=pymysql.cursors.DictCursor) return connection
И одна из самых важных функций — отправка сообщения пользователю. Я вывел её в отдельную функцию send_message, так как это нерационально возить такую махину постоянно по всему коду:
def send_message(id_user, id_keyboard, message_text): try: vk.messages.send( user_id=id_user, random_id=get_random_id(), keyboard=open(id_keyboard, ‘r’, encoding=’UTF-8′).read(), message=message_text) except: print(«Ошибка отправки сообщения у id» + id_user)
Ничего не забыли? Ах да, нам же нужны крутые цитаты Дурова и Цукерберга:
durov_quote() и zuckerberg_quote()
def durov_quote(): durov = [‘Лучшее решение из возможных — самое простое. И наоборот.’, ‘Что такое университет? Это же раздробленная структура с удельными княжествами.’, ‘Коммуникация переоценена. Час одиночества продуктивнее недели разговоров.’, ‘Проблемы — это спрятанные решения.’, ‘Врать вредно для духовной целостности.’] return random.choice(durov) def zuckerberg_quote(): zuckerberg = [‘В мире, который меняется очень быстро, единственная стратегия, которая гарантированно ‘ ‘провальна — не рисковать.’, ‘Двигайтесь быстро и разрушайте препятствия. Если вы ничего не разрушаете, ‘ ‘Вы движетесь недостаточно быстро.’, ‘Вопрос не в том, что мы хотим знать о человеке. Вопрос стоит так:’ ‘«Что люди хотят рассказать о себе?»’, ‘Люди могут быть очень умными или иметь отличные профессиональные навыки, ‘ ‘но если они действительно не верят в свое дело, они не будут по-настоящему работать.’, ‘Вопрос, который я задаю себе почти каждый день: сделал ли я самую важную вещь, которую ‘ ‘я мог бы сделать? Если я не чувствую, что я работаю над самой важной проблемой, где я ‘ ‘могу помочь, я не буду чувствовать, что хорошо провожу свое время’] return random.choice(zuckerberg)
Для них мы использовали встроенные списки Python и подключили библиотеку random.
И весь код main.py (целиком) под спойлером для удобства:
main.py
import vk_api from vk_api import VkUpload from vk_api.utils import get_random_id from vk_api.bot_longpoll import VkBotLongPoll, VkBotEventType import threading import random import pymysql import pymysql.cursors import requests def get_connection(): connection = pymysql.connect(host=»%name_host%», user=»%name_user%», password=»%password_user%», db=»vktest», charset=»utf8mb4″, cursorclass=pymysql.cursors.DictCursor) return connection def send_message(id_user, id_keyboard, message_text): try: vk.messages.send( user_id=id_user, random_id=get_random_id(), keyboard=open(id_keyboard, ‘r’, encoding=’UTF-8′).read(), message=message_text) except: print(«Ошибка отправки сообщения у id» + id_user) def add_new_line(id_user): connection = get_connection() try: with connection.cursor() as cursor: sql = «INSERT INTO user (iduser, position) VALUES (%s, %s)» cursor.execute(sql, (id_user, «1»)) connection.commit() finally: connection.close() return def take_position(id_user): connection = get_connection() try: with connection.cursor() as cursor: sql = «SELECT position FROM user WHERE iduser = %s» cursor.execute(sql, (id_user)) line = cursor.fetchone() if line is None: return_count = 0 else: return_count = line[«position»] finally: connection.close() return return_count def update_position(id_user, new_position): connection = get_connection() try: with connection.cursor() as cursor: sql = «UPDATE user SET position = %s WHERE iduser = %s» cursor.execute(sql, (new_position, id_user)) connection.commit() finally: connection.close() return def durov_quote(): durov = [‘Лучшее решение из возможных — самое простое. И наоборот.’, ‘Что такое университет? Это же раздробленная структура с удельными княжествами.’, ‘Коммуникация переоценена. Час одиночества продуктивнее недели разговоров.’, ‘Проблемы — это спрятанные решения.’, ‘Врать вредно для духовной целостности.’] return random.choice(durov) def zuckerberg_quote(): zuckerberg = [‘В мире, который меняется очень быстро, единственная стратегия, которая гарантированно ‘ ‘провальна — не рисковать.’, ‘Двигайтесь быстро и разрушайте препятствия. Если вы ничего не разрушаете, ‘ ‘Вы движетесь недостаточно быстро.’, ‘Вопрос не в том, что мы хотим знать о человеке. Вопрос стоит так:’ ‘«Что люди хотят рассказать о себе?»’, ‘Люди могут быть очень умными или иметь отличные профессиональные навыки, ‘ ‘но если они действительно не верят в свое дело, они не будут по-настоящему работать.’, ‘Вопрос, который я задаю себе почти каждый день: сделал ли я самую важную вещь, которую ‘ ‘я мог бы сделать? Если я не чувствую, что я работаю над самой важной проблемой, где я ‘ ‘могу помочь, я не буду чувствовать, что хорошо провожу свое время’] return random.choice(zuckerberg) def processing_message(id_user, message_text): number_position = take_position(id_user) if number_position == 0: send_message(id_user, «keyboard_main.json», «Тебя приветствует бот!») add_new_line(id_user) elif number_position == 1: if message_text == «Цитаты Дурова»: update_position(id_user, «2») send_message(id_user, «keyboard_durov.json», durov_quote()) elif message_text == «Цитаты Цукерберга»: update_position(id_user, «3») send_message(id_user, «keyboard_zuckerberg.json», zuckerberg_quote()) else: send_message(id_user, «keyboard_main.json», «Непонятная команда») elif number_position == 2: if message_text == «Хочу ещё Дурова»: send_message(id_user, «keyboard_durov.json», durov_quote()) elif message_text == «Выйти в главное меню»: update_position(id_user, «1») send_message(id_user, «keyboard_main.json», «Мы в главном меню») else: send_message(id_user, «keyboard_durov.json», «Непонятная команда») elif number_position == 3: if message_text == «Хочу ещё Цукерберга»: send_message(id_user, «keyboard_zuckerberg.json», zuckerberg_quote()) elif message_text == «Выйти в главное меню»: update_position(id_user, «1») send_message(id_user, «keyboard_main.json», «Мы в главном меню») else: send_message(id_user, «keyboard_zuckerberg.json», «Непонятная команда») else: send_message(id_user, «keyboard_main.json», «Произошла какая-то ошибка») if __name__ == ‘__main__’: while True: session = requests.Session() vk_session = vk_api.VkApi(token=»%Токен сообщества VK%») vk = vk_session.get_api() upload = VkUpload(vk_session) longpoll = VkBotLongPoll(vk_session, «%ID сообщества VK%») try: for event in longpoll.listen(): if event.type == VkBotEventType.MESSAGE_NEW and event.from_user: threading.Thread(target=processing_message, args=(event.obj.from_id, event.obj.text)).start() except Exception: pass
Где найти документацию по VK Api
Соцсеть в своей документации предлагает подробное описание своего API для сторонних разработчиков. Главная страница этой документации располагается вот здесь: https://vk.com/dev/manuals. Тут описаны программные объекты и их методы, перечень возвращаемых кодов и ошибок, права доступа, шаблоны запросов и так далее.
Программист, желающий разработать какое-либо приложение (подключаемое внутри ВК либо работающее на отдельном сайте) может в коде своей программы предусмотреть вызовы методов ВК, используя эти самые правила взаимодействия API. Это даст возможность обращаться к функционалу социальной сети Вконтакте. Например, можно получать список подписчиков того или иного сообщества, делать массовые рассылки сообщений, публиковать новые записи, и так далее. Возможности здесь очень широкие. Именно на возможностях API ВК основаны многие полезные приложения. Например, музыкальные плееры для проигрывания музыки из страниц друзей, разнообразные игры, счётчики «Мои гости», альтернативные мобильные клиенты, такие как VK Settings, VK Coffee, Kate Mobile и так далее.
Полноценной работе с API Вконтакте посвящены многочисленные статьи в Интернете, эта информация очень обширна. Мы здесь покажем решение только двух конкретных задач.