Skip to main content

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)

Source distribution for nebula-client-py 1.5.0
File Size Uploaded
nebula_client_py-1.5.0.tar.gz 13.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for nebula-client-py 1.5.0
File Interpreter ABI Platform
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

Release history Release notifications | RSS feed

This release

1.5.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page