UNPKG

andrade-soulseek-downloader

Version:

Simple, safe Soulseek download library with built-in rate limiting to prevent bans

1,076 lines (835 loc) โ€ข 32.7 kB
# ๐ŸŽต Soulseek Downloader <div align="center"> ![npm version](https://img.shields.io/npm/v/andrade-soulseek-downloader) ![npm downloads](https://img.shields.io/npm/dm/andrade-soulseek-downloader) ![license](https://img.shields.io/npm/l/andrade-soulseek-downloader) ![node version](https://img.shields.io/node/v/andrade-soulseek-downloader) [![TypeScript](https://img.shields.io/badge/TypeScript-100%25-blue.svg)](https://www.typescriptlang.org/) [![Tests](https://img.shields.io/badge/Tests-42%20passing-green.svg)](#testing) [![Architecture](https://img.shields.io/badge/Architecture-Hexagonal-purple.svg)](#architecture) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com) **๐Ÿš€ Enterprise-grade Soulseek downloader with YouTube fallback, automatic quality selection, ban protection, and clean architecture** [Installation](#-installation) โ€ข [Quick Start](#-quick-start) โ€ข [Features](#-features) โ€ข [API](#-api-reference) โ€ข [Architecture](#-architecture) โ€ข [Examples](#-examples) </div> --- ## ๐Ÿ“– Table of Contents - [โšก Key Features](#-key-features) - [๐Ÿ—๏ธ Architecture Highlights](#๏ธ-architecture-highlights) - [๐Ÿ›ก๏ธ Ban Protection](#๏ธ-ban-protection) - [๐Ÿ“ฆ Installation](#-installation) - [๐Ÿš€ Quick Start](#-quick-start) - [๐ŸŽฏ How It Works](#-how-it-works) - [๐Ÿ“š API Reference](#-api-reference) - [โš™๏ธ Configuration](#๏ธ-configuration) - [๐Ÿ’ก Examples](#-examples) - [๐Ÿ“ File Organization](#-file-organization) - [๐ŸŽฌ YouTube Fallback](#-youtube-fallback) - [๐ŸŽจ Quality Selection](#-quality-selection-algorithm) - [๐Ÿ“Š Architecture](#-architecture) - [๐Ÿงช Testing](#-testing) - [๐Ÿ”ง Advanced Usage](#-advanced-usage) - [๐Ÿ“ TypeScript Support](#-typescript-support) - [โ“ FAQ](#-faq) - [๐Ÿ› Troubleshooting](#-troubleshooting) - [๐Ÿค Contributing](#-contributing) ## โšก Key Features | Feature | Description | |---------|-------------| | ๐ŸŽฏ **Quality-First Downloads** | Automatically downloads the highest quality available (FLAC โ†’ 320kbps โ†’ 256kbps โ†’ 192kbps) | | ๐ŸŽฌ **YouTube Fallback** | Automatically downloads from YouTube if track not found on Soulseek | | ๐Ÿ›ก๏ธ **Ban Protection** | Built-in rate limiting and queue management to prevent Soulseek bans | | ๐Ÿ“Š **Smart Selection** | Intelligent file selection based on bitrate, availability, speed, and filename match | | ๐Ÿ”„ **Auto-Retry Logic** | Tries multiple sources with progressive quality fallback | | ๐ŸŽจ **Beautiful UI** | Colored output, progress bars, and status indicators | | โšก **Simple API** | Just one function: `soulseekDownload(artist, title)` | | ๐Ÿ” **Safe Defaults** | Conservative rate limits and single connection by default | | ๐Ÿ“ˆ **Progress Tracking** | Real-time download progress with speed indicators | | ๐Ÿ—๏ธ **Clean Architecture** | Hexagonal architecture with SOLID principles | | ๐Ÿ”ท **TypeScript First** | Full TypeScript support with comprehensive type definitions | | ๐Ÿงช **Well Tested** | 49 comprehensive unit tests with high coverage | | ๐Ÿ“ฆ **Barrel Exports** | Clean imports with organized module structure | ## ๐Ÿ—๏ธ Architecture Highlights This project follows **enterprise-grade software architecture principles**: ### ๐Ÿ”ท **Hexagonal Architecture (Ports & Adapters)** - **Domain Layer**: Pure business logic with entities and services - **Application Layer**: Use cases and orchestration - **Infrastructure Layer**: External adapters (Soulseek, filesystem, logging) - **Presentation Layer**: CLI and API interfaces ### ๐Ÿ”ง **SOLID Principles Applied** - **S**ingle Responsibility: Each class has one clear purpose - **O**pen/Closed: Extensible through interfaces - **L**iskov Substitution: Proper inheritance and polymorphism - **I**nterface Segregation: Focused, minimal interfaces - **D**ependency Inversion: Depend on abstractions, not concretions ### ๐Ÿญ **Dependency Injection** - Uses InversifyJS for clean dependency management - Testable and mockable components - Easy to extend and modify ### ๐Ÿ“‚ **Clean File Organization** - **One symbol per file** for maximum clarity - **kebab-case naming** following Node.js conventions - **Barrel files** for clean imports - **Structured by layer** not by feature ## ๐Ÿ›ก๏ธ Ban Protection This library includes **automatic protection** against Soulseek bans: ```mermaid graph LR A[Search Request] --> B{Rate Limiter} B -->|Wait 5s| C[Execute Search] C --> D[Download Request] D --> E{Rate Limiter} E -->|Wait 3s| F[Execute Download] F --> G{Success?} G -->|No| H[Error Cooldown 10s] H --> D G -->|Yes| I[Complete] style B fill:#ff9999 style E fill:#ff9999 style H fill:#ffcc99 ``` ### Protection Features: - โฑ๏ธ **5-second delay** between searches - โฑ๏ธ **3-second delay** between downloads - โฑ๏ธ **10-second cooldown** after errors - ๐Ÿ”’ **Single connection** limit - ๐Ÿ“ฆ **Queue management** for multiple requests - ๐Ÿ”„ **Progressive backoff** on repeated failures ## ๐Ÿ“ฆ Installation ```bash # pnpm (recommended) pnpm add andrade-soulseek-downloader # npm npm install andrade-soulseek-downloader # yarn yarn add andrade-soulseek-downloader ``` ### TypeScript Support TypeScript definitions are **included automatically** - no need for separate `@types` packages! ## ๐Ÿš€ Quick Start ### 1๏ธโƒฃ Set up environment variables Create a `.env` file in your project root: ```env # Required SOULSEEK_USER=your_username SOULSEEK_PASSWORD=your_password SOULSEEK_SHARED_MUSIC_DIR=/path/to/shared/music SOULSEEK_DOWNLOAD_DIR=/path/to/downloads # Optional (defaults shown) SOULSEEK_MIN_QUALITY_BITRATE=128 # Minimum acceptable quality SOULSEEK_MAX_QUALITY_BITRATE=320 # Maximum quality (optional - avoids FLAC/WAV) SOULSEEK_SEARCH_DELAY=5000 # ms between searches SOULSEEK_DOWNLOAD_DELAY=3000 # ms between downloads SOULSEEK_MAX_ATTEMPTS=10 # Max download attempts SOULSEEK_DOWNLOAD_TIMEOUT=120000 # Download timeout in ms ``` ### 2๏ธโƒฃ Use the Simple API ```typescript import { soulseekDownload } from 'andrade-soulseek-downloader'; async function downloadTrack() { const filePath = await soulseekDownload('Daft Punk', 'One More Time'); if (filePath) { console.log(`โœ… Downloaded to: ${filePath}`); } else { console.log('โŒ Download failed'); } } downloadTrack(); ``` ### 3๏ธโƒฃ Or use CommonJS ```javascript const { soulseekDownload } = require('andrade-soulseek-downloader'); async function downloadTrack() { const filePath = await soulseekDownload('Daft Punk', 'One More Time'); if (filePath) { console.log(`โœ… Downloaded to: ${filePath}`); } else { console.log('โŒ Download failed'); } } downloadTrack(); ``` ## ๐ŸŽฏ How It Works ```mermaid flowchart TD Start([User calls soulseekDownload]) --> Connect{Connected?} Connect -->|No| Connect2[Connect to Soulseek] Connect -->|Yes| Search[Search for files] Connect2 --> Search Search --> Found{Files found?} Found -->|No| YouTube[๐ŸŽฌ Try YouTube] Found -->|Yes| Filter[Filter by min bitrate] YouTube -->|Success| Done[โœ… Return file path] YouTube -->|Failed| Failed[โŒ Return null] Filter --> Group[Group by quality] Group --> Sort[Sort each group by:<br/>1. Slot availability<br/>2. Connection speed<br/>3. Filename match] Sort --> Try320[Try 320kbps files] Try320 -->|Success| Done Try320 -->|All failed| Try256[Try 256kbps files] Try256 -->|Success| Done Try256 -->|All failed| Try192[Try 192kbps files] Try192 -->|Success| Done Try192 -->|All failed| Try128[Try 128kbps files] Try128 -->|Success| Done Try128 -->|All failed| YouTube2[๐ŸŽฌ Try YouTube as fallback] YouTube2 -->|Success| Done YouTube2 -->|Failed| Failed style Done fill:#90EE90 style Failed fill:#FFB6C1 style YouTube fill:#FFE5B4 style YouTube2 fill:#FFE5B4 ``` ## ๐Ÿ“š API Reference ### Simple API (Recommended) ```typescript // Download with optional folder and filename customization soulseekDownload( artist: string, title: string, folderName?: string, // Optional: subfolder in SOULSEEK_DOWNLOAD_DIR customFileName?: string // Optional: custom filename (without extension) ): Promise<string | null> // Clean up when done soulseekDisconnect(): Promise<void> ``` #### Parameters | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `artist` | string | โœ… | Artist name to search for | | `title` | string | โœ… | Track title to search for | | `folderName` | string | โŒ | Optional subfolder path. Supports nested paths like "Genre/Artist" | | `customFileName` | string | โŒ | Optional custom filename (extension added automatically) | #### Examples ```typescript // Basic usage await soulseekDownload('Daft Punk', 'One More Time'); // โ†’ "/downloads/Daft Punk - One More Time.mp3" // With folder await soulseekDownload('Daft Punk', 'One More Time', 'Electronic'); // โ†’ "/downloads/Electronic/Daft Punk - One More Time.mp3" // With custom filename await soulseekDownload('Daft Punk', 'One More Time', null, 'daft_punk_01'); // โ†’ "/downloads/daft_punk_01.mp3" // With both await soulseekDownload('Daft Punk', 'One More Time', 'Electronic/Daft_Punk', 'track_01'); // โ†’ "/downloads/Electronic/Daft_Punk/track_01.mp3" ``` ### Advanced API ```typescript import { SoulseekDownloader, DownloadConfig, SearchOptions, SoulseekSearchResult } from 'andrade-soulseek-downloader'; const downloader = new SoulseekDownloader({ maxAttempts: 10, downloadTimeout: 120000, preferSlotsAvailable: true, minSpeed: 100000, searchDelay: 5000, downloadDelay: 3000, maxConcurrent: 1, cooldownAfterError: 10000 }); // Manual control await downloader.connect(); const results = await downloader.search(options); const filePath = await downloader.download(result, artist, title); await downloader.disconnect(); ``` ## โš™๏ธ Configuration ### Environment Variables | Variable | Default | Description | |----------|---------|-------------| | `SOULSEEK_USER` | - | Your Soulseek username **(required)** | | `SOULSEEK_PASSWORD` | - | Your Soulseek password **(required)** | | `SOULSEEK_SHARED_MUSIC_DIR` | - | Path to your shared music folder **(required)** | | `SOULSEEK_DOWNLOAD_DIR` | - | Where to save downloads **(required)** | | `SOULSEEK_MIN_QUALITY_BITRATE` | `128` | Minimum acceptable bitrate (kbps) | | `SOULSEEK_MAX_QUALITY_BITRATE` | *(none)* | Maximum acceptable bitrate (kbps) - prevents large FLAC/WAV files | | `SOULSEEK_SEARCH_DELAY` | `5000` | Delay between searches (ms) | | `SOULSEEK_DOWNLOAD_DELAY` | `3000` | Delay between downloads (ms) | | `SOULSEEK_MAX_ATTEMPTS` | `10` | Maximum download attempts | | `SOULSEEK_DOWNLOAD_TIMEOUT` | `120000` | Download timeout (ms) | | `SOULSEEK_MAX_CONCURRENT` | `1` | Max concurrent operations **(keep at 1!)** | | `SOULSEEK_ERROR_COOLDOWN` | `10000` | Cooldown after errors (ms) | ### Programmatic Configuration ```typescript const config: DownloadConfig = { maxAttempts: 15, // Try up to 15 different sources downloadTimeout: 180000, // 3 minute timeout per download preferSlotsAvailable: true, // Prefer users with open slots minSpeed: 500000, // Minimum 500kb/s connection minQualityBitrate: 192, // Minimum quality maxQualityBitrate: 320, // Maximum quality (avoid FLAC/WAV) searchDelay: 3000, // 3 second delay between searches downloadDelay: 2000, // 2 second delay between downloads maxConcurrent: 1, // Always 1 to prevent bans cooldownAfterError: 15000 // 15 second cooldown after errors }; ``` ### Quality Control Examples ```typescript // Example 1: High quality only (FLAC/WAV allowed) process.env.SOULSEEK_MIN_QUALITY_BITRATE = '320'; // No max limit = allows FLAC/WAV downloads // Example 2: Avoid large files (cap at 320kbps) process.env.SOULSEEK_MIN_QUALITY_BITRATE = '256'; process.env.SOULSEEK_MAX_QUALITY_BITRATE = '320'; // Example 3: Specific quality only (exactly 320kbps) process.env.SOULSEEK_MIN_QUALITY_BITRATE = '320'; process.env.SOULSEEK_MAX_QUALITY_BITRATE = '320'; // Example 4: Mobile-friendly sizes (avoid large files) process.env.SOULSEEK_MIN_QUALITY_BITRATE = '128'; process.env.SOULSEEK_MAX_QUALITY_BITRATE = '256'; ``` ## ๐Ÿ’ก Examples ### Basic Usage ```typescript import { soulseekDownload, soulseekDisconnect } from 'andrade-soulseek-downloader'; // Basic download const file1 = await soulseekDownload('The Beatles', 'Hey Jude'); // Download to a specific folder const file2 = await soulseekDownload('Pink Floyd', 'Comfortably Numb', 'Classic Rock'); // Download with custom folder and filename const file3 = await soulseekDownload( 'Led Zeppelin', 'Stairway to Heaven', 'Rock/Led Zeppelin', // Creates nested folders 'stairway_to_heaven_remastered' // Custom filename (extension added automatically) ); // Clean up when done await soulseekDisconnect(); ``` ### Batch Downloads ```typescript import { soulseekDownload, soulseekDisconnect } from 'andrade-soulseek-downloader'; const tracks = [ { artist: 'The Beatles', title: 'Hey Jude' }, { artist: 'Pink Floyd', title: 'Comfortably Numb' }, { artist: 'Led Zeppelin', title: 'Stairway to Heaven' } ]; // Download to organized folders for (const track of tracks) { console.log(`Downloading ${track.artist} - ${track.title}...`); // Downloads each track to an artist-specific folder const filePath = await soulseekDownload( track.artist, track.title, track.artist.replace(/[^a-zA-Z0-9]/g, '_') // Artist folder ); if (filePath) { console.log(`โœ… Downloaded: ${filePath}`); } else { console.log(`โŒ Failed: ${track.artist} - ${track.title}`); } } await soulseekDisconnect(); ``` ### Advanced Configuration ```typescript import { SoulseekDownloader, DownloadConfig, SearchOptions } from 'andrade-soulseek-downloader'; const config: DownloadConfig = { maxAttempts: 20, downloadTimeout: 300000, // 5 minutes preferSlotsAvailable: true, minSpeed: 1000000, // 1 MB/s minimum searchDelay: 2000, downloadDelay: 1000 }; const downloader = new SoulseekDownloader(config); try { await downloader.connect(); const searchOptions: SearchOptions = { artist: 'Daft Punk', title: 'One More Time', minBitrate: 320, // Only high quality timeout: 60000, maxResults: 50, strictMatching: true }; const results = await downloader.search(searchOptions); console.log(`Found ${results.length} high-quality results`); if (results.length > 0) { const bestResult = results[0]; // Already sorted by quality // Download with custom folder and filename const filePath = await downloader.download( bestResult, 'Daft Punk', 'One More Time', 'Electronic/Daft_Punk', // Custom folder 'one_more_time_hq' // Custom filename ); console.log(`Downloaded: ${filePath}`); } } finally { await downloader.disconnect(); } ``` ### Custom Folders and Filenames ```typescript import { soulseekDownload } from 'andrade-soulseek-downloader'; // Organize downloads by genre const genres = { 'The Beatles': 'Rock/Classic', 'Miles Davis': 'Jazz', 'Daft Punk': 'Electronic/House' }; for (const [artist, genre] of Object.entries(genres)) { // Downloads to genre-specific folders await soulseekDownload( artist, 'Greatest Hits', genre, // Creates folder structure `${artist.toLowerCase().replace(/ /g, '_')}_greatest_hits` // Custom filename ); } // Create a compilation folder const compilationTracks = [ { artist: 'Queen', title: 'Bohemian Rhapsody', filename: '01_queen_bohemian_rhapsody' }, { artist: 'Led Zeppelin', title: 'Stairway to Heaven', filename: '02_led_zeppelin_stairway' }, { artist: 'Pink Floyd', title: 'Comfortably Numb', filename: '03_pink_floyd_comfortably_numb' } ]; for (const track of compilationTracks) { await soulseekDownload( track.artist, track.title, 'Compilations/Rock_Classics_2024', // Nested folder structure track.filename // Sequential naming for playlist ); } ``` ### CLI Usage ```bash # Global installation pnpm install -g andrade-soulseek-downloader # Command line usage (basic - no custom params via CLI yet) soulseek-download "The Beatles" "Hey Jude" # Or run directly with pnpm pnpm start "The Beatles" "Hey Jude" ``` ### Error Handling ```javascript try { const filePath = await soulseekDownload('Artist', 'Title'); if (!filePath) { // Download failed but no error thrown console.log('Could not find or download the track'); } } catch (error) { // Connection or configuration error console.error('Fatal error:', error.message); } ``` ### Custom Download Handler ```javascript async function downloadWithMetadata(artist, title) { console.log(`๐ŸŽต Searching for ${artist} - ${title}`); const startTime = Date.now(); const filePath = await soulseekDownload(artist, title); if (filePath) { const duration = (Date.now() - startTime) / 1000; const stats = require('fs').statSync(filePath); return { success: true, path: filePath, size: stats.size, duration: duration, timestamp: new Date().toISOString() }; } return { success: false, artist, title }; } ``` ## ๐Ÿ“ File Organization Organize your music library with custom folders and filenames: ### Folder Structure Examples ```typescript // Organize by genre await soulseekDownload('Pink Floyd', 'Comfortably Numb', 'Rock/Progressive'); await soulseekDownload('Miles Davis', 'So What', 'Jazz/Modal'); await soulseekDownload('Daft Punk', 'One More Time', 'Electronic/House'); // Organize by year await soulseekDownload('The Beatles', 'Hey Jude', '1960s/1968'); await soulseekDownload('Nirvana', 'Smells Like Teen Spirit', '1990s/1991'); // Create compilations const tracks = ['track1', 'track2', 'track3']; for (let i = 0; i < tracks.length; i++) { await soulseekDownload( artist[i], tracks[i], 'Compilations/Summer_2024', `${String(i+1).padStart(2, '0')}_${artist[i].toLowerCase()}` ); } ``` ### Custom Filename Patterns ```typescript // Sequential numbering for playlists await soulseekDownload('Artist', 'Title', 'Playlist', '01_intro'); await soulseekDownload('Artist', 'Title', 'Playlist', '02_main_theme'); await soulseekDownload('Artist', 'Title', 'Playlist', '03_outro'); // Include metadata in filename const safeArtist = artist.replace(/[^a-zA-Z0-9]/g, '_'); const safeTitle = title.replace(/[^a-zA-Z0-9]/g, '_'); await soulseekDownload( artist, title, 'Library', `${safeArtist}-${safeTitle}-${bitrate}kbps` ); // Date-based organization const date = new Date().toISOString().split('T')[0]; await soulseekDownload(artist, title, date, `${date}_${safeTitle}`); ``` ### Automatic Features - **Directory Creation**: Folders are created automatically if they don't exist - **Nested Paths**: Supports multi-level folder structures (e.g., "Genre/Artist/Album") - **Extension Handling**: File extensions are added automatically based on source - **Special Characters**: Handles special characters safely in paths - **YouTube Support**: Custom folders and filenames work with YouTube fallback too ## ๐ŸŽฌ YouTube Fallback The library includes **automatic YouTube fallback** when tracks are not found on Soulseek: ### How YouTube Fallback Works 1. **Primary Search**: First searches Soulseek network for the track 2. **Automatic Fallback**: If no results on Soulseek, automatically searches YouTube 3. **Quality Download**: Downloads audio in 192kbps MP3 format 4. **Smart Conversion**: Automatically converts to MP3 using ffmpeg 5. **Same Location**: Saves to the same download directory as Soulseek files ### YouTube Features - ๐Ÿ” **Intelligent Search**: Uses relevance scoring to find the best match - ๐ŸŽต **Audio Only**: Downloads only audio stream for efficiency - ๐Ÿ”„ **Auto Conversion**: Converts to MP3 format automatically - ๐Ÿ“Š **Progress Tracking**: Shows download and conversion progress - ๐ŸŽฏ **Best Match**: Selects most relevant video based on title, duration, and views ### When YouTube is Used - **No Soulseek Results**: When search returns no files on Soulseek - **All Downloads Failed**: After all Soulseek download attempts fail - **Seamless Integration**: Works automatically without configuration ### Example Output ```bash โš  No results found on Soulseek, trying YouTube as fallback... [YouTube] Searching for: Artist - Title [YouTube] Found best match: Artist - Title (Official Audio) [YouTube] Downloading... Downloading from YouTube: 100.00% (3.74 MB / 3.74 MB) Converting to MP3... Converting: 100% โœ… Successfully downloaded from YouTube: /downloads/Artist - Title.mp3 ``` ## ๐ŸŽจ Quality Selection Algorithm The library uses a sophisticated scoring system to select the best file: ```mermaid pie title Quality Score Components (100 points total) "Bitrate (50pts)" : 50 "Slot Availability (25pts)" : 25 "Connection Speed (15pts)" : 15 "Filename Match (10pts)" : 10 ``` ### Quality Score Calculation ```javascript Quality Score = (Bitrate/320 ร— 50) + (Slots ร— 25) + (Speed/5MB ร— 15) + (Match ร— 10) ``` ### Priority Order 1. **Highest Bitrate First** - FLAC/WAV โ†’ 320kbps โ†’ 256kbps โ†’ 192kbps โ†’ 128kbps 2. **Within each bitrate:** - Users with available slots - Faster connection speeds - Better filename matches ## ๐Ÿ“Š Architecture This project demonstrates **professional software architecture** patterns: ```mermaid graph TB subgraph "Presentation Layer" CLI[CLI Handler] API[API Handler] end subgraph "Application Layer" UC[Use Cases] DTO[DTOs] end subgraph "Domain Layer" E[Entities] VO[Value Objects] DS[Domain Services] R[Repository Interfaces] end subgraph "Infrastructure Layer" REPO[Soulseek Repository] LOG[Console Logger] RL[Rate Limiter] DI[DI Container] end CLI --> UC API --> UC UC --> DS UC --> R DS --> E DS --> VO R --> REPO UC --> LOG UC --> RL style CLI fill:#e3f2fd style API fill:#e3f2fd style UC fill:#e8f5e8 style E fill:#fff3e0 style VO fill:#fff3e0 style DS fill:#fff3e0 style REPO fill:#fce4ec ``` ### Directory Structure ``` src/ โ”œโ”€โ”€ presentation/ # User interfaces โ”‚ โ”œโ”€โ”€ api/ # HTTP/Function API โ”‚ โ””โ”€โ”€ cli/ # Command line interface โ”œโ”€โ”€ application/ # Use cases & orchestration โ”‚ โ”œโ”€โ”€ use-cases/ # Business workflows โ”‚ โ””โ”€โ”€ dto/ # Data transfer objects โ”œโ”€โ”€ domain/ # Core business logic โ”‚ โ”œโ”€โ”€ entities/ # Business objects โ”‚ โ”œโ”€โ”€ value-objects/ # Immutable values โ”‚ โ”œโ”€โ”€ services/ # Domain services โ”‚ โ””โ”€โ”€ repositories/ # Repository interfaces (ports) โ”œโ”€โ”€ infrastructure/ # External concerns โ”‚ โ”œโ”€โ”€ repositories/ # Repository implementations (adapters) โ”‚ โ”œโ”€โ”€ services/ # External services โ”‚ โ””โ”€โ”€ container/ # Dependency injection โ”œโ”€โ”€ shared/ # Shared utilities โ”‚ โ”œโ”€โ”€ interfaces/ # Common interfaces โ”‚ โ””โ”€โ”€ types/ # Type definitions โ””โ”€โ”€ core/ # Core components ``` ### Benefits of This Architecture - ๐Ÿงช **Highly Testable**: Easy to mock and test each layer - ๐Ÿ”ง **Maintainable**: Clear separation of concerns - ๐Ÿ”„ **Flexible**: Easy to swap implementations - ๐Ÿ“ˆ **Scalable**: Can grow with your needs - ๐Ÿ›ก๏ธ **Robust**: Handles errors gracefully - ๐Ÿ“ **Self-Documenting**: Clear intent and structure ## ๐Ÿงช Testing The project includes **comprehensive test coverage**: ```bash # Run all tests pnpm test # Run tests with coverage pnpm test:coverage # Run specific test suites pnpm test:unit pnpm test:integration # Watch mode for development pnpm test:watch ``` ### Test Statistics - **49 Tests** across all layers - **4 Test Suites** covering domain, application, and infrastructure - **High Coverage** on critical business logic - **Fast Execution** (< 1 second) ### Test Architecture - **Unit Tests**: Domain entities, value objects, services - **Integration Tests**: Use cases with mocked dependencies - **Mocking**: Comprehensive mocks for external dependencies - **Test Utilities**: Shared test factories and helpers ## ๐Ÿ“ TypeScript Support ### Built-in Type Definitions Full TypeScript support is **included by default**: ```typescript import { soulseekDownload, // Function SoulseekDownloader, // Class DownloadConfig, // Interface SearchOptions, // Interface SoulseekSearchResult, // Interface RateLimiter // Class } from 'andrade-soulseek-downloader'; // All types are automatically available const config: DownloadConfig = { maxAttempts: 10, downloadTimeout: 120000 // TypeScript will provide IntelliSense here }; ``` ### Type Safety Features - **Comprehensive Interfaces**: All public APIs are fully typed - **Generic Support**: Type-safe generic functions where applicable - **Strict Null Checks**: Proper handling of nullable values - **IntelliSense Support**: Full autocomplete in supported editors - **Compile-time Safety**: Catch errors before runtime ### Import Options ```typescript // Barrel imports (recommended) import { Track, Bitrate } from 'andrade-soulseek-downloader/domain'; import { DownloadTrackUseCase } from 'andrade-soulseek-downloader/application'; // Specific imports import { soulseekDownload } from 'andrade-soulseek-downloader/presentation/api'; import { SoulseekDownloader } from 'andrade-soulseek-downloader/core'; // Root imports (simple) import { soulseekDownload, SoulseekDownloader } from 'andrade-soulseek-downloader'; ``` ## ๐Ÿ”ง Advanced Usage ### Custom Rate Limiting ```typescript import { RateLimiter, RateLimitConfig } from 'andrade-soulseek-downloader'; const customConfig: RateLimitConfig = { searchDelay: 2000, // Faster searches (be careful!) downloadDelay: 4000, // Slower downloads (safer) maxConcurrent: 1, // Always 1 for safety cooldownAfterError: 20000 // Longer cooldown }; const rateLimiter = RateLimiter.getInstance(customConfig); // Use with your own functions const results = await rateLimiter.executeSearch(async () => { return await customSearchFunction(); }); ``` ### Extending the Domain ```typescript import { Track, TrackSelectionService } from 'andrade-soulseek-downloader/domain'; class CustomTrackSelectionService extends TrackSelectionService { selectBestTracks(tracks: Track[], maxPerBitrate: number = 5): Track[] { // Your custom selection logic const filtered = tracks.filter(track => track.getBitrate().getValue() >= 256 && track.hasAvailableSlots() ); return super.selectBestTracks(filtered, maxPerBitrate); } } ``` ### Custom Logging ```typescript import { ILogger } from 'andrade-soulseek-downloader/shared'; class CustomLogger implements ILogger { info(message: string): void { // Send to your logging service console.log(`[INFO] ${new Date().toISOString()} ${message}`); } success(message: string): void { // Custom success handling console.log(`[SUCCESS] ${message}`); } // ... implement other methods } // Use with dependency injection container.bind<ILogger>('ILogger').to(CustomLogger); ``` ## โ“ FAQ ### **Q: Is this safe to use? Will I get banned?** A: Yes, it's designed with safety first. The built-in rate limiting prevents bans by enforcing conservative delays between operations. ### **Q: What audio quality can I expect?** A: The library automatically finds the highest quality available on Soulseek, preferring lossless formats (FLAC) when possible, then falling back to 320kbps, 256kbps, etc. If the track is not found on Soulseek, it will download from YouTube at 192kbps MP3. ### **Q: Can I use this in production?** A: Yes! The architecture is enterprise-grade with proper error handling, logging, and testing. However, always respect Soulseek's terms of service. ### **Q: Does it work with TypeScript?** A: Absolutely! Full TypeScript support is built-in with comprehensive type definitions. ### **Q: Can I customize the download behavior?** A: Yes, the architecture is designed for extensibility. You can inject custom services, modify selection algorithms, or add your own retry logic. ### **Q: Does it download from YouTube automatically?** A: Yes! If a track is not found on Soulseek or all download attempts fail, the library automatically tries to download from YouTube as a fallback option. ### **Q: How do I report issues?** A: Please open an issue on GitHub with detailed information about your problem, including logs and environment details. ## ๐Ÿ› Troubleshooting ### Common Issues #### **Connection Problems** ```bash Error: Failed to connect to Soulseek ``` **Solution**: Check your username/password and network connection. #### **No Results Found** ```bash No tracks found ``` **Solutions**: - Try broader search terms - Lower the minimum bitrate requirement - Check if the artist/track exists on Soulseek #### **Download Timeouts** ```bash Download timeout for user X ``` **Solutions**: - Increase `SOULSEEK_DOWNLOAD_TIMEOUT` - The library will automatically try other sources #### **Rate Limit Warnings** ```bash Warning: Multiple SoulseekDownloader instances detected! ``` **Solution**: Use only one instance of SoulseekDownloader, or use the simple API functions. ### Debug Mode Enable debug logging: ```bash DEBUG=true pnpm start "Artist" "Title" ``` Or programmatically: ```typescript process.env.DEBUG = 'true'; import { soulseekDownload } from 'andrade-soulseek-downloader'; ``` ## ๐Ÿค Contributing We welcome contributions! Here's how to get started: ### Development Setup ```bash # Clone the repository git clone https://github.com/andrade/soulseek-downloader.git cd soulseek-downloader # Install dependencies pnpm install # Run tests pnpm test # Build the project pnpm build # Run in development pnpm dev ``` ### Code Standards - **TypeScript**: All code must be in TypeScript - **Architecture**: Follow hexagonal architecture patterns - **Testing**: Maintain high test coverage - **Formatting**: Code is automatically formatted - **Conventions**: Use kebab-case for files, PascalCase for classes ### Submitting Changes 1. Fork the repository 2. Create a feature branch: `git checkout -b feature/amazing-feature` 3. Make your changes following the architecture patterns 4. Add tests for new functionality 5. Ensure all tests pass: `pnpm test` 6. Build successfully: `pnpm build` 7. Commit your changes: `git commit -m 'Add amazing feature'` 8. Push to the branch: `git push origin feature/amazing-feature` 9. Open a Pull Request ### Architecture Guidelines When contributing, please: - Keep domain logic pure (no external dependencies) - Use dependency injection for external concerns - Follow the single responsibility principle - Add comprehensive tests for new features - Update documentation for API changes --- ## ๐Ÿ“ Changelog ### v1.0.14 (Latest) - ๐ŸŽฏ Added optional `folderName` parameter for organizing downloads into custom folders - ๐Ÿ“ Added optional `customFileName` parameter for custom file naming - ๐Ÿ“ Support for nested folder structures (e.g., "Genre/Artist/Album") - ๐ŸŽฌ Custom parameters work with YouTube fallback - ๐Ÿงช Added comprehensive tests for new features ### v1.0.13 - ๐Ÿ”ง Fixed dependency issue: moved `inversify` and `reflect-metadata` to dependencies - ๐Ÿ“Š Enhanced logging for Soulseek failures and YouTube fallback - ๐ŸŽฌ Clear indication when YouTube is used as fallback - ๐Ÿ“ Improved error messages and user guidance ### v1.0.12 - ๐ŸŽฌ Added automatic YouTube fallback when Soulseek fails - ๐Ÿ” Improved search algorithms - ๐Ÿ›ก๏ธ Enhanced rate limiting --- ## ๐Ÿ“„ License MIT ยฉ [andrade](https://github.com/andrade) --- <div align="center"> **โญ Star this repo if you found it helpful!** [Report Bug](https://github.com/andrade/soulseek-downloader/issues) โ€ข [Request Feature](https://github.com/andrade/soulseek-downloader/issues) โ€ข [Contribute](https://github.com/andrade/soulseek-downloader/pulls) </div>