Metadata-Version: 2.4
Name: waterlink
Version: 1.0.4
Summary: A modern, async, fully-typed Lavalink v4 client for Python Discord bots.
Project-URL: Homepage, https://github.com/yup-console/waterlink
Project-URL: Repository, https://github.com/yup-console/waterlink
Project-URL: Issues, https://github.com/yup-console/waterlink/issues
Project-URL: Changelog, https://github.com/yup-console/waterlink/blob/main/CHANGELOG.md
Author: waterlink contributors
License-Expression: MIT
License-File: LICENSE
Keywords: asyncio,bot,discord,lavalink,music,voice
Classifier: Development Status :: 5 - Production/Stable
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Chat
Classifier: Topic :: Multimedia :: Sound/Audio
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: aiohttp<4,>=3.9
Provides-Extra: dev
Requires-Dist: discord-py<3,>=2.4; extra == 'dev'
Requires-Dist: mypy>=1.13; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.3; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Provides-Extra: discordpy
Requires-Dist: discord-py<3,>=2.4; extra == 'discordpy'
Provides-Extra: disnake
Requires-Dist: disnake<3,>=2.9; extra == 'disnake'
Provides-Extra: nextcord
Requires-Dist: nextcord<3,>=2.6; extra == 'nextcord'
Provides-Extra: pycord
Requires-Dist: py-cord<3,>=2.4; extra == 'pycord'
Description-Content-Type: text/markdown

# waterlink

**A modern, fully-typed, async Lavalink v4 client for Python Discord bots.**

waterlink wraps Lavalink's REST and websocket protocol behind a clean,
Pythonic API: node pooling with load-aware routing, a rich queue engine,
typed audio filters, autoplay, crossfade, state persistence across
restarts, and helpers for popular Lavalink plugins — all with full type
hints and zero required dependencies beyond `aiohttp`.

It auto-detects whichever Discord library you're already using —
**discord.py**, **py-cord**, **nextcord**, or **disnake** — so you don't
install anything extra for voice support.

[![PyPI](https://img.shields.io/pypi/v/waterlink)](https://pypi.org/project/waterlink/)
[![Python](https://img.shields.io/pypi/pyversions/waterlink)](https://pypi.org/project/waterlink/)
[![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
[![GitHub](https://img.shields.io/badge/github-yup--console%2Fwaterlink-black)](https://github.com/yup-console/waterlink)

---

## Features

- **Lavalink v4 native** — REST + websocket built against the current protocol, including session resuming.
- **Multi-node pooling** with pluggable routing strategies (lowest load, round robin, region-aware).
- **Multi-library support** — auto-detects discord.py, py-cord, nextcord, or disnake.
- **Rich queue engine** — history, shuffle, dedupe, track/queue loop modes, priority insertion.
- **Typed audio filters** — equalizer, timescale, karaoke, tremolo, vibrato, rotation, distortion, channel mix, low-pass, all validated.
- **Autoplay** — keeps audio flowing with a pluggable "related track" strategy once the queue empties.
- **Clean metadata** — turns noisy YouTube-style results like `"Tere Liye | Arijit Singh | Viral | T-Series"` by `"T-Series"` into title `"Tere Liye"` by artist `"Arijit Singh"`, opt-in per client or per search call.
- **Crossfade** — smooth client-side volume ramping across track transitions.
- **State persistence** — snapshot and restore queues/players across bot restarts (JSON file backend included, or bring your own).
- **Plugin helpers** — typed convenience wrappers for LavaSrc and SponsorBlock.
- **Observability** — structured logging, an in-process metrics collector (with Prometheus text export), and a watchdog for stalled playback/stale nodes.
- **Fully typed** — ships a `py.typed` marker; passes `mypy --strict`.

## Installation

```bash
pip install waterlink[discordpy]
# or: waterlink[pycord] / waterlink[nextcord] / waterlink[disnake]
```

waterlink only requires `aiohttp` itself — the extras above just also
install a supported Discord library if you don't already have one.

You'll also need a running [Lavalink](https://github.com/lavalink-devs/Lavalink)
v4 server.

## Quick start

```python
import discord
from discord.ext import commands
import waterlink

intents = discord.Intents.default()
bot = commands.Bot(command_prefix="!", intents=intents)
client: waterlink.WaterlinkClient | None = None

@bot.event
async def on_ready():
    global client
    client = waterlink.WaterlinkClient(bot=bot)
    await client.add_node(host="localhost", port=2333, password="youshallnotpass")
    print(f"waterlink ready, using {client.library_name}")

@bot.command()
async def play(ctx: commands.Context, *, query: str):
    if ctx.author.voice is None:
        return await ctx.send("Join a voice channel first.")

    player = await client.connect(ctx.guild.id, ctx.author.voice.channel.id)

    result = await client.search(query)
    if isinstance(result, waterlink.TrackResult):
        track = result.track
    elif isinstance(result, waterlink.SearchTracksResult) and result.tracks:
        track = result.tracks[0]
    else:
        return await ctx.send("No results found.")

    await player.enqueue(track.with_requester(ctx.author.id))
    await ctx.send(f"Queued **{track.title}**")

bot.run("YOUR_TOKEN")
```

See [`examples/`](examples/) for a fuller bot with queue management,
filters, autoplay, and persistence wired up.

## Core concepts

### Node pool

```python
node = await client.add_node(
    name="main",
    host="lavalink.example.com",
    port=443,
    password="secret",
    secure=True,
    region="us-east",
)
```

Add as many nodes as you like; `client.connect()` picks the best one
automatically (`RoutingStrategy.LOWEST_LOAD` by default).

### Queue & playback

```python
player = client.get_player(guild_id)
await player.enqueue(track)
await player.skip()
await player.pause()
await player.resume()
await player.seek(30_000)
player.set_loop_mode(waterlink.LoopMode.QUEUE)
```

### Filters

```python
chain = waterlink.FilterChain()
chain.set_timescale(waterlink.Timescale(speed=1.25, pitch=1.1))
chain.set_equalizer(waterlink.Equalizer.bass_boost())
await player.set_filters(chain)
```

### Autoplay

```python
autoplay = waterlink.AutoplayEngine(client.events)
autoplay.enable(guild_id)
```

### Clean metadata

YouTube search results are often uploaded by a label, not the artist —
so `track.title` and `track.author` can come back as
`"Tere Liye | Arijit Singh | Viral | T-Series"` / `"T-Series"` instead of
`"Tere Liye"` / `"Arijit Singh"`. Enable automatic cleanup:

```python
client = waterlink.WaterlinkClient(bot=bot, clean_metadata=True)
# or per call:
result = await client.search(query, clean=True)
```

```python
track = result.tracks[0]
print(track.title)   # "Tere Liye"
print(track.author)  # "Arijit Singh"
print(track.extra["raw_title"])   # original, untouched, if you need it
print(track.extra["raw_author"])
```

You can also clean a single track directly, or add your own label names
(useful for regional labels not already in the default list):

```python
cleaned = waterlink.clean_track(track)

cleaner = waterlink.TitleCleaner(extra_label_names=("my regional label",))
cleaned = cleaner.clean_track(track)
```

### Events

```python
@client.events.on(waterlink.TrackStartEvent)
async def on_track_start(event: waterlink.TrackStartEvent):
    print(f"Now playing {event.track.title} in guild {event.player.guild_id}")
```

### Persistence

```python
backend = waterlink.JSONFileBackend("state/")
snapshot = waterlink.PlayerSnapshot.capture(player)
await backend.save(snapshot)
```

## Documentation

Full API reference and guides live in [`docs/`](docs/). Highlights:

- [Getting started](docs/getting-started.md)
- [Player & queue](docs/guide/player.md)
- [Filters](docs/guide/filters.md)
- [Nodes & pooling](docs/guide/nodes.md)
- [Events](docs/guide/events.md)
- [Persistence & observability](docs/guide/persistence-observability.md)

## Contributing

Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md).

```bash
git clone https://github.com/yup-console/waterlink
cd waterlink
pip install -e ".[dev]"
pytest
```

## License

MIT — see [LICENSE](LICENSE).
