nebula_client
An asyncio/discord.py client for NebulaAudio, styled after wavelink /
lavalink.py: connect a Node, use Player as your voice client, await node.search(...) to load tracks, await player.play(track) to play them.
Install
pip install nebula-client-py
Repository: https://github.com/ItzAntonio101/nebula-client-js
Status — read before wiring this into a real bot
Fully working: Node REST calls (search, fetch_info, fetch_stats,
player PATCH/DELETE), the WebSocket listener (reconnect w/ backoff, event
parsing), all events (TrackStart, TrackEnd, TrackException,
TrackStuck, PlayerUpdate, NodeReady), queueing/looping, Filters
builder, Track/Playlist/SearchResult models.
The one thing that doesn't produce audio yet: NebulaAudio's Java server
does not implement Discord's voice UDP/RTP transport (this is documented in
its own README/javadoc — AudioPlayer.sendFrame() is a labeled no-op seam).
That means calling player.play(track) here will correctly tell the node to
start decoding and will fire real TrackStartEvent/TrackEndEvents — but no
Opus packets currently leave the server to Discord's voice socket. This
client's on_voice_server_update/on_voice_state_update hooks capture the
endpoint/token/session_id discord.py hands them (that part's correct and
matches Lavalink's contract), but forwarding them anywhere is a no-op with a
log line pointing back at the gap, rather than a silent pretend-success.
Everything in this client is written to be correct for when that gap is
closed server-side — the REST/WS contract, event flow, and queue logic don't
need to change once real audio transmission exists, only
_maybe_dispatch_voice_update needs to actually send the voice payload.
Example bot
import discord
from discord.ext import commands
import nebula_client
intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix="!", intents=intents)
@bot.event
async def on_ready():
node = nebula_client.Node(uri="http://localhost:2333", password="supersecret")
await nebula_client.NodePool.connect(client=bot, nodes=[node])
print(f"Logged in as {bot.user}")
@bot.event
async def on_nebula_track_start(event: nebula_client.TrackStartEvent):
print(f"Now playing: {event.track.title} in guild {event.player.guild_id}")
@bot.command()
async def play(ctx: commands.Context, *, query: str):
if not ctx.author.voice:
return await ctx.send("Join a voice channel first.")
player: nebula_client.Player = ctx.voice_client or await ctx.author.voice.channel.connect(cls=nebula_client.Player)
result = await player.node.search(query if "://" in query or ":" in query.split(" ")[0] else f"ytsearch:{query}")
if not result:
return await ctx.send("Nothing found.")
track = result.tracks[0]
track.requester = ctx.author
if player.is_playing:
player.add_to_queue(track)
await ctx.send(f"Queued: **{track.title}**")
else:
await player.play(track)
await ctx.send(f"Now playing: **{track.title}**")
@bot.command()
async def skip(ctx: commands.Context):
if ctx.voice_client:
await ctx.voice_client.skip()
await ctx.send("Skipped.")
@bot.command()
async def volume(ctx: commands.Context, value: int):
if ctx.voice_client:
await ctx.voice_client.set_volume(value)
await ctx.send(f"Volume set to {value}%")
@bot.command()
async def nightcore(ctx: commands.Context):
if ctx.voice_client:
await ctx.voice_client.set_filters(nebula_client.Filters().set_nightcore())
await ctx.send("Nightcore on.")
bot.run("YOUR_TOKEN")
Layout
nebula_client/
├── __init__.py - public API surface
├── node.py - Node (REST + WebSocket) and NodePool registry
├── player.py - Player(discord.VoiceProtocol)
├── track.py - Track / Playlist / SearchResult
├── filters.py - Filters builder
├── events.py - event dataclasses dispatched via client.dispatch()
├── enums.py - LoadType, TrackEndReason, NodeStatus
└── exceptions.py - exception hierarchy
Release files for nebula-client-py 1.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| nebula_client_py-1.5.0.tar.gz | 13.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| nebula_client_py-1.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 29.1 kB
Release files / nebula_client_py-1.5.0.tar.gz
| Download URL | nebula_client_py-1.5.0.tar.gz |
|---|---|
| Size | 13.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1584239769a956265fb3aebf66423e6f97c989f6693efc23781e64bb4565064c
|
|
BLAKE2b-256 checksum How to use checksums |
4486c4e933567edf5fad43c60bda695560aad09f7ff909cd185ff7fee46a6e3e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.14
|
Release files / nebula_client_py-1.5.0-py3-none-any.whl
| Download URL | nebula_client_py-1.5.0-py3-none-any.whl |
|---|---|
| Size | 15.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9770a7365e8aa4fea2245fbd2e5dc29b9b77d52507c49f9e293dcc89c0b7da77
|
|
BLAKE2b-256 checksum How to use checksums |
61bed37f8ac914f8312bd71db54bf3be5443fa7d72c36a900d5853dd166fd767
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.14
|