or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

audio-filters.mdevents-exceptions.mdindex.mdnode-management.mdplayer-control.mdqueue-system.mdtrack-search.md

events-exceptions.mddocs/

0

# Events & Exceptions

1

2

Rich event system for track lifecycle, player state changes, and WebSocket events, plus comprehensive exception hierarchy for robust error handling. The event system enables responsive music bots that can react to playback changes, while the exception hierarchy provides detailed error information for debugging and user feedback.

3

4

## Capabilities

5

6

### Exception Hierarchy

7

8

Comprehensive exception system for handling various error conditions in wavelink operations.

9

10

```python { .api }

11

# Base exception class

12

class WavelinkException(Exception):

13

"""

14

Base wavelink exception class.

15

16

All wavelink exceptions derive from this exception, making it easy

17

to catch any wavelink-related error.

18

"""

19

20

# Node-related exceptions

21

class NodeException(WavelinkException):

22

"""

23

Generic Node error with optional HTTP status code.

24

25

Attributes:

26

- status: int | None - HTTP status code if available

27

"""

28

def __init__(self, msg: str | None = None, status: int | None = None):

29

"""

30

Initialize NodeException.

31

32

Parameters:

33

- msg: Error message

34

- status: HTTP status code

35

"""

36

37

status: int | None

38

39

class InvalidClientException(WavelinkException):

40

"""

41

Exception raised when an invalid discord.Client is provided

42

while connecting a wavelink.Node.

43

"""

44

45

class AuthorizationFailedException(WavelinkException):

46

"""

47

Exception raised when Lavalink fails to authenticate a Node

48

with the provided password.

49

"""

50

51

class InvalidNodeException(WavelinkException):

52

"""

53

Exception raised when a Node is tried to be retrieved from the

54

Pool without existing, or the Pool is empty.

55

"""

56

57

# Lavalink server exceptions

58

class LavalinkException(WavelinkException):

59

"""

60

Exception raised when Lavalink returns an invalid response.

61

62

Attributes:

63

- timestamp: int - Error timestamp

64

- status: int - HTTP response status code

65

- error: str - Error message from Lavalink

66

- trace: str | None - Stack trace if available

67

- path: str - Request path that caused the error

68

"""

69

def __init__(self, msg: str | None = None, /, *, data: dict):

70

"""

71

Initialize LavalinkException from error response data.

72

73

Parameters:

74

- msg: Custom error message

75

- data: Error response data from Lavalink

76

"""

77

78

timestamp: int

79

status: int

80

error: str

81

trace: str | None

82

path: str

83

84

class LavalinkLoadException(WavelinkException):

85

"""

86

Exception raised when an error occurred loading tracks via Lavalink.

87

88

Attributes:

89

- error: str - Error message from Lavalink

90

- severity: str - Error severity level

91

- cause: str - Cause of the error

92

"""

93

def __init__(self, msg: str | None = None, /, *, data: dict):

94

"""

95

Initialize LavalinkLoadException from load error data.

96

97

Parameters:

98

- msg: Custom error message

99

- data: Load error data from Lavalink

100

"""

101

102

error: str

103

severity: str

104

cause: str

105

106

# Player-related exceptions

107

class InvalidChannelStateException(WavelinkException):

108

"""

109

Exception raised when a Player tries to connect to an invalid channel

110

or has invalid permissions to use this channel.

111

"""

112

113

class ChannelTimeoutException(WavelinkException):

114

"""

115

Exception raised when connecting to a voice channel times out.

116

"""

117

118

# Queue-related exceptions

119

class QueueEmpty(WavelinkException):

120

"""

121

Exception raised when you try to retrieve from an empty queue.

122

"""

123

124

# Cache-related exceptions

125

class CapacityZero(WavelinkException):

126

"""

127

Exception raised when LFU cache has zero capacity.

128

"""

129

```

130

131

### Event Payload Classes

132

133

Data structures containing information passed to event handlers for various wavelink events.

134

135

```python { .api }

136

# Node events

137

class NodeReadyEventPayload:

138

"""Data structure for node ready events."""

139

resumed: bool

140

session_id: str

141

142

# Track events

143

class TrackStartEventPayload:

144

"""Data structure for track start events."""

145

track: dict

146

player: Player

147

148

class TrackEndEventPayload:

149

"""Data structure for track end events."""

150

track: dict

151

reason: str

152

player: Player

153

154

class TrackExceptionEventPayload:

155

"""Data structure for track exception events."""

156

track: dict

157

exception: dict

158

player: Player

159

160

class TrackStuckEventPayload:

161

"""Data structure for track stuck events."""

162

track: dict

163

threshold_ms: int

164

player: Player

165

166

# WebSocket events

167

class WebsocketClosedEventPayload:

168

"""Data structure for WebSocket closed events."""

169

code: int

170

reason: str

171

by_remote: bool

172

player: Player

173

174

# Player events

175

class PlayerUpdateEventPayload:

176

"""Data structure for player update events."""

177

state: dict

178

player: Player

179

180

# Statistics events

181

class StatsEventMemory:

182

"""Memory statistics data."""

183

free: int

184

used: int

185

allocated: int

186

reservable: int

187

188

class StatsEventCPU:

189

"""CPU statistics data."""

190

cores: int

191

system_load: float

192

lavalink_load: float

193

194

class StatsEventFrames:

195

"""Frame statistics data."""

196

sent: int

197

nulled: int

198

deficit: int

199

200

class StatsEventPayload:

201

"""Node statistics event data."""

202

players: int

203

playing_players: int

204

uptime: int

205

memory: StatsEventMemory

206

cpu: StatsEventCPU

207

frame_stats: StatsEventFrames | None

208

209

# Response payloads

210

class StatsResponsePayload:

211

"""Statistics response from node."""

212

players: int

213

playing_players: int

214

uptime: int

215

memory: dict

216

cpu: dict

217

frame_stats: dict | None

218

219

class PlayerStatePayload:

220

"""Player state information."""

221

time: int

222

position: int

223

connected: bool

224

ping: int

225

226

class VoiceStatePayload:

227

"""Voice state information."""

228

token: str

229

endpoint: str

230

session_id: str

231

232

class PlayerResponsePayload:

233

"""Player response information."""

234

guild_id: str

235

track: dict | None

236

volume: int

237

paused: bool

238

state: PlayerStatePayload

239

voice: VoiceStatePayload

240

filters: dict

241

242

class GitResponsePayload:

243

"""Git information response."""

244

branch: str

245

commit: str

246

commit_time: int

247

248

class VersionResponsePayload:

249

"""Version information response."""

250

semver: str

251

major: int

252

minor: int

253

patch: int

254

pre_release: str | None

255

build: str | None

256

257

class PluginResponsePayload:

258

"""Plugin information response."""

259

name: str

260

version: str

261

262

class InfoResponsePayload:

263

"""Node information response."""

264

version: VersionResponsePayload

265

build_time: int

266

git: GitResponsePayload

267

jvm: str

268

lavaplayer: str

269

source_managers: list[str]

270

filters: list[str]

271

plugins: list[PluginResponsePayload]

272

273

class ExtraEventPayload:

274

"""Extra event data for custom events."""

275

data: dict

276

```

277

278

### Event Handler Methods

279

280

Event handlers that can be implemented in your bot to respond to wavelink events.

281

282

```python { .api }

283

# Event handler signatures for discord.py bots

284

async def on_wavelink_node_ready(payload: NodeReadyEventPayload) -> None:

285

"""Called when a node connects and is ready."""

286

287

async def on_wavelink_track_start(payload: TrackStartEventPayload) -> None:

288

"""Called when a track starts playing."""

289

290

async def on_wavelink_track_end(payload: TrackEndEventPayload) -> None:

291

"""Called when a track finishes playing."""

292

293

async def on_wavelink_track_exception(payload: TrackExceptionEventPayload) -> None:

294

"""Called when a track encounters an exception."""

295

296

async def on_wavelink_track_stuck(payload: TrackStuckEventPayload) -> None:

297

"""Called when a track gets stuck."""

298

299

async def on_wavelink_websocket_closed(payload: WebsocketClosedEventPayload) -> None:

300

"""Called when the WebSocket connection closes."""

301

302

async def on_wavelink_player_update(payload: PlayerUpdateEventPayload) -> None:

303

"""Called when player state updates."""

304

305

async def on_wavelink_stats_update(payload: StatsEventPayload) -> None:

306

"""Called when node statistics update."""

307

308

async def on_wavelink_extra_event(payload: ExtraEventPayload) -> None:

309

"""Called for custom events from Lavalink plugins."""

310

```

311

312

## Usage Examples

313

314

### Exception Handling

315

316

```python

317

import wavelink

318

import discord

319

from discord.ext import commands

320

321

@bot.event

322

async def on_command_error(ctx, error):

323

"""Global error handler for wavelink exceptions."""

324

if isinstance(error, commands.CommandInvokeError):

325

error = error.original

326

327

if isinstance(error, wavelink.WavelinkException):

328

# Handle wavelink-specific errors

329

if isinstance(error, wavelink.LavalinkLoadException):

330

embed = discord.Embed(

331

title="❌ Failed to Load Track",

332

description=f"**Error**: {error.error}\n**Cause**: {error.cause}",

333

color=discord.Color.red()

334

)

335

await ctx.send(embed=embed)

336

337

elif isinstance(error, wavelink.QueueEmpty):

338

await ctx.send("❌ The queue is empty!")

339

340

elif isinstance(error, wavelink.InvalidChannelStateException):

341

await ctx.send("❌ Cannot connect to that voice channel!")

342

343

elif isinstance(error, wavelink.ChannelTimeoutException):

344

await ctx.send("❌ Connection to voice channel timed out!")

345

346

elif isinstance(error, wavelink.AuthorizationFailedException):

347

await ctx.send("❌ Failed to authenticate with Lavalink server!")

348

349

elif isinstance(error, wavelink.NodeException):

350

embed = discord.Embed(

351

title="❌ Node Error",

352

description=f"Status Code: {error.status}" if error.status else "Unknown node error",

353

color=discord.Color.red()

354

)

355

await ctx.send(embed=embed)

356

357

else:

358

# Generic wavelink error

359

await ctx.send(f"❌ Wavelink error: {error}")

360

else:

361

# Handle non-wavelink errors

362

await ctx.send(f"❌ An error occurred: {error}")

363

364

@bot.command()

365

async def safe_play(ctx, *, query: str):

366

"""Play command with comprehensive error handling."""

367

try:

368

# Ensure player exists

369

if not ctx.voice_client:

370

if not ctx.author.voice:

371

return await ctx.send("❌ You're not in a voice channel!")

372

373

try:

374

player = await ctx.author.voice.channel.connect(cls=wavelink.Player)

375

except wavelink.InvalidChannelStateException:

376

return await ctx.send("❌ I don't have permission to connect to that channel!")

377

except wavelink.ChannelTimeoutException:

378

return await ctx.send("❌ Connection timed out!")

379

else:

380

player = ctx.voice_client

381

382

# Search for tracks

383

try:

384

tracks = await wavelink.Pool.fetch_tracks(query)

385

except wavelink.LavalinkLoadException as e:

386

return await ctx.send(f"❌ Search failed: {e.error}")

387

except wavelink.InvalidNodeException:

388

return await ctx.send("❌ No Lavalink nodes available!")

389

390

if not tracks:

391

return await ctx.send("❌ No tracks found!")

392

393

# Handle results

394

if isinstance(tracks, wavelink.Playlist):

395

added = player.queue.put(tracks)

396

await ctx.send(f"✅ Added playlist: **{tracks.name}** ({added} tracks)")

397

else:

398

track = tracks[0]

399

if player.playing:

400

player.queue.put(track)

401

await ctx.send(f"✅ Added to queue: **{track.title}**")

402

else:

403

await player.play(track)

404

await ctx.send(f"▶️ Now playing: **{track.title}**")

405

406

except wavelink.WavelinkException as e:

407

await ctx.send(f"❌ Wavelink error: {e}")

408

except Exception as e:

409

await ctx.send(f"❌ Unexpected error: {e}")

410

```

411

412

### Event Handling

413

414

```python

415

@bot.event

416

async def on_wavelink_node_ready(payload: wavelink.NodeReadyEventPayload):

417

"""Handle node ready events."""

418

print(f"Node is ready! Session ID: {payload.session_id}")

419

print(f"Resumed previous session: {payload.resumed}")

420

421

@bot.event

422

async def on_wavelink_track_start(payload: wavelink.TrackStartEventPayload):

423

"""Handle track start events."""

424

player = payload.player

425

track = player.current

426

427

if track:

428

# Send now playing message to the last channel

429

guild = player.guild

430

if guild:

431

# Find a suitable channel to send the message

432

channel = discord.utils.get(guild.text_channels, name='music')

433

if not channel:

434

channel = guild.text_channels[0] # Fallback to first channel

435

436

embed = discord.Embed(

437

title="🎵 Now Playing",

438

description=f"**{track.title}**\nby {track.author}",

439

color=discord.Color.green()

440

)

441

442

if track.artwork:

443

embed.set_thumbnail(url=track.artwork)

444

445

duration = f"{track.length // 60000}:{(track.length // 1000) % 60:02d}"

446

embed.add_field(name="Duration", value=duration, inline=True)

447

embed.add_field(name="Source", value=track.source.name, inline=True)

448

embed.add_field(name="Queue", value=f"{player.queue.count} tracks", inline=True)

449

450

try:

451

await channel.send(embed=embed)

452

except discord.HTTPException:

453

pass # Ignore if we can't send messages

454

455

@bot.event

456

async def on_wavelink_track_end(payload: wavelink.TrackEndEventPayload):

457

"""Handle track end events."""

458

player = payload.player

459

460

# Auto-play next track if queue isn't empty

461

if not player.queue.is_empty:

462

try:

463

next_track = player.queue.get()

464

await player.play(next_track)

465

except wavelink.QueueEmpty:

466

pass # Queue became empty between check and get

467

elif player.autoplay != wavelink.AutoPlayMode.disabled:

468

# Let AutoPlay handle track recommendation

469

pass

470

else:

471

# Disconnect after inactivity timeout

472

await asyncio.sleep(300) # Wait 5 minutes

473

if not player.playing and player.queue.is_empty:

474

await player.disconnect()

475

476

@bot.event

477

async def on_wavelink_track_exception(payload: wavelink.TrackExceptionEventPayload):

478

"""Handle track exceptions."""

479

player = payload.player

480

exception = payload.exception

481

482

print(f"Track exception in guild {player.guild.id if player.guild else 'Unknown'}")

483

print(f"Exception: {exception}")

484

485

# Try to play next track if available

486

if not player.queue.is_empty:

487

try:

488

next_track = player.queue.get()

489

await player.play(next_track)

490

except wavelink.QueueEmpty:

491

pass

492

493

@bot.event

494

async def on_wavelink_track_stuck(payload: wavelink.TrackStuckEventPayload):

495

"""Handle stuck tracks."""

496

player = payload.player

497

threshold = payload.threshold_ms

498

499

print(f"Track stuck for {threshold}ms in guild {player.guild.id if player.guild else 'Unknown'}")

500

501

# Skip to next track

502

try:

503

await player.skip()

504

except Exception:

505

pass

506

507

@bot.event

508

async def on_wavelink_websocket_closed(payload: wavelink.WebsocketClosedEventPayload):

509

"""Handle WebSocket disconnections."""

510

player = payload.player

511

code = payload.code

512

reason = payload.reason

513

514

print(f"WebSocket closed for guild {player.guild.id if player.guild else 'Unknown'}")

515

print(f"Code: {code}, Reason: {reason}, By Remote: {payload.by_remote}")

516

517

# Handle specific close codes

518

if code == 4014: # Disconnected

519

# Voice channel was deleted or bot was disconnected

520

try:

521

await player.disconnect()

522

except Exception:

523

pass

524

525

@bot.event

526

async def on_wavelink_player_update(payload: wavelink.PlayerUpdateEventPayload):

527

"""Handle player state updates."""

528

player = payload.player

529

state = payload.state

530

531

# Update any player tracking/UI if needed

532

# This event fires frequently, so be careful with heavy operations

533

pass

534

535

@bot.event

536

async def on_wavelink_stats_update(payload: wavelink.StatsEventPayload):

537

"""Handle node statistics updates."""

538

stats = payload

539

540

print(f"Node Stats - Players: {stats.players}, Playing: {stats.playing_players}")

541

print(f"Memory: {stats.memory.used}MB used, CPU: {stats.cpu.lavalink_load:.2f}%")

542

543

# Monitor node health

544

if stats.memory.used > 1000: # Over 1GB memory usage

545

print("⚠️ High memory usage detected!")

546

547

if stats.cpu.lavalink_load > 80: # Over 80% CPU

548

print("⚠️ High CPU usage detected!")

549

```

550

551

### Advanced Error Recovery

552

553

```python

554

class RobustPlayer(wavelink.Player):

555

"""Enhanced player with automatic error recovery."""

556

557

def __init__(self, *args, **kwargs):

558

super().__init__(*args, **kwargs)

559

self.reconnect_attempts = 0

560

self.max_reconnect_attempts = 3

561

562

async def handle_disconnect(self):

563

"""Handle unexpected disconnections with retry logic."""

564

if self.reconnect_attempts < self.max_reconnect_attempts:

565

self.reconnect_attempts += 1

566

567

try:

568

# Wait before reconnecting

569

await asyncio.sleep(5 * self.reconnect_attempts)

570

571

# Attempt to reconnect

572

if self.channel:

573

await self.connect(self.channel)

574

print(f"Successfully reconnected (attempt {self.reconnect_attempts})")

575

576

# Resume playback if there was a current track

577

if self.current and not self.playing:

578

await self.play(self.current, start=self.position)

579

580

except Exception as e:

581

print(f"Reconnection attempt {self.reconnect_attempts} failed: {e}")

582

583

if self.reconnect_attempts >= self.max_reconnect_attempts:

584

print("Max reconnection attempts reached, giving up")

585

else:

586

print("Already exceeded max reconnection attempts")

587

588

async def safe_play(self, track, **kwargs):

589

"""Play with automatic retry on failure."""

590

for attempt in range(3):

591

try:

592

await self.play(track, **kwargs)

593

return

594

except Exception as e:

595

print(f"Play attempt {attempt + 1} failed: {e}")

596

if attempt < 2:

597

await asyncio.sleep(1)

598

else:

599

raise

600

601

# Use the robust player

602

@bot.command()

603

async def connect_robust(ctx):

604

"""Connect with enhanced error recovery."""

605

if ctx.voice_client:

606

return await ctx.send("Already connected!")

607

608

if not ctx.author.voice:

609

return await ctx.send("You're not in a voice channel!")

610

611

try:

612

player = await ctx.author.voice.channel.connect(cls=RobustPlayer)

613

await ctx.send("✅ Connected with enhanced error recovery!")

614

except Exception as e:

615

await ctx.send(f"❌ Failed to connect: {e}")

616

```

617

618

### Custom Event Logging

619

620

```python

621

import logging

622

from datetime import datetime

623

624

# Configure logging

625

logging.basicConfig(level=logging.INFO)

626

wavelink_logger = logging.getLogger('wavelink_events')

627

628

@bot.event

629

async def on_wavelink_track_start(payload):

630

"""Log track start events."""

631

player = payload.player

632

track = player.current

633

634

wavelink_logger.info(

635

f"Track started - Guild: {player.guild.id if player.guild else 'Unknown'}, "

636

f"Track: {track.title if track else 'Unknown'}, "

637

f"User Count: {len(player.channel.members) if player.channel else 0}"

638

)

639

640

@bot.event

641

async def on_wavelink_track_end(payload):

642

"""Log track end events with reason."""

643

player = payload.player

644

reason = payload.reason

645

646

wavelink_logger.info(

647

f"Track ended - Guild: {player.guild.id if player.guild else 'Unknown'}, "

648

f"Reason: {reason}, Queue: {player.queue.count} tracks"

649

)

650

651

@bot.event

652

async def on_wavelink_track_exception(payload):

653

"""Log track exceptions for debugging."""

654

player = payload.player

655

exception = payload.exception

656

657

wavelink_logger.error(

658

f"Track exception - Guild: {player.guild.id if player.guild else 'Unknown'}, "

659

f"Exception: {exception.get('message', 'Unknown')}, "

660

f"Severity: {exception.get('severity', 'Unknown')}"

661

)

662

663

# Command to view recent logs

664

@bot.command()

665

@commands.has_permissions(administrator=True)

666

async def view_logs(ctx, lines: int = 20):

667

"""View recent wavelink event logs."""

668

# In a production bot, you'd read from actual log files

669

await ctx.send(f"Check the console for the last {lines} wavelink events.")

670

```