Moving From Wavelink¶
This guide is for migrating from Wavelink to SonoLink.
It walks through the main differences between SonoLink and Wavelink 3.x, so you can get a feel for what changes and what stays familiar.
If you are not migrating an existing Wavelink bot, start with Getting Started instead.
What stays familiar¶
Lavalink remains the backend.
SonoLink supports discord.py, py-cord, disnake, and nextcord, each using a custom
discord.VoiceProtocolfor voice integration.The main runtime objects are still a client-level coordinator, nodes, players, queues, tracks, playlists, and filters.
What changes¶
Most of the work comes down to replacing Wavelink’s top-level helper APIs with explicit SonoLink objects and adjusting for the return types used in Wavelink 3.x.
Concept mapping¶
Wavelink |
SonoLink |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
Connection lifecycle¶
In Wavelink, node management is centered on the class-level pool API.
The Wavelink docs show connecting with wavelink.Pool.connect(...) after building
wavelink.Node(...) objects.
In SonoLink, the equivalent flow is explicit and instance-based:
import sonolink
sl_client = sonolink.Client(bot)
sl_client.create_node(
uri="http://localhost:2333",
password="youshallnotpass",
id="main",
)
async def setup_hook() -> None:
await sl_client.start()
Note
sonolink.Client.start() should be called once your Discord client is ready,
typically in discord.Client.setup_hook() (discord.py), discord.on_connect() (py-cord),
disnake.on_connect() (disnake) or nextcord.on_connect() (nextcord)
rather than in the on_ready event.
Settings¶
SonoLink introduces a settings system with no direct Wavelink equivalent. Settings are dataclass-like objects passed at node or player creation time, giving you structured control over behavior that Wavelink left to ad-hoc configuration:
sonolink.models.InactivitySettings— controls how long a player waits before disconnecting when the channel is inactive, and what counts as inactive.sonolink.models.CacheSettings— configures the node-level LFU search result cache.sonolink.models.AutoPlaySettings— configures autoplay mode, search provider, discovery count, and seed limits.sonolink.models.HistorySettings— enables or limits the track history thatpreviousand autoplay depend on. Autoplay requires history to be enabled — see Autoplay for details.
from sonolink.models.settings import AutoPlaySettings, CacheSettings, HistorySettings, InactivitySettings
from sonolink.gateway.enums import AutoPlayMode, InactivityMode
sl_client.create_node(
uri="http://localhost:2333",
password="youshallnotpass",
inactivity_settings=InactivitySettings(
timeout=300,
mode=InactivityMode.ALL_BOTS,
),
cache_settings=CacheSettings(
enabled=True,
max_items=1000,
),
)
player = sl_client.get_best_node().create_player(
autoplay_settings=AutoPlaySettings(
mode=AutoPlayMode.ENABLED,
discovery_count=10,
),
history_settings=HistorySettings(
enabled=True,
max_items=100,
),
)
Searching¶
In Wavelink, the search flow is usually:
# Wavelink
tracks = await wavelink.Playable.search(query)
if not tracks:
return
if isinstance(tracks, wavelink.Playlist):
play_track = tracks.tracks[0]
rest = tracks.tracks[1:]
else:
play_track = tracks[0]
rest = tracks[1:]
In SonoLink, searching returns a wrapper object that makes the result type explicit:
# SonoLink
result = await sl_client.search_track(query)
if result.is_error() or result.is_empty() or result.result is None:
return
data = result.result
rest = []
if isinstance(data, list):
play_track = data[0]
rest = data[1:]
elif isinstance(data, sonolink.models.Playlist):
play_track = data.tracks[0]
rest = data.tracks[1:]
else:
play_track = data
sonolink.models.SearchResult covers all possible outcomes: a single track, a playlist,
a search result list, an empty result, or an error result.
Players and voice connection¶
You still connect through Discord with:
player = await voice_channel.connect(cls=sonolink.Player)
If you need to pre-configure a player with volume, filters, queue mode, autoplay, or history
settings before connecting, you can either instantiate sonolink.Player directly or use
sonolink.Node.create_player() — both are equivalent:
from sonolink.models import Filters, Karaoke
player = sonolink.Player(
node=node,
volume=100,
filters=Filters(
karaoke=Karaoke(level=0.5),
),
)
# or
player = node.create_player(
volume=100,
filters=Filters(
karaoke=Karaoke(level=0.5),
),
)
await voice_channel.connect(cls=player)
See Working With Players for the full player reference.
Playback flow¶
SonoLink does not expose a playing property — use sonolink.Player.current instead
(see State helpers). The common pattern is to call sonolink.Player.play() directly
when nothing is currently playing, and put remaining tracks into the queue. Queue progression
after a track ends is handled automatically:
# play_track and rest come from the search result — see Searching above
if not vc.current:
await vc.play(play_track)
else:
rest = [play_track, *rest]
for track in rest:
await vc.queue.put_wait(track)
When a track ends, SonoLink automatically calls sonolink.Player.skip() internally, which
pulls the next track from the queue, falls back to autoplay if the queue is empty, and stops
the player if neither applies. You do not need to drive this manually.
Filters¶
Wavelink uses a single wavelink.Filters object that is mutated in place and re-applied
via player.set_filters:
# Wavelink
filters = player.filters
filters.karaoke.set(level=0.5)
await player.set_filters(filters)
SonoLink works similarly but constructs a fresh sonolink.models.Filters object each
time, passing filter instances directly as constructor arguments. The seek parameter
optionally restarts the current track position after applying:
# SonoLink
from sonolink.models import Filters, Karaoke
filters = Filters(karaoke=Karaoke(level=0.5))
await player.set_filters(filters, seek=True)
See Filters And Playback State for the full filter reference.
Events¶
Wavelink dispatches events through the Discord client with the wavelink_ prefix, while
SonoLink works the same way, using the sonolink_ prefix instead:
Wavelink |
SonoLink |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Handled internally; configure via |
(no equivalent) |
Autoplay¶
Wavelink exposes an auto_queue concept. SonoLink’s autoplay is configured
through sonolink.models.AutoPlaySettings at player creation time and toggled via
sonolink.Player.autoplay, which accepts an sonolink.AutoPlayMode value:
sonolink.AutoPlayMode.DISABLED— autoplay is completely disabled. The player stops when the queue empties.sonolink.AutoPlayMode.PARTIAL— SonoLink manages progression autonomously but does not fill the queue with recommended tracks.sonolink.AutoPlayMode.ENABLED— when the queue empties, SonoLink discovers related tracks and fills the queue automatically. Tracks added to the standard queue are treated as priority.
The search provider used for discovery is configured via sonolink.SearchProvider in
sonolink.models.AutoPlaySettings:
sonolink.SearchProvider.YOUTUBE— YouTube Radio mix based on the track identifier.sonolink.SearchProvider.SPOTIFY— Spotify recommendations based on the track identifier.sonolink.SearchProvider.DEEZER— Deezer track or artist radio based on the identifier.
Warning
Autoplay uses the track history as its seed. Ensure sonolink.models.HistorySettings
has history enabled when creating the player, otherwise autoplay will have no reference
track to discover from. See Settings for an example.
State helpers¶
Wavelink exposes player.playing and player.paused as the primary state checks.
SonoLink does not have a playing property — replace any player.playing checks with
player.current is not None:
# Wavelink
if player.playing:
...
# SonoLink
if player.current is not None:
...
The full public player state in SonoLink is:
sonolink.Player.current— the track currently playing, orNone.sonolink.Player.paused— whether the player is paused.sonolink.Player.position— the current playback position in milliseconds.sonolink.Player.volume— the current volume, between 0 and 1000.sonolink.Player.queue— thesonolink.Queueholding upcoming and historical tracks.sonolink.Player.autoplay— the currentsonolink.AutoPlayModefor this player.