python

Основы работы с LongPoll сервером ВКонтакте

  • вторник, 8 августа 2017 г. в 03:12:22
https://habrahabr.ru/post/335106/
  • Вконтакте API
  • Python
  • API


Доброго времени суток!
Недавно я решил познакомиться с API крупнейшей социальной сети Европы — ВКонтакте. В разделе «Для разработчиков» содержится довольно подробная документация, а в интернете существует немалое количество статей, помогающих освоиться с VK API, поэтому я решил, что серьезных проблем в изучении быть не должно. Однако, когда я добрался до LongPoll сервера, обнаружилось, что статей по работе с ним практически нет, а официальная документация не настолько полна, чтобы полностью понять изучаемый материал. Пришлось методом проб и ошибок пытаться понять принцип работы LongPoll-а, что через некоторое время мне сделать все-таки удалось. Я решил поделиться изученным материалом с другими людьми, чтобы сократить их время изучения нового. Ниже вы можете ознакомиться с разделами, про которые мне удалось написать.

Основы работы с API (те, кто знаком с базовыми методами и запросами к ним, могут пропустить этот пункт)
Что же такое API ВКонтакте? Вот как трактует это официальная документация:
Callback API — это инструмент для отслеживания активности пользователей в Вашем сообществе ВКонтакте.

Другими словами, это способ отправлять запросы на серверы ВКонтакте, чтобы получить ответ, содержащий некоторую информацию. С помощью VK API можно сделать практически все: отправить сообщение пользователю, изменить пароль учетной записи, создать альбом с фотографиями и так далее. Лично я для работы с API использую библиотеку vk_api, но можно обойтись и библиотекой requests. Сейчас мы рассмотрим базовые принципы работы. Если вы решили использовать библиотеку vk_api, то пример запроса будет выглядеть так:

import vk_api
session = vk_api.VkApi(token='{ACCESS_TOKEN}')
print(session.method('users.get', {'user_ids': 210700286, 'fields': 'photo_50, city, verified'}))

Разберем по строчкам:

import vk_api
Здесь импортируется нужная нам библиотека.

session = vk_api.VkApi(token='{ACCESS_TOKEN}')
Этой строкой мы авторизируемся через access token (способы его получения доступно описаны в документации и интернете, я не буду заострять на них внимание). Заметьте: не для всех методов нужна авторизация. О том, когда она требуется, будет написано в документации метода на сайте ВКонтакте.

print(session.method('users.get', {'user_ids': 210700286, 'fields': 'photo_50, city'}))
Эта часть кода заслуживает отдельного внимания. Что же здесь происходит? Мы вызываем метод method, передавая ему два аргумента: первый — название метода API, второй — словарь из параметров этого метода. Список всех методов и их параметров находится в документации API. В данном случае мы вызываем метод «users.get» и передаем следующие параметры: «user_ids» — список id пользователей, для которых мы хотим получить данные, «fields»: дополнительную информацию о пользователях: аватарку и город проживания. Результатом выполнения программы будет следующая выведенная строка: [{'id': 210700286, 'first_name': 'Lindsey', 'last_name': 'Stirling', 'city': {'id': 5331, 'title': 'Los Angeles'}, 'photo_50': 'https://pp.userapi.com/c636821/v636821286/38a75/Ay-bEZoJZw8.jpg'}]

Если допустить какую-либо ошибку, например, указать неправильное название вызываемого метода, будет возбуждено исключение: vk_api.exceptions.ApiError: [5] User authorization failed: no access_token passed..
Примечание: если ошибка допущена в названии необязательного параметра, исключение сгенерировано не будет, а неизвестный параметр будет проигнорирован.

Если вы решили использовать библиотеку requests, придется немного углубиться в документацию API, чтобы узнать формат запроса и ответа.
Запрос должен выглядеть так: https://api.vk.com/method/{METHOD_NAME}?{PARAMS}&v={API_VERSION}, где {METHOD_NAME} — название метода, {PARAMS} — параметры вызываемого метода, а {API_VERSION} — версия API, которую должен использовать сервер при формировании ответа.
В качестве ответа нам будет возвращен JSON объект с информацией о результатах вызова метода. Давайте реализуем запрос к API с помощью библиотеки requests:

import requests
data = requests.get('https://api.vk.com/method/{METHOD_NAME}'.format(METHOD_NAME='users.get'),
                    params={'user_ids': 210700286, 'fields': 'photo_50, city'}).json()
print(data)

Итак, разберем написанное.

import requests
Импортирование библиотеки requests

data = requests.get('https://api.vk.com/method/{METHOD_NAME}'.format(METHOD_NAME='users.get'),
                    params={'user_ids': 210700286, 'fields': 'photo_50, city'}).json()
Эта строка записывает в переменную data ответ сервера. Мы передаем функции get два аргумента: адрес, к которому нужно сделать запрос (в нашем случае — форматированную строку), и словарь из параметров этого метода. Если метод не вызывается без access token-а, нужно добавить его в словарь с ключем 'access_token'. Параметр «v» (версия API) не является обязательным, так как по умолчанию будет использоваться последняя версия, но, если нужно использовать иную версию, ее тоже нужно добавить в словарь с ключом 'v'. К пришедшему от сервера ответу применяется метод json(), который обрабатывает JSON-объекты.

Последняя строка выводит результат работы, в нашем случае — {'response': [{'uid': 210700286, 'first_name': 'Lindsey', 'last_name': 'Stirling', 'city': 5331, 'photo_50': 'https://pp.userapi.com/c636821/v636821286/38a75/Ay-bEZoJZw8.jpg'}]}.

Если передать неправильное название метода, будет выведено следующее: {'error': {'error_code': 5, 'error_msg': 'User authorization failed: no access_token passed.', 'request_params': [{'key': 'oauth', 'value': '1'}, {'key': 'method', 'value': 'user.get'}, {'key': 'user_ids', 'value': '210700286'}, {'key': 'fields', 'value': 'photo_50, city'}]}}.

Что такое LongPoll?
Для начала обратимся за помощью к документации:
Long Polling — это технология, которая позволяет получать информацию о новых событиях с помощью «длинных запросов». Сервер получает запрос, но отправляет ответ на него не сразу, а лишь тогда, когда произойдет какое-либо событие (например, поступит новое входящее сообщение), либо истечет заданное время ожидания.
Другими словами, получая от вас запрос, сервер ждет, когда произойдет событие, о котором он должен вас уведомить, и, когда оно происходит, Long Poll сервер отправляет ответ на ваш запрос, содержащий информацию о случившемся событии.
Мы напишем программу, которая будет уведомлять пользователя о некоторых изменениях в его аккаунте, которые мы будем получать от Long Poll сервера. Чтобы начать получать ответы от сервера, необходимо получить три обязательных параметра, необходимых для работы Long Poll-a: server, key и ts.
key — секретный ключ сессии;
server — адрес сервера;
ts — номер последнего события, начиная с которого нужно получать данные.

Их не нужно получать каждый раз, вызывая Long Poll, — будет достаточно одного вызова. Из документации следует, что эти значения можно получить, вызвав метод messages.getLongPollServer. Напишем программу, которая сделает запрос с этим методом и будем использовать полученные данные для получения к Long Poll.

import requests
token = ''  # здесь вы должны написать свой access_token
data = requests.get('https://api.vk.com/method/messages.getLongPollServer',
                    params={'access_token': token}).json()['response']  # получение ответа от сервера
print(data)

Если вы все сделали правильно, программа выведет словарь с тремя ключами: {'key': '###############################', 'server': 'imv4.vk.com/im####', 'ts': 0000000000}

Запрос к Long Poll серверу
Теперь, используя значения, хранящиеся в словаре, который был создан в предыдущей части, мы можем сделать запрос к Long Poll серверу! Чтобы это сделать, нужно сделать запрос к следующему адресу: https://{$server}?act=a_check&key={$key}&ts={$ts}&wait=25&mode=2&version=2, где {$server}, {$key} и {$ts}- значения из словаря с ключами 'server', 'key' и 'ts' соответственно, wait — время, которое Long Poll сервер будет ожидать обновлений, а mode — дополнительные опции ответа. Напишем программу, которая сделает один запрос на Long Poll сервер

import requests
token = ''  # здесь вы должны написать свой access_token
params = requests.get('https://api.vk.com/method/messages.getLongPollServer',
                       params={'access_token': token}).json()['response']  # получение ответа от сервера
response = requests.get('https://{server}?act=a_check&key={key}&ts={ts}&wait=90&mode=2&version=2'.format(server=data['server'], key=data['key'], ts=data['ts'])).json()  # отправление запроса на Long Poll сервер со временем ожидания 90 секунд и опциями ответа 2
print(response)

Если за 90 секунд произошло какое-либо событие, которое попадает в список обрабатываемых Long Poll сервером, на экран выведется нечто похожее: {'ts': 0000000000, 'updates': [[9, -999999999, 0, 1501588841]]}. Что же значит пришедший ответ и как с ним дальше можно работать?

Во-первых, вы могли заметить, что в ответе содержится параметр 'ts', который мы использовали при отправлении запроса. Он здесь неспроста. Все события, попадающие в список обрабатываемых Long Poll сервера, нумеруются. Когда вы создаете новый аккаунт, первое такое событие имеет номер 1, второе — 2 и так далее. При отправлении запроса на Long Poll, нужно передавать этот параметр для того, чтобы сервер знал, начиная с какого номера нужно присылать обновления. В каждом ответе сервера также приходит этот параметр, чтобы вы использовали его при следующем вызове Long Poll сервера.

Во-вторых, в словаре содержится ключ 'updates' с говорящим названием. Не трудно догадаться, что значение по этому ключу хранит обновления, случившиеся после отправления запроса. Формат обновлений — массив массивов. Каждый массив в массиве — произошедшее обновление, которое нужно обработать. Если в первом массиве массивов больше одного, это значит, что несколько событий произошли одновременно. Параметр 'ts' содержит номер последнего из них. Если массив, доступный по ключу 'updates', пуст, то за время wait не произошло ни одного события. Вы спросите: «А что это за непонятные цифры в массивах?». Ответ довольно прост — это информация о случившемся событии, которую можно преобразовать в более понятный вид. Их обработкой мы займемся позже, а в следующей части напишем программу, которая будет постоянно обращаться к Long Poll серверу.

Циклические запросы к Long Poll и коды событий
Для того, чтобы постоянно отправлять запросы к Long Poll, я решил использовать цикл, чтобы не переполнять стек рекурсией. Ниже приведена реализация программы, которая обращается к Long Poll и выводит обновления на экран

import requests
token = ''  # здесь вы должны написать свой access_token
data = requests.get('https://api.vk.com/method/messages.getLongPollServer',
                    params={'access_token': token}).json()['response']  # получение ответа от сервера
while True:
    response = requests.get('https://{server}?act=a_check&key={key}&ts={ts}&wait=20&mode=2&version=2'.format(server=data['server'], key=data['key'], ts=data['ts'])).json()  # отправление запроса на Long Poll сервер со временем ожидания 20 и опциями ответа 2
    updates = response['updates']
    if updates:  # проверка, были ли обновления
        for element in updates:  # проход по всем обновлениям в ответе
            print(element)
    data['ts'] = response['ts']  # обновление номера последнего обновления

Данная программа циклически посылает запросы к Long Poll-у, проверяет, были ли обновления, и выводит пришедшие обновления на экран. Но выходные данные этой программы в том виде, в котором они есть сейчас, никуда не годятся. Сейчас вывод программы выглядит подобным образом:
[8, -999999999, 1, 1501592696]
[8, -999999999, 7, 1501592862]
[9, -999999999, 0, 1501592882]
[9, -999999999, 1, 1501592583]
[8, -999999999, 4, 1501592893]
[9, -999999999, 0, 1501592900]

Первая цифра в каждом массиве означает код события. Используя ее, можно понять, какое событие произошло. Вот список кодов событий с кратким описанием (из официальной документации):
1 — Замена флагов сообщения (FLAGS:=$flags);
2 — Установка флагов сообщения (FLAGS|=$mask);
3 — Сброс флагов сообщения (FLAGS&=~$mask);
4 — Добавление нового сообщения;
6 — Прочтение всех входящих сообщений в $peer_id, пришедших до сообщения с $local_id.
7 — Прочтение всех исходящих сообщений в $peer_id, пришедших до сообщения с $local_id.
8 — Друг $user_id стал онлайн. $extra не равен 0, если в mode был передан флаг 64. В младшем байте (остаток от деления на 256) числа extra лежит идентификатор платформы $timestamp — время последнего действия пользователя $user_id на сайте;
9 — Друг $user_id стал оффлайн ($flags равен 0, если пользователь покинул сайт (например, нажал выход) и 1, если оффлайн по таймауту (например, статус away)). $timestamp — время последнего действия пользователя $user_id на сайте;
10 — Сброс флагов диалога $peer_id. Соответствует операции (PEER_FLAGS &= ~$flags). Только для диалогов сообществ;
11 — Замена флагов диалога $peer_id. Соответствует операции (PEER_FLAGS:= $flags). Только для диалогов сообществ;
12 — Установка флагов диалога $peer_id. Соответствует операции (PEER_FLAGS|= $flags). Только для диалогов сообществ;
13 — Удаление всех сообщений в диалоге $peer_id с идентификаторами вплоть до $local_id;
14 — Восстановление недавно (менее 20 дней назад) удаленных сообщений в диалоге $peer_id с идентификаторами вплоть до $local_id;
51 — Один из параметров (состав, тема) беседы $chat_id были изменены. $self — 1 или 0 (вызваны ли изменения самим пользователем);
61 — Пользователь $user_id набирает текст в диалоге. Событие приходит раз в ~5 секунд при постоянном наборе текста. $flags = 1;
62 — Пользователь $user_id набирает текст в беседе $chat_id;
70 — Пользователь $user_id совершил звонок с идентификатором $call_id;
80 — Счетчик непрочитанных в левом меню стал равен $count;
112 — Изменились настройки оповещений. $peer_id — идентификатор чата/собеседника.

Таким образом, если первое число в массиве — 8, то кто-то из ваших друзей стал онлайн, если 9 — оффлайн и так далее. Остальные числа в массиве тоже имеют значение, но к ним мы подберемся позже, так как их значение зависит от кода события.

Обработка приходящих событий
В этой части мы реализуем обработку событий, которые приходят нам в качестве ответа. Напомню, что эти события представлены в виде массивов с информацией, которую нужно обработать. Начать предлагаю с кода 80 — обновление счетчика непрочитанных сообщений, так как по моему мнению это событие является наименее сложным. Вот пример событий с кодом 80:
[80, 0, 0]
[80, 1, 0]

Первый параметр, как вы уже знаете, — код события. Остаются 2 элемента: 0 и 0 в первом случае и 1 0 во втором. Последний параметр при коде 80 должен быть всегда равен нулю, поэтому его можно игнорировать; для нас важен лишь второй. Во втором параметре указано, сколько на данный момент у пользователя непрочитанных сообщений. Минимальное значение — 0 (нет новых сообщений), максимальное — не ограничено. Этого достаточно, чтобы обрабатывать все события с кодом 80. Реализуем это в код:

# часть кода в этом примере опущена для сокращения места, без нее код работать не будет
while True:
    response = requests.get('https://{server}?act=a_check&key={key}&ts={ts}&wait=20&mode=2&version=2'.format(server=data['server'], key=data['key'], ts=data['ts'])).json()  # отправление запроса на Long Poll сервер со временем ожидания 20 и опциями ответа 2
    updates = response['updates']
    if updates:  # проверка, были ли обновления
        for element in updates:  # проход по всем обновлениям в ответе
            action_code = element[0]  # запись в переменную кода события
            if action_code == 80:  # проверка кода события
                print('количество непрочитанных сообщений стало равно', element[1])  # вывод
    data['ts'] = response['ts']  # обновление номера последнего обновления

Если запустить эту программу, она будет выводить на экран сообщения об изменении количества непрочитанных сообщений. Следующие относительно несложные обновления имеют коды 8 и 9 — друг стал онлайн и оффлайн соответственно. Дополним нашу программу, чтобы она смогла обрабатывать и их. Начнем с кода 9. Напишем строку, которая будет проверять код события:

elif action_code == 9:

Далее рассмотрим формат приходящих обновлений, имеющих код 9:
[9, -000000000, 1, 1501744865]
С индексом 0, как вы уже знаете, хранится код обновления — 9. Далее идет отрицательное id пользователя, ставшего оффлайн (чтобы получить действительное id, нужно умножить на -1, дабы избавиться от минуса). Элемент с индексом 3 может принимать лишь 2 значения: 0 или 1. 1 означает, что отметка «оффлайн» поставлена по истечении тайм-аута неактивности, а 0 — что пользователь покинул сайт явно, например, нажав кнопку «выход». Последнее значение в ответе — время последнего действия пользователя на сайте в Unix time. Запишем все полученные сведения в переменные:

user_id = element[1] * -1  # id пользователя, ставшего оффлайн
user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'fields': 'sex'}).json()['response'][0]  # имя, фамилия и пол пользователя с id = user_id 
timeout = bool(element[2])  # был ли поставлен статус оффлайн по истечении тайм-аута
last_visit = element[3]  # время последнего действия пользователя на сайте в Unix time

Как вы могли заметить, помимо имени и фамилии пользователя, мы получаем еще и его пол. Это нужно, чтобы программа правильно использовала рода в предложениях и не писала что-то вроде «Иван Иванов вышела из ВКонтакте». Переменная timeout хранит False, если пользователь явно покинул сайт, и True, если истекло время тайм-аута. Теперь можно вывести на экран полученные данные:

# не забудьте написать вначале "import time", здесь используется эта библиотека
if user['sex'] == 1:
    verb = ['стала', 'вышла']
else:
    verb = ['стал', 'вышел']
if timeout:
    print(user['first_name'], user['last_name'], verb[0], 'оффлайн по истечении тайм-аута. Время последнего действия на сайте:', time.ctime(last_visit).split()[3])
else:
    print(user['first_name'], user['last_name'], verb[1], 'из ВКонтакте. Время последнего действия на сайте:', time.ctime(last_visit).split()[3])

Единственное, что я вижу неочевидным в этом коде — вывод времени:

time.ctime(last_visit).split()[3]
Давайте разберемся с этой частью кода. Функция ctime библиотеки time принимает в качестве аргумента число — время в Unix time и возвращает строку вида 'Thu Jan 1 03:00:00 1970'
. Так как нам нужно лишь время последнего действия на сайте, мы разбиваем эту строку методом split по пробелам и получаем массив, где в индексе 0 хранится день недели, в индексе 1 — месяц, 2 — число, 3 — время, а 4 — год. Мы извлекаем элемент с индексом 3, то есть время.

Теперь напишем реализацию обработки обновлений с кодом 8. Их формат выглядит так: [8, -6892937, 4, 1501750273]. Второй элемент в массиве — отрицательное id пользователя, ставшего онлайн, третий — платформа, с которой зашел пользователь, а четвертый — время последнего действия пользователя на сайте в Unix time. Реализуем полученные данные в код:

user_id = element[1] * -1  # id пользователя, ставшего онлайн
user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'fields': 'sex'}).json()['response'][0]  # имя, фамилия и пол пользователя с id = user_id
platform = element[2]  # код платформы пользователя
last_visit = element[3]  # время последнего визита в Unix time
if user['sex'] == 1:
    verb = 'стала'
else:
    verb = 'стал'

Здесь мы записали данные из ответа в переменные, а также поставили глагол в нужный род. Параметр platform сейчас содержит число в диапазоне от 1 до 7 включительно. Каждое число обозначает платформу, с которой пользователь совершил действие. Переведем это число в текст:

# определение платформы по ее коду
if platform == 1:
    platform = 'официальную мобильную версию web-сайта VK'
elif platform == 2:
    platform = 'официальное приложение VK для iPhone'
elif platform == 3:
    platform = 'официальное приложение VK для iPad'
elif platform == 4:
    platform = 'официальное приложение VK для Android'
elif platform == 5:
    platform = 'официальное приложение VK для Windows Phone'
elif platform == 6:
    platform = 'официальное приложение VK для Windows'
elif platform == 7:
  platform = 'официальную web-версию VK'

Теперь можно вывести информацию на экран:

print(user['first_name'], user['last_name'], verb, 'онлайн через', platform, 'в', time.ctime(last_visit).split()[3])

Далее мы рассмотрим коды 61 и 62. Они сообщают о том, что кто-то набирает сообщение. Разница в том, что обновления с кодом 61 оповещают о наборе текста в личных сообщениях, а 62 — в беседах. Обновления с кодом 62 выглядят так: [62, 000000000, 000]. Второй элемент массива — id пользователя, набирающего сообщения, а третий — идентификатор беседы. Обновления с кодом 61 имеют следующий вид: [61, 000000000, 1]. Здесь значение имеет лишь второй элемент — id пользователя, набирающего сообщение. Напишем обработчик этих событий:

elif action_code == 61:
    user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': element[1]}).json()['response'][0]  # получение имени и фамилии пользователя
    print(user['first_name'], user['last_name'], 'набирает сообщение')
elif action_code == 62:
    user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': element[1]}).json()['response'][0]  # получение имени и фамилии пользователя, набирающего сообщение
    chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': element[2], 'access_token': token}).json()['response']['title']  # получение названия беседы
    print(user['first_name'], user['last_name'], 'набирает сообщение в беседе "{}"'.format(chat))

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

Обработка сообщений и флаги
Наиболее интересные обновления, как мне кажется, — добавление сообщений. Эти обновления имеют код 4 и возвращают информацию о входящих и исходящих сообщений. Вот пример обновления с кодом 4: [4, $ts, $flag, $id, $unixtime, $text, {'title': ' ... '}]. Здесь $ts — номер пришедшего события, $flag — флаги сообщения, $id — id собеседника или 2000000000 + id беседы (в случае с коллективными диалогами), $unixtime — время добавления сообщения в Unix time, а последний элемент — словарь, содержащий информацию о вложениях, отправителе и изменениях в настройках беседы. Для начала разберемся с тем, где было добавлено сообщение: в личной переписке или беседе. Если сообщение было отправлено в беседе, то, как я уже написал, в поле $id будет указано число, получившиеся при сложении 2000000000 и chat_id (идентификатор беседы). Если сообщение было добавлено в личной переписке, в поле $id будет значиться id собеседника, которое всегда меньше, чем 2000000000 + chat_id любой беседы. Из этого можно сделать вывод, что, если $id — 2000000000 > 0, то сообщение было отправлено в беседе, если меньше, то в личной переписке. Если сообщение было отправлено в беседе, то id пользователя, написавшего сообщение, будет указано в словаре под ключем 'from'. Напишем обработчик сообщений:

elif action_code == 4:
    if element[3] - 2000000000 > 0:  # проверяем, было ли отправлено сообщение в беседе
        user_id = element[6]['from']  # id отправителя
        chat_id = element[3] - 2000000000  # id беседы
        chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': chat_id, 'access_token': token}).json()['response']['title']  # получение названия беседы
        user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'name_case': 'gen'}).json()['response'][0]  # получение имени и фамилии пользователя, отправившего сообщение
        time_ = element[4]  # время отправления сообщения
        text = element[5]  # текст сообщения
        if text:  # проверяем, что сообщение содержит текст
            print(time.ctime(time_).split()[3] + ':', 'Сообщение от', user['first_name'], user['last_name'], 'в беседе "{}"'.format(chat) + ':', text)
    else:
        user_id = element[3]  # id собеседника
        user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'name_case': 'gen'}).json()['response'][0]  # получение имени и фамилии пользователя, отправившего сообщение
        time_ = element[4]  # время отправления сообщения
        text = element[5]  # текст сообщения
        if text:  # проверяем, что сообщение содержит текст
            print(time.ctime(time_).split()[3] + ':', 'Сообщение от', user['first_name'], user['last_name'] + ':', text)

Однако, у данной программы довольно много недостатков. Вот некоторые из них:
  • Неумение отличить исходящие сообщения от входящих;
  • игнорирования медиа-вложений;
  • игнорирование сообщений, сигнализирующих об изменении настроек беседы;
  • неумение обработать специальные сиволы в сообщениях

Две из четырех проблем можно решить, используя флаги сообщений, которые хранятся в массиве обновления под индексом 2. Этот элемент массива — число, получившиеся в результате сложения некоторых параметров, приведенных ниже (из официальной документации):
+1: сообщение не прочитано
+2: исходящее сообщение
+4: на сообщение был создан ответ
+8: помеченное сообщение
+16: сообщение отправлено через чат
+32: сообщение отправлено другом. Не применяется для сообщений из групповых бесед
+64: сообщение помечено как «Спам»
+128: сообщение удалено (в корзине)
+256: сообщение проверено пользователем на спам
+512: сообщение содержит медиаконтент
+65536: приветственное сообщение от сообщества. Диалог с таким сообщением не нужно поднимать в списке (отображать его только при открытии диалога напрямую). Флаг недоступен для версий <2.

Теперь нужно научить программу отличать исходящие сообщения от входящих. Чтобы проверить, есть ли среди слагаемых флага сообщения нужное нам число, мы будем использовать побитовое И. Создадим массив, в котором будут храниться слагаемые флага:

summands = []  # массив, где мы будем хранить слагаемые
flag = element[2]  # флаг сообщения
for number in [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 65536]:  # проходим циклом по возможным слагаемым
    if flag & number:  # проверяем, является ли число слагаемым с помощью побитового И
        summands.append(number)  # если является, добавляем его в массив

Выводить на экран информацию об исходящих сообщениях, как мне кажется, бессмысленно, поэтому мы добавим строку

if 2 not in summands:

Сейчас часть программы, работающая с сообщениями, выглядит так:

elif action_code == 4:
    summands = []  # массив, где мы будем хранить слагаемые
    flag = element[2]  # флаг сообщения
    for number in [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 65536]:  # проходим циклом по возможным слагаемым
        if flag & number:  # проверяем, является ли число слагаемым с помощью побитового И
            summands.append(number)  # если является, добавляем его в массив
    if 2 not in summands:
        if element[3] - 2000000000 > 0:  # проверяем, было ли отправлено сообщение в беседе
            user_id = element[6]['from']  # id отправителя
            chat_id = element[3] - 2000000000  # id беседы
            chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': chat_id, 'access_token': token}).json()['response']['title']  # получение названия беседы
            user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'name_case': 'gen'}).json()['response'][0]  # получение имени и фамилии пользователя, отправившего сообщение
            time_ = element[4]  # время отправления сообщения
            text = element[5]  # текст сообщения
            if text:  # проверяем, что сообщение содержит текст
                print(time.ctime(time_).split()[3] + ':', 'Сообщение от', user['first_name'], user['last_name'], 'в беседе "{}"'.format(chat) + ':', text)
        else:
            user_id = element[3]  # id собеседника
            user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'name_case': 'gen'}).json()['response'][0]  # получение имени и фамилии пользователя, отправившего сообщение
            time_ = element[4]  # время отправления сообщения
            text = element[5]  # текст сообщения
            if text:  # проверяем, что сообщение содержит текст
                print(time.ctime(time_).split()[3] + ':', 'Сообщение от', user['first_name'], user['last_name'] + ':', text)

Теперь научимся работать с медиа-вложениями. Вот пример обновления, говорящего о том, что пришло сообщение с прикрепленными фотографией, песней, видео, документом и гео-позицией: [[4, $ts, $flag, $id, $unixtime, $text, {'attach1_type': 'photo', 'attach1': '$photoId', 'attach2_type': 'video', 'attach2': '$videoId', 'attach3_type': 'audio', 'attach3': '$audioId', 'attach4_type': 'doc', 'attach4': '$docId', 'geo': '2_SVK-le', 'geo_provider': '4', 'title': ' ... '}]. Чтобы получить доступ к этим вложениям, нужно будет обращаться к API для получения ссылки на файл. Методы для выполнения этого действия (photos.getById и docs.getById) нашлись лишь для фотографий и документов (аудиосообщения приходят как документы, поэтому ссылку для их прослушивания получить удастся). Для музыки с недавних пор недоступен ни один метод из-за авторских прав, а для видео нужный метод попросту отсутствует. Для дальнейшей работы с вложениями напишем код, который создаст два массива (для фото и для документов) со ссылками на просмотр вложений.

if 512 in summands:  # проверка, есть ли медиа-вложения
    index = 1
    photos = []  # массив для хранения id фотографий
    docs = []  # массив для хранения id документов
    media_type = 'attach1_type'
    while media_type in element[6].keys():  # проверка, существует ли медиа-вложение с таким индексом
        media_type = element[6]['attach{}_type'.format(index)]  # если существует, сохраняем его тип
        if media_type == 'photo':  # является ли вложение фотографией
            photos.append(element[6]['attach{}'.format(index)])  # добавляем id фотографии в массив
        elif media_type == 'doc':  # является ли вложение документом
            docs.append(element[6]['attach{}'.format(index)])  # добавляем id документа в массив
        index += 1  # увеличиваем индекс
        media_type = 'attach{}_type'.format(index)
    change = lambda ids, type_: requests.get('https://api.vk.com/method/{}.getById'.format(type_), params={type_: ids, 'access_token': token}).json()  # функция, возвращающаяся ссылки на объекты
    if photos:  # проверка, были ли во вложениях фотографии
        photos = change(', '.join(photos), 'photos')  # если были, то перезаписываем переменную photos на словарь
        if 'response' in photos.keys():
            photos = [attachment['src_xbig'] for attachment in photos['response']]  # перезаписываем на ссылки
            print('сообщение содержит следующие фотографии:', ', '.join(photos))
        else:
            pass  # скорее всего, возникла ошибка доступа
    if docs:  # проверка, были ли во вложениях документы
        docs = change(', '.join(docs), 'docs')  # если были, то перезаписываем переменную docs на словарь
        if 'response' in docs.keys():
            docs = [attachment['url'] for attachment in docs['response']]  # перезаписываем на ссылки
            print('сообщение содержит следующие документы:', ', '.join(docs))
        else:
            pass  # скорее всего, возникла ошибка доступа

Уберем строки

if text:
, чтобы при выводе данных было понятно, какие медиа-вложения к каким сообщениям относятся.

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

  • Изменение названия беседы: [4, $ts, $flag, $chat_id, $unixtime, '', {'source_act': 'chat_title_update', 'source_text': 'Новое название', 'source_old_text': 'Старое название', 'from': '$id'}]
  • Обновление фотографии беседы: [4, $ts, $flag, $chat_id, $unixtime, '', {'attach1_type': 'photo', 'attach1': '247178624_456242629', 'source_act': 'chat_photo_update', 'from': '247178624'}]
  • Добавление пользователя в беседу: [4, $ts, $flag, $chat_id, $unixtime, '', {'source_act': 'chat_invite_user', 'source_mid': '$added_user_id', 'from': '$adder_id'}]
  • Исключение пользователя из беседы (выход пользователя из беседы): [4, $ts, $flag, $chat_id, $unixtime, '', {'source_act': 'chat_kick_user', 'source_mid': '&removed_user_id', 'from': '&remover_id'}]
  • Создание новой беседы: [4, $ts, $flag, $chat_id, $unixtime, '', {'source_act': 'chat_create', 'source_text': 'Название', 'from': '$creator_id'}]

Вот программа, которая обрабатывает изменения такого вида:

elif action_code == 4:
    if 'source_act' not in element[6].keys():
        # <код, обрабатывающий сообщения>
    else:
        source_act = element[6]
        if source_act['source_act'] == 'chat_title_update':  # было ли обновление вызвано изменением названия беседы
            changer_id = source_act['from']  # id человека, изменившего названия
            source_text = source_act['source_text']  # новое название беседы
            source_old_text = source_act['source_old_text']  # старое название беседы
            changer = requests.get('https://api.vk.com/method/users.get', params={'user_ids': changer_id, 'fields': 'sex'}).json()['response'][0]  # получение имени и фамилии пользователя, изменившего название
            if changer['sex']:
                verb = 'изменила'
            else:
                verb = 'изменил'
            print(changer['first_name'], changer['last_name'], verb, 'название беседы с "{}" на "{}"'.format(source_old_text, source_text))
        elif source_act['source_act'] == 'chat_photo_update':
            chat_id = element[3] - 2000000000  # id беседы
            chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': chat_id, 'access_token': token}).json()['response']['title']  # получение названия беседы
            user_id = source_act['from']  # id пользователя, обновившего фото
            photo_id = source_act['attach1']  # id фотографии
            photo = requests.get('https://api.vk.com/method/photos.getById', params={'photos': photo_id, 'access_token': token}).json()  # ссылка на фотографию
            user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'fields': 'sex'}).json()['response'][0]  # имя и фамилия пользователя, обновившего фото
            if 'error' not in photo.keys():  # не возникло ли ошибок при получении ссылки
                if user['sex']:
                    verb = 'обновил'
                else:
                    verb = 'обновила'
                print(user['first_name'], user['last_name'], verb, 'фотографию беседы "{}" на'.format(chat), photo['response'][0]['src_xbig'])
            else:
                pass  # вероятнее всего, отсутствуют права для выполнения запроса
        elif source_act['source_act'] == 'chat_invite_user':
            chat_id = element[3] - 2000000000  # id беседы
            chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': chat_id, 'access_token': token}).json()['response']['title']  # получение названия беседы
            invited_id = source_act['source_mid']  # id приглашенного
            inviter_id = source_act['from']  # id пригласившего
            if invited_id == inviter_id:  # вернулся ли пользователь в беседу или был добавлен кем-то из участников
                user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': inviter_id, 'fields': 'sex'}).json()['response'][0]  # имя и фамилия вернувшегося
                if user['sex']:
                    verb = 'вернулась'
                else:
                    verb = 'вернулся'
                print(user['first_name'], user['last_name'], verb, 'в беседу "{}"'.format(chat))
            else:
                inviter_user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': inviter_id, 'fields': 'sex'}).json()['response'][0]  # имя и фамилия добавившего
                invited_user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': invited_id, 'name_case': 'acc'}).json()['response'][0]  # имя и фамилия добавленного
                if inviter_user['sex']:
                    verb = 'добавила'
                else:
                    verb = 'добавил'
                print(inviter_user['first_name'], inviter_user['last_name'], verb, 'в беседу "{}"'.format(chat), invited_user['first_name'], invited_user['last_name'])
        elif source_act['source_act'] == 'chat_kick_user':
            chat_id = element[3] - 2000000000  # id беседы
            chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': chat_id, 'access_token': token}).json()['response']['title']  # получение названия беседы
            removed_id = source_act['source_mid']  # id исключенного
            remover_id = source_act['from']  # id исключившего
            if removed_id == remover_id:  # вышел ли пользователь сам или был исключен
                user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': remover_id, 'fields': 'sex'}).json()['response'][0]  # имя и фамилия вышедшего
                if user['sex']:
                    verb = 'вышла'
                else:
                    verb = 'вышел'
                print(user['first_name'], user['last_name'], verb, 'из беседы "{}"'.format(chat))
            else:
                remover_user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': remover_id, 'fields': 'sex'}).json()['response'][0]  # имя и фамилия исключившего
                removed_user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': removed_id, 'name_case': 'acc'}).json()['response'][0]  # имя и фамилия исключенного
                if remover_user['sex']:
                    verb = 'исключила'
                else:
                     verb = 'исключил'
                print(remover_user['first_name'], remover_user['last_name'], verb, 'из беседы "{}"'.format(chat), removed_user['first_name'], removed_user['last_name'])
        elif source_act['source_act'] == 'chat_create':
            chat = source_act['source_text']  # название беседы
            creator_id = source_act['from']  # id создателя
            creator = requests.get('https://api.vk.com/method/users.get', params={'user_ids': creator_id, 'fields': 'sex'}).json()['response'][0]  # имя, фамилия и пол создателя
            if creator['sex']:
                verb = 'создала'
            else:
                verb = 'создал'
            print(creator['first_name'], creator['last_name'], verb, 'беседу "{}"'.format(chat))


Наводим красивости. Заключительная часть
В этой части мы рассмотрим лишь две детали: ошибку при запросе к Long Poll серверу и отображение спец. символов в сообщениях.

Об ошибке: если запустить программу в том виде, в котором она есть сейчас, через некоторое время возникнет ошибка KeyError на строке 8: response = requests.get('https://{server}?act=a_check&key={key}&ts={ts}&wait=20&mode=2&version=2'.format(server=data['server'], key=data['key'], ts=data['ts'])).json()['response'] # отправление запроса на Long Poll сервер со временем ожидания 20 и опциями ответа 2. Дело в том, что на наш запрос сервер вернул ошибку «error 2», что означает, что используемый параметр &key устарел и нужно получить новый. Для этого мы несколько изменим существующий код на:

response = requests.get('https://{server}?act=a_check&key={key}&ts={ts}&wait=20&mode=2&version=2'.format(server=data['server'], key=data['key'], ts=data['ts'])).json()  # отправление запроса на Long Poll сервер со временем ожидания 20 и опциями ответа 2
try:
    updates = response['updates']
except KeyError:  # если в этом месте возбуждается исключение KeyError, значит параметр key устарел, и нужно получить новый
    data = requests.get('https://api.vk.com/method/messages.getLongPollServer', params={'access_token': token}).json()['response']  # получение ответа от сервера
     continue  # переходим на следующую итерацию цикла, чтобы сделать повторный запрос

Теперь проблема решена! Остается последнее: спец. символы в сообщениях. Дело в том, что некоторые символы ВК возвращает не в привычном для нас виде. Так, например, если в сообщении есть амперсант, он будет заменен на &_amp (нижнее подчеркивание нужно, чтобы Хабр не заменил эту надпись на амерсант). Подобных символов много и всех их нужно вывести правильно. Для этого сохраним подобные символы и их коды в словарь, а затем заменим коды в сообщении на символы с помощью функции sub библиотеки re (не забудьте ее импортировать!).

import re
# <...>
symbols = {'<br>': '\n', '&_amp;': '&', '&_quot;': '"', '&_lt;': '<', '&_gt;': '>', '&_tilde;': '~', '&_circ;': 'ˆ', '&_ndash;': '–', '&_mdash;': '—', '&_euro;': '€', '&_permil;': '‰'}  # из каждого ключа уберите нижнее подчеркивание
for code, value in symbols.items():
    text = re.sub(code, value, text)


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

import re
import time

import requests

token = ''  # здесь вы должны написать свой access_token
data = requests.get('https://api.vk.com/method/messages.getLongPollServer',
                    params={'access_token': token}).json()['response']  # получение ответа от сервера

while True:
    response = requests.get('https://{server}?act=a_check&key={key}&ts={ts}&wait=20&mode=2&version=2'.format(server=data['server'], key=data['key'], ts=data['ts'])).json()  # отправление запроса на Long Poll сервер со временем ожидания 20 и опциями ответа 2
    try:
        updates = response['updates']
    except KeyError:  # если в этом месте возбуждается исключение KeyError, значит параметр key устарел, и нужно получить новый
        data = requests.get('https://api.vk.com/method/messages.getLongPollServer', params={'access_token': token}).json()['response']  # получение ответа от сервера
        continue  # переходим на следующую итерацию цикла, чтобы сделать повторный запрос

    if updates:  # проверка, были ли обновления
        for element in updates:  # проход по всем обновлениям в ответе
            action_code = element[0]
            if action_code == 80:  # проверка кода события
                print('количество непрочитанных сообщений стало равно', element[1])  # вывод
            elif action_code == 9:
                user_id = element[1] * -1  # id пользователя, ставшего оффлайн
                user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'fields': 'sex'}).json()['response'][0]  # имя и фамилия пользователя с id = user_id
                timeout = bool(element[2])  # был ли поставлен статус оффлайн по истечении тайм-аута
                last_visit = element[3]  # дата последнего действия пользователя на сайте
                if user['sex'] == 1:
                    verb = ['стала', 'вышла']
                else:
                    verb = ['стал', 'вышел']
                if timeout:
                    print(user['first_name'], user['last_name'], verb[0], 'оффлайн по истечении тайм-аута. Время последнего действия на сайте:', time.ctime(last_visit).split()[3])
                else:
                    print(user['first_name'], user['last_name'], verb[1], 'из ВКонтакте. Время последнего действия на сайте:', time.ctime(last_visit).split()[3])
            elif action_code == 8:
                user_id = element[1] * -1  # id пользователя, ставшего онлайн
                user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'fields': 'sex'}).json()['response'][0]  # имя и фамилия пользователя с id = user_id
                platform = element[2]  # код платформы пользователя
                last_visit = element[3]  # время последнего визита в Unix time
                if user['sex'] == 1:
                    verb = 'стала'
                else:
                    verb = 'стал'

                # определение платформы по ее коду
                if platform == 1:
                    platform = 'официальную мобильную версию web-сайта VK'
                elif platform == 2:
                    platform = 'официальное приложение VK для iPhone'
                elif platform == 3:
                    platform = 'официальное приложение VK для iPad'
                elif platform == 4:
                    platform = 'официальное приложение VK для Android'
                elif platform == 5:
                    platform = 'официальное приложение VK для Windows Phone'
                elif platform == 6:
                    platform = 'официальное приложение VK для Windows'
                elif platform == 7:
                    platform = 'официальную web-версию VK'
                print(user['first_name'], user['last_name'], verb, 'онлайн через', platform, 'в', time.ctime(last_visit).split()[3])

            elif action_code == 61:
                user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': element[1]}).json()['response'][0]  # получение имени и фамилии пользователя
                print(user['first_name'], user['last_name'], 'набирает сообщение')
            elif action_code == 62:
                user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': element[1]}).json()['response'][0]  # получение имени и фамилии пользователя, набирающего сообщение
                chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': element[2], 'access_token': token}).json()['response']['title']  # получение названия беседы
                print(user['first_name'], user['last_name'], 'набирает сообщение в беседе "{}"'.format(chat))
            elif action_code == 4:
                if 'source_act' not in element[6].keys():
                    summands = []  # массив, где мы будем хранить слагаемые
                    flag = element[2]  # флаг сообщения
                    for number in [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 65536]:  # проходим циклом по возможным слагаемым
                        if flag & number:  # проверяем, является ли число слагаемым с помощью побитового И
                            summands.append(number)  # если является, добавляем его в массив
                    if 2 not in summands:
                        if element[3] - 2000000000 > 0:  # проверяем, было ли отправлено сообщение в беседе
                            user_id = element[6]['from']  # id отправителя
                            chat_id = element[3] - 2000000000  # id беседы
                            chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': chat_id, 'access_token': token}).json()['response']['title']  # получение названия беседы
                            user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'name_case': 'gen'}).json()['response'][0]  # получение имени и фамилии пользователя, отправившего сообщение
                            time_ = element[4]  # время отправления сообщения
                            text = element[5]  # текст сообщения
                            symbols = {'<br>': '\n', '&_amp;': '&', '&_quot;': '"', '&_lt;': '<', '&_gt;': '>', '&_tilde;': '~', '&_circ;': 'ˆ', '&_ndash;': '–', '&_mdash;': '—', '&_euro;': '€', '&_permil;': '‰'}  # из каждого ключа уберите нижнее подчеркивание
                            for code, value in symbols.items():
                                text = re.sub(code, value, text)
                            print(time.ctime(time_).split()[3] + ':', 'Сообщение от', user['first_name'], user['last_name'], 'в беседе "{}"'.format(chat) + ':', text)
                        else:
                            user_id = element[3]  # id собеседника
                            user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'name_case': 'gen'}).json()['response'][0]  # получение имени и фамилии пользователя, отправившего сообщение
                            time_ = element[4]  # время отправления сообщения
                            text = element[5]  # текст сообщения
                            symbols = {'<br>': '\n', '&_amp;': '&', '&_quot;': '"', '&_lt;': '<', '&_gt;': '>', '&_tilde;': '~', '&_circ;': 'ˆ', '&_ndash;': '–', '&_mdash;': '—', '&_euro;': '€', '&_permil;': '‰'}  # из каждого ключа уберите нижнее подчеркивание
                            for code, value in symbols.items():
                                text = re.sub(code, value, text)
                            print(time.ctime(time_).split()[3] + ':', 'Сообщение от', user['first_name'], user['last_name'] + ':', text)

                        if 512 in summands:  # проверка, были ли медиа-вложения
                            index = 1
                            photos = []  # массив для хранения id фотографий
                            docs = []  # массив для хранения id документов
                            media_type = 'attach1_type'
                            while media_type in element[6].keys():  # проверка, существует ли медиа-вложение с таким индексом
                                media_type = element[6]['attach{}_type'.format(index)]  # если существует, сохраняем его тип
                                if media_type == 'photo':  # является ли вложение фотографией
                                    photos.append(element[6]['attach{}'.format(index)])  # добавляем id фотографии в массив
                                elif media_type == 'doc':  # является ли вложение документом
                                    docs.append(element[6]['attach{}'.format(index)])  # добавляем id документа в массив
                                index += 1  # увеличиваем индекс
                                media_type = 'attach{}_type'.format(index)
                            change = lambda ids, type_: requests.get('https://api.vk.com/method/{}.getById'.format(type_), params={type_: ids, 'access_token': token}).json()  # функция, возвращающаяся ссылки на объекты
                            if photos:  # проверка, были ли во вложениях фотографии
                                photos = change(', '.join(photos), 'photos')  # если были, то перезаписываем переменную photos на словарь
                                if 'response' in photos.keys():
                                    photos = [attachment['src_xbig'] for attachment in photos['response']]  # перезаписываем на ссылки
                                    print('сообщение содержит следующие фотографии:', ', '.join(photos))
                                else:
                                    pass  # скорее всего, возникла ошибка доступа
                            if docs:  # проверка, были ли во вложениях документы
                                docs = change(', '.join(docs), 'docs')  # если были, то перезаписываем переменную docs на словарь
                                if 'response' in docs.keys():
                                    docs = [attachment['url'] for attachment in docs['response']]  # перезаписываем на ссылки
                                    print('сообщение содержит следующие документы:', ', '.join(docs))
                                else:
                                    pass  # скорее всего, возникла ошибка доступа
                else:
                    source_act = element[6]
                    if source_act['source_act'] == 'chat_title_update':  # было ли обновление вызвано изменением названия беседы
                        changer_id = source_act['from']  # id человека, изменившего названия
                        source_text = source_act['source_text']  # новое название беседы
                        source_old_text = source_act['source_old_text']  # старое название беседы
                        changer = requests.get('https://api.vk.com/method/users.get', params={'user_ids': changer_id, 'fields': 'sex'}).json()['response'][0]  # получение имени и фамилии пользователя, изменившего название
                        if changer['sex']:
                            verb = 'изменила'
                        else:
                            verb = 'изменил'
                        print(changer['first_name'], changer['last_name'], verb, 'название беседы с "{}" на "{}"'.format(source_old_text, source_text))
                    elif source_act['source_act'] == 'chat_photo_update':
                        chat_id = element[3] - 2000000000  # id беседы
                        chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': chat_id, 'access_token': token}).json()['response']['title']  # получение названия беседы
                        user_id = source_act['from']  # id пользователя, обновившего фото
                        photo_id = source_act['attach1']  # id фотографии
                        photo = requests.get('https://api.vk.com/method/photos.getById', params={'photos': photo_id, 'access_token': token}).json()  # ссылка на фотографию
                        user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': user_id, 'fields': 'sex'}).json()['response'][0]  # имя и фамилия пользователя, обновившего фото
                        if 'error' not in photo.keys():  # не возникло ли ошибок при получении ссылки
                            if user['sex']:
                                verb = 'обновил'
                            else:
                                verb = 'обновила'
                            print(user['first_name'], user['last_name'], verb, 'фотографию беседы "{}" на'.format(chat), photo['response'][0]['src_xbig'])
                        else:
                            pass  # вероятнее всего, отсутствуют права для выполнения запроса
                    elif source_act['source_act'] == 'chat_invite_user':
                        chat_id = element[3] - 2000000000  # id беседы
                        chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': chat_id, 'access_token': token}).json()['response']['title']  # получение названия беседы
                        invited_id = source_act['source_mid']  # id приглашенного
                        inviter_id = source_act['from']  # id пригласившего
                        if invited_id == inviter_id:  # вернулся ли пользователь в беседу или был добавлен кем-то из участников
                            user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': inviter_id, 'fields': 'sex'}).json()['response'][0]  # имя и фамилия вернувшегося
                            if user['sex']:
                                verb = 'вернулась'
                            else:
                                verb = 'вернулся'
                            print(user['first_name'], user['last_name'], verb, 'в беседу "{}"'.format(chat))
                        else:
                            inviter_user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': inviter_id, 'fields': 'sex'}).json()['response'][0]  # имя и фамилия добавившего
                            invited_user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': invited_id, 'name_case': 'acc'}).json()['response'][0]  # имя и фамилия добавленного
                            if inviter_user['sex']:
                                verb = 'добавила'
                            else:
                                verb = 'добавил'
                            print(inviter_user['first_name'], inviter_user['last_name'], verb, 'в беседу "{}"'.format(chat), invited_user['first_name'], invited_user['last_name'])
                    elif source_act['source_act'] == 'chat_kick_user':
                        chat_id = element[3] - 2000000000  # id беседы
                        chat = requests.get('https://api.vk.com/method/messages.getChat', params={'chat_id': chat_id, 'access_token': token}).json()['response']['title']  # получение названия беседы
                        removed_id = source_act['source_mid']  # id исключенного
                        remover_id = source_act['from']  # id исключившего
                        if removed_id == remover_id:  # вышел ли пользователь сам или был исключен
                            user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': remover_id, 'fields': 'sex'}).json()['response'][0]  # имя и фамилия вышедшего
                            if user['sex']:
                                verb = 'вышла'
                            else:
                                verb = 'вышел'
                            print(user['first_name'], user['last_name'], verb, 'из беседы "{}"'.format(chat))
                        else:
                            remover_user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': remover_id, 'fields': 'sex'}).json()['response'][0]  # имя и фамилия исключившего
                            removed_user = requests.get('https://api.vk.com/method/users.get', params={'user_ids': removed_id, 'name_case': 'acc'}).json()['response'][0]  # имя и фамилия исключенного
                            if remover_user['sex']:
                                verb = 'исключила'
                            else:
                                verb = 'исключил'
                            print(remover_user['first_name'], remover_user['last_name'], verb, 'из беседы "{}"'.format(chat), removed_user['first_name'], removed_user['last_name'])
                    elif source_act['source_act'] == 'chat_create':
                        chat = source_act['source_text']  # название беседы
                        creator_id = source_act['from']  # id создателя
                        creator = requests.get('https://api.vk.com/method/users.get', params={'user_ids': creator_id, 'fields': 'sex'}).json()['response'][0]  # имя, фамилия и пол создателя
                        if creator['sex']:
                            verb = 'создала'
                        else:
                            verb = 'создал'
                        print(creator['first_name'], creator['last_name'], verb, 'беседу "{}"'.format(chat))

    data['ts'] = response['ts']  # обновление номера последнего обновления

В этом руководстве я рассмотрел не все возможности Long Poll-a, такие интересные вещи, как замена флагов сообщений или отметки «прочитано» и «непрочитанно» остались незатронутыми, но вы всегда можете дописать программу.
На этом у меня все, надеюсь, вы узнали для себя что-то новое. До скорых встреч!

Источники: