Обход лимитов Яндекс.Диска

Обход лимитов Яндекс.Диска

Купил я себе домой 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. Если не работает, вы уж там сами с нейронками доковыряйте. Спасибо.