diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..0744e6f --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,607 @@ +# 🏗️ Архитектура YandexMusic + +Полное описание архитектуры, компонентов и взаимодействия между слоями решения YandexMusic. + +## 📋 Содержание + +- [Обзор архитектуры](#обзор-архитектуры) +- [Структура слоёв](#структура-слоёв) +- [Основные компоненты](#основные-компоненты) +- [Потоки данных](#потоки-данных) +- [Паттерны проектирования](#паттерны-проектирования) +- [Модульная структура](#модульная-структура) +- [Расширяемость](#расширяемость) + +## 🔍 Обзор архитектуры + +### Многослойная архитектура + +Проект построен на классической трёхслойной архитектуре с элементами DDD: + +``` +┌────────────────────────────────────────┐ +│ CLI Layer (YaMusicCli) │ ← Точка входа +├────────────────────────────────────────┤ +│ Client Layer (YandexMusic) │ ← Удобный интерфейс +├────────────────────────────────────────┤ +│ API Layer (YandexMusic.API) │ ← Логика API +├────────────────────────────────────────┤ +│ Data Layer (Models) │ ← Данные и сущности +├────────────────────────────────────────┤ +│ Infrastructure Layer │ ← HTTP, Serialization +└────────────────────────────────────────┘ +``` + +### Ключевые принципы + +- **Separation of Concerns** - каждый слой отвечает за одно +- **Dependency Injection** - слабая связанность через интерфейсы +- **Single Responsibility** - каждый класс решает одну задачу +- **Open/Closed Principle** - открыто для расширения, закрыто для модификации +- **Asynchronous First** - всё асинхронное по умолчанию + +## 📦 Структура слоёв + +### 1. CLI Layer (YaMusicCli) + +**Назначение:** Командная строка интерфейс для пользователей. + +``` +YaMusicCli/ +├── Program.cs # Точка входа, парсинг аргументов +└── Commands/ # Команды для CLI + ├── SearchCommand + ├── PlaylistCommand + └── TrackCommand +``` + +**Ответственность:** + +- Парсинг команд пользователя +- Вывод результатов в консоль +- Обработка пользовательского ввода +- Форматирование данных для вывода + +**Зависимости:** YandexMusic (Client Layer) + +### 2. Client Layer (YandexMusic) + +**Назначение:** Удобный оборачиватель (wrapper) для работы с API. + +``` +YandexMusic/ +└── YandexMusicClient.cs + ├── Initialization (конструктор) + ├── Authorization (авторизация) + ├── Properties (доступ к API и хранилищу) + └── Cleanup (dispose) +``` + +**Основной класс:** + +```csharp +public class YandexMusicClient : IDisposable +{ + // Инициализация + public YandexMusicClient( + CookieContainer? cookieContainer = null, + IWebProxy? proxy = null, + TimeSpan? timeout = null, + string? userAgent = null) + + // Авторизация + public async Task AuthorizeAsync(string login, string password) + public async Task AuthorizeByTokenAsync(string token) + + // Свойства доступа + public YandexMusicApi Api { get; } // API Яндекс Музыки + public AuthStorage AuthStorage { get; } // Хранилище данных + public HttpClient HttpClient { get; } // HttpClient + public YAccount Account { get; } // Информация об аккаунте + public bool IsAuthorized { get; } // Статус авторизации + public YnisonPlayer? Ynison { get; } // WebSocket плеер +} +``` + +**Ответственность:** + +- Упрощение работы с API +- Управление HttpClient и cookies +- Обработка авторизации +- Инициализация компонентов + +**Зависимости:** YandexMusic.API (API Layer) + +### 3. API Layer (YandexMusic.API) + +**Назначение:** Низкоуровневой доступ к API Яндекс Музыки. + +``` +YandexMusic.API/ +├── YandexMusicApi.cs # Главный класс - фасад +├── API/ # API классы по функциям +│ ├── YAlbumAPI +│ ├── YArtistAPI +│ ├── YTrackAPI +│ ├── YPlaylistAPI +│ ├── YRadioAPI +│ ├── YSearchAPI +│ └── ... (остальные API) +├── Requests/ # Построители запросов +│ ├── Common/ +│ ├── Album/ +│ ├── Artist/ +│ └── ... (по категориям) +├── Models/ # Модели данных +│ ├── Common/ +│ ├── Album/ +│ ├── Track/ +│ └── ... (по типам) +└── Common/ # Вспомогательные компоненты + ├── AuthStorage + ├── Providers/ + ├── Encryptor + └── Ynison/ +``` + +**Главный класс:** + +```csharp +public class YandexMusicApi +{ + // API Методы + public YAlbumAPI Album { get; } + public YArtistAPI Artist { get; } + public YTrackAPI Track { get; } + public YPlaylistAPI Playlist { get; } + public YRadioAPI Radio { get; } + public YSearchAPI Search { get; } + // ... и так далее для всех веток API +} +``` + +**API Классы:** + +Каждый API класс наследуется от `YCommonAPI`: + +```csharp +public abstract class YCommonAPI +{ + protected readonly YandexMusicApi api; + protected YCommonAPI(YandexMusicApi yandex) => api = yandex; +} + +// Пример конкретного API +public class YTrackAPI : YCommonAPI +{ + public YTrackAPI(YandexMusicApi yandex) : base(yandex) { } + + public async Task GetTrackAsync(string trackId) { } + public async Task> GetTracksAsync(IEnumerable ids) { } + // ... остальные методы +} +``` + +**Ответственность:** + +- Построение HTTP запросов +- Вызов провайдера запросов +- Десериализация ответов +- Обработка ошибок API + +### 4. Data Layer (Models) + +**Назначение:** Модели данных, отражающие структуру API Яндекс Музыки. + +``` +Models/ +├── Common/ # Общие модели +│ ├── YBaseModel # Базовая модель с контекстом +│ ├── YResponse # Ответ от API +│ ├── YError # Ошибка +│ └── ... (остальные) +├── Album/ # Модели альбомов +│ ├── YAlbum +│ └── YAlbumMenuItem +├── Track/ # Модели треков +│ ├── YTrack +│ ├── YTrackSupplement +│ └── YTrackSimilar +├── Playlist/ # Модели плейлистов +│ ├── YPlaylist +│ ├── YPlaylistChange +│ └── YPlaylistMadeFor +└── ... (остальные модели) +``` + +**Базовая модель:** + +```csharp +public abstract class YBaseModel +{ + [JsonIgnore] + public YExecutionContext? Context { get; set; } +} + +// Пример модели +public class YTrack : YBaseModel +{ + [JsonPropertyName("id")] + public string Id { get; set; } + + [JsonPropertyName("title")] + public string Title { get; set; } + + [JsonPropertyName("duration_ms")] + public int DurationMs { get; set; } + + // ... остальные свойства +} +``` + +**Ответственность:** + +- Представление данных API +- System.Text.Json сериализация/десериализация +- Хранение контекста выполнения (опционально) + +### 5. Infrastructure Layer + +**Назначение:** Низкоуровневая инфраструктура для работы с HTTP и сериализацией. + +``` +Common/ +├── Providers/ # Провайдеры запросов +│ ├── IRequestProvider # Интерфейс +│ ├── DefaultRequestProvider +│ ├── CommonRequestProvider +│ └── MockRequestProvider +├── AuthStorage # Управление авторизацией +├── Encryptor # Шифрование данных +├── DataDownloader # Загрузка файлов +└── Ynison/ # WebSocket + ├── YnisonPlayer + └── YnisonWebSocket +``` + +**IRequestProvider:** + +```csharp +public interface IRequestProvider +{ + // Выполняет HTTP запрос + Task GetWebResponseAsync( + HttpRequestMessage message, + HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead); + + // Преобразует ответ в модель + Task GetDataFromResponseAsync( + YandexMusicApi api, + HttpResponseMessage response); +} +``` + +## 🔄 Потоки данных + +### Типичный поток запроса + +``` +1. User Action + ↓ +2. YandexMusicClient Method + ↓ +3. YandexMusicApi.SomeAPI.SomeMethodAsync() + ↓ +4. YRequestBuilder builds HttpRequestMessage + ↓ +5. IRequestProvider.GetWebResponseAsync() + ↓ +6. HttpClient sends request + ↓ +7. Server responds with HttpResponseMessage + ↓ +8. IRequestProvider.GetDataFromResponseAsync() + ↓ +9. System.Text.Json deserializes to Model + ↓ +10. Model returned to caller +``` + +### Пример: Получение трека + +```csharp +// 1. Пользователь вызывает метод +var track = await client.Api.Track.GetTrackAsync("trackId123"); + +// 2. YTrackAPI.GetTrackAsync() создаёт запрос +var builder = new YGetTracksBuilder(api); +var message = builder.Build("trackId123"); + +// 3. Запрос отправляется через провайдер +var response = await authStorage.Provider.GetWebResponseAsync(message); + +// 4. Ответ преобразуется в модель +var track = await provider.GetDataFromResponseAsync(api, response); + +// 5. Модель возвращается пользователю +return track; +``` + +## 🎯 Паттерны проектирования + +### 1. Facade Pattern (YandexMusicApi) + +```csharp +// Фасад предоставляет простой интерфейс к сложной подсистеме +public class YandexMusicApi +{ + public YAlbumAPI Album { get; } // Скрывает сложность + public YArtistAPI Artist { get; } // инициализации + public YTrackAPI Track { get; } // каждого компонента +} +``` + +### 2. Builder Pattern (Request Builders) + +```csharp +// Построитель для конструирования запросов +public class YSearchBuilder : YRequestBuilder +{ + private string _query; + private int _page = 0; + + public YSearchBuilder Query(string q) { _query = q; return this; } + public YSearchBuilder Page(int p) { _page = p; return this; } + public override HttpRequestMessage Build() { /* ... */ } +} + +// Использование +var search = new YSearchBuilder() + .Query("Beatles") + .Page(1) + .Build(); +``` + +### 3. Strategy Pattern (IRequestProvider) + +```csharp +// Интерфейс позволяет выбрать стратегию обработки запросов +public interface IRequestProvider +{ + Task GetWebResponseAsync(HttpRequestMessage message); +} + +// Разные реализации для разных сценариев +public class DefaultRequestProvider : IRequestProvider { } +public class MockRequestProvider : IRequestProvider { } +public class CustomRequestProvider : IRequestProvider { } +``` + +### 4. Dependency Injection + +```csharp +// Зависимости передаются через конструктор +public class YTrackAPI : YCommonAPI +{ + protected readonly YandexMusicApi api; // Injected + + public YTrackAPI(YandexMusicApi yandex) => api = yandex; +} +``` + +### 5. Template Method Pattern + +```csharp +// Базовый класс определяет структуру +public abstract class YCommonAPI +{ + protected readonly YandexMusicApi api; + protected YCommonAPI(YandexMusicApi yandex) => api = yandex; +} + +// Подклассы реализуют специфичные методы +public class YAlbumAPI : YCommonAPI +{ + // Реализация методов для альбомов +} +``` + +## 🧩 Модульная структура + +### Логическое разделение на модули + +``` +Core Module (YandexMusic.API) +├── Authentication Module +│ ├── AuthStorage +│ └── Authorization methods +├── Request Module +│ ├── IRequestProvider +│ ├── Request builders +│ └── HttpContext +├── Model Module +│ └── All data models +└── API Module + ├── YAlbumAPI + ├── YTrackAPI + ├── YPlaylistAPI + └── ... (each API endpoint) + +Client Module (YandexMusic) +├── YandexMusicClient +└── Client configuration + +CLI Module (YaMusicCli) +├── Program.cs +└── CLI commands +``` + +### Зависимости между модулями + +``` +CLI Module + ↓ depends on +Client Module + ↓ depends on +Core Module + ├── Authentication Module + ├── Request Module + ├── Model Module + └── API Module +``` + +## 🔌 Расширяемость + +### Как добавить новый API метод + +```csharp +// 1. Создайте builder в Requests/YourCategory/ +public class YGetYourResourceBuilder : YRequestBuilder +{ + public override HttpRequestMessage Build() + { + // Реализуйте построение запроса + } +} + +// 2. Добавьте метод в соответствующий API класс +public class YYourResourceAPI : YCommonAPI +{ + public async Task GetYourResourceAsync(string id) + { + var builder = new YGetYourResourceBuilder(api); + var request = builder.Build(); + return await new YRequest(request, api, api.AuthStorage).GetResponseAsync(); + } +} + +// 3. Используйте новый метод +var resource = await api.YourResource.GetYourResourceAsync("id123"); +``` + +### Как создать custom RequestProvider + +```csharp +public class MyCustomRequestProvider : IRequestProvider +{ + private readonly AuthStorage _storage; + + public MyCustomRequestProvider(AuthStorage storage) + { + _storage = storage; + } + + public async Task GetWebResponseAsync( + HttpRequestMessage message, + HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) + { + // Ваша логика обработки запроса + using var handler = new HttpClientHandler(); + using var client = new HttpClient(handler); + return await client.SendAsync(message, completionOption); + } + + public async Task GetDataFromResponseAsync( + YandexMusicApi api, + HttpResponseMessage response) + { + // Ваша логика десериализации + var content = await response.Content.ReadAsStringAsync(); + return JsonSerializer.Deserialize(content) ?? default!; + } +} + +// Использование +var provider = new MyCustomRequestProvider(storage); +var storage = new AuthStorage(provider); +``` + +### Как создать extension method + +```csharp +public static class YPlaylistExtensions +{ + public static async Task> GetAllTracksAsync( + this YPlaylist playlist, + YandexMusicApi api) + { + // Расширение функциональности существующего класса + var allTracks = new List(); + var currentPage = 0; + + while (true) + { + var page = await api.Playlist.GetPlaylistAsync( + playlist.Uid, + page: currentPage); + + if (page?.Tracks?.Count == 0) + break; + + allTracks.AddRange(page.Tracks ?? []); + currentPage++; + } + + return allTracks; + } +} +``` + +## 📊 Диаграмма взаимодействия компонентов + +``` +┌──────────────────┐ +│ CLI Layer │ +│ (YaMusicCli) │ +└────────┬─────────┘ + │ + ↓ +┌──────────────────────────┐ +│ Client Layer │ +│ (YandexMusicClient) │ +└────────┬─────────────────┘ + │ + ↓ +┌────────────────────────────────────┐ +│ API Layer │ +│ ┌─────────────────────────────┐ │ +│ │ YandexMusicApi (Facade) │ │ +│ └──┬──────────────────────────┘ │ +│ │ │ +│ ┌──┴─────────┬─────────────────┐│ +│ ↓ ↓ ↓│ +│ YTrackAPI YPlaylistAPI YSearchAPI │ +│ │ │ │ │ +│ └──────┬─────┴────────┬────────┘ │ +└─────────┼──────────────┼───────────┘ + │ │ + ↓ ↓ + ┌──────────────────────────┐ + │ IRequestProvider │ + │ (Strategy Pattern) │ + └──────┬───────────────────┘ + │ + ┌──┴───┬──────────┐ + ↓ ↓ ↓ + Default Common Mock + Provider Provider Provider + │ │ │ + └──────┼────────┘ + ↓ + ┌──────────────────┐ + │ HttpClient │ + └──────┬───────────┘ + │ + ↓ + ┌──────────────────┐ + │ Network Layer │ + └──────────────────┘ +``` + +## 🔐 Безопасность архитектуры + +1. **Инкапсуляция** - приватные поля и контролируемый доступ +2. **Валидация** - все входные параметры валидируются +3. **Null-safety** - полная поддержка nullable reference types +4. **Async-safe** - асинхронные операции без blocking +5. **Encryption** - встроенное шифрование для чувствительных данных diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e09e719 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,410 @@ +# 🤝 Руководство для участников + +Спасибо за интерес к проекту YandexMusic! Этот документ содержит рекомендации для тех, кто хочет внести свой вклад в развитие проекта. + +## 📋 Содержание + +- [Кодекс поведения](#кодекс-поведения) +- [Как начать](#как-начать) +- [Процесс разработки](#процесс-разработки) +- [Стандарты кода](#стандарты-кода) +- [Commit сообщения](#commit-сообщения) +- [Pull Requests](#pull-requests) +- [Отчёты об ошибках](#отчёты-об-ошибках) +- [Предложения по улучшениям](#предложения-по-улучшениям) + +## 🤐 Кодекс поведения + +- Будьте уважительны к другим участникам +- Не допускайте дискриминацию, оскорбления и враждебного поведения +- Критикуйте идеи, а не людей +- Решайте конфликты конструктивно + +## 🚀 Как начать + +### 1. Подготовка окружения + +```bash +# Клонируем репозиторий +git clone https://git.frigat.duckdns.org/FrigaT/YandexMusic.git +cd YandexMusic + +# Создаём ветку для своей работы +git checkout -b feature/my-feature + +# Восстанавливаем зависимости +dotnet restore + +# Собираем проект +dotnet build +``` + +### 2. Установка инструментов + +- **Visual Studio 2026 Enterprise** (рекомендуется) +- **Visual Studio Code** + C# extension (альтернатива) +- **.NET 10 SDK** + +### 3. Запуск тестов + +```bash +dotnet test +``` + +## 🔄 Процесс разработки + +### Workflow + +``` +1. Выберите issue или создайте новый +2. Создайте feature-ветку +3. Внесите изменения +4. Напишите/обновите тесты +5. Убедитесь что код собирается и тесты проходят +6. Создайте Pull Request +7. Ждите review +8. Исправьте замечания если нужно +9. Merge в master +``` + +### Версионирование веток + +``` +master → Production версия +├── feature/* → Новые функции +├── bugfix/* → Исправления ошибок +├── refactor/* → Рефакторинг кода +└── docs/* → Обновления документации +``` + +## 📐 Стандарты кода + +### Общие правила + +- **Язык:** C# 12 +- **Платформа:** .NET 10 +- **Стиль:** Microsoft C# Coding Conventions +- **Документация:** Все публичные члены должны иметь XML документацию на русском + +### Пример документированного кода + +```csharp +namespace YandexMusic.API.API; + +/// API для работы с альбомами. +public class YAlbumAPI : YCommonAPI +{ + /// Инициализирует новый экземпляр. + /// Экземпляр основного API. + public YAlbumAPI(YandexMusicApi yandex) : base(yandex) { } + + /// Получает информацию об альбоме по идентификатору. + /// Идентификатор альбома + /// Модель альбома или null если не найден + /// Если albumId null + public async Task GetAlbumAsync(string albumId) + { + ArgumentNullException.ThrowIfNull(albumId); + + // Реализация + return await Task.FromResult(null); + } +} +``` + +### Правила именования + +```csharp +// Классы: PascalCase +public class YandexMusicApi { } + +// Методы: PascalCase с Async суффиксом для асинхронных +public async Task GetTrackAsync(string trackId) { } + +// Свойства: PascalCase +public YandexMusicApi Api { get; } + +// Приватные поля: _camelCase +private readonly HttpClient _httpClient; + +// Параметры: camelCase +public void DoSomething(string userName, int userId) { } + +// Локальные переменные: camelCase +var trackList = new List(); +``` + +### Code Style + +Используйте `.editorconfig` для автоматического форматирования: + +```ini +# Отступы - 4 пробела +indent_size = 4 + +# Новая строка для фигурных скобок +csharp_new_line_before_open_brace = all + +# Используйте var где возможно +csharp_style_var_for_built_in_types = true + +# Null-forgiving operator с осторожностью +csharp_style_null_forgiving_operator = false +``` + +### Nullable Reference Types + +Всегда включен `enable`. Правила: + +```csharp +// ✅ Хорошо - явно указано может быть null +public string? GetUserName() { } + +// ✅ Хорошо - явно не null +public string GetTitle() => "Title"; + +// ❌ Плохо - неявная nullable ссылка +public object GetSomething() { } + +// ✅ Хорошо - используйте null-coalescing +var result = value ?? defaultValue; + +// ✅ Хорошо - используйте null-conditional +var count = list?.Count ?? 0; +``` + +### Асинхронное программирование + +```csharp +// ✅ Хорошо - async/await +public async Task GetTrackAsync(string id) +{ + return await api.Track.GetTrackAsync(id); +} + +// ❌ Плохо - Task.Result блокирует поток +public YTrack? GetTrack(string id) +{ + return api.Track.GetTrackAsync(id).Result; +} + +// ✅ Хорошо - ConfigureAwait(false) в библиотеках +public async Task GetTrackAsync(string id) +{ + return await api.Track.GetTrackAsync(id).ConfigureAwait(false); +} +``` + +### Обработка ошибок + +```csharp +// ✅ Хорошо +public async Task GetTrackAsync(string trackId) +{ + ArgumentNullException.ThrowIfNull(trackId); + ArgumentException.ThrowIfNullOrWhiteSpace(trackId); + + try + { + return await _provider.GetTrackAsync(trackId); + } + catch (HttpRequestException ex) + { + Logger.LogError(ex, "Ошибка при получении трека"); + throw; + } +} + +// ❌ Плохо - молча игнорируем ошибки +public async Task GetTrackAsync(string trackId) +{ + try + { + return await _provider.GetTrackAsync(trackId); + } + catch { } + return null; +} +``` + +### Структура файла + +```csharp +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace YandexMusic.API.API; + +/// API для работы с треками. +public class YTrackAPI : YCommonAPI +{ + /// Инициализирует новый экземпляр. + public YTrackAPI(YandexMusicApi yandex) : base(yandex) { } + + /// Получает трек. + public async Task GetTrackAsync(string trackId) + { + // реализация + } + + /// Получает несколько треков. + public async Task> GetTracksAsync(IEnumerable trackIds) + { + // реализация + } +} +``` + +## 📝 Commit сообщения + +### Формат + +``` +(): + + + +