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
Markdown
# ๐ต Soulseek Downloader
<div align="center">




[](https://www.typescriptlang.org/)
[](#testing)
[](#architecture)
[](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>