Я, как и многие, пользуюсь величайшим облачным хранилищем — перепиской с самим собой. Туда улетают интересные посты, статьи, лайфхаки, мемы и фильмы, которые «посмотрю потом». Увидел я как-то этот мем и задумался, что нужно бы забэкапить это дело.

Привет! Меня зовут Игорь. В статье рассказываю про инструмент для бэкапа сообщений из VK с сохранением вложений.

Введение

За долгое время в переписке с самим собой накопилась большая коллекция полезной информации. Половину из которой я уже забыл. Доступ к ней не очень удобный, приходится проскроллить не одну сотню сообщений. А бывает и сообщества удаляют свои посты или ограничивают доступ, и вместе с этим пропадает то, что ты сохранил :(

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

Решил написать скриптик самостоятельно, который будет конвертировать сообщения в md файлы со всеми вложениями. Да и давно хотелось попробовать AI-агентскую разработку.

Требования и получившийся инструмент

Требования сформировал достаточно быстро:

  • сохранить переписку со всеми вложениями: посты, фотографии, видео, гифки, файлы, ну и конечно же мемы :).

    Причём сохранять в читаемом виде, решил использовать markdown файлы;

  • иметь удобный доступ для чтения этой библиотеки (я заметки храню на GitHub). В результате на 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: Тык

Дополнительно

Если вас тоже интересует улучшение рабочих процессов, то возможно вас заинтересуют другие мои статьи: