A modern, async, fully-typed Lavalink v4 client for Python Discord bots.
Project description
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.
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.typedmarker; passesmypy --strict.
Installation
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 v4 server.
Quick start
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/ for a fuller bot with queue management,
filters, autoplay, and persistence wired up.
Core concepts
Node pool
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
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
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
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:
client = waterlink.WaterlinkClient(bot=bot, clean_metadata=True)
# or per call:
result = await client.search(query, clean=True)
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):
cleaned = waterlink.clean_track(track)
cleaner = waterlink.TitleCleaner(extra_label_names=("my regional label",))
cleaned = cleaner.clean_track(track)
Events
@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
backend = waterlink.JSONFileBackend("state/")
snapshot = waterlink.PlayerSnapshot.capture(player)
await backend.save(snapshot)
Documentation
Full API reference and guides live in docs/. Highlights:
Contributing
Contributions are welcome — see CONTRIBUTING.md.
git clone https://github.com/yup-console/waterlink
cd waterlink
pip install -e ".[dev]"
pytest
License
MIT — see LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file waterlink-1.0.1.tar.gz.
File metadata
- Download URL: waterlink-1.0.1.tar.gz
- Upload date:
- Size: 54.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.8.0 pkginfo/1.12.1.2 readme-renderer/42.0 requests/2.31.0 requests-toolbelt/1.0.0 urllib3/2.7.0 tqdm/4.68.3 importlib-metadata/7.2.1 keyring/25.7.0 rfc3986/2.0.0 colorama/0.4.6 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fc936e65cbbe3f15316202a9484c9c784794b3ca232dd67fe5a0074dbad46197
|
|
| MD5 |
d18461334d076a9e4dd48b3303f00752
|
|
| BLAKE2b-256 |
e1ff8bffa0cd90956250a966a6c085dba21618592e900e5ade10163061fbb411
|
File details
Details for the file waterlink-1.0.1-py3-none-any.whl.
File metadata
- Download URL: waterlink-1.0.1-py3-none-any.whl
- Upload date:
- Size: 55.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.8.0 pkginfo/1.12.1.2 readme-renderer/42.0 requests/2.31.0 requests-toolbelt/1.0.0 urllib3/2.7.0 tqdm/4.68.3 importlib-metadata/7.2.1 keyring/25.7.0 rfc3986/2.0.0 colorama/0.4.6 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e555b533fd22e1872463d9453d207f02e5c5e91a966ddb18d71be946b1d5e214
|
|
| MD5 |
6ce9a20076f4bb8bd27e7a5f37b9e427
|
|
| BLAKE2b-256 |
be2a810df3cfebfa80cc9657e546f70b1be519370027fff978d2f070b75c76a4
|