@stella_project/stellalib 1.0.1 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +461 -159
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,44 +1,144 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
A powerful
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">StellaLib</h1>
|
|
3
|
+
<p align="center">A powerful Lavalink v3 + v4 client for TypeScript — with auto version detection, session persistence, smart autoplay, and graceful shutdown.</p>
|
|
4
|
+
</p>
|
|
5
|
+
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="https://www.npmjs.com/package/@stella_project/stellalib"><img src="https://img.shields.io/npm/v/@stella_project/stellalib.svg?style=flat-square&color=blue" alt="npm version" /></a>
|
|
8
|
+
<a href="https://www.npmjs.com/package/@stella_project/stellalib"><img src="https://img.shields.io/npm/dm/@stella_project/stellalib.svg?style=flat-square" alt="npm downloads" /></a>
|
|
9
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-OSL--3.0-blue.svg?style=flat-square" alt="License" /></a>
|
|
10
|
+
<a href="https://github.com/Roki-Stella-Projects/StellaLib"><img src="https://img.shields.io/github/stars/Roki-Stella-Projects/StellaLib?style=flat-square" alt="GitHub stars" /></a>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Table of Contents
|
|
16
|
+
|
|
17
|
+
- [What is StellaLib?](#what-is-stellalib)
|
|
18
|
+
- [How it Works](#how-it-works)
|
|
19
|
+
- [Architecture](#architecture)
|
|
20
|
+
- [Installation](#installation)
|
|
21
|
+
- [Quick Start](#quick-start)
|
|
22
|
+
- [Core Concepts](#core-concepts)
|
|
23
|
+
- [Manager](#manager)
|
|
24
|
+
- [Node](#node)
|
|
25
|
+
- [Player](#player)
|
|
26
|
+
- [Queue](#queue)
|
|
27
|
+
- [Rest](#rest)
|
|
28
|
+
- [Filters](#filters)
|
|
29
|
+
- [Multi-Version Support (v3 + v4)](#multi-version-support-v3--v4)
|
|
30
|
+
- [Session Persistence](#session-persistence)
|
|
31
|
+
- [Smart Autoplay](#smart-autoplay)
|
|
32
|
+
- [Search with Fallback](#search-with-fallback)
|
|
33
|
+
- [Audio Filters](#audio-filters)
|
|
34
|
+
- [Events Reference](#events-reference)
|
|
35
|
+
- [Configuration Reference](#configuration-reference)
|
|
36
|
+
- [Requirements](#requirements)
|
|
37
|
+
- [Documentation](#documentation)
|
|
38
|
+
- [Changelog](#changelog)
|
|
39
|
+
- [License](#license)
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## What is StellaLib?
|
|
44
|
+
|
|
45
|
+
**StellaLib** is a TypeScript client library that connects your Discord bot to [Lavalink](https://github.com/lavalink-devs/Lavalink) — a standalone audio server that handles music playback, search, and streaming. StellaLib manages the entire lifecycle: connecting to Lavalink nodes, creating guild-level players, searching tracks, controlling playback, and handling events.
|
|
46
|
+
|
|
47
|
+
Unlike other Lavalink clients, StellaLib:
|
|
48
|
+
|
|
49
|
+
- **Auto-detects** whether your Lavalink server is v3 or v4 and adapts automatically
|
|
50
|
+
- **Persists sessions** across bot restarts so music keeps playing
|
|
51
|
+
- **Has a smart autoplay engine** that picks the best next track based on listening history
|
|
52
|
+
- **Handles failures gracefully** with reconnect backoff, rate limit retries, and search fallback
|
|
53
|
+
|
|
54
|
+
## How it Works
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
┌──────────────┐ raw voice events ┌──────────────────┐ WebSocket/REST ┌──────────┐
|
|
58
|
+
│ Discord.js │ ──────────────────────► │ StellaManager │ ◄──────────────────► │ Lavalink │
|
|
59
|
+
│ (your bot) │ ◄────── send payloads ── │ │ │ Server │
|
|
60
|
+
└──────────────┘ └──────────────────┘ └──────────┘
|
|
61
|
+
│
|
|
62
|
+
┌─────────────┼─────────────┐
|
|
63
|
+
▼ ▼ ▼
|
|
64
|
+
┌──────────┐ ┌──────────┐ ┌──────────┐
|
|
65
|
+
│ Node 1 │ │ Node 2 │ │ Node N │
|
|
66
|
+
│ (v4 auto)│ │ (v3 auto)│ │ │
|
|
67
|
+
└──────────┘ └──────────┘ └──────────┘
|
|
68
|
+
│
|
|
69
|
+
┌─────┼─────┐
|
|
70
|
+
▼ ▼ ▼
|
|
71
|
+
┌────────┐ ┌────────┐
|
|
72
|
+
│Player A│ │Player B│ (one per guild)
|
|
73
|
+
│ Queue │ │ Queue │
|
|
74
|
+
│Filters │ │Filters │
|
|
75
|
+
└────────┘ └────────┘
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Flow:**
|
|
79
|
+
|
|
80
|
+
1. Your bot receives raw Discord voice events and forwards them to `StellaManager`
|
|
81
|
+
2. The Manager routes voice data to the correct `Node` (Lavalink server connection)
|
|
82
|
+
3. Each Node auto-detects its Lavalink version (v3 or v4) and adapts its protocol
|
|
83
|
+
4. `Player` instances (one per guild) handle playback, queue, volume, and filters
|
|
84
|
+
5. The Node's `Rest` client handles track loading, player updates, and session management
|
|
85
|
+
6. Events flow back from Lavalink → Node → Manager → your bot's event handlers
|
|
86
|
+
|
|
87
|
+
## Architecture
|
|
88
|
+
|
|
89
|
+
StellaLib is composed of several core classes that work together:
|
|
90
|
+
|
|
91
|
+
| Class | What it does |
|
|
92
|
+
|---|---|
|
|
93
|
+
| **`StellaManager`** | The entry point. Manages all nodes and players, handles search, voice state updates, caching, and shutdown. You create one Manager per bot. |
|
|
94
|
+
| **`StellaNode`** | Represents a connection to a single Lavalink server. Handles WebSocket connection, heartbeat, reconnect, version detection, session resume, and autoplay logic. |
|
|
95
|
+
| **`StellaPlayer`** | One player per Discord guild. Controls playback (`play`, `pause`, `stop`, `seek`), manages the queue, applies filters, and handles voice readiness. |
|
|
96
|
+
| **`StellaQueue`** | Extends `Array` with music-specific methods: `add()`, `remove()`, `clear()`, `shuffle()`, repeat modes, and `current`/`previous` track tracking. |
|
|
97
|
+
| **`StellaRest`** | HTTP client for Lavalink's REST API. Version-aware (v3 vs v4 endpoints), with rate limit retry, request deduplication, and timeout handling. |
|
|
98
|
+
| **`StellaFilters`** | Manages audio filters and equalizer settings per player. Built-in presets for common effects. |
|
|
99
|
+
| **`LRUCache`** | Bounded least-recently-used cache with TTL expiry for search results. Reduces redundant API calls. |
|
|
100
|
+
| **`FileSessionStore`** | Persists Lavalink session IDs to a JSON file. Enables seamless resume after bot restarts. |
|
|
101
|
+
|
|
102
|
+
### Project Structure
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
src/
|
|
106
|
+
Structures/
|
|
107
|
+
Manager.ts — Main hub: nodes, players, search, voice, cache, shutdown
|
|
108
|
+
Node.ts — Lavalink node: WS, heartbeat, reconnect, version detect, autoplay
|
|
109
|
+
Player.ts — Guild player: playback, queue, voice ready, filters, move node
|
|
110
|
+
Queue.ts — Queue: add/remove/shuffle, repeat modes, current/previous
|
|
111
|
+
Rest.ts — REST client: version-aware endpoints, retry, dedup, timeout
|
|
112
|
+
Filters.ts — Audio filter management and presets
|
|
113
|
+
LRUCache.ts — Bounded LRU cache with TTL and memory estimation
|
|
114
|
+
SessionStore.ts — FileSessionStore for session persistence
|
|
115
|
+
Types.ts — All TypeScript interfaces, types, and event definitions
|
|
116
|
+
Utils.ts — TrackUtils (build/validate tracks), Structure, Plugin system
|
|
117
|
+
Utils/
|
|
118
|
+
FiltersEqualizers.ts — Equalizer band presets for each filter
|
|
119
|
+
ManagerCheck.ts — Manager option validation
|
|
120
|
+
NodeCheck.ts — Node option validation
|
|
121
|
+
PlayerCheck.ts — Player option validation
|
|
122
|
+
index.ts — Re-exports everything
|
|
123
|
+
```
|
|
25
124
|
|
|
26
125
|
## Installation
|
|
27
126
|
|
|
28
127
|
```bash
|
|
29
|
-
npm install stellalib
|
|
128
|
+
npm install @stella_project/stellalib
|
|
30
129
|
# or
|
|
31
|
-
yarn add stellalib
|
|
130
|
+
yarn add @stella_project/stellalib
|
|
32
131
|
# or
|
|
33
|
-
bun add stellalib
|
|
132
|
+
bun add @stella_project/stellalib
|
|
34
133
|
```
|
|
35
134
|
|
|
36
135
|
## Quick Start
|
|
37
136
|
|
|
38
137
|
```ts
|
|
39
138
|
import { Client, GatewayIntentBits } from "discord.js";
|
|
40
|
-
import { StellaManager, FileSessionStore } from "stellalib";
|
|
139
|
+
import { StellaManager, FileSessionStore } from "@stella_project/stellalib";
|
|
41
140
|
|
|
141
|
+
// 1. Create your Discord client
|
|
42
142
|
const client = new Client({
|
|
43
143
|
intents: [
|
|
44
144
|
GatewayIntentBits.Guilds,
|
|
@@ -48,38 +148,73 @@ const client = new Client({
|
|
|
48
148
|
],
|
|
49
149
|
});
|
|
50
150
|
|
|
151
|
+
// 2. Create the StellaLib manager
|
|
51
152
|
const manager = new StellaManager({
|
|
52
153
|
nodes: [
|
|
53
154
|
{
|
|
54
|
-
identifier: "main",
|
|
55
|
-
host: "localhost",
|
|
56
|
-
port: 2333,
|
|
57
|
-
password: "youshallnotpass"
|
|
58
|
-
resumeStatus: true,
|
|
59
|
-
resumeTimeout: 120,
|
|
60
|
-
heartbeatInterval: 30000,
|
|
155
|
+
identifier: "main", // Unique name for this node
|
|
156
|
+
host: "localhost", // Lavalink server host
|
|
157
|
+
port: 2333, // Lavalink server port
|
|
158
|
+
password: "youshallnotpass",// Lavalink password
|
|
159
|
+
resumeStatus: true, // Enable session resuming
|
|
160
|
+
resumeTimeout: 120, // Seconds Lavalink waits for reconnect
|
|
161
|
+
heartbeatInterval: 30000, // Ping interval in ms
|
|
61
162
|
},
|
|
62
163
|
],
|
|
63
|
-
autoPlay: true,
|
|
64
|
-
defaultSearchPlatform: "spotify",
|
|
65
|
-
searchFallback: ["soundcloud", "youtube
|
|
66
|
-
sessionStore: new FileSessionStore("./sessions.json"),
|
|
67
|
-
caches: { enabled: true, time: 60000, maxSize: 200 },
|
|
164
|
+
autoPlay: true, // Enable autoplay when queue ends
|
|
165
|
+
defaultSearchPlatform: "spotify", // Default search source
|
|
166
|
+
searchFallback: ["soundcloud", "youtube"], // Fallback if primary fails
|
|
167
|
+
sessionStore: new FileSessionStore("./sessions.json"), // Persist sessions
|
|
168
|
+
caches: { enabled: true, time: 60000, maxSize: 200 }, // Search cache
|
|
68
169
|
send(id, payload) {
|
|
170
|
+
// Required: how to send voice payloads to Discord
|
|
69
171
|
const guild = client.guilds.cache.get(id);
|
|
70
172
|
if (guild) guild.shard.send(payload);
|
|
71
173
|
},
|
|
72
174
|
});
|
|
73
175
|
|
|
74
|
-
// Forward raw Discord events to StellaLib
|
|
176
|
+
// 3. Forward raw Discord events to StellaLib (required for voice)
|
|
75
177
|
client.on("raw", (d) => manager.updateVoiceState(d));
|
|
76
178
|
|
|
179
|
+
// 4. Initialize manager when bot is ready
|
|
77
180
|
client.on("ready", () => {
|
|
78
181
|
console.log(`Bot ready as ${client.user?.tag}`);
|
|
79
182
|
manager.init(client.user!.id);
|
|
80
183
|
});
|
|
81
184
|
|
|
82
|
-
//
|
|
185
|
+
// 5. Handle events
|
|
186
|
+
manager.on("NodeConnect", (node) => {
|
|
187
|
+
console.log(`Connected to ${node.options.identifier} (Lavalink v${node.version})`);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
manager.on("TrackStart", (player, track) => {
|
|
191
|
+
console.log(`Now playing: ${track.title}`);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// 6. Play music (example in a command handler)
|
|
195
|
+
async function play(guildId: string, voiceChannelId: string, query: string) {
|
|
196
|
+
// Create or get player
|
|
197
|
+
let player = manager.players.get(guildId);
|
|
198
|
+
if (!player) {
|
|
199
|
+
player = manager.create({
|
|
200
|
+
guild: guildId,
|
|
201
|
+
voiceChannel: voiceChannelId,
|
|
202
|
+
textChannel: "TEXT_CHANNEL_ID",
|
|
203
|
+
volume: 50,
|
|
204
|
+
selfDeafen: true,
|
|
205
|
+
});
|
|
206
|
+
player.connect();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Search and queue
|
|
210
|
+
const res = await manager.search(query, "USER_ID");
|
|
211
|
+
if (res.tracks.length) {
|
|
212
|
+
player.queue.add(res.tracks[0]);
|
|
213
|
+
if (!player.playing) player.play();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 7. Graceful shutdown
|
|
83
218
|
for (const sig of ["SIGINT", "SIGTERM"]) {
|
|
84
219
|
process.on(sig, async () => {
|
|
85
220
|
await manager.shutdown();
|
|
@@ -87,184 +222,351 @@ for (const sig of ["SIGINT", "SIGTERM"]) {
|
|
|
87
222
|
});
|
|
88
223
|
}
|
|
89
224
|
|
|
90
|
-
// Play a track
|
|
91
|
-
manager.on("NodeConnect", async () => {
|
|
92
|
-
const player = manager.create({
|
|
93
|
-
guild: "GUILD_ID",
|
|
94
|
-
voiceChannel: "VOICE_CHANNEL_ID",
|
|
95
|
-
textChannel: "TEXT_CHANNEL_ID",
|
|
96
|
-
});
|
|
97
|
-
player.connect();
|
|
98
|
-
|
|
99
|
-
const res = await manager.search("never gonna give you up");
|
|
100
|
-
if (res.tracks.length) {
|
|
101
|
-
player.queue.add(res.tracks[0]);
|
|
102
|
-
player.play();
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
225
|
client.login("YOUR_BOT_TOKEN");
|
|
107
226
|
```
|
|
108
227
|
|
|
109
|
-
##
|
|
228
|
+
## Core Concepts
|
|
110
229
|
|
|
111
|
-
|
|
230
|
+
### Manager
|
|
112
231
|
|
|
113
|
-
|
|
114
|
-
import { FileSessionStore } from "stellalib";
|
|
232
|
+
`StellaManager` is the central hub. You create **one instance** and it manages everything.
|
|
115
233
|
|
|
234
|
+
```ts
|
|
116
235
|
const manager = new StellaManager({
|
|
236
|
+
nodes: [...], // Array of Lavalink node configs
|
|
237
|
+
send: (id, payload) => { ... }, // How to send to Discord gateway
|
|
238
|
+
autoPlay: true, // Auto-play next track when queue ends
|
|
239
|
+
defaultSearchPlatform: "spotify",
|
|
240
|
+
searchFallback: ["soundcloud", "youtube music"],
|
|
117
241
|
sessionStore: new FileSessionStore("./sessions.json"),
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
// ...
|
|
122
|
-
}],
|
|
123
|
-
// ...
|
|
242
|
+
caches: { enabled: true, time: 60000, maxSize: 200 },
|
|
243
|
+
clientName: "StellaLib",
|
|
244
|
+
shards: 1,
|
|
124
245
|
});
|
|
125
246
|
|
|
126
|
-
//
|
|
127
|
-
|
|
247
|
+
// Initialize after Discord client is ready
|
|
248
|
+
manager.init(client.user!.id);
|
|
128
249
|
```
|
|
129
250
|
|
|
130
|
-
|
|
251
|
+
**Key methods:**
|
|
252
|
+
- `manager.init(clientId)` — Connect all nodes
|
|
253
|
+
- `manager.create(options)` — Create a player for a guild
|
|
254
|
+
- `manager.get(guildId)` — Get existing player
|
|
255
|
+
- `manager.search(query, requester?)` — Search tracks with fallback
|
|
256
|
+
- `manager.updateVoiceState(data)` — Forward raw Discord voice events
|
|
257
|
+
- `manager.shutdown()` — Gracefully close everything
|
|
258
|
+
- `manager.getStats()` — Get node/player/cache statistics
|
|
259
|
+
|
|
260
|
+
### Node
|
|
261
|
+
|
|
262
|
+
`StellaNode` represents a single Lavalink server connection. Nodes are created automatically from the `nodes` config.
|
|
263
|
+
|
|
264
|
+
**What it does automatically:**
|
|
265
|
+
- Detects Lavalink version (v3 or v4) before connecting
|
|
266
|
+
- Establishes WebSocket with the correct URL and headers
|
|
267
|
+
- Sends heartbeat pings to detect dead connections
|
|
268
|
+
- Reconnects with exponential backoff + jitter on disconnect
|
|
269
|
+
- Configures session resuming (v3: WS op, v4: REST PATCH)
|
|
270
|
+
- Syncs player state after resume (v4 only)
|
|
271
|
+
- Handles autoplay logic when queue ends
|
|
272
|
+
|
|
273
|
+
**Properties:**
|
|
274
|
+
- `node.version` — Detected Lavalink version (`3` or `4`)
|
|
275
|
+
- `node.connected` — Whether WebSocket is open
|
|
276
|
+
- `node.stats` — CPU, memory, players, uptime stats
|
|
277
|
+
- `node.info` — Cached Lavalink server info (plugins, sources)
|
|
278
|
+
- `node.penalties` — Calculated penalty score for load balancing
|
|
279
|
+
|
|
280
|
+
### Player
|
|
281
|
+
|
|
282
|
+
`StellaPlayer` controls playback for **one Discord guild**. Created via `manager.create()`.
|
|
131
283
|
|
|
132
284
|
```ts
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
285
|
+
const player = manager.create({
|
|
286
|
+
guild: "GUILD_ID",
|
|
287
|
+
voiceChannel: "VOICE_CHANNEL_ID",
|
|
288
|
+
textChannel: "TEXT_CHANNEL_ID",
|
|
289
|
+
volume: 50,
|
|
290
|
+
selfDeafen: true,
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
player.connect(); // Join voice channel
|
|
294
|
+
player.play(); // Play first track in queue
|
|
295
|
+
player.pause(true); // Pause
|
|
296
|
+
player.pause(false); // Resume
|
|
297
|
+
player.stop(); // Stop current track (plays next)
|
|
298
|
+
player.seek(30000); // Seek to 30 seconds
|
|
299
|
+
player.setVolume(80); // Set volume (0-100)
|
|
300
|
+
player.setTrackRepeat(true); // Repeat current track
|
|
301
|
+
player.setQueueRepeat(true); // Repeat entire queue
|
|
302
|
+
player.setAutoplay(true, botUser); // Enable smart autoplay
|
|
303
|
+
player.moveNode("other-node"); // Move to another Lavalink node
|
|
304
|
+
player.destroy(); // Leave channel and clean up
|
|
138
305
|
```
|
|
139
306
|
|
|
140
|
-
|
|
307
|
+
### Queue
|
|
141
308
|
|
|
142
|
-
|
|
309
|
+
`StellaQueue` extends JavaScript's `Array` with music-specific helpers:
|
|
143
310
|
|
|
144
311
|
```ts
|
|
145
|
-
//
|
|
146
|
-
player.
|
|
312
|
+
player.queue.add(track); // Add track(s) to end
|
|
313
|
+
player.queue.add([track1, track2]); // Add multiple
|
|
314
|
+
player.queue.remove(0); // Remove by index
|
|
315
|
+
player.queue.clear(); // Clear all queued tracks
|
|
316
|
+
player.queue.shuffle(); // Randomize order
|
|
317
|
+
player.queue.current; // Currently playing track
|
|
318
|
+
player.queue.previous; // Previously played track
|
|
319
|
+
player.queue.totalSize; // current + queued count
|
|
320
|
+
player.queue.size; // Queued count (excluding current)
|
|
147
321
|
```
|
|
148
322
|
|
|
149
|
-
|
|
150
|
-
- **Duration similarity** — Prefer tracks close in length to recent plays
|
|
151
|
-
- **Author/title overlap** — Prioritize same artist or related keywords
|
|
152
|
-
- **Source consistency** — Stay on the same platform when possible
|
|
153
|
-
- **Diversity** — Avoid repeating the same artist too many times
|
|
154
|
-
- **History tracking** — Never replay recently heard tracks (last 50)
|
|
323
|
+
### Rest
|
|
155
324
|
|
|
156
|
-
|
|
325
|
+
`StellaRest` handles all HTTP communication with Lavalink. It's version-aware — the same method call works for both v3 and v4.
|
|
157
326
|
|
|
158
|
-
|
|
327
|
+
| Method | v3 behavior | v4 behavior |
|
|
328
|
+
|---|---|---|
|
|
329
|
+
| `loadTracks(id)` | `GET /loadtracks` → normalized | `GET /v4/loadtracks` |
|
|
330
|
+
| `updatePlayer(opts)` | WS ops (`play`, `pause`, etc.) | `PATCH /v4/sessions/.../players/...` |
|
|
331
|
+
| `destroyPlayer(id)` | WS `destroy` op | `DELETE /v4/sessions/.../players/...` |
|
|
332
|
+
| `configureResume(t)` | WS `configureResuming` op | `PATCH /v4/sessions/...` |
|
|
333
|
+
| `getInfo()` | `GET /version` | `GET /v4/info` |
|
|
334
|
+
| `decodeTracks(arr)` | `POST /decodetracks` | `POST /v4/decodetracks` |
|
|
159
335
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
});
|
|
336
|
+
**Built-in resilience:**
|
|
337
|
+
- Auto-retry on 429 rate limits (up to 3 retries with `Retry-After`)
|
|
338
|
+
- GET request deduplication (concurrent identical GETs share one request)
|
|
339
|
+
- Configurable request timeout
|
|
340
|
+
- Request/failure counters
|
|
166
341
|
|
|
167
|
-
|
|
168
|
-
const result = await manager.search("natori セレナーデ");
|
|
169
|
-
```
|
|
342
|
+
### Filters
|
|
170
343
|
|
|
171
|
-
|
|
344
|
+
Built-in audio filter presets:
|
|
172
345
|
|
|
173
346
|
```ts
|
|
174
|
-
// Toggle filters
|
|
175
|
-
await player.filters.setFilter("nightcore", true);
|
|
176
347
|
await player.filters.setFilter("bassboost", true);
|
|
348
|
+
await player.filters.setFilter("nightcore", true);
|
|
177
349
|
await player.filters.setFilter("vaporwave", true);
|
|
178
350
|
await player.filters.setFilter("eightD", true);
|
|
351
|
+
await player.filters.setFilter("slowmo", true);
|
|
352
|
+
await player.filters.setFilter("soft", true);
|
|
353
|
+
await player.filters.setFilter("trebleBass", true);
|
|
354
|
+
await player.filters.setFilter("tv", true);
|
|
355
|
+
await player.filters.setFilter("distort", true);
|
|
179
356
|
|
|
180
|
-
//
|
|
181
|
-
await player.filters.clearFilters();
|
|
357
|
+
await player.filters.clearFilters(); // Remove all
|
|
182
358
|
```
|
|
183
359
|
|
|
184
|
-
|
|
360
|
+
Each preset applies specific equalizer bands, timescale, rotation, or other Lavalink audio parameters.
|
|
185
361
|
|
|
186
|
-
##
|
|
362
|
+
## Multi-Version Support (v3 + v4)
|
|
363
|
+
|
|
364
|
+
StellaLib **automatically detects** your Lavalink server version before connecting. No configuration needed.
|
|
365
|
+
|
|
366
|
+
**How detection works:**
|
|
367
|
+
1. Before WebSocket connect, the Node sends `GET /v4/info` to the server
|
|
368
|
+
2. If it responds `200 OK` → **Lavalink v4** detected (server info is cached)
|
|
369
|
+
3. If it fails, tries `GET /version` → **Lavalink v3** detected
|
|
370
|
+
4. Falls back to v4 if both fail
|
|
371
|
+
|
|
372
|
+
**What adapts automatically:**
|
|
373
|
+
|
|
374
|
+
| Aspect | Lavalink v3 | Lavalink v4 |
|
|
375
|
+
|---|---|---|
|
|
376
|
+
| **WebSocket URL** | `ws://host:port` | `ws://host:port/v4/websocket` |
|
|
377
|
+
| **Player control** | WebSocket ops (`play`, `stop`, `pause`, `seek`, `volume`, `filters`) | REST `PATCH` |
|
|
378
|
+
| **Session resume** | `Resume-Key` header + WS `configureResuming` | `Session-Id` header + REST `PATCH` |
|
|
379
|
+
| **Track loading** | `/loadtracks` (response normalized to v4 format) | `/v4/loadtracks` |
|
|
380
|
+
| **Server info** | `/version` (returns version string) | `/v4/info` (returns full info JSON) |
|
|
381
|
+
| **Player sync** | Not available (v3 limitation) | Full player state sync on resume |
|
|
382
|
+
| **Track data** | `track` field → mapped to `encoded` | `encoded` field |
|
|
383
|
+
| **Load types** | `TRACK_LOADED` → `track`, `SEARCH_RESULT` → `search`, etc. | Already v4 format |
|
|
187
384
|
|
|
188
385
|
```ts
|
|
189
|
-
|
|
190
|
-
|
|
386
|
+
manager.on("NodeConnect", (node) => {
|
|
387
|
+
console.log(`Lavalink v${node.version}`); // 3 or 4
|
|
388
|
+
});
|
|
191
389
|
```
|
|
192
390
|
|
|
193
|
-
##
|
|
391
|
+
## Session Persistence
|
|
392
|
+
|
|
393
|
+
StellaLib persists session IDs so music **keeps playing** after bot restarts.
|
|
194
394
|
|
|
195
395
|
```ts
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
manager
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
manager.on("PlayerCreate", (player) => { });
|
|
208
|
-
manager.on("PlayerDestroy", (player) => { });
|
|
209
|
-
manager.on("PlayerMove", (player, oldChannel, newChannel) => { });
|
|
210
|
-
manager.on("PlayerDisconnect", (player, oldChannel) => { });
|
|
211
|
-
manager.on("PlayerStateUpdate", (oldPlayer, newPlayer) => { });
|
|
212
|
-
manager.on("Debug", (message) => { });
|
|
396
|
+
import { FileSessionStore } from "@stella_project/stellalib";
|
|
397
|
+
|
|
398
|
+
const manager = new StellaManager({
|
|
399
|
+
sessionStore: new FileSessionStore("./sessions.json"),
|
|
400
|
+
nodes: [{
|
|
401
|
+
resumeStatus: true, // Tell Lavalink to hold the session
|
|
402
|
+
resumeTimeout: 120, // Seconds to wait before destroying session
|
|
403
|
+
// ...
|
|
404
|
+
}],
|
|
405
|
+
// ...
|
|
406
|
+
});
|
|
213
407
|
```
|
|
214
408
|
|
|
215
|
-
|
|
409
|
+
**How it works:**
|
|
410
|
+
1. On connect, Node loads saved session ID from the store
|
|
411
|
+
2. Sends it as `Session-Id` (v4) or `Resume-Key` (v3) header
|
|
412
|
+
3. Lavalink resumes the session — players keep their state
|
|
413
|
+
4. On disconnect/shutdown, session ID is persisted to the store
|
|
414
|
+
|
|
415
|
+
**Custom stores** (e.g., Redis, database):
|
|
216
416
|
|
|
417
|
+
```ts
|
|
418
|
+
const manager = new StellaManager({
|
|
419
|
+
sessionStore: {
|
|
420
|
+
async get(nodeId) { return await redis.get(`session:${nodeId}`); },
|
|
421
|
+
async set(nodeId, sessionId) { await redis.set(`session:${nodeId}`, sessionId); },
|
|
422
|
+
async delete(nodeId) { await redis.del(`session:${nodeId}`); },
|
|
423
|
+
},
|
|
424
|
+
// ...
|
|
425
|
+
});
|
|
217
426
|
```
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
Filters.ts — Audio filters and presets
|
|
226
|
-
LRUCache.ts — Bounded LRU cache with TTL
|
|
227
|
-
SessionStore.ts — FileSessionStore for session persistence
|
|
228
|
-
Types.ts — All TypeScript interfaces and types
|
|
229
|
-
Utils.ts — TrackUtils, Structure, Plugin helpers
|
|
230
|
-
Utils/
|
|
231
|
-
FiltersEqualizers.ts — Equalizer band presets
|
|
232
|
-
ManagerCheck.ts — Manager option validation
|
|
233
|
-
NodeCheck.ts — Node option validation
|
|
234
|
-
PlayerCheck.ts — Player option validation
|
|
235
|
-
index.ts — Re-exports everything
|
|
427
|
+
|
|
428
|
+
## Smart Autoplay
|
|
429
|
+
|
|
430
|
+
When the queue ends and autoplay is enabled, StellaLib's auto-mix engine picks the best next track.
|
|
431
|
+
|
|
432
|
+
```ts
|
|
433
|
+
player.setAutoplay(true, client.user);
|
|
236
434
|
```
|
|
237
435
|
|
|
238
|
-
|
|
436
|
+
**How the engine works:**
|
|
239
437
|
|
|
240
|
-
|
|
438
|
+
1. **Seed collection** — Gathers the last 5 played tracks as context seeds
|
|
439
|
+
2. **Source detection** — Identifies if the listener was on Spotify, YouTube, or SoundCloud
|
|
440
|
+
3. **Recommendation fetch** — Uses Spotify `sprec:` (seed artists + seed tracks) or YouTube Mix
|
|
441
|
+
4. **Candidate scoring** — Each candidate is scored on:
|
|
442
|
+
- Duration similarity to recent tracks
|
|
443
|
+
- Author/title keyword overlap
|
|
444
|
+
- Remix/cover penalty (avoids non-originals)
|
|
445
|
+
- History check (never replays last 50 tracks)
|
|
446
|
+
5. **Best transition** — Picks the highest-scoring candidate
|
|
447
|
+
6. **Cross-platform mirror** — If needed, re-searches on SoundCloud/YouTube for a streamable version
|
|
448
|
+
7. **Fallback chain** — If recommendations fail, tries theme-based search, then random from same artist
|
|
241
449
|
|
|
242
|
-
|
|
243
|
-
|---|---|---|
|
|
244
|
-
| **WebSocket URL** | `ws://host:port` | `ws://host:port/v4/websocket` |
|
|
245
|
-
| **Player ops** | WebSocket ops (`play`, `stop`, `pause`, etc.) | REST PATCH |
|
|
246
|
-
| **Session resume** | `Resume-Key` header + WS `configureResuming` | `Session-Id` header + REST PATCH |
|
|
247
|
-
| **Load tracks** | `/loadtracks` (normalized to v4 format) | `/v4/loadtracks` |
|
|
248
|
-
| **Server info** | `/version` | `/v4/info` |
|
|
249
|
-
| **Player sync** | Not available | Full player state sync |
|
|
450
|
+
## Search with Fallback
|
|
250
451
|
|
|
251
452
|
```ts
|
|
252
|
-
// No configuration needed — version is auto-detected
|
|
253
453
|
const manager = new StellaManager({
|
|
254
|
-
|
|
454
|
+
defaultSearchPlatform: "spotify",
|
|
455
|
+
searchFallback: ["soundcloud", "youtube music", "youtube"],
|
|
255
456
|
// ...
|
|
256
457
|
});
|
|
257
458
|
|
|
258
|
-
//
|
|
259
|
-
manager.
|
|
260
|
-
|
|
261
|
-
|
|
459
|
+
// Searches Spotify first. If empty, tries SoundCloud, then YouTube Music, then YouTube.
|
|
460
|
+
const result = await manager.search("natori セレナーデ", userId);
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
**Supported platforms:** `spotify`, `soundcloud`, `youtube`, `youtube music`, `deezer`, `tidal`, `applemusic`, `bandcamp`, `jiosaavn`
|
|
464
|
+
|
|
465
|
+
## Audio Filters
|
|
466
|
+
|
|
467
|
+
| Filter | Effect |
|
|
468
|
+
|---|---|
|
|
469
|
+
| `bassboost` | Boosts low frequencies |
|
|
470
|
+
| `nightcore` | Speeds up + higher pitch |
|
|
471
|
+
| `vaporwave` | Slows down + lower pitch |
|
|
472
|
+
| `eightD` | Rotating stereo panning |
|
|
473
|
+
| `slowmo` | Slower playback speed |
|
|
474
|
+
| `soft` | Reduces harsh frequencies |
|
|
475
|
+
| `trebleBass` | Boosts both high and low bands |
|
|
476
|
+
| `tv` | Tinny speaker simulation |
|
|
477
|
+
| `distort` | Audio distortion effect |
|
|
478
|
+
|
|
479
|
+
## Events Reference
|
|
480
|
+
|
|
481
|
+
| Event | Parameters | Description |
|
|
482
|
+
|---|---|---|
|
|
483
|
+
| `NodeCreate` | `(node)` | Node instance created |
|
|
484
|
+
| `NodeConnect` | `(node)` | WebSocket connection established |
|
|
485
|
+
| `NodeReconnect` | `(node)` | Attempting reconnection |
|
|
486
|
+
| `NodeDisconnect` | `(node, reason)` | WebSocket disconnected |
|
|
487
|
+
| `NodeDestroy` | `(node)` | Node destroyed |
|
|
488
|
+
| `NodeError` | `(node, error)` | Error on node |
|
|
489
|
+
| `NodeRaw` | `(payload)` | Raw WebSocket message |
|
|
490
|
+
| `TrackStart` | `(player, track, payload)` | Track started playing |
|
|
491
|
+
| `TrackEnd` | `(player, track, payload)` | Track finished |
|
|
492
|
+
| `TrackStuck` | `(player, track, payload)` | Track got stuck |
|
|
493
|
+
| `TrackError` | `(player, track, payload)` | Track playback error |
|
|
494
|
+
| `QueueEnd` | `(player, track, payload)` | Queue finished (all tracks played) |
|
|
495
|
+
| `PlayerCreate` | `(player)` | Player created for a guild |
|
|
496
|
+
| `PlayerDestroy` | `(player)` | Player destroyed |
|
|
497
|
+
| `PlayerMove` | `(player, oldChannel, newChannel)` | Bot moved to different voice channel |
|
|
498
|
+
| `PlayerDisconnect` | `(player, oldChannel)` | Bot disconnected from voice |
|
|
499
|
+
| `PlayerStateUpdate` | `(oldPlayer, newPlayer)` | Player state changed |
|
|
500
|
+
| `SocketClosed` | `(player, payload)` | Lavalink WebSocket closed for player |
|
|
501
|
+
| `Debug` | `(message)` | Debug log message |
|
|
502
|
+
|
|
503
|
+
## Configuration Reference
|
|
504
|
+
|
|
505
|
+
### Manager Options
|
|
506
|
+
|
|
507
|
+
```ts
|
|
508
|
+
interface ManagerOptions {
|
|
509
|
+
nodes: NodeOptions[]; // Lavalink server configs (required)
|
|
510
|
+
send: (id: string, payload: Payload) => void; // Discord gateway send (required)
|
|
511
|
+
clientId?: string; // Bot user ID (set by init())
|
|
512
|
+
clientName?: string; // Client identifier sent to Lavalink
|
|
513
|
+
shards?: number; // Shard count
|
|
514
|
+
autoPlay?: boolean; // Enable autoplay on queue end
|
|
515
|
+
defaultSearchPlatform?: SearchPlatform;// Default search source
|
|
516
|
+
searchFallback?: string[]; // Fallback platforms
|
|
517
|
+
sessionStore?: SessionStore; // Session persistence store
|
|
518
|
+
caches?: {
|
|
519
|
+
enabled: boolean;
|
|
520
|
+
time: number; // TTL in ms
|
|
521
|
+
maxSize: number; // Max cached entries
|
|
522
|
+
};
|
|
523
|
+
plugins?: Plugin[]; // Custom plugins
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Node Options
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
interface NodeOptions {
|
|
531
|
+
host: string; // Lavalink host
|
|
532
|
+
port: number; // Lavalink port
|
|
533
|
+
password: string; // Lavalink password
|
|
534
|
+
identifier?: string; // Unique node name
|
|
535
|
+
secure?: boolean; // Use wss:// and https://
|
|
536
|
+
retryAmount?: number; // Max reconnect attempts
|
|
537
|
+
retryDelay?: number; // Base delay between retries (ms)
|
|
538
|
+
requestTimeout?: number; // REST request timeout (ms)
|
|
539
|
+
resumeStatus?: boolean; // Enable session resuming
|
|
540
|
+
resumeTimeout?: number; // Seconds Lavalink holds session
|
|
541
|
+
heartbeatInterval?: number;// WebSocket ping interval (ms)
|
|
542
|
+
}
|
|
262
543
|
```
|
|
263
544
|
|
|
264
545
|
## Requirements
|
|
265
546
|
|
|
266
547
|
- **Node.js** >= 18.0.0
|
|
267
548
|
- **Lavalink** v3.x or v4.x
|
|
549
|
+
- **Discord.js** v14+ (or any library that exposes raw gateway events)
|
|
550
|
+
|
|
551
|
+
## Documentation
|
|
552
|
+
|
|
553
|
+
For detailed guides and API reference, see the [docs/](docs/) folder:
|
|
554
|
+
|
|
555
|
+
- [Getting Started](docs/01-getting-started.md)
|
|
556
|
+
- [Architecture](docs/02-architecture.md)
|
|
557
|
+
- [Manager](docs/03-manager.md)
|
|
558
|
+
- [Node](docs/04-node.md)
|
|
559
|
+
- [Player](docs/05-player.md)
|
|
560
|
+
- [Queue](docs/06-queue.md)
|
|
561
|
+
- [Events](docs/07-events.md)
|
|
562
|
+
- [Filters](docs/08-filters.md)
|
|
563
|
+
- [Session Persistence](docs/09-session-persistence.md)
|
|
564
|
+
- [Multi-Version Support](docs/10-multi-version.md)
|
|
565
|
+
- [Autoplay Engine](docs/11-autoplay.md)
|
|
566
|
+
|
|
567
|
+
## Changelog
|
|
568
|
+
|
|
569
|
+
See [CHANGELOG.md](CHANGELOG.md) for a detailed list of changes per version.
|
|
268
570
|
|
|
269
571
|
## License
|
|
270
572
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stella_project/stellalib",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.21",
|
|
4
4
|
"description": "StellaLib — A powerful Lavalink v3+v4 client for TypeScript with auto version detection, session persistence, smart autoplay, and graceful shutdown",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|