# 🎵 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 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)
- [🎨 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) |
| 🛡️ **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** | 42 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_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
    A[Start Download] --> B[Search Soulseek Network]
    B --> C{Results Found?}
    C -->|No| D[Return null]
    C -->|Yes| E[Group by Quality]
    E --> F[Sort by Availability & Speed]
    F --> G[Try Highest Quality First]
    G --> H{Download Success?}
    H -->|Yes| I[Return File Path]
    H -->|No| J{More Options?}
    J -->|Yes| K[Try Next Quality]
    K --> G
    J -->|No| L[Progressive Fallback]
    L --> G
    
    style E fill:#e1f5fe
    style F fill:#e8f5e8
    style G fill:#fff3e0
    style H fill:#fce4ec
```

### Quality Selection Algorithm

1. **🔍 Search Phase**: Finds all available files
2. **📊 Quality Grouping**: Groups files by bitrate (FLAC, 320kbps, 256kbps, etc.)
3. **⭐ Smart Scoring**: Scores each file based on:
   - Bitrate quality (50 points max)
   - Slot availability (25 points)
   - Connection speed (15 points)  
   - Filename match (10 points)
4. **🎯 Progressive Download**: Tries best options first, falls back gracefully

## 📚 API Reference

### Simple API (Recommended)

```typescript
// Single function for most use cases
soulseekDownload(artist: string, title: string): Promise<string | null>

// Clean up when done
soulseekDisconnect(): Promise<void>
```

### 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` | *(required)* | Your Soulseek username |
| `SOULSEEK_PASSWORD` | *(required)* | Your Soulseek password |
| `SOULSEEK_SHARED_MUSIC_DIR` | *(required)* | Directory to share (can be empty) |
| `SOULSEEK_DOWNLOAD_DIR` | *(required)* | Where to save downloads |
| `SOULSEEK_MIN_QUALITY_BITRATE` | `128` | Minimum acceptable bitrate (kbps) |
| `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) |

### 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
  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
};
```

## 💡 Examples

### Basic Usage

```typescript
import { soulseekDownload, soulseekDisconnect } from 'andrade-soulseek-downloader';

// Download a single track
const file1 = await soulseekDownload('The Beatles', 'Hey Jude');
const file2 = await soulseekDownload('Pink Floyd', 'Comfortably Numb');

// 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' }
];

for (const track of tracks) {
  console.log(`Downloading ${track.artist} - ${track.title}...`);
  const filePath = await soulseekDownload(track.artist, track.title);
  
  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
    const filePath = await downloader.download(bestResult, 'Daft Punk', 'One More Time');
    console.log(`Downloaded: ${filePath}`);
  }
  
} finally {
  await downloader.disconnect();
}
```

### CLI Usage

```bash
# Global installation
pnpm install -g andrade-soulseek-downloader

# Command line usage
soulseek-download "The Beatles" "Hey Jude"

# Or run directly with pnpm
pnpm start "The Beatles" "Hey Jude"
```

## 📊 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
- **42 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, preferring lossless formats (FLAC) when possible, then falling back to 320kbps, 256kbps, etc.

### **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: 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

---

## 📄 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>