Разбираюсь как работает Long Poll ВКонтакте, пишу сниффер для перехвата событий и нахожу недокументированные коды.
Введение
Когда я разрабатывал браузерное расширение VKify для улучшения функционала ВКонтакте, передо мной встала интересная задача: отслеживать действия пользователей в реальном времени — кто печатает, кто прочитал сообщение, кто удалил его.
Официальная документация VK API по Long Poll существует, но она неполная и местами устарела. Многие коды событий не документированы, а структура данных может отличаться от описанной. Пришлось заниматься реверс-инжинирингом протокола самостоятельно.
В этой статье расскажу:
- Что такое Long Poll и зачем он нужен
- Как перехватывать сетевые запросы в браузере
- Как я написал сниффер для анализа событий
- Какие подводные камни встретил (спойлер: онлайн-статус, возможно, идёт через WebSocket)
- Готовый код, который вы можете использовать
Что такое Long Poll?
Long Polling — это техника получения обновлений от сервера в реальном времени. В отличие от обычных запросов, клиент отправляет запрос и сервер держит соединение открытым, пока не появятся новые данные или не истечёт таймаут.
ВКонтакте использует Long Poll для мгновенной доставки событий:
- Новые сообщения
- Статусы “печатает…”
- Прочтение сообщений
- Удаление сообщений
- И многое другое
Запросы идут на endpoint вида:
https://api.vk.com/ruimXXXXXXXXX?version=21&mode=682
Где XXXXXXXXX — ваш user ID.
Шаг 1: Простой перехват fetch
Начнём с базового перехвата. Идея простая: подменяем глобальную функцию fetch, анализируем ответы и пропускаем их дальше.
/**
* Базовый перехватчик Long Poll
* Вставьте в консоль браузера на странице VK
*/
(function() {
const originalFetch = window.fetch;
window.fetch = async function(url, options) {
const response = await originalFetch.apply(this, arguments);
const urlStr = url?.toString() || '';
// Ловим только Long Poll запросы
if (/api\.vk\.com\/ruim/.test(urlStr)) {
try {
const clone = response.clone();
const data = await clone.json();
if (data.updates && data.updates.length > 0) {
console.log('[Long Poll] Получено событий:', data.updates.length);
data.updates.forEach(update => {
console.log(' Код:', update[0], '| Данные:', update.slice(1));
});
}
} catch (e) {}
}
return response;
};
console.log('✅ Перехватчик Long Poll активирован');
})();
Вставьте этот код в консоль браузера (F12 → Console) на любой странице ВКонтакте и начните переписываться. Вы увидите поток событий.
Шаг 2: Продвинутый сниффер для анализа
Простой перехватчик хорош для начала, но мне нужно было систематизировать данные. Я написал полноценный сниффер, который:
- Собирает статистику по каждому коду события
- Сохраняет примеры для анализа структуры
- Позволяет искать и экспортировать данные
/**
* VK Long Poll Event Sniffer
* Продвинутый инструмент для анализа событий
*
* После вставки введите __sniffer.help() для справки
*/
(function() {
'use strict';
if (window.__vkEventSniffer) {
console.log('[Sniffer] Уже запущен. Команды: __sniffer.help()');
return;
}
// Хранилище событий
const eventCodes = new Map(); // code -> { count, samples, lastSeen }
let totalEvents = 0;
let isRunning = true;
const originalFetch = window.fetch;
window.fetch = async function(url, options) {
const response = await originalFetch.apply(this, arguments);
if (!isRunning) return response;
const urlStr = url?.toString() || '';
if (/api\.vk\.com\/ruim/.test(urlStr)) {
try {
const clone = response.clone();
const data = await clone.json();
if (data.updates?.length > 0) {
console.groupCollapsed(
`%c[Long Poll]%c ${data.updates.length} событий`,
'background: #5181b8; color: white; padding: 2px 8px; border-radius: 4px;',
'color: #666; margin-left: 8px;'
);
data.updates.forEach(update => {
const code = update[0];
// Обновляем статистику
if (!eventCodes.has(code)) {
eventCodes.set(code, { count: 0, samples: [], lastSeen: null });
// Уведомляем о новом типе события
console.log(
`%c🆕 Обнаружен новый код события: ${code}%c`,
'background: #4CAF50; color: white; padding: 2px 8px; border-radius: 4px; font-weight: bold;',
''
);
}
const stats = eventCodes.get(code);
stats.count++;
stats.lastSeen = new Date().toLocaleTimeString();
if (stats.samples.length < 5) {
stats.samples.push([...update]);
}
totalEvents++;
// Выводим в консоль
console.log(`📨 Code ${code}:`, update.slice(1));
});
console.groupEnd();
}
} catch (e) {}
}
return response;
};
// API для управления
window.__sniffer = {
// Показать все найденные коды
codes() {
if (eventCodes.size === 0) {
console.log('%c⚠️ Пока не обнаружено ни одного события.%c\n\nУбедитесь, что вы:\n1. Находитесь на странице VK\n2. Открыли диалог с кем-то\n3. Получаете/отправляете сообщения',
'color: #FF9800; font-weight: bold;', '');
return;
}
console.log('%c\n📊 Статистика событий\n', 'font-size: 14px; font-weight: bold;');
const sorted = [...eventCodes.entries()].sort((a, b) => b[1].count - a[1].count);
console.table(sorted.map(([code, stats]) => ({
'Код': code,
'Количество': stats.count,
'Последний раз': stats.lastSeen,
'Пример (первые 4 элемента)': JSON.stringify(stats.samples[0]?.slice(0, 4) || [])
})));
console.log(`\nВсего событий: ${totalEvents}`);
console.log(`Уникальных кодов: ${eventCodes.size}`);
return sorted;
},
// Детальный анализ конкретного кода
inspect(code) {
const stats = eventCodes.get(code);
if (!stats) {
console.log(`❌ Код ${code} не найден.`);
if (eventCodes.size > 0) {
console.log(`Доступные коды:`, [...eventCodes.keys()].join(', '));
} else {
console.log('Пока не обнаружено ни одного события. Подождите или спровоцируйте события (напишите кому-нибудь).');
}
return;
}
console.log(`%c\n🔍 Анализ события ${code}\n`, 'font-size: 14px; font-weight: bold;');
console.log(`Всего перехвачено: ${stats.count}`);
console.log(`Последнее: ${stats.lastSeen}\n`);
console.log('Примеры (до 5 штук):');
stats.samples.forEach((sample, i) => {
console.log(`\n#${i + 1}:`);
sample.forEach((value, idx) => {
const type = typeof value;
const display = type === 'object' ? JSON.stringify(value) : value;
console.log(` [${idx}] (${type}): ${display}`);
});
});
return stats;
},
// Поиск по содержимому
search(keyword) {
if (eventCodes.size === 0) {
console.log('⚠️ Нет данных для поиска.');
return [];
}
const found = [];
eventCodes.forEach((stats, code) => {
stats.samples.forEach(sample => {
if (JSON.stringify(sample).toLowerCase().includes(keyword.toLowerCase())) {
found.push({ code, sample });
}
});
});
if (found.length === 0) {
console.log(`Ничего не найдено для "${keyword}"`);
} else {
console.log(`Найдено ${found.length} совпадений для "${keyword}":`, found);
}
return found;
},
// Управление
stop() { isRunning = false; console.log('⏸️ Сниффер приостановлен'); },
start() { isRunning = true; console.log('▶️ Сниффер запущен'); },
clear() { eventCodes.clear(); totalEvents = 0; console.log('🗑️ Статистика очищена'); },
// Экспорт
export() {
if (eventCodes.size === 0) {
console.log('⚠️ Нет данных для экспорта.');
return null;
}
const data = {
exportedAt: new Date().toISOString(),
totalEvents,
events: Object.fromEntries(eventCodes)
};
console.log(JSON.stringify(data, null, 2));
return data;
},
// Статус
status() {
console.log(`
%c📊 Статус сниффера%c
━━━━━━━━━━━━━━━━━━━━━
Активен: ${isRunning ? '✅ Да' : '❌ Нет'}
Всего событий: ${totalEvents}
Уникальных кодов: ${eventCodes.size}
${eventCodes.size > 0 ? `Коды: ${[...eventCodes.keys()].join(', ')}` : ''}
`,
'font-weight: bold; font-size: 12px;', ''
);
},
// Справка
help() {
console.log(`
%c╔═══════════════════════════════════════════════════════════╗
║ VK Long Poll Event Sniffer ║
╚═══════════════════════════════════════════════════════════╝%c
%cКак использовать:%c
1. Оставьте консоль открытой
2. Откройте диалог с кем-нибудь
3. Попросите собеседника написать вам, начать печатать и т.д.
4. События будут появляться в консоли автоматически
%cКоманды:%c
__sniffer.status() — Текущий статус и найденные коды
__sniffer.codes() — Подробная статистика всех событий
__sniffer.inspect(63) — Детальный анализ события с кодом 63
__sniffer.search('id') — Поиск по содержимому
__sniffer.export() — Экспорт всех данных в JSON
%cУправление:%c
__sniffer.stop() — Приостановить перехват
__sniffer.start() — Возобновить перехват
__sniffer.clear() — Очистить статистику
`,
'color: #5181b8; font-weight: bold;', '',
'color: #4CAF50; font-weight: bold;', '',
'color: #2196F3; font-weight: bold;', '',
'color: #FF9800; font-weight: bold;', ''
);
}
};
window.__vkEventSniffer = true;
console.log(`
%c🔍 VK Event Sniffer активирован!%c
%cЧто дальше:%c
• Откройте любой диалог ВКонтакте
• Попросите собеседника написать вам сообщение
• События появятся здесь автоматически
%cКоманды:%c __sniffer.help() — справка, __sniffer.status() — статус
`,
'color: #5181b8; font-weight: bold; font-size: 14px;', '',
'color: #4CAF50; font-weight: bold;', '',
'background: #151515; padding: 2px 6px; border-radius: 3px;', ''
);
})();
Как пользоваться:
- Вставьте код в консоль на странице VK
- Откройте диалог с кем-нибудь
- Попросите собеседника начать печатать или отправить сообщение
- События появятся в консоли автоматически
- Введите
__sniffer.codes()чтобы увидеть статистику
Сниффер в действии
После активации сниффер начинает перехватывать все Long Poll события. Каждый раз, когда приходит пакет данных, он группируется в консоли с указанием количества событий:

На скриншоте видно, как сниффер логирует входящие события. Каждая группа [Long Poll] содержит список событий с их кодами и данными. Обратите внимание на коды: 63 — это событие “печатает”, 10004 — новое сообщение, и так далее. Именно так я и собирал информацию о структуре каждого типа события.
После того как накопится достаточно данных, можно вызвать команду __sniffer.codes() для просмотра статистики:

Таблица показывает все обнаруженные коды событий, сколько раз каждый встречался, когда был последний раз, и пример данных. Это позволяет быстро понять, какие события происходят чаще всего и на какие стоит обратить внимание при анализе.
Шаг 3: Что я обнаружил
После нескольких часов тестирования с помощью друга (спасибо, что терпел мои “напиши мне”, “удали сообщение”, “выйди из сети”) я составил карту событий.
Документированные события (работают)
| Код | Событие | Структура |
|---|---|---|
| 63 | Печатает сообщение |
[63, userId, [chatIds], flag, peerId]
|
| 64 | Записывает голосовое |
[64, userId, [chatIds], flag, timestamp]
|
| 52 | Заявка в друзья |
[52, type, userId, ?]
|
| 90 | Действия друзей |
[90, actionType, userId]
|
[63, userId, [chatIds], flag, peerId]
[64, userId, [chatIds], flag, timestamp]
[52, type, userId, ?]
[90, actionType, userId]
События сообщений (10000+)
| Код | Событие | Структура |
|---|---|---|
| 10002 | Удалил для всех |
[10002, ?, flags, peerId, localId]
|
| 10004 | Новое сообщение |
[10004, ?, flags, ?, fromId, peerId, text, {...}]
|
| 10005 | Редактирование |
[10005, ?, flags, fromId, timestamp, text, {...}]
|
| 10007 | Прочитал сообщение |
[10007, peerId, ?, ?, msgId]
|
[10002, ?, flags, peerId, localId]
[10004, ?, flags, ?, fromId, peerId, text, {...}]
[10005, ?, flags, fromId, timestamp, text, {...}]
[10007, peerId, ?, ?, msgId]
Подкоды события 90 (друзья)
| Подкод | Значение |
|---|---|
| 1 | Отправил заявку в друзья |
| 2 | Принял вашу заявку |
| 3 | Удалил вас из друзей |
Шаг 4: Загадка онлайн-статуса
По документации, события 8 (онлайн) и 9 (оффлайн) должны приходить через Long Poll. Но я их не видел!
Расширил поиск и начал перехватывать все сетевые запросы:
/**
* Универсальный сетевой сниффер
*/
(function() {
const originalFetch = window.fetch;
const keywords = ['online', 'offline', 'last_seen', 'presence'];
window.fetch = async function(url, options) {
const response = await originalFetch.apply(this, arguments);
try {
const clone = response.clone();
const text = await clone.text();
// Ищем ключевые слова
const found = keywords.filter(kw => text.toLowerCase().includes(kw));
if (found.length > 0) {
console.log(`%c[MATCH: ${found.join(', ')}]%c ${url}`,
'background: #4CAF50; color: white; padding: 2px 6px;', '');
console.log(text.substring(0, 500));
}
} catch (e) {}
return response;
};
console.log('✅ Сетевой сниффер активирован. Ищу:', keywords.join(', '));
})();
И тут я заметил интересное в логах:
[WebSocket CREATED] wss://eh.vk.com/?v=1.000&format=json&app_id=6287487
Похоже, ВКонтакте перенёс онлайн-статусы на WebSocket!
Шаг 5: Перехват WebSocket
WebSocket — это другой протокол, и перехватывать его нужно иначе:
/**
* WebSocket сниффер для VK
*/
(function() {
const OriginalWebSocket = window.WebSocket;
window.WebSocket = function(url, protocols) {
console.log('%c[WS Created]%c ' + url,
'background: #9C27B0; color: white; padding: 2px 6px;', '');
const ws = protocols
? new OriginalWebSocket(url, protocols)
: new OriginalWebSocket(url);
ws.addEventListener('message', async function(event) {
let data = event.data;
// Данные могут приходить как Blob
if (data instanceof Blob) {
const buffer = await data.arrayBuffer();
const bytes = new Uint8Array(buffer);
// Первый байт — тип сообщения, остальное — JSON
const jsonStr = new TextDecoder().decode(bytes.slice(1));
try {
data = JSON.parse(jsonStr);
} catch (e) {
data = jsonStr;
}
}
console.log('%c[WS Message]%c',
'background: #2196F3; color: white; padding: 2px 6px;', '', data);
});
return ws;
};
// Копируем константы
window.WebSocket.CONNECTING = OriginalWebSocket.CONNECTING;
window.WebSocket.OPEN = OriginalWebSocket.OPEN;
window.WebSocket.CLOSING = OriginalWebSocket.CLOSING;
window.WebSocket.CLOSED = OriginalWebSocket.CLOSED;
console.log('✅ WebSocket сниффер активирован');
})();
Данные через eh.vk.com приходят в бинарном формате. Мне удалось частично их декодировать, но полностью разобрать протокол не получилось — данные приходили как Blob, и после декодирования первого байта (тип сообщения) оставался JSON, но структура была сложной.
Важно: Я не могу утверждать на 100%, что онлайн-статусы идут именно через этот WebSocket. Возможно, они передаются как-то иначе, или VK использует комбинацию методов. Если у вас получится разобраться глубже — напишите, будет интересно!
В рамках своего проекта я решил не тратить на это больше времени и убрал функцию отслеживания онлайн-статуса.
Итоговый модуль слежки
На основе своих исследований я написал production-ready модуль. Он выводит сообщение при активации и логирует все распознанные события:
/**
* VKify Spy Module
* Production-версия для браузерного расширения
*
* Использование:
* 1. Вставьте в консоль на странице VK
* 2. Откройте диалог и ждите событий
* 3. Или подпишитесь на события: window.addEventListener('vkify-spy-event', e => console.log(e.detail))
*/
(function() {
'use strict';
if (window.__vkifySpyModule) {
console.log('[VKify Spy] Уже запущен');
return;
}
window.__vkifySpyModule = true;
// Конфигурация событий (собрана в ходе реверс-инжиниринга)
const EVENT_CONFIG = {
63: { action: 'печатает сообщение', icon: '⌨️', category: 'typing' },
64: { action: 'записывает голосовое', icon: '🎤', category: 'voice' },
52: { action: 'событие друзей', icon: '👥', category: 'friends' },
90: { action: 'событие друзей', icon: '👥', category: 'friends' },
115: { action: 'звонит вам', icon: '📞', category: 'calls' },
10002: { action: 'удалил сообщение для всех', icon: '🗑️', category: 'delete' },
10004: { action: 'отправил сообщение', icon: '💬', category: 'messages' },
10005: { action: 'отредактировал сообщение', icon: '✏️', category: 'edit' },
10007: { action: 'прочитал сообщение', icon: '👁️', category: 'read' }
};
let eventCount = 0;
function parseEvent(update) {
const code = update[0];
const config = EVENT_CONFIG[code];
if (!config) return null;
let userId = null;
let extra = {};
switch (code) {
case 63: case 64:
userId = update[1];
break;
case 52:
userId = update[2];
extra.type = update[1];
break;
case 90:
userId = update[2];
extra.actionType = update[1];
break;
case 115:
userId = update[1]?.user_id || update[1]?.peer_id;
break;
case 10002:
userId = update[3];
break;
case 10004:
if (update[2] & 2) return null; // Исходящее сообщение
userId = update[4];
extra.text = update[6]?.substring(0, 100);
break;
case 10005:
if (update[2] & 2) return null;
userId = update[3];
extra.text = update[5]?.substring(0, 100);
break;
case 10007:
userId = update[1];
break;
}
if (!userId) return null;
return { code, userId, ...config, extra, timestamp: new Date().toISOString() };
}
// Перехват Long Poll
const originalFetch = window.fetch;
window.fetch = async function(url, options) {
const response = await originalFetch.apply(this, arguments);
if (/api\.vk\.com\/ruim/.test(url?.toString() || '')) {
try {
const clone = response.clone();
const data = await clone.json();
data.updates?.forEach(update => {
const event = parseEvent(update);
if (event) {
eventCount++;
console.log(
`%c[VKify Spy]%c ${event.icon} User ${event.userId}: ${event.action}`,
'background: #5181b8; color: white; padding: 2px 6px; border-radius: 3px;',
'color: inherit;',
event.extra
);
// Отправляем кастомное событие для подписчиков
window.dispatchEvent(new CustomEvent('vkify-spy-event', {
detail: event
}));
}
});
} catch (e) {}
}
return response;
};
// Публичное API
window.__vkifySpy = {
getEventCount: () => eventCount,
getConfig: () => EVENT_CONFIG,
status() {
console.log(`[VKify Spy] Активен. Перехвачено событий: ${eventCount}`);
}
};
console.log(`
%c🕵️ VKify Spy Module активирован!%c
%cМодуль перехватывает:%c
• Печать сообщений (⌨️)
• Голосовые сообщения (🎤)
• Новые сообщения (💬)
• Прочтение сообщений (👁️)
• Удаление сообщений (🗑️)
• Редактирование (✏️)
• События друзей (👥)
• Звонки (📞)
%cКак проверить:%c
1. Откройте любой диалог
2. Попросите собеседника написать вам
3. События появятся в консоли
%cКоманды:%c __vkifySpy.status()
`,
'color: #5181b8; font-weight: bold; font-size: 14px;', '',
'color: #4CAF50; font-weight: bold;', '',
'color: #2196F3; font-weight: bold;', '',
'background: #151515; padding: 2px 6px; border-radius: 3px;', ''
);
})();

Выводы и рекомендации
Что я узнал
- Long Poll всё ещё работает для большинства событий мессенджера
- Онлайн-статусы, возможно, вынесены на WebSocket (
eh.vk.com) — данные приходят в бинарном формате, полностью разобрать не удалось - Структура событий не всегда соответствует документации — нужно тестировать
- Коды 10000+ — это “новые” события для сообщений
Советы по реверс-инжинирингу
- Начинайте с простого — базовый перехват fetch даёт 80% информации
- Собирайте статистику — сохраняйте примеры каждого типа событий
- Тестируйте с помощником — некоторые события видны только со стороны
- Проверяйте WebSocket — современные приложения всё больше используют его
- Документируйте находки — через месяц вы забудете, что означает
update[3]
Этичность
Помните, что перехват данных — это инструмент. Используйте его для:
- ✅ Улучшения собственного опыта использования
- ✅ Образовательных целей
- ✅ Разработки полезных инструментов
И не используйте для:
- ❌ Слежки за людьми без их ведома
- ❌ Сбора чужих персональных данных
- ❌ Нарушения ToS сервисов
Полезные ссылки
- VK Long Poll Server Documentation — официальная документация (неполная)
- Chrome DevTools Network Panel — для анализа запросов
- MDN: Fetch API — для понимания перехвата
- MDN: WebSocket — для работы с WS
Статья написана в рамках разработки VKify



