An asyncio/discord.py client for NebulaAudio
Project description
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
Project details
Release history Release notifications | RSS feed
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 nebula_client_py-1.5.0.tar.gz.
File metadata
- Download URL: nebula_client_py-1.5.0.tar.gz
- Upload date:
- Size: 13.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1584239769a956265fb3aebf66423e6f97c989f6693efc23781e64bb4565064c
|
|
| MD5 |
660c5ce0fa506fe5a8de4c3f288a57c4
|
|
| BLAKE2b-256 |
4486c4e933567edf5fad43c60bda695560aad09f7ff909cd185ff7fee46a6e3e
|
File details
Details for the file nebula_client_py-1.5.0-py3-none-any.whl.
File metadata
- Download URL: nebula_client_py-1.5.0-py3-none-any.whl
- Upload date:
- Size: 15.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9770a7365e8aa4fea2245fbd2e5dc29b9b77d52507c49f9e293dcc89c0b7da77
|
|
| MD5 |
ca2340dfbdad41fad84cbd296b523dc6
|
|
| BLAKE2b-256 |
61bed37f8ac914f8312bd71db54bf3be5443fa7d72c36a900d5853dd166fd767
|