Skip to main content

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

nebula_client_py-1.5.0.tar.gz (13.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nebula_client_py-1.5.0-py3-none-any.whl (15.2 kB view details)

Uploaded Python 3

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

Hashes for nebula_client_py-1.5.0.tar.gz
Algorithm Hash digest
SHA256 1584239769a956265fb3aebf66423e6f97c989f6693efc23781e64bb4565064c
MD5 660c5ce0fa506fe5a8de4c3f288a57c4
BLAKE2b-256 4486c4e933567edf5fad43c60bda695560aad09f7ff909cd185ff7fee46a6e3e

See more details on using hashes here.

File details

Details for the file nebula_client_py-1.5.0-py3-none-any.whl.

File metadata

File hashes

Hashes for nebula_client_py-1.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9770a7365e8aa4fea2245fbd2e5dc29b9b77d52507c49f9e293dcc89c0b7da77
MD5 ca2340dfbdad41fad84cbd296b523dc6
BLAKE2b-256 61bed37f8ac914f8312bd71db54bf3be5443fa7d72c36a900d5853dd166fd767

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page