Metadata-Version: 2.4
Name: osu-finder
Version: 1.0.0
Summary: Smart search and analysis of osu! maps using advanced filters and profiles
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: aiohttp>=3.8.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: rosu-pp-py>=4.0.2
Requires-Dist: rich>=13.0.0

# osu! Map Finder

Модульный скрипт для авто-поиска, глубокого анализа (PP/SR/страйны **с учётом
модов**, рассчитанные локально) и скачивания карт osu! по гибким фильтрам.

## Архитектура

```
osu_map_finder/
├── main.py                  # точка входа, оркестрация пайплайна, вывод в консоль (rich)
├── config.example.yaml       # шаблон конфига — скопируйте в config.yaml
├── requirements.txt
└── osu_finder/
    ├── config.py             # ConfigManager: загрузка и валидация config.yaml
    ├── local_cache.py         # LocalCache: индекс уже скачанных карт (папка Songs)
    ├── api_client.py           # OsuApiClient: официальный osu! API v2 (OAuth2)
    ├── pp_analyzer.py            # PpAnalyzer: расчёт PP/SR/страйнов через rosu-pp-py
    ├── downloader.py              # Downloader: скачивание .osz + открытие в ОС
    └── models.py                   # dataclasses: DiffAnalysis, BeatmapCandidate
```

Пайплайн (main.py → `run()`):

1. **LocalCache** сканирует папку `Songs` и строит `set()` из BeatmapSet ID.
2. **OsuApiClient** получает OAuth2-токен (Client Credentials Grant) и
   постранично обходит `/beatmapsets/search` с базовыми фильтрами
   (режим, статус, ключевые слова, сортировка).
3. Каждый новый (не в локальном кэше) сет проверяется по всем сложностям:
   для каждой сложности с зеркала скачивается **только лёгкий .osu файл**.
4. **PpAnalyzer** парсит .osu через `rosu_pp_py.Beatmap` и для каждой
   заданной в конфиге мод-комбинации считает звёзды, Aim/Speed strain и PP
   (`rosu_pp_py.Difficulty` / `Performance`). BPM и длительность
   пересчитываются под моды через `clock_rate` из
   `rosu_pp_py.BeatmapAttributesBuilder` — то же значение, что использует
   сама игра (1.5× для DT/NC, 0.75× для HT/DC и т.д.), без ручных таблиц.
5. Результат проверяется по `advanced_filters` (длина, BPM, PP, звёзды,
   стиль geймплея jump/stream/hybrid — по соотношению `speed/aim strain`).
6. Если хотя бы одна сложность/мод-комбо прошла фильтр — **Downloader**
   скачивает полный `.osz` с зеркала и (если `auto_open: true`) открывает
   его в ОС (`os.startfile` на Windows — двойной клик по `.osz` в
   установленном клиенте osu! запускает импорт карты).

## Установка

```bash
pip install -r requirements.txt
cp config.example.yaml config.yaml
# отредактируйте config.yaml под себя
python main.py --config config.yaml
```

Флаги: `--verbose` / `-v` — подробный (DEBUG) вывод.

## Получение client_id / client_secret

1. Зайдите на https://osu.ppy.sh/home/account/edit#oauth
2. "New OAuth Application" → любое имя, Callback URL можно оставить пустым
   (используется только Client Credentials Grant — без входа пользователя).
3. Скопируйте `Client ID` и `Client Secret` в `config.yaml`.

## Важные оговорки

- **`/beatmapsets/search` официально не задокументирован** — сама osu! в
  своей документации помечает этот эндпоинт как "TODO: documentation".
  Параметры (`q`, `s`, `m`, `sort`, `g`, `l`) в `api_client.py`
  (`_build_search_params`) определены по фактическому поведению
  веб-клиента и сторонних библиотек (aiosu, osu-api-v2-js). Если osu!
  когда-нибудь поменяет их — правьте только этот один метод.
- **Зеркало для скачивания.** Официальный API v2 не отдаёт файлы карт без
  авторизации реального пользователя (`lazer`-скоуп), поэтому используется
  сторонний мирроринг-сервис (по умолчанию `catboy.best`). Такие зеркала
  периодически меняют домен/эндпоинты (на смену chimu.moe в своё время
  пришёл catboy.best) — если скачивание перестало работать, обновите
  `mirror.base_url` / `mirror.osu_file_path` / `mirror.osz_download_path`
  в конфиге под актуальный сервис.
- **Rate limit.** osu! просит не более ~60 запросов/мин к официальному API
  (см. `execution.request_delay`, по умолчанию 1 запрос/сек). Скрипт также
  сам обрабатывает HTTP 429 с экспоненциальной паузой.
- Скрипт предназначен для личного использования (поиск карт под свой
  уровень/стиль игры) — пожалуйста, не используйте его для агрессивного
  массового скрапинга, это противоречит Terms of Use osu! API.

## Пример config.yaml

См. `config.example.yaml` — там расписан каждый параметр.
