lavalink-client 1.0.0 → 1.0.1
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 +93 -164
- package/dist/cjs/index.d.ts +9 -0
- package/dist/cjs/index.js +12 -0
- package/dist/cjs/structures/Filters.d.ts +231 -0
- package/dist/cjs/structures/Filters.js +481 -0
- package/dist/cjs/structures/LavalinkManager.d.ts +124 -0
- package/dist/cjs/structures/LavalinkManager.js +168 -0
- package/dist/cjs/structures/LavalinkManagerStatics.d.ts +3 -0
- package/dist/cjs/structures/LavalinkManagerStatics.js +84 -0
- package/dist/cjs/structures/Node.d.ts +245 -0
- package/dist/cjs/structures/Node.js +603 -0
- package/dist/cjs/structures/NodeManager.d.ts +61 -0
- package/dist/cjs/structures/NodeManager.js +35 -0
- package/dist/cjs/structures/Player.d.ts +191 -0
- package/dist/cjs/structures/Player.js +395 -0
- package/dist/cjs/structures/Queue.d.ts +107 -0
- package/dist/cjs/structures/Queue.js +215 -0
- package/dist/cjs/structures/Track.d.ts +47 -0
- package/dist/cjs/structures/Track.js +2 -0
- package/dist/cjs/structures/Utils.d.ts +258 -0
- package/dist/cjs/structures/Utils.js +179 -0
- package/dist/esm/index.d.ts +9 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/structures/Filters.d.ts +231 -0
- package/dist/esm/structures/Filters.js +477 -0
- package/dist/esm/structures/LavalinkManager.d.ts +124 -0
- package/dist/esm/structures/LavalinkManager.js +164 -0
- package/dist/esm/structures/LavalinkManagerStatics.d.ts +3 -0
- package/dist/esm/structures/LavalinkManagerStatics.js +81 -0
- package/dist/esm/structures/Node.d.ts +245 -0
- package/dist/esm/structures/Node.js +598 -0
- package/dist/esm/structures/NodeManager.d.ts +61 -0
- package/dist/esm/structures/NodeManager.js +31 -0
- package/dist/esm/structures/Player.d.ts +191 -0
- package/dist/esm/structures/Player.js +391 -0
- package/dist/esm/structures/Queue.d.ts +107 -0
- package/dist/esm/structures/Queue.js +208 -0
- package/dist/esm/structures/Track.d.ts +47 -0
- package/dist/esm/structures/Track.js +1 -0
- package/dist/esm/structures/Utils.d.ts +258 -0
- package/dist/esm/structures/Utils.js +173 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +13 -0
- package/dist/structures/Filters.d.ts +230 -0
- package/dist/structures/Filters.js +472 -0
- package/dist/structures/LavalinkManager.d.ts +47 -0
- package/dist/structures/LavalinkManager.js +36 -0
- package/dist/structures/LavalinkManagerStatics.d.ts +3 -0
- package/dist/structures/LavalinkManagerStatics.js +76 -0
- package/dist/structures/Node.d.ts +171 -0
- package/dist/structures/Node.js +462 -0
- package/dist/structures/NodeManager.d.ts +58 -0
- package/dist/structures/NodeManager.js +25 -0
- package/dist/structures/Player.d.ts +101 -0
- package/dist/structures/Player.js +232 -0
- package/dist/structures/PlayerManager.d.ts +62 -0
- package/dist/structures/PlayerManager.js +26 -0
- package/dist/structures/Queue.d.ts +93 -0
- package/dist/structures/Queue.js +160 -0
- package/dist/structures/QueueManager.d.ts +77 -0
- package/dist/structures/QueueManager.js +74 -0
- package/dist/structures/Track.d.ts +27 -0
- package/dist/structures/Track.js +2 -0
- package/dist/structures/Utils.d.ts +183 -0
- package/dist/structures/Utils.js +43 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/structures/Filters.d.ts +231 -0
- package/dist/types/structures/LavalinkManager.d.ts +124 -0
- package/dist/types/structures/LavalinkManagerStatics.d.ts +3 -0
- package/dist/types/structures/Node.d.ts +245 -0
- package/dist/types/structures/NodeManager.d.ts +61 -0
- package/dist/types/structures/Player.d.ts +191 -0
- package/dist/types/structures/Queue.d.ts +107 -0
- package/dist/types/structures/Track.d.ts +47 -0
- package/dist/types/structures/Utils.d.ts +258 -0
- package/package.json +63 -26
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NodeManager = void 0;
|
|
4
|
+
const stream_1 = require("stream");
|
|
5
|
+
const Node_1 = require("./Node");
|
|
6
|
+
const Utils_1 = require("./Utils");
|
|
7
|
+
const Player_1 = require("./Player");
|
|
8
|
+
class NodeManager extends stream_1.EventEmitter {
|
|
9
|
+
nodes = new Utils_1.MiniMap();
|
|
10
|
+
constructor(LavalinkManager) {
|
|
11
|
+
super();
|
|
12
|
+
this.LavalinkManager = LavalinkManager;
|
|
13
|
+
if (this.LavalinkManager.options.nodes)
|
|
14
|
+
this.LavalinkManager.options.nodes.forEach(node => this.createNode(node));
|
|
15
|
+
}
|
|
16
|
+
createNode(options) {
|
|
17
|
+
if (this.nodes.has(options.id || options.host))
|
|
18
|
+
return this.nodes.get(options.id || options.host);
|
|
19
|
+
const newNode = new Node_1.LavalinkNode(options, this);
|
|
20
|
+
this.nodes.set(newNode.id, newNode);
|
|
21
|
+
return newNode;
|
|
22
|
+
}
|
|
23
|
+
get leastUsedNodes() {
|
|
24
|
+
return [...this.nodes.values()].filter(v => v);
|
|
25
|
+
}
|
|
26
|
+
deleteNode(node) {
|
|
27
|
+
const decodeNode = typeof node === "string" ? this.nodes.get(node) : node || this.leastUsedNodes[0];
|
|
28
|
+
if (!decodeNode)
|
|
29
|
+
throw new Error("Node was not found");
|
|
30
|
+
decodeNode.destroy(Player_1.DestroyReasons.NodeDeleted);
|
|
31
|
+
this.nodes.delete(decodeNode.id);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.NodeManager = NodeManager;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { FilterManager, LavalinkFilterData } from "./Filters";
|
|
2
|
+
import { LavalinkManager } from "./LavalinkManager";
|
|
3
|
+
import { LavalinkNode } from "./Node";
|
|
4
|
+
import { Queue } from "./Queue";
|
|
5
|
+
import { Track } from "./Track";
|
|
6
|
+
import { LavalinkPlayerVoiceOptions, SearchPlatform, SearchResult } from "./Utils";
|
|
7
|
+
type PlayerDestroyReasons = "QueueEmpty" | "NodeDestroy" | "NodeDeleted" | "LavalinkNoVoice" | "NodeReconnectFail" | "PlayerReconnectFail" | "Disconnected" | "ChannelDeleted";
|
|
8
|
+
export type DestroyReasonsType = PlayerDestroyReasons | string;
|
|
9
|
+
export declare const DestroyReasons: Record<PlayerDestroyReasons, PlayerDestroyReasons>;
|
|
10
|
+
export type RepeatMode = "queue" | "track" | "off";
|
|
11
|
+
export interface PlayerOptions {
|
|
12
|
+
guildId: string;
|
|
13
|
+
voiceChannelId: string;
|
|
14
|
+
volume?: number;
|
|
15
|
+
vcRegion?: string;
|
|
16
|
+
selfDeaf?: boolean;
|
|
17
|
+
selfMute?: boolean;
|
|
18
|
+
textChannelId?: string;
|
|
19
|
+
node?: LavalinkNode | string;
|
|
20
|
+
instaUpdateFiltersFix?: boolean;
|
|
21
|
+
applyVolumeAsFilter?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface PlayOptions {
|
|
24
|
+
/** Which Track to play | don't provide, if it should pick from the Queue */
|
|
25
|
+
track?: Track;
|
|
26
|
+
/** Encoded Track to use, instead of the queue system... */
|
|
27
|
+
encodedTrack?: string | null;
|
|
28
|
+
/** Encoded Track to use&search, instead of the queue system (yt only)... */
|
|
29
|
+
identifier?: string;
|
|
30
|
+
/** The position to start the track. */
|
|
31
|
+
position?: number;
|
|
32
|
+
/** The position to end the track. */
|
|
33
|
+
endTime?: number;
|
|
34
|
+
/** Whether to not replace the track if a play payload is sent. */
|
|
35
|
+
noReplace?: boolean;
|
|
36
|
+
/** If to start "paused" */
|
|
37
|
+
paused?: boolean;
|
|
38
|
+
/** The Volume to start with */
|
|
39
|
+
volume?: number;
|
|
40
|
+
/** The Lavalink Filters to use | only with the new REST API */
|
|
41
|
+
filters?: Partial<LavalinkFilterData>;
|
|
42
|
+
voice?: LavalinkPlayerVoiceOptions;
|
|
43
|
+
}
|
|
44
|
+
export interface Player {
|
|
45
|
+
filterManager: FilterManager;
|
|
46
|
+
LavalinkManager: LavalinkManager;
|
|
47
|
+
options: PlayerOptions;
|
|
48
|
+
node: LavalinkNode;
|
|
49
|
+
queue: Queue;
|
|
50
|
+
}
|
|
51
|
+
export declare class Player {
|
|
52
|
+
/** The Guild Id of the Player */
|
|
53
|
+
guildId: string;
|
|
54
|
+
/** The Voice Channel Id of the Player */
|
|
55
|
+
voiceChannelId: string | null;
|
|
56
|
+
/** The Text Channel Id of the Player */
|
|
57
|
+
textChannelId: string | null;
|
|
58
|
+
/** States if the Bot is supposed to be outputting audio */
|
|
59
|
+
playing: boolean;
|
|
60
|
+
/** States if the Bot is paused or not */
|
|
61
|
+
paused: boolean;
|
|
62
|
+
/** Repeat Mode of the Player */
|
|
63
|
+
repeatMode: RepeatMode;
|
|
64
|
+
/** Player's ping */
|
|
65
|
+
ping: {
|
|
66
|
+
lavalink: number;
|
|
67
|
+
ws: number;
|
|
68
|
+
};
|
|
69
|
+
/** The Display Volume */
|
|
70
|
+
volume: number;
|
|
71
|
+
/** The Volume Lavalink actually is outputting */
|
|
72
|
+
lavalinkVolume: number;
|
|
73
|
+
/** The current Positin of the player (Calculated) */
|
|
74
|
+
position: number;
|
|
75
|
+
/** The current Positin of the player (from Lavalink) */
|
|
76
|
+
lastPosition: number;
|
|
77
|
+
/** When the player was created [Timestamp in Ms] (from lavalink) */
|
|
78
|
+
createdTimeStamp: number;
|
|
79
|
+
/** The Player Connection's State (from Lavalink) */
|
|
80
|
+
connected: boolean | undefined;
|
|
81
|
+
/** Voice Server Data (from Lavalink) */
|
|
82
|
+
voice: LavalinkPlayerVoiceOptions;
|
|
83
|
+
private readonly data;
|
|
84
|
+
/**
|
|
85
|
+
* Create a new Player
|
|
86
|
+
* @param options
|
|
87
|
+
* @param LavalinkManager
|
|
88
|
+
*/
|
|
89
|
+
constructor(options: PlayerOptions, LavalinkManager: LavalinkManager);
|
|
90
|
+
/**
|
|
91
|
+
* Set custom data.
|
|
92
|
+
* @param key
|
|
93
|
+
* @param value
|
|
94
|
+
*/
|
|
95
|
+
set(key: string, value: unknown): void;
|
|
96
|
+
/**
|
|
97
|
+
* Get custom data.
|
|
98
|
+
* @param key
|
|
99
|
+
*/
|
|
100
|
+
get<T>(key: string): T;
|
|
101
|
+
/**
|
|
102
|
+
* CLears all the custom data.
|
|
103
|
+
*/
|
|
104
|
+
clearData(): void;
|
|
105
|
+
/**
|
|
106
|
+
* Get all custom Data
|
|
107
|
+
*/
|
|
108
|
+
getAllData(): Record<string, unknown>;
|
|
109
|
+
/**
|
|
110
|
+
* Play the next track from the queue / a specific track, with playoptions for Lavalink
|
|
111
|
+
* @param options
|
|
112
|
+
*/
|
|
113
|
+
play(options?: Partial<PlayOptions>): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Set the Volume for the Player
|
|
116
|
+
* @param volume The Volume in percent
|
|
117
|
+
* @param ignoreVolumeDecrementer If it should ignore the volumedecrementer option
|
|
118
|
+
*/
|
|
119
|
+
setVolume(volume: number, ignoreVolumeDecrementer?: boolean): Promise<void>;
|
|
120
|
+
/**
|
|
121
|
+
*
|
|
122
|
+
* @param query Query for your data
|
|
123
|
+
* @param requestUser
|
|
124
|
+
*/
|
|
125
|
+
search(query: {
|
|
126
|
+
query: string;
|
|
127
|
+
source?: SearchPlatform;
|
|
128
|
+
} | string, requestUser: unknown): Promise<SearchResult>;
|
|
129
|
+
/**
|
|
130
|
+
* Pause the player
|
|
131
|
+
*/
|
|
132
|
+
pause(): Promise<void>;
|
|
133
|
+
/**
|
|
134
|
+
* Resume the Player
|
|
135
|
+
*/
|
|
136
|
+
resume(): Promise<void>;
|
|
137
|
+
/**
|
|
138
|
+
* Seek to a specific Position
|
|
139
|
+
* @param position
|
|
140
|
+
*/
|
|
141
|
+
seek(position: number): Promise<any>;
|
|
142
|
+
/**
|
|
143
|
+
* Set the Repeatmode of the Player
|
|
144
|
+
* @param repeatMode
|
|
145
|
+
*/
|
|
146
|
+
setRepeatMode(repeatMode: RepeatMode): Promise<void>;
|
|
147
|
+
/**
|
|
148
|
+
* Skip the current song, or a specific amount of songs
|
|
149
|
+
* @param amount provide the index of the next track to skip to
|
|
150
|
+
*/
|
|
151
|
+
skip(skipTo?: number): Promise<true | void>;
|
|
152
|
+
/**
|
|
153
|
+
* Connects the Player to the Voice Channel
|
|
154
|
+
* @returns
|
|
155
|
+
*/
|
|
156
|
+
connect(): Promise<void>;
|
|
157
|
+
/**
|
|
158
|
+
* Disconnects the Player from the Voice Channel, but keeps the player in the cache
|
|
159
|
+
* @param force If false it throws an error, if player thinks it's already disconnected
|
|
160
|
+
* @returns
|
|
161
|
+
*/
|
|
162
|
+
disconnect(force?: boolean): Promise<void>;
|
|
163
|
+
/**
|
|
164
|
+
* Destroy the player and disconnect from the voice channel
|
|
165
|
+
*/
|
|
166
|
+
destroy(reason?: string): Promise<void>;
|
|
167
|
+
/**
|
|
168
|
+
* Move the player on a different Audio-Node
|
|
169
|
+
* @param newNode New Node / New Node Id
|
|
170
|
+
*/
|
|
171
|
+
changeNode(newNode: LavalinkNode | string): Promise<string>;
|
|
172
|
+
/** Converts the Player including Queue to a Json state */
|
|
173
|
+
toJSON(): {
|
|
174
|
+
guildId: string;
|
|
175
|
+
voiceChannelId: string;
|
|
176
|
+
textChannelId: string;
|
|
177
|
+
position: number;
|
|
178
|
+
lastPosition: number;
|
|
179
|
+
volume: number;
|
|
180
|
+
lavalinkVolume: number;
|
|
181
|
+
repeatMode: RepeatMode;
|
|
182
|
+
paused: boolean;
|
|
183
|
+
playing: boolean;
|
|
184
|
+
createdTimeStamp: number;
|
|
185
|
+
filters: {};
|
|
186
|
+
equalizer: import("./Filters").EQBand[];
|
|
187
|
+
queue: import("./Queue").StoredQueue;
|
|
188
|
+
nodeId: string;
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
export {};
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Player = exports.DestroyReasons = void 0;
|
|
4
|
+
const Filters_1 = require("./Filters");
|
|
5
|
+
const LavalinkManagerStatics_1 = require("./LavalinkManagerStatics");
|
|
6
|
+
const Queue_1 = require("./Queue");
|
|
7
|
+
const Utils_1 = require("./Utils");
|
|
8
|
+
exports.DestroyReasons = {
|
|
9
|
+
QueueEmpty: "QueueEmpty",
|
|
10
|
+
NodeDestroy: "NodeDestroy",
|
|
11
|
+
NodeDeleted: "NodeDeleted",
|
|
12
|
+
LavalinkNoVoice: "LavalinkNoVoice",
|
|
13
|
+
NodeReconnectFail: "NodeReconnectFail",
|
|
14
|
+
Disconnected: "Disconnected",
|
|
15
|
+
PlayerReconnectFail: "PlayerReconnectFail",
|
|
16
|
+
ChannelDeleted: "ChannelDeleted"
|
|
17
|
+
};
|
|
18
|
+
class Player {
|
|
19
|
+
/** The Guild Id of the Player */
|
|
20
|
+
guildId;
|
|
21
|
+
/** The Voice Channel Id of the Player */
|
|
22
|
+
voiceChannelId = null;
|
|
23
|
+
/** The Text Channel Id of the Player */
|
|
24
|
+
textChannelId = null;
|
|
25
|
+
/** States if the Bot is supposed to be outputting audio */
|
|
26
|
+
playing = false;
|
|
27
|
+
/** States if the Bot is paused or not */
|
|
28
|
+
paused = false;
|
|
29
|
+
/** Repeat Mode of the Player */
|
|
30
|
+
repeatMode = "off";
|
|
31
|
+
/** Player's ping */
|
|
32
|
+
ping = {
|
|
33
|
+
/* Response time for rest actions with Lavalink Server */
|
|
34
|
+
lavalink: 0,
|
|
35
|
+
/* Latency of the Discord's Websocket Voice Server */
|
|
36
|
+
ws: 0
|
|
37
|
+
};
|
|
38
|
+
/** The Display Volume */
|
|
39
|
+
volume = 100;
|
|
40
|
+
/** The Volume Lavalink actually is outputting */
|
|
41
|
+
lavalinkVolume = 100;
|
|
42
|
+
/** The current Positin of the player (Calculated) */
|
|
43
|
+
position = 0;
|
|
44
|
+
/** The current Positin of the player (from Lavalink) */
|
|
45
|
+
lastPosition = 0;
|
|
46
|
+
/** When the player was created [Timestamp in Ms] (from lavalink) */
|
|
47
|
+
createdTimeStamp;
|
|
48
|
+
/** The Player Connection's State (from Lavalink) */
|
|
49
|
+
connected = false;
|
|
50
|
+
/** Voice Server Data (from Lavalink) */
|
|
51
|
+
voice = {
|
|
52
|
+
endpoint: null,
|
|
53
|
+
sessionId: null,
|
|
54
|
+
token: null
|
|
55
|
+
};
|
|
56
|
+
data = {};
|
|
57
|
+
/**
|
|
58
|
+
* Create a new Player
|
|
59
|
+
* @param options
|
|
60
|
+
* @param LavalinkManager
|
|
61
|
+
*/
|
|
62
|
+
constructor(options, LavalinkManager) {
|
|
63
|
+
this.options = options;
|
|
64
|
+
this.filterManager = new Filters_1.FilterManager(this);
|
|
65
|
+
this.LavalinkManager = LavalinkManager;
|
|
66
|
+
this.guildId = this.options.guildId;
|
|
67
|
+
this.voiceChannelId = this.options.voiceChannelId;
|
|
68
|
+
this.textChannelId = this.options.textChannelId || null;
|
|
69
|
+
this.node = this.LavalinkManager.nodeManager.leastUsedNodes.filter(v => options.vcRegion ? v.options?.regions?.includes(options.vcRegion) : true)[0] || this.LavalinkManager.nodeManager.leastUsedNodes[0] || null;
|
|
70
|
+
if (!this.node)
|
|
71
|
+
throw new Error("No available Node was found, please add a LavalinkNode to the Manager via Manager.NodeManager#createNode");
|
|
72
|
+
if (this.LavalinkManager.options.playerOptions.volumeDecrementer)
|
|
73
|
+
this.volume *= this.LavalinkManager.options.playerOptions.volumeDecrementer;
|
|
74
|
+
this.LavalinkManager.emit("playerCreate", this);
|
|
75
|
+
if (typeof options.volume === "number" && !isNaN(options.volume))
|
|
76
|
+
this.setVolume(options.volume);
|
|
77
|
+
this.queue = new Queue_1.Queue(this.guildId, {}, new Queue_1.QueueSaver(this.LavalinkManager.options.queueStore, this.LavalinkManager.options.queueOptions), this.LavalinkManager.options.queueChangesWatcher);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Set custom data.
|
|
81
|
+
* @param key
|
|
82
|
+
* @param value
|
|
83
|
+
*/
|
|
84
|
+
set(key, value) {
|
|
85
|
+
this.data[key] = value;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get custom data.
|
|
90
|
+
* @param key
|
|
91
|
+
*/
|
|
92
|
+
get(key) {
|
|
93
|
+
return this.data[key];
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* CLears all the custom data.
|
|
97
|
+
*/
|
|
98
|
+
clearData() {
|
|
99
|
+
const toKeep = Object.keys(this.data).filter(v => v.startsWith("internal_"));
|
|
100
|
+
for (const key in this.data) {
|
|
101
|
+
if (toKeep.includes(key))
|
|
102
|
+
continue;
|
|
103
|
+
delete this.data[key];
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Get all custom Data
|
|
109
|
+
*/
|
|
110
|
+
getAllData() {
|
|
111
|
+
return Object.fromEntries(Object.entries(this.data).filter(v => !v[0].startsWith("internal_")));
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Play the next track from the queue / a specific track, with playoptions for Lavalink
|
|
115
|
+
* @param options
|
|
116
|
+
*/
|
|
117
|
+
async play(options) {
|
|
118
|
+
if (this.get("internal_queueempty")) {
|
|
119
|
+
clearTimeout(this.get("internal_queueempty"));
|
|
120
|
+
this.set("internal_queueempty", undefined);
|
|
121
|
+
}
|
|
122
|
+
if (options?.track && this.LavalinkManager.utils.isTrack(options?.track)) {
|
|
123
|
+
await this.queue.add(options?.track, 0);
|
|
124
|
+
await (0, Utils_1.queueTrackEnd)(this.queue, this.repeatMode === "queue");
|
|
125
|
+
}
|
|
126
|
+
if (!this.queue.current && this.queue.tracks.length)
|
|
127
|
+
await (0, Utils_1.queueTrackEnd)(this.queue, this.repeatMode === "queue");
|
|
128
|
+
const track = this.queue.current;
|
|
129
|
+
if (!track)
|
|
130
|
+
throw new Error(`There is no Track in the Queue, nor provided in the PlayOptions`);
|
|
131
|
+
if (typeof options?.volume === "number" && !isNaN(options?.volume)) {
|
|
132
|
+
this.volume = Math.max(Math.min(options?.volume, 500), 0);
|
|
133
|
+
let vol = Number(this.volume);
|
|
134
|
+
if (this.LavalinkManager.options.playerOptions.volumeDecrementer)
|
|
135
|
+
vol *= this.LavalinkManager.options.playerOptions.volumeDecrementer;
|
|
136
|
+
this.lavalinkVolume = Math.floor(vol * 100) / 100;
|
|
137
|
+
options.volume = vol;
|
|
138
|
+
}
|
|
139
|
+
const finalOptions = {
|
|
140
|
+
encodedTrack: track.encoded,
|
|
141
|
+
volume: this.lavalinkVolume,
|
|
142
|
+
position: 0,
|
|
143
|
+
...options,
|
|
144
|
+
};
|
|
145
|
+
if ("track" in finalOptions)
|
|
146
|
+
delete finalOptions.track;
|
|
147
|
+
if ((typeof finalOptions.position !== "undefined" && isNaN(finalOptions.position)) || (typeof finalOptions.position === "number" && (finalOptions.position < 0 || finalOptions.position >= track.info.duration)))
|
|
148
|
+
throw new Error("PlayerOption#position must be a positive number, less than track's duration");
|
|
149
|
+
if ((typeof finalOptions.volume !== "undefined" && isNaN(finalOptions.volume) || (typeof finalOptions.volume === "number" && finalOptions.volume < 0)))
|
|
150
|
+
throw new Error("PlayerOption#volume must be a positive number");
|
|
151
|
+
if ((typeof finalOptions.endTime !== "undefined" && isNaN(finalOptions.endTime)) || (typeof finalOptions.endTime === "number" && (finalOptions.endTime < 0 || finalOptions.endTime >= track.info.duration)))
|
|
152
|
+
throw new Error("PlayerOption#endTime must be a positive number, less than track's duration");
|
|
153
|
+
if (typeof finalOptions.position === "number" && typeof finalOptions.endTime === "number" && finalOptions.endTime < finalOptions.position)
|
|
154
|
+
throw new Error("PlayerOption#endTime must be bigger than PlayerOption#position");
|
|
155
|
+
if ("noReplace" in finalOptions)
|
|
156
|
+
delete finalOptions.noReplace;
|
|
157
|
+
const now = performance.now();
|
|
158
|
+
await this.node.updatePlayer({
|
|
159
|
+
guildId: this.guildId,
|
|
160
|
+
noReplace: options?.noReplace ?? false,
|
|
161
|
+
playerOptions: finalOptions,
|
|
162
|
+
});
|
|
163
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Set the Volume for the Player
|
|
167
|
+
* @param volume The Volume in percent
|
|
168
|
+
* @param ignoreVolumeDecrementer If it should ignore the volumedecrementer option
|
|
169
|
+
*/
|
|
170
|
+
async setVolume(volume, ignoreVolumeDecrementer = false) {
|
|
171
|
+
volume = Number(volume);
|
|
172
|
+
if (isNaN(volume))
|
|
173
|
+
throw new TypeError("Volume must be a number.");
|
|
174
|
+
this.volume = Math.max(Math.min(volume, 500), 0);
|
|
175
|
+
volume = Number(this.volume);
|
|
176
|
+
if (this.LavalinkManager.options.playerOptions.volumeDecrementer && !ignoreVolumeDecrementer)
|
|
177
|
+
volume *= this.LavalinkManager.options.playerOptions.volumeDecrementer;
|
|
178
|
+
this.lavalinkVolume = Math.floor(volume * 100) / 100;
|
|
179
|
+
const now = performance.now();
|
|
180
|
+
if (this.LavalinkManager.options.playerOptions.applyVolumeAsFilter) {
|
|
181
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { filters: { volume: volume / 100 } } });
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { volume } });
|
|
185
|
+
}
|
|
186
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
*
|
|
191
|
+
* @param query Query for your data
|
|
192
|
+
* @param requestUser
|
|
193
|
+
*/
|
|
194
|
+
async search(query, requestUser) {
|
|
195
|
+
// transform the query object
|
|
196
|
+
const Query = {
|
|
197
|
+
query: typeof query === "string" ? query : query.query,
|
|
198
|
+
source: LavalinkManagerStatics_1.DefaultSources[(typeof query === "string" ? undefined : query.source) ?? this.LavalinkManager.options.playerOptions.defaultSearchPlatform] ?? (typeof query === "string" ? undefined : query.source) ?? this.LavalinkManager.options.playerOptions.defaultSearchPlatform
|
|
199
|
+
};
|
|
200
|
+
// if user does player.search("ytsearch:Hello")
|
|
201
|
+
const foundSource = [...Object.keys(LavalinkManagerStatics_1.DefaultSources)].find(source => Query.query.startsWith(`${source}:`));
|
|
202
|
+
if (foundSource && LavalinkManagerStatics_1.DefaultSources[foundSource]) {
|
|
203
|
+
Query.source = LavalinkManagerStatics_1.DefaultSources[foundSource]; // set the source to ytsearch:
|
|
204
|
+
Query.query = Query.query.replace(`${foundSource}:`, ""); // remove ytsearch: from the query
|
|
205
|
+
}
|
|
206
|
+
// request the data
|
|
207
|
+
const res = await this.node.request(`/loadtracks?identifier=${!/^https?:\/\//.test(Query.query) ? `${Query.source}:` : ""}${encodeURIComponent(Query.query)}`);
|
|
208
|
+
// transform the data which can be Error, Track or Track[] to enfore [Track]
|
|
209
|
+
const resTracks = res.loadType === "playlist" ? res.data?.tracks : res.loadType === "track" ? [res.data] : res.loadType === "search" ? Array.isArray(res.data) ? res.data : [res.data] : [];
|
|
210
|
+
return {
|
|
211
|
+
loadType: res.loadType,
|
|
212
|
+
exception: res.loadType === "error" ? res.data : null,
|
|
213
|
+
pluginInfo: res.pluginInfo || {},
|
|
214
|
+
playlist: res.loadType === "playlist" ? {
|
|
215
|
+
title: res.data.info?.name || res.data.pluginInfo?.name || null,
|
|
216
|
+
author: res.data.info?.author || res.data.pluginInfo?.author || null,
|
|
217
|
+
thumbnail: (res.data.info?.artworkUrl) || (res.data.pluginInfo?.artworkUrl) || ((typeof res.data?.info?.selectedTrack !== "number" || res.data?.info?.selectedTrack === -1) ? null : resTracks[res.data?.info?.selectedTrack] ? (resTracks[res.data?.info?.selectedTrack]?.info?.artworkUrl || resTracks[res.data?.info?.selectedTrack]?.info?.pluginInfo?.artworkUrl) : null) || null,
|
|
218
|
+
uri: res.data.info?.url || res.data.info?.uri || res.data.info?.link || res.data.pluginInfo?.url || res.data.pluginInfo?.uri || res.data.pluginInfo?.link || null,
|
|
219
|
+
selectedTrack: typeof res.data?.info?.selectedTrack !== "number" || res.data?.info?.selectedTrack === -1 ? null : resTracks[res.data?.info?.selectedTrack] ? this.LavalinkManager.utils.buildTrack(resTracks[res.data?.info?.selectedTrack], requestUser) : null,
|
|
220
|
+
duration: resTracks.length ? resTracks.reduce((acc, cur) => acc + (cur?.info?.duration || 0), 0) : 0,
|
|
221
|
+
} : null,
|
|
222
|
+
tracks: resTracks.length ? resTracks.map(t => this.LavalinkManager.utils.buildTrack(t, requestUser)) : []
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Pause the player
|
|
227
|
+
*/
|
|
228
|
+
async pause() {
|
|
229
|
+
if (this.paused && !this.playing)
|
|
230
|
+
throw new Error("Player is already paused - not able to pause.");
|
|
231
|
+
this.paused = true;
|
|
232
|
+
const now = performance.now();
|
|
233
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { paused: true } });
|
|
234
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Resume the Player
|
|
239
|
+
*/
|
|
240
|
+
async resume() {
|
|
241
|
+
if (!this.paused)
|
|
242
|
+
throw new Error("Player isn't paused - not able to resume.");
|
|
243
|
+
this.paused = false;
|
|
244
|
+
const now = performance.now();
|
|
245
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { paused: false } });
|
|
246
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Seek to a specific Position
|
|
251
|
+
* @param position
|
|
252
|
+
*/
|
|
253
|
+
async seek(position) {
|
|
254
|
+
if (!this.queue.current)
|
|
255
|
+
return undefined;
|
|
256
|
+
position = Number(position);
|
|
257
|
+
if (isNaN(position))
|
|
258
|
+
throw new RangeError("Position must be a number.");
|
|
259
|
+
if (!this.queue.current.info.isSeekable || this.queue.current.info.isStream)
|
|
260
|
+
throw new RangeError("Current Track is not seekable / a stream");
|
|
261
|
+
if (position < 0 || position > this.queue.current.info.duration)
|
|
262
|
+
position = Math.max(Math.min(position, this.queue.current.info.duration), 0);
|
|
263
|
+
this.position = position;
|
|
264
|
+
this.lastPosition = position;
|
|
265
|
+
const now = performance.now();
|
|
266
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { position } });
|
|
267
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Set the Repeatmode of the Player
|
|
272
|
+
* @param repeatMode
|
|
273
|
+
*/
|
|
274
|
+
async setRepeatMode(repeatMode) {
|
|
275
|
+
if (!["off", "track", "queue"].includes(repeatMode))
|
|
276
|
+
throw new RangeError("Repeatmode must be either 'off', 'track', or 'queue'");
|
|
277
|
+
this.repeatMode = repeatMode;
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Skip the current song, or a specific amount of songs
|
|
282
|
+
* @param amount provide the index of the next track to skip to
|
|
283
|
+
*/
|
|
284
|
+
async skip(skipTo = 0) {
|
|
285
|
+
if (!this.queue.tracks.length)
|
|
286
|
+
throw new RangeError("Can't skip more than the queue size");
|
|
287
|
+
if (typeof skipTo === "number" && skipTo > 1) {
|
|
288
|
+
if (skipTo > this.queue.tracks.length)
|
|
289
|
+
throw new RangeError("Can't skip more than the queue size");
|
|
290
|
+
await this.queue.splice(0, skipTo - 1);
|
|
291
|
+
}
|
|
292
|
+
if (!this.playing)
|
|
293
|
+
return await this.play();
|
|
294
|
+
const now = performance.now();
|
|
295
|
+
await this.node.updatePlayer({ guildId: this.guildId, playerOptions: { encodedTrack: null } });
|
|
296
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Connects the Player to the Voice Channel
|
|
301
|
+
* @returns
|
|
302
|
+
*/
|
|
303
|
+
async connect() {
|
|
304
|
+
if (!this.options.voiceChannelId)
|
|
305
|
+
throw new RangeError("No Voice Channel id has been set.");
|
|
306
|
+
await this.LavalinkManager.options.sendToShard(this.guildId, {
|
|
307
|
+
op: 4,
|
|
308
|
+
d: {
|
|
309
|
+
guild_id: this.guildId,
|
|
310
|
+
channel_id: this.options.voiceChannelId,
|
|
311
|
+
self_mute: this.options.selfMute ?? false,
|
|
312
|
+
self_deaf: this.options.selfDeaf ?? true,
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Disconnects the Player from the Voice Channel, but keeps the player in the cache
|
|
319
|
+
* @param force If false it throws an error, if player thinks it's already disconnected
|
|
320
|
+
* @returns
|
|
321
|
+
*/
|
|
322
|
+
async disconnect(force = false) {
|
|
323
|
+
if (!force && !this.options.voiceChannelId)
|
|
324
|
+
throw new RangeError("No Voice Channel id has been set.");
|
|
325
|
+
await this.LavalinkManager.options.sendToShard(this.guildId, {
|
|
326
|
+
op: 4,
|
|
327
|
+
d: {
|
|
328
|
+
guild_id: this.guildId,
|
|
329
|
+
channel_id: null,
|
|
330
|
+
self_mute: false,
|
|
331
|
+
self_deaf: false,
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
this.voiceChannelId = null;
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Destroy the player and disconnect from the voice channel
|
|
339
|
+
*/
|
|
340
|
+
async destroy(reason) {
|
|
341
|
+
await this.disconnect(true);
|
|
342
|
+
await this.queue.utils.destroy();
|
|
343
|
+
this.LavalinkManager.deletePlayer(this.guildId);
|
|
344
|
+
await this.node.destroyPlayer(this.guildId);
|
|
345
|
+
this.LavalinkManager.emit("playerDestroy", this, reason);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Move the player on a different Audio-Node
|
|
350
|
+
* @param newNode New Node / New Node Id
|
|
351
|
+
*/
|
|
352
|
+
async changeNode(newNode) {
|
|
353
|
+
const updateNode = typeof newNode === "string" ? this.LavalinkManager.nodeManager.nodes.get(newNode) : newNode;
|
|
354
|
+
if (!updateNode)
|
|
355
|
+
throw new Error("Could not find the new Node");
|
|
356
|
+
const data = this.toJSON();
|
|
357
|
+
await this.node.destroyPlayer(this.guildId);
|
|
358
|
+
this.node = updateNode;
|
|
359
|
+
await this.connect();
|
|
360
|
+
const now = performance.now();
|
|
361
|
+
await this.node.updatePlayer({
|
|
362
|
+
guildId: this.guildId,
|
|
363
|
+
noReplace: false,
|
|
364
|
+
playerOptions: {
|
|
365
|
+
position: data.position,
|
|
366
|
+
volume: data.volume,
|
|
367
|
+
paused: data.paused,
|
|
368
|
+
filters: { ...data.filters, equalizer: data.equalizer },
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
this.ping.lavalink = Math.round((performance.now() - now) / 10) / 100;
|
|
372
|
+
return this.node.id;
|
|
373
|
+
}
|
|
374
|
+
/** Converts the Player including Queue to a Json state */
|
|
375
|
+
toJSON() {
|
|
376
|
+
return {
|
|
377
|
+
guildId: this.guildId,
|
|
378
|
+
voiceChannelId: this.voiceChannelId,
|
|
379
|
+
textChannelId: this.textChannelId,
|
|
380
|
+
position: this.position,
|
|
381
|
+
lastPosition: this.lastPosition,
|
|
382
|
+
volume: this.volume,
|
|
383
|
+
lavalinkVolume: this.lavalinkVolume,
|
|
384
|
+
repeatMode: this.repeatMode,
|
|
385
|
+
paused: this.paused,
|
|
386
|
+
playing: this.playing,
|
|
387
|
+
createdTimeStamp: this.createdTimeStamp,
|
|
388
|
+
filters: this.filterManager?.data || {},
|
|
389
|
+
equalizer: this.filterManager?.equalizerBands || [],
|
|
390
|
+
queue: this.queue?.utils?.getStored?.() || { current: null, tracks: [], previous: [] },
|
|
391
|
+
nodeId: this.node?.id,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
exports.Player = Player;
|