Купил я себе домой NAS и начал потихоньку перетаскивать туда всякое. Сделал синк фотографий, добавил vaultwarden и занялся удалением всякого из облаков. И если с паролями все более менее просто, то с фотографиями, которые хранятся фактически у государства, было чуток посложнее. Покоя мне это не давало, решил удалить.

Захожу в интерфейс ЯДиска, удаляю все папки. Тут все хорошо. Хочу удалить фото - тут все плохо. Ну я попробовал зарегать аппку через их ЛК, но фотки недоступны через нее.

Если API не работает, остаётся единственный вариант - смотреть, что делает браузер. Весь интерфейс Яндекс.Диска живёт на одном endpoint:

POST https://disk.yandex.ru/models-v2?m={method}

Один URL на всё. Листинг файлов, удаление, альбомы, шаринг - всё проходит через него. По сути это BFF - Backend For Frontend. Фронтенд шлёт один тип запросов, а внутри уже происходит маршрутизация в разные сервисы.

Тело запроса всегда выглядит примерно одинаково:

{
    "sk": "...",
    "connection_id": "...",
    "apiMethod": "mpfs/space",
    "requestParams": {...}
}

Тут есть три важных момента.

Первый - sk. Это CSRF-токен, который вшивается прямо в HTML страницы при загрузке. Его можно просто вытащить регуляркой из ответа. Если он протух - сервер возвращает ошибку с новым токеном, и фронт просто повторяет запрос.

Второй - connection_id. Формально это идентификатор сессии запроса. На практике можно генерировать что угодно похожее на число, сервер не особо придирается.

Третий - apiMethod. Ну тут придется остановиться чуток поподробнее. Это и есть наш роутинг внутри разных систем.

Что я смог увидеть. mpfs/... - операции с файловой системой, intapi/... - всё, что связано с фотографиями, альбомами и индексами.

Собственно, тут видно, почему обычный API не видит фотографии, потому что они лежат в другом месте - в mpfs, а вот UI фотографий работает через отдельный индекс. И тут становится понятно, что у них внутри это разделено (хотя об этом и из интерфейса намекают), буквально два разных мира.

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

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

На практике ничего подобного. Говорят эти штуки вообще на разных языках, хотя и похожие.

Photoslice возвращает пути в таком виде: photounlim:/2022-07-12 09-12-16.JPG

А методы удаления из mpfs ожидают уже другой формат: /photounlim/2022-07-12 09-12-16.JPG

Это мешает удалению, потому что я получил 404 в ответ. Ну ок, поправил. Вроде удаляет. Сервер возвращает, что все получилось, запрашиваю фото, а файлы все еще там, причем вообще стабильно. Удаляешь кластер из 10 фотоу, получаешь тот же кластер с теми же фотками. И это сломало мне Яндекс.Диск в браузере.

Я захожу в Диск, открываю фото, он мне показывает сразу старые фотки, потом начинает делать тысячи запросов в models-v2, я улетаю в капчу как бот. А все потому, что MPFS считает файл удаленным, а Photoslice продолжает его отдавать в списке.

А произошло вот что. После удаления через API файлы исчезли из MPFS, но photoslice продолжал их отдавать как живые. Фронтенд Диска при открытии раздела "Фото" запрашивает кластеры через photo-get-clusters-with-resources — тяжёлый метод, который тянет полные метаданные с превью. Когда он начинает запрашивать кластер, а ресурсов в нём нет, то он пробует ещё раз. И ещё. В итоге браузер нагенерил сотни запросов, под сотню мегабайт JSON-ответов в консоли, и Яндекс закономерно решил, что я - бот. Капча, бан, и даже после прохождения капчи тот же цикл, потому что фантомные кластеры никуда не делись. Фактически рассинхрон двух внутренних систем сломал мне не API, а сам продукт.

Удалить по списку не получается, значит надо найти и отфильтровать уже удаленные файлы (кстати можно было бы получить список и просто пропускать 404, но это не наш путь).

Я нашел еще два метода - photo-get-clusters, photo-get-snapshot, дельты - всё это выглядит как кандидаты. Попробовал, но снапшот уже схлопнулся, там нет удобной разбивки по файлам, clusters возвращает вообще устаревшие данные, а дельты неудобны для полной синхронизации, тут скорее для инкрементального клиента подойдет.

Иду дальше смотреть, вижу метод - intapi/photo-get-clusters-with-resources. Это та же выдача кластеров, но с дополнительным слоем - реальными ресурсами из MPFS. И именно здесь появляется то, чего нет больше нигде:

{
    "resources": {
        "fetched": [...],
        "missing": [...]
    }
}

Тут наверно и так понятно, что fetched - файлы, которые реально существуют в MPFS, а missing - файлы, которые есть в photoslice-индексе, но уже удалены. Соответственно идем по кластерам, собираем только fetched, а missing игнорируем, что хотя бы делает процесс конечным. Только вот метод тяжелый и поэтому по нему приходится идти батчами по 5-10 кластеров за запрос, задержкой в несколько секунд и без особого фанатизма.

Что вышло в итоге:

  • Получить список кластеров

  • Для каждого - запросить with-resources

  • Отфильтровать fetched

  • Преобразовать пути

  • Отправить в bulk delete

Отдельная история это антибот. Яндекс проверяет не только частоту запросов, но и TLS fingerprint клиента, как мне показалось. Насколько жёстко я до конца не разобрался, но рисковать не хотелось. Стандартный Python requests имеет характерный TLS-отпечаток, который отличается от браузерного, и в теории может палиться. Поэтому на всякий случай я использовал curl_cffi с имперсонацией Firefox - библиотека умеет притворяться конкретным браузером на уровне TLS-хэндшейка.

from curl_cffi import requests
session = requests.Session(impersonate="firefox")

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

По итогу, у меня получился скрипт, который удаляет все 12 с лишним тысяч фотографий из облака яндекса примерно за час без единого клика.

Небольшой бонус. ИИ Яндекса пытается классифицировать ваши фотки на скриншоты, видео, но еще и на beautiful и unbeautiful.

И вот вам пример beautiful фотки:

А в unbeautiful попали какие-то смазанные фотки, фото моего колена, whiteboard с работы с архитектурой проекта и т.д.

Тем, кто дочитал, даю ссылку на gist. Если не работает, вы уж там сами с нейронками доковыряйте. Спасибо.

Благостно!
Благостно!
Список всех найденных методов

Все методы вызываются через POST https://disk.yandex.ru/models-v2?m={method}.

mpfs/ — файловая система

  • mpfs/space — информация о месте (used, free, trash, files_count)

  • mpfs/resources — листинг директории

  • mpfs/info — метаданные ресурса

  • mpfs/mkdir — создание папки

  • mpfs/url — получение URL для скачивания

  • mpfs/store — загрузка файла

  • mpfs/dir-size — размер директории

  • mpfs/bulk-resource-info — пакетная проверка ресурсов

  • mpfs/bulk-async-delete — массовое удаление (в корзину)

  • mpfs/bulk-async-copy — массовое копирование

  • mpfs/bulk-async-move — массовое перемещение

  • mpfs/bulk-async-restore — массовое восстановление из корзины

  • mpfs/bulk-operation-status — статус асинхронной операции по oid

  • mpfs/bulk-download-prepare — подготовка ZIP-архива для скачивания

  • mpfs/async-trash-drop-all — очистка корзины

  • mpfs/active-operations — список текущих асинхронных операций

  • mpfs/get-user-info — профиль пользователя (uid, login, email, timezone, тарифы)

  • mpfs/settings-set — обновление настроек

  • mpfs/link-app — подписанная ссылка на скачивание десктопного клиента

  • mpfs/get-download-url-scheme — схема URL для скачивания

  • mpfs/user-update-editor — обновление предпочтений редактора

  • mpfs/docs-resources — список документов

  • mpfs/photoslice-update-settings — настройки фотосреза (параметры неизвестны)

mpfs/ — альбомы

  • mpfs/albums-list — список всех альбомов

  • mpfs/album-with-items — альбом с содержимым

  • mpfs/album-get — информация об альбоме

  • mpfs/album-items — содержимое альбома

  • mpfs/album-append-items — добавление файлов в альбом

  • mpfs/album-publish — публикация альбома

  • mpfs/album-unpublish — снятие публикации альбома

  • mpfs/album-set-attr — обновление атрибутов альбома

  • mpfs/album-remove — удаление альбома

  • mpfs/album-get-faces — альбомы по распознанным лицам

  • mpfs/album-get-favorite — альбом "Избранное"

  • mpfs/album-find-in-favorites — поиск ресурса в избранном

  • mpfs/album-bulk-download-prepare — ZIP-архив альбома

  • mpfs/albums-create-with-items — создание альбома с файлами

  • mpfs/bulk-album-item-remove — массовое удаление из альбома

  • mpfs/bulk-albums-exclude-from-generated — исключение из автогенерируемых альбомов

mpfs/ — публичный доступ

  • mpfs/set-public — сделать ресурс публичным

  • mpfs/set-private — сделать ресурс приватным

  • mpfs/get-public-settings — настройки публичной ссылки

  • mpfs/set-public-settings — обновление настроек публичной ссылки

  • mpfs/get-available-public-settings — доступные параметры публикации

  • mpfs/bulk-async-share-remove-public-links — массовое удаление публичных ссылок

  • mpfs/public-album-download-url — URL скачивания публичного альбома

mpfs/ — общие папки

  • mpfs/share-invite-user — приглашение в общую папку

  • mpfs/share-activate-invite — принятие приглашения

  • mpfs/share-reject-invite — отклонение приглашения

  • mpfs/share-remove-invite — удаление приглашения

  • mpfs/share-change-rights — изменение прав пользователя

  • mpfs/share-kick-from-group — удаление пользователя из папки

  • mpfs/share-leave-group — выход из общей папки

  • mpfs/share-unshare-folder — прекращение общего доступа

  • mpfs/share-users-in-group — список участников общей папки

  • mpfs/share-list-not-approved-folders — папки с ожидающими приглашениями

mpfs/ — версионирование

  • mpfs/versioning-list — список версий файла

  • mpfs/versioning-restore — восстановление версии

  • mpfs/versioning-save — сохранение версии

mpfs/ — Office, семья, VD, Wfolio

  • mpfs/office-action-check — проверка действия в офисном редакторе

  • mpfs/office-set-access-state — настройка доступа к документу

  • mpfs/create-family-folder — создание семейной папки

  • mpfs/get-family-album — семейный альбом

  • mpfs/get-family-slice — семейный фотосрез

  • mpfs/vd-info — информация о виртуальном диске

  • mpfs/vd-space — место на виртуальном диске

  • mpfs/vd-permissions-list — права доступа к VD

  • mpfs/vd-permissions-update — обновление прав VD

  • mpfs/wfolio-create-project — создание проекта Wfolio (фотобанк)

  • mpfs/wfolio-delete-project — удаление проекта Wfolio

intapi/ — фотосрез

  • intapi/photo-init-snapshot — инициализация снапшота, возвращает текущую ревизию

  • intapi/photo-get-snapshot — полный снапшот кластеров (id, from, to, size, albums)

  • intapi/photo-get-clusters — кластеры с путями файлов (лёгкий, без превью)

  • intapi/photo-get-clusters-with-resources — кластеры с полными метаданными + fetched/missing

  • intapi/photo-get-deltas — инкрементальные изменения между ревизиями

  • intapi/photo-get-albums-slices — ИИ-категории: beautiful, unbeautiful, screenshots, videos, camera

intapi/ — прочее

  • intapi/journal — лента последних действий

  • intapi/journal-counters — счётчики непрочитанных событий

  • intapi/journal-group — группировка событий журнала

  • intapi/add-to-docs — добавление файла в "Документы"

  • intapi/remember-block — блок "Воспоминания" (автоподборки фото)

  • intapi/video-streams — видеопотоки для воспроизведения

И не повторяйте это, если хотите продолжить пользоваться Яндекс.Диском! У меня интерфейс до сих пор думает, что фотки есть, а на самом деле их нет. Может быть, у них там что-то посинкается в итоге, но я бы на вашем месте не ломал себе всё.