
Я, как и многие, пользуюсь величайшим облачным хранилищем — перепиской с самим собой. Туда улетают интересные посты, статьи, лайфхаки, мемы и фильмы, которые «посмотрю потом». Увидел я как-то этот мем и задумался, что нужно бы забэкапить это дело.
Привет! Меня зовут Игорь. В статье рассказываю про инструмент для бэкапа сообщений из VK с сохранением вложений.
Введение
За долгое время в переписке с самим собой накопилась большая коллекция полезной информации. Половину из которой я уже забыл. Доступ к ней не очень удобный, приходится проскроллить не одну сотню сообщений. А бывает и сообщества удаляют свои посты или ограничивают доступ, и вместе с этим пропадает то, что ты сохранил :(

Изначально решил поискать уже что-то готовое, но полезного и работающего не попалось. Из всего что попалось, более менее была полезна информация про возможность запросить у VK “что VK о тебе знает и хранит”. Посмотреть можно тут: «Как получить архив со своими данными?». Через архив можно получить переписку, лайки, группы, список друзей, чёрные списки, денежные переводы, но к сожалению вложения не скачиваются.
Решил написать скриптик самостоятельно, который будет конвертировать сообщения в md файлы со всеми вложениями. Да и давно хотелось попробовать AI-агентскую разработку.
Требования и получившийся инструмент
Требования сформировал достаточно быстро:
сохранить переписку со всеми вложениями: посты, фотографии, видео, гифки, файлы, ну и конечно же мемы :).

Причём сохранять в читаемом виде, решил использовать markdown файлы;
иметь удобный доступ для чтения этой библиотеки (я заметки храню на GitHub). В результате на GitHub у меня это выглядит так:

Как моя структура бэкапа выглядит на GitHub позднее добавилась идея добавить web-редактор, в котором можно будет избавляться от мусорных сообщений, переименовывать статьи и раскладывать сообщения по разделам;
работать полностью локально, без SaaS и любой передачи сообщений на сторонние серверы.
В итоге получился вот такой вот веб-редактор:

Ссылка исходников на GitHub: Тык.
Всё полностью локально - данные не покидают компьютер. Для тех, кто не хочет ставить Python, есть портативный exe, прикреплённый к релизу: скачал, запустил, браузер открылся сам.
Дальнейшая часть статьи посвящена тому как шла работа, какие были идеи и какие появились наблюдения из работы с ИИ-агентом. Если подробности не интересуют, то эту часть можно пропустить.
P.S. В конце статьи есть ссылки на другие мои статьи, если вам это интересно.
Часть 1. Какие были идеи и что получилось
Сначала думал решить задачу мини-приложением внутри VK, работая непосредственно с API VK. С агентом за пару часов накидали весь код по выгрузке сырых сообщений, но упёрся в ограничение, что теперь API VK не предоставляет доступ к сообщениям (Документация).

Доступ формально можно получить через личную переписку с техподдержкой, но у меня не получилось :(
Переписка с техподдержкой:
Запрос на подключение пермишена messages
Здравствуйте.
Я хотел бы получить доступ по REST API к сообщениям своего профиля. Нужен режим readonly (возможности отправлять, изменять, удалять и т.д. не нужны. Нужно только чтение).
Я хотел бы иметь возможность читать свою переписку из диалогов с помощью самописных скриптов. Важно, чтобы были доступны возможности читать вложения, пересланные сообщения от других людей и самого себя, пересланные посты из сообществ. P.S. Вариант “создание архива профиля” мне не подходит, уже пробовал.
По сути мне нужен функционал messages.getHistory и другие функции связанные с чтением сообщений. https://dev.vk.com/ru/method/messages.getHistory
AppId: XXXXXXXX В открытый доступ приложение публиковать не собираюсь, нужно только для личного использования. Скрипты собираюсь использовать без серверной логики. Вся логика будет на клиенте, взаимодействие через vk-bridge.
Ответ техподдержки на подключение пермишена messages
Здравствуйте, Игорь!
Спасибо за ожидание.
С 15 января 2019 года мы частично ограничили доступ к методам секции messages: vk.cc/cog0ot. Сделали это мы в целях безопасности наших пользователей. Если ключ доступа с такими правами окажется у злоумышленника, в его руки могут попасть личные данные пользователя. Уверен, вы понимаете, насколько это может быть опасно.
По умолчанию методы этой секции доступны для вызова только с ключом доступа сообщества и от его имени.
Пошёл искать обходные пути. Порыскав в интернете нашёл, что доступ к пермишенам messages не закрыт у чатов сообществ. Решил попробовать вариант с пересылкой сообщений в чат сообщества, после чего можно с помощью REST API достать сырые json данные через API VK. В итоге сделал такую реализацию(код в истории гита где-то болтается), но такое решение требует ручной пересылки сообщений в чат сообщества. Решил попробовать написать код, который через консоль браузера сможет переслать сообщения юзера в чат сообщества. Идея с сообществом не нравилось, но решил попробовать.
Если кому нужно, вот код для пересылки сообщений из диалога в чат сообщества.
Код
Код можно упростить. Это был эксперимент. Для работы зайти в диалог и запустить в консоли браузера. Пересылает текущие загруженные сообщения на клиенте. Ограничение пересылки 100 за одно сообщение.
let commynityChatPeerId = -239911892 // перейти в чат сообщества и скопировать из url let fromChatPeerId = 123456789 // перейти в диалог откуда хотите переслать сообщения и скопировать из url let article = document.querySelector('article[class*="ConvoHistory__messageBlock"]'); let key = Object.keys(article).find(k => k.startsWith('__reactFiber$')); let fiber = article[key]; function findStoreProvider(fiber) { while (fiber) { if (fiber.type?.$$typeof === Symbol.for('react.provider')) { const val = fiber.memoizedState?.value || fiber.pendingProps?.value; if (val?.getState) return val; // store {getState, subscribe, dispatch} } fiber = fiber.return; // или fiber._debugOwner } return null; } storeProvider = findStoreProvider(fiber) let messagesToForward = storeProvider.getState().convos.get(fromChatPeerId).history.confirmed .filter(n => n.kind === "Node") .map(n => n.item); storeProvider.dispatch({ type: "UserHitSend", entrypoint: "forward_messages_target", text: "auto sent", attaches: {}, timestamp: Date.now(), peerId: commynityChatPeerId, extra: {}, forwarded: messagesToForward.filter(m => m.kind === "Normal") });
В итоге получилось добиться желаемого, но вариант с сообществом меня не очень устраивал. Как-то сложно и не удобно… Раз всё равно пришлось возиться с ручным выполнение скриптов в консоли, то решил попробовать просто сохранить сообщения в сырой json, без пересылки в чат сообщества. Были опасения, что загруженные сообщения на клиенте могут содержать не все поля, но к счастью данные оказались те же самые и в полном объёме.
В итоге написал небольшой скриптик, который выкачивает сообщения из диалога в обычный json-файл. Как им пользоваться подробно описано в веб-редакторе.

Код вставлю для тех кому просто интересно посмотреть как скачать данные сообщений.
Код
async function getHistory(peerId, fromDate, fromMessageId) { debugLog("getting history started...") const limit = 200; let items = []; let groups = new Map(); let profiles = new Map(); while (true) { let response = await window.vkApi.api("messages.getHistory", { peer_id: peerId, offset: items.length, count: limit, extended: 1, v: "5.199" }); if(!response) break; if(!response.items?.length) break; let filteredItems = response.items.filter(i => (fromMessageId == null || i.id > fromMessageId) && (fromDate == null || i.date > fromDate) ); items.push(...filteredItems); if(response.groups) response.groups.forEach(i => groups.set(i.id, i)); if(response.profiles) response.profiles.forEach(i => profiles.set(i.id, i)); console.log(`Loaded ${items.length}/${response.count}`); let isAnyItemFiltered = filteredItems.length != response.items.length; if(isAnyItemFiltered) break; if(items.length >= response.count) break; await new Promise(r => setTimeout(r, 350)); // 0.35s } return { peer_id: peerId, items: items, groups: Array.from(groups.values()), profiles: Array.from(profiles.values()) }; } function saveToFile(data, filename) { let blob = new Blob([JSON.stringify(data, null, 0)], {type: 'application/json'}); let a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; a.click(); URL.revokeObjectURL(a.href); } let peerId = 123456789; // нужно заполнить let fromDate = null; let fromMessageId = null; let fromDateUnixTimestamp = fromDate != null ? Math.floor(fromDate.getTime() / 1000) : null; let data = await getHistory(peerId, fromDateUnixTimestamp, fromMessageId); let currentDate = Date.now(); let filename = `messages_peerId_${peerId}_${currentDate}.json`; data.currentDate = currentDate; saveToFile(data, filename);
Основная работа была сделана. Осталось только написать веб-редактор, который будет конвертировать сырые json данные в удобный для чтения markdown, скачивать вложения и давать возможность разгребать сообщения. Но этот процесс описывать не буду, думаю он не так интересен.
Если заинтересовала работа веб-редактора, то можно почитать readme файл на GitHub (Тык).
Часть 2. Наблюдения из разработки с агентом
Сразу дисклеймер: это был мой первый опыт агентской разработки, код написан агентом практически целиком. Качество кода оставляет желать лучшего, но для личных нужд сойдёт.
Теперь наблюдения:
Галлюцинации. Иногда модель статистически смещала веса для генерации токенов и вставляла символы других языков - например, китайские иероглифы посреди русского текста. Иногда внезапно переходил на английский. Плюс забавные ошибки орфографии. Возможно это фича запикивания матов :)


Обфускация умерла. Самое интересное: чтобы сделать вызовы к API VK, агент разобрался, как устроен обфусцированный JS-код веб-версии VK. То, ради чего раньше пришлось бы вручную перебирать обфусцированный код, агент сделал сам.
Заодно из исходников я узнал, что сообщения в VK можно выделять через Shift и пересылать пачкой до 100 штук за раз.
Лимиты токенов. Лимиты модели агента небольшие, но удалось разобрать обфусцированный код. За лимиты выходил один-два раза, когда забыл сказать агенту, что изучение надо вести экономно.
Прокрастинация. Пока агент думает, руки сами тянутся отвлечься. Постоянные переключения «поставил задачу-жду-вернулся» съедают фокус и время.
Выбор плохого решения агентом. Когда агент выбирает плохое решение или не до конца додумал задачу, я могу его поправить, потому что знаю контекст всего приложения. В продакшене на большой кодовой базе так делать уже будет трудновато.
UI и код вперемешку. Отделение UI от логики и данных получилось так себе. Возможно, если бы обсудил архитектуру с самого начала, вышло бы лучше.
Не инициализирует начальное состояние UI. Постоянно забывает подготовить состояние элементов интерфейса перед показом. Любопытно, что ровно это же наблюдается у начинающих программистов.
«И так сойдёт». Основной девиз агента. Генерит кучу неуверенного кода. Новый, не обучаемый джун be like :)

Выводы
Я выполнил свою задачу по бэкапу. Из моих ~1700 сообщений, осталось около 700, больше половины оказалось мусором, 100-200 постов из сообществ были удалены или скрыты авторами сообществ. Размер репозитория с контентом на GitHub занял 0.6 Gb
Впечатления от ИИ-агента интересные. С одной стороны для личных решений, которые можно в любой момент выкинуть, агент отлично справился с задачей. Web-разработка не моя специализация и тут он точно ускорил мне работу. С точки зрения продакшен разработки , его работа оставляет желать лучше, но думаю во многом играет роль моих скиллов работы с ИИ-агентом. Чтож посмотрю на более мощных моделях.
Если кто-то хочет потыкать проект на GitHub: Тык
Дополнительно
Если вас тоже интересует улучшение рабочих процессов, то возможно вас заинтересуют другие мои статьи:
Как при помощи ведения дневника событий искать точки роста, оценивать свой результат, проводить ретроспективу и давать конкретную обратную связь: Статья:«Activity Journal. Самоорганизация и анализ рабочего времени. На что уходят рабочие часы?»
Локальный инструмент для выгрузки данных из Google Sheets в json. Статья: «Gamedev. Парсинг данных из Google Sheets и Excel в json без привлечения программистов»
Как с помощью код-анализа можно ускорить изучение\понимание кодовой базы: Статья: «Unity, Roslyn и code analysis. Прикладная задача визуализации графа состояний»

