# JioSaavn Client

[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://python.org)
[![aiohttp](https://img.shields.io/badge/aiohttp-3.8+-blue.svg)](https://docs.aiohttp.org)
[![requests](https://img.shields.io/badge/requests-2.28+-blue.svg)](https://requests.readthedocs.io)

Python API client for the [Unofficial JioSaavn API](https://github.com/appujet/jiosaavn-plugin-api). Full-featured sync and async support with typed models, media URL decryption, and all endpoints covered.

> **Educational & Research Purpose Only**: This project is created solely for educational and research purposes. Use responsibly and respect JioSaavn's terms of service.

---

## Installation

```bash
pip install jiosaavnclient
```

Or install from source:

```bash
git clone https://github.com/xylen-py/JioSaavnClient.git
cd JioSaavnClient
pip install .
```

**Dependencies**: `requests`, `aiohttp`, `pycryptodome`

---

## Quick Start

### Sync Client

```python
from jiosaavn import JioSaavnClient

client = JioSaavnClient()

results = client.search("Tum Hi Ho")
for track in results:
    print(f"{track.title} - {track.author} ({track.duration}s)")

track = client.get_track("aRZbUYD7")
print(track.title, track.author, track.encryptedMediaUrl)

album = client.get_album("1OBiF1NfYdk_")
for track in album:
    print(track.title)

recs = client.get_recommendations("aRZbUYD7", limit=5)
for track in recs:
    print(track.title)

client.close()
```

### Async Client

```python
import asyncio
from jiosaavn import AsyncJioSaavnClient

async def main():
    async with AsyncJioSaavnClient() as client:
        results = await client.search("Kesariya")
        for track in results:
            print(f"{track.title} - {track.author}")

        track = await client.get_track("Bt07_OpM")
        print(track.encryptedMediaUrl)

asyncio.run(main())
```

### Media URL Decryption

```python
from jiosaavn import JioSaavnClient, decrypt_media_url, get_direct_url, get_all_quality_urls

client = JioSaavnClient()
track = client.get_track_by_id("Bt07_OpM")

direct_url = decrypt_media_url(track.encryptedMediaUrl)
print(direct_url)

hq_url = get_direct_url(track.encryptedMediaUrl, quality="320kbps")
print(hq_url)

all_urls = get_all_quality_urls(track.encryptedMediaUrl)
for quality, url in all_urls.items():
    print(f"{quality}: {url}")

client.close()
```

### Custom Base URL

```python
client = JioSaavnClient(base_url="https://your-api.vercel.app/api", timeout=30)
```

---

## API Reference

### Search

| Method | Parameters | Returns |
|---|---|---|
| `search(query)` | `query`: search string | `SearchResult` (iterable) |

### Track Methods

| Method | Parameters | Returns |
|---|---|---|
| `get_track(identifier)` | ID or full JioSaavn URL | `Track` |
| `get_track_by_id(track_id)` | Track ID | `Track` |
| `get_track_by_url(url)` | Full JioSaavn URL | `Track` |

### Album Methods

| Method | Parameters | Returns |
|---|---|---|
| `get_album(identifier)` | ID or full JioSaavn URL | `Album` (iterable) |
| `get_album_by_id(album_id)` | Album ID | `Album` |
| `get_album_by_url(url)` | Full JioSaavn URL | `Album` |

### Artist Methods

| Method | Parameters | Returns |
|---|---|---|
| `get_artist(identifier)` | ID or full JioSaavn URL | `Artist` (iterable) |
| `get_artist_by_id(artist_id)` | Artist ID | `Artist` |
| `get_artist_by_url(url)` | Full JioSaavn URL | `Artist` |

### Playlist Methods

| Method | Parameters | Returns |
|---|---|---|
| `get_playlist(identifier, limit=100)` | ID or URL, track limit | `Playlist` (iterable) |
| `get_playlist_by_id(playlist_id, limit=100)` | Playlist ID, track limit | `Playlist` |
| `get_playlist_by_url(url, limit=100)` | Full JioSaavn URL, limit | `Playlist` |

### Recommendations & Streaming

| Method | Parameters | Returns |
|---|---|---|
| `get_recommendations(track_id, limit=10)` | Track ID, limit | `RecommendationResult` (iterable) |
| `get_encrypted_media_url(encrypted_url)` | Encrypted URL string | `Dict` with `auth_url`, `type`, `status` |

---

## Models Reference

### Track

| Field | Type | Description |
|---|---|---|
| `identifier` | `str` | Track ID |
| `title` | `str` | Track title |
| `length` | `int` | Duration in milliseconds |
| `uri` | `str` | JioSaavn permalink |
| `artworkUrl` | `str` | 500x500 artwork URL |
| `author` | `str` | Primary artist name |
| `encryptedMediaUrl` | `str` | Encrypted stream URL (decrypt with `decrypt_media_url()`) |
| `albumUrl` | `str` | Album page URL |
| `artistUrl` | `str` | Artist page URL |
| `albumName` | `str` | Album name |
| `artistArtworkUrl` | `str` | Artist 500x500 artwork |
| `previewUrl` | `str` | Preview/snippet URL |
| **Properties** | | |
| `duration` | `int` | Duration in seconds |
| `duration_ms` | `int` | Duration in milliseconds |
| `url` | `str` | JioSaavn permalink |

### Album

| Field | Type | Description |
|---|---|---|
| `id` | `str` | Album ID |
| `name` | `str` | Album title |
| `uri` | `str` | Album permalink |
| `artworkUrl` | `str` | 500x500 artwork |
| `author` | `str` | Album subtitle/artist |
| `totalSongs` | `int` | Total track count |
| `tracks` | `List[Track]` | Album tracks |

Albums are **iterable**: `for track in album:` and support `len(album)`.

### Artist

| Field | Type | Description |
|---|---|---|
| `name` | `str` | Artist name |
| `uri` | `str` | Artist page URL |
| `artworkUrl` | `str` | 500x500 artwork |
| `tracks` | `List[Track]` | Top tracks (up to 50) |

Artists are **iterable**: `for track in artist:` and support `len(artist)`.

### Playlist

| Field | Type | Description |
|---|---|---|
| `title` | `str` | Playlist title |
| `uri` | `str` | Playlist permalink |
| `artworkUrl` | `str` | 500x500 artwork |
| `totalSongs` | `int` | Total track count |
| `tracks` | `List[Track]` | Playlist tracks |

Playlists are **iterable**: `for track in playlist:` and support `len(playlist)`.

### SearchResult / RecommendationResult

Both are **iterable** with `total`, `is_empty` properties and `len()`, `[]` support.

---

## Media URL Decryption

JioSaavn encrypts media URLs using DES ECB encryption. This library includes a built-in decryptor.

| Function | Parameters | Returns |
|---|---|---|
| `decrypt_media_url(encrypted_url)` | Encrypted URL from track | Decrypted CDN URL |
| `get_direct_url(encrypted_url, quality="320kbps")` | Encrypted URL, quality | Direct HTTPS URL at specified quality |
| `get_all_quality_urls(encrypted_url)` | Encrypted URL | Dict of all quality URLs |

**Available qualities**: `12kbps`, `48kbps`, `96kbps`, `160kbps`, `320kbps`

---

## Exceptions

| Exception | Trigger | Attributes |
|---|---|---|
| `JioSaavnError` | Base exception | `message` |
| `JioSaavnAPIError` | HTTP 4xx/5xx | `status_code`, `message` |
| `JioSaavnNotFoundError` | Resource not found (404) | `message` |
| `JioSaavnRateLimitError` | Rate limited (429) | `message` |
| `JioSaavnConnectionError` | Network/timeout failures | `message` |

```python
from jiosaavn import JioSaavnClient, JioSaavnNotFoundError

client = JioSaavnClient()
try:
    track = client.get_track_by_id("invalid")
except JioSaavnNotFoundError:
    print("Track not found")
```

---

## Utilities

```python
from jiosaavn import extract_id, extract_type, is_jiosaavn_url

extract_id("https://www.jiosaavn.com/song/tum-hi-ho/OgwhbhtDRw", "track")
extract_type("https://www.jiosaavn.com/album/aashiqui-2/1OBiF1NfYdk_")
is_jiosaavn_url("https://www.jiosaavn.com/song/tum-hi-ho/OgwhbhtDRw")
```

---

## Test Results

<details>
<summary><b>Search</b> — search("Tum Hi Ho")</summary>

```
10 results

  Tum Hi Ho | Mithoon | 262s | aRZbUYD7
  Tum Hi Ho (From "Aashiqui 2") | Arijit Singh | 261s | Bt07_OpM
  Tum Hi Ho Bandhu | Pritam | 283s | qyo-hm5x
```

</details>

<details>
<summary><b>Get Track by ID</b> — get_track_by_id("aRZbUYD7")</summary>

```
title:             Tum Hi Ho
author:            Mithoon
duration:          262s
identifier:        aRZbUYD7
encryptedMediaUrl: ID2ieOjCrwfgWvL5sXl4B1ImC5QfbsDy...
```

</details>

<details>
<summary><b>Decrypt Media URL</b></summary>

```
Decrypted: https://aac.saavncdn.com/840/c9e70fb62d66fa6e14f6b7cdbc56cc0...mp4

All qualities:
  12kbps:  https://aac.saavncdn.com/840/.../_12.mp4
  48kbps:  https://aac.saavncdn.com/840/.../_48.mp4
  96kbps:  https://aac.saavncdn.com/840/.../_96.mp4
  160kbps: https://aac.saavncdn.com/840/.../_160.mp4
  320kbps: https://aac.saavncdn.com/840/.../_320.mp4
```

</details>

<details>
<summary><b>Recommendations</b> — get_recommendations("aRZbUYD7", limit=3)</summary>

```
3 tracks

  Apna Bana Le | Sachin-Jigar
  Saiyaara | Tanishk Bagchi
  Raataan Lambiyan | Tanishk Bagchi
```

</details>

<details>
<summary><b>Encrypted Media URL</b> — get_encrypted_media_url(encrypted_url)</summary>

```
auth_url: https://web.saavncdn.com/840/c9e70fb62d66fa6e14f6b7cdbc56cc0...
type:     mp4
status:   success
```

</details>

---

## License

MIT
