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