@students-dev/audify-js 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/LICENSE +21 -0
- package/README.md +139 -7
- package/dist/cjs/index.js +380 -7
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +378 -7
- package/dist/esm/index.js.map +1 -1
- package/dist/types/engine/AudioEngine.d.ts +44 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/providers/LavalinkProvider.d.ts +48 -0
- package/dist/types/providers/SpotifyProvider.d.ts +54 -0
- package/package.json +12 -8
package/dist/esm/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import SpotifyWebApi from 'spotify-web-api-node';
|
|
2
|
+
import { LavalinkManager } from 'lavalink-client';
|
|
1
3
|
import { promises } from 'fs';
|
|
2
4
|
import { extname } from 'path';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Loop modes for playback
|
|
6
8
|
*/
|
|
7
|
-
const LOOP_MODES
|
|
9
|
+
const LOOP_MODES = {
|
|
8
10
|
OFF: 'off',
|
|
9
11
|
TRACK: 'track',
|
|
10
12
|
QUEUE: 'queue'
|
|
@@ -13,7 +15,7 @@ const LOOP_MODES$1 = {
|
|
|
13
15
|
/**
|
|
14
16
|
* Repeat modes (alias for loop modes)
|
|
15
17
|
*/
|
|
16
|
-
const REPEAT_MODES = LOOP_MODES
|
|
18
|
+
const REPEAT_MODES = LOOP_MODES;
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Filter types
|
|
@@ -124,7 +126,7 @@ class Player {
|
|
|
124
126
|
this.currentTime = 0;
|
|
125
127
|
this.duration = 0;
|
|
126
128
|
this.volume = 1;
|
|
127
|
-
this.loopMode = LOOP_MODES
|
|
129
|
+
this.loopMode = LOOP_MODES.OFF;
|
|
128
130
|
this.eventBus = new EventBus();
|
|
129
131
|
}
|
|
130
132
|
|
|
@@ -237,14 +239,14 @@ class Player {
|
|
|
237
239
|
*/
|
|
238
240
|
handleTrackEnd() {
|
|
239
241
|
switch (this.loopMode) {
|
|
240
|
-
case LOOP_MODES
|
|
242
|
+
case LOOP_MODES.TRACK:
|
|
241
243
|
// Replay current track
|
|
242
244
|
break;
|
|
243
|
-
case LOOP_MODES
|
|
245
|
+
case LOOP_MODES.QUEUE:
|
|
244
246
|
// Play next in queue
|
|
245
247
|
this.audioEngine.queue.getNext();
|
|
246
248
|
break;
|
|
247
|
-
case LOOP_MODES
|
|
249
|
+
case LOOP_MODES.OFF:
|
|
248
250
|
}
|
|
249
251
|
}
|
|
250
252
|
|
|
@@ -738,6 +740,276 @@ class Queue {
|
|
|
738
740
|
}
|
|
739
741
|
}
|
|
740
742
|
|
|
743
|
+
/**
|
|
744
|
+
* Spotify provider for client-side API integration
|
|
745
|
+
*/
|
|
746
|
+
class SpotifyProvider {
|
|
747
|
+
constructor(options = {}) {
|
|
748
|
+
this.spotifyApi = new SpotifyWebApi({
|
|
749
|
+
clientId: options.clientId,
|
|
750
|
+
clientSecret: options.clientSecret,
|
|
751
|
+
redirectUri: options.redirectUri,
|
|
752
|
+
accessToken: options.accessToken,
|
|
753
|
+
refreshToken: options.refreshToken
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Set access token
|
|
759
|
+
* @param {string} token - OAuth access token
|
|
760
|
+
*/
|
|
761
|
+
setAccessToken(token) {
|
|
762
|
+
this.spotifyApi.setAccessToken(token);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* Set refresh token
|
|
767
|
+
* @param {string} token - OAuth refresh token
|
|
768
|
+
*/
|
|
769
|
+
setRefreshToken(token) {
|
|
770
|
+
this.spotifyApi.setRefreshToken(token);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Refresh access token
|
|
775
|
+
* @returns {Promise<Object>} Token response
|
|
776
|
+
*/
|
|
777
|
+
async refreshAccessToken() {
|
|
778
|
+
try {
|
|
779
|
+
const data = await this.spotifyApi.refreshAccessToken();
|
|
780
|
+
this.spotifyApi.setAccessToken(data.body.access_token);
|
|
781
|
+
return data.body;
|
|
782
|
+
} catch (error) {
|
|
783
|
+
throw new Error(`Failed to refresh token: ${error.message}`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
/**
|
|
788
|
+
* Search tracks
|
|
789
|
+
* @param {string} query - Search query
|
|
790
|
+
* @param {Object} options - Search options
|
|
791
|
+
* @returns {Promise<Array>} Array of track objects
|
|
792
|
+
*/
|
|
793
|
+
async searchTracks(query, options = {}) {
|
|
794
|
+
try {
|
|
795
|
+
const data = await this.spotifyApi.searchTracks(query, {
|
|
796
|
+
limit: options.limit || 20,
|
|
797
|
+
offset: options.offset || 0
|
|
798
|
+
});
|
|
799
|
+
|
|
800
|
+
return data.body.tracks.items.map(track => this._formatTrack(track));
|
|
801
|
+
} catch (error) {
|
|
802
|
+
throw new Error(`Failed to search tracks: ${error.message}`);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/**
|
|
807
|
+
* Get track by ID
|
|
808
|
+
* @param {string} trackId - Spotify track ID
|
|
809
|
+
* @returns {Promise<Object>} Track object
|
|
810
|
+
*/
|
|
811
|
+
async getTrack(trackId) {
|
|
812
|
+
try {
|
|
813
|
+
const data = await this.spotifyApi.getTrack(trackId);
|
|
814
|
+
return this._formatTrack(data.body);
|
|
815
|
+
} catch (error) {
|
|
816
|
+
throw new Error(`Failed to get track: ${error.message}`);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Get tracks by IDs
|
|
822
|
+
* @param {Array<string>} trackIds - Array of Spotify track IDs
|
|
823
|
+
* @returns {Promise<Array>} Array of track objects
|
|
824
|
+
*/
|
|
825
|
+
async getTracks(trackIds) {
|
|
826
|
+
try {
|
|
827
|
+
const data = await this.spotifyApi.getTracks(trackIds);
|
|
828
|
+
return data.body.tracks.map(track => this._formatTrack(track));
|
|
829
|
+
} catch (error) {
|
|
830
|
+
throw new Error(`Failed to get tracks: ${error.message}`);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Get audio features for track
|
|
836
|
+
* @param {string} trackId - Spotify track ID
|
|
837
|
+
* @returns {Promise<Object>} Audio features
|
|
838
|
+
*/
|
|
839
|
+
async getAudioFeatures(trackId) {
|
|
840
|
+
try {
|
|
841
|
+
const data = await this.spotifyApi.getAudioFeaturesForTrack(trackId);
|
|
842
|
+
return data.body;
|
|
843
|
+
} catch (error) {
|
|
844
|
+
throw new Error(`Failed to get audio features: ${error.message}`);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Format Spotify track to internal format
|
|
850
|
+
* @param {Object} spotifyTrack - Spotify track object
|
|
851
|
+
* @returns {Object} Formatted track
|
|
852
|
+
* @private
|
|
853
|
+
*/
|
|
854
|
+
_formatTrack(spotifyTrack) {
|
|
855
|
+
return {
|
|
856
|
+
id: spotifyTrack.id,
|
|
857
|
+
title: spotifyTrack.name,
|
|
858
|
+
artist: spotifyTrack.artists.map(artist => artist.name).join(', '),
|
|
859
|
+
duration: Math.floor(spotifyTrack.duration_ms / 1000),
|
|
860
|
+
thumbnail: spotifyTrack.album.images[0]?.url,
|
|
861
|
+
url: spotifyTrack.external_urls.spotify,
|
|
862
|
+
source: 'spotify',
|
|
863
|
+
album: spotifyTrack.album.name,
|
|
864
|
+
popularity: spotifyTrack.popularity,
|
|
865
|
+
preview_url: spotifyTrack.preview_url,
|
|
866
|
+
metadata: {
|
|
867
|
+
spotifyId: spotifyTrack.id,
|
|
868
|
+
artists: spotifyTrack.artists,
|
|
869
|
+
album: spotifyTrack.album
|
|
870
|
+
}
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
/**
|
|
876
|
+
* Lavalink provider for connecting to Lavalink server
|
|
877
|
+
*/
|
|
878
|
+
class LavalinkProvider {
|
|
879
|
+
constructor(options = {}) {
|
|
880
|
+
this.host = options.host || 'localhost';
|
|
881
|
+
this.port = options.port || 2333;
|
|
882
|
+
this.password = options.password || 'youshallnotpass';
|
|
883
|
+
this.secure = options.secure || false;
|
|
884
|
+
this.manager = null;
|
|
885
|
+
this.node = null;
|
|
886
|
+
this.isConnected = false;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Connect to Lavalink server
|
|
891
|
+
* @returns {Promise<void>}
|
|
892
|
+
*/
|
|
893
|
+
async connect() {
|
|
894
|
+
try {
|
|
895
|
+
this.manager = new LavalinkManager({
|
|
896
|
+
nodes: [{
|
|
897
|
+
host: this.host,
|
|
898
|
+
port: this.port,
|
|
899
|
+
password: this.password,
|
|
900
|
+
secure: this.secure,
|
|
901
|
+
id: 'main'
|
|
902
|
+
}],
|
|
903
|
+
sendToShard: (guildId, payload) => {
|
|
904
|
+
// This would need to be implemented to send to Discord gateway
|
|
905
|
+
// For now, this is a placeholder
|
|
906
|
+
console.log('Send to shard:', guildId, payload);
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
await this.manager.connect();
|
|
911
|
+
this.node = this.manager.nodes.get('main');
|
|
912
|
+
this.isConnected = true;
|
|
913
|
+
} catch (error) {
|
|
914
|
+
throw new Error(`Failed to connect to Lavalink: ${error.message}`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Disconnect from Lavalink server
|
|
920
|
+
*/
|
|
921
|
+
disconnect() {
|
|
922
|
+
if (this.manager) {
|
|
923
|
+
this.manager.destroy();
|
|
924
|
+
this.isConnected = false;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
/**
|
|
929
|
+
* Create a player for a guild/channel
|
|
930
|
+
* @param {string} guildId - Guild ID
|
|
931
|
+
* @param {string} channelId - Voice channel ID
|
|
932
|
+
* @returns {Object} Player instance
|
|
933
|
+
*/
|
|
934
|
+
createPlayer(guildId, channelId) {
|
|
935
|
+
if (!this.isConnected) {
|
|
936
|
+
throw new Error('Not connected to Lavalink');
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
return this.manager.createPlayer({
|
|
940
|
+
guildId,
|
|
941
|
+
voiceChannelId: channelId,
|
|
942
|
+
textChannelId: channelId, // Optional
|
|
943
|
+
selfDeaf: false,
|
|
944
|
+
selfMute: false
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Load track from Lavalink
|
|
950
|
+
* @param {string} identifier - Track identifier (URL or search query)
|
|
951
|
+
* @returns {Promise<Object>} Track info
|
|
952
|
+
*/
|
|
953
|
+
async loadTrack(identifier) {
|
|
954
|
+
if (!this.isConnected) {
|
|
955
|
+
throw new Error('Not connected to Lavalink');
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
try {
|
|
959
|
+
const result = await this.node.rest.loadTracks(identifier);
|
|
960
|
+
|
|
961
|
+
if (result.loadType === 'TRACK_LOADED') {
|
|
962
|
+
return this._formatTrack(result.tracks[0]);
|
|
963
|
+
} else if (result.loadType === 'PLAYLIST_LOADED') {
|
|
964
|
+
return result.tracks.map(track => this._formatTrack(track));
|
|
965
|
+
} else if (result.loadType === 'SEARCH_RESULT') {
|
|
966
|
+
return result.tracks.map(track => this._formatTrack(track));
|
|
967
|
+
} else {
|
|
968
|
+
throw new Error('No tracks found');
|
|
969
|
+
}
|
|
970
|
+
} catch (error) {
|
|
971
|
+
throw new Error(`Failed to load track: ${error.message}`);
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* Format Lavalink track to internal format
|
|
977
|
+
* @param {Object} lavalinkTrack - Lavalink track object
|
|
978
|
+
* @returns {Object} Formatted track
|
|
979
|
+
* @private
|
|
980
|
+
*/
|
|
981
|
+
_formatTrack(lavalinkTrack) {
|
|
982
|
+
const info = lavalinkTrack.info;
|
|
983
|
+
return {
|
|
984
|
+
id: lavalinkTrack.track,
|
|
985
|
+
title: info.title,
|
|
986
|
+
artist: info.author,
|
|
987
|
+
duration: Math.floor(info.length / 1000),
|
|
988
|
+
thumbnail: info.artworkUrl,
|
|
989
|
+
url: info.uri,
|
|
990
|
+
source: 'lavalink',
|
|
991
|
+
isrc: info.isrc,
|
|
992
|
+
metadata: {
|
|
993
|
+
lavalinkTrack: lavalinkTrack.track,
|
|
994
|
+
identifier: info.identifier,
|
|
995
|
+
sourceName: info.sourceName
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Get node stats
|
|
1002
|
+
* @returns {Promise<Object>} Node stats
|
|
1003
|
+
*/
|
|
1004
|
+
async getStats() {
|
|
1005
|
+
if (!this.isConnected) {
|
|
1006
|
+
throw new Error('Not connected to Lavalink');
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
return await this.node.rest.getStats();
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
741
1013
|
/**
|
|
742
1014
|
* Main audio engine class
|
|
743
1015
|
*/
|
|
@@ -749,6 +1021,8 @@ class AudioEngine {
|
|
|
749
1021
|
this.filters = null;
|
|
750
1022
|
this.queue = new Queue();
|
|
751
1023
|
this.eventBus = new EventBus();
|
|
1024
|
+
this.spotifyProvider = null;
|
|
1025
|
+
this.lavalinkProvider = null;
|
|
752
1026
|
this.isReady = false;
|
|
753
1027
|
|
|
754
1028
|
this.initialize();
|
|
@@ -931,6 +1205,100 @@ class AudioEngine {
|
|
|
931
1205
|
};
|
|
932
1206
|
}
|
|
933
1207
|
|
|
1208
|
+
/**
|
|
1209
|
+
* Initialize Spotify provider
|
|
1210
|
+
* @param {Object} options - Spotify options
|
|
1211
|
+
*/
|
|
1212
|
+
initSpotify(options = {}) {
|
|
1213
|
+
this.spotifyProvider = new SpotifyProvider(options);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* Load Spotify track and add to queue
|
|
1218
|
+
* @param {string} trackId - Spotify track ID
|
|
1219
|
+
* @param {Object} options - Options including token
|
|
1220
|
+
* @returns {Promise<Track>} Added track
|
|
1221
|
+
*/
|
|
1222
|
+
async loadSpotifyTrack(trackId, options = {}) {
|
|
1223
|
+
if (!this.spotifyProvider) {
|
|
1224
|
+
throw new Error('Spotify provider not initialized');
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if (options.token) {
|
|
1228
|
+
this.spotifyProvider.setAccessToken(options.token);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
const trackData = await this.spotifyProvider.getTrack(trackId);
|
|
1232
|
+
const track = new Track(trackData.url, trackData);
|
|
1233
|
+
this.add(track);
|
|
1234
|
+
return track;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/**
|
|
1238
|
+
* Search Spotify tracks
|
|
1239
|
+
* @param {string} query - Search query
|
|
1240
|
+
* @param {Object} options - Search options
|
|
1241
|
+
* @returns {Promise<Array>} Search results
|
|
1242
|
+
*/
|
|
1243
|
+
async searchSpotifyTracks(query, options = {}) {
|
|
1244
|
+
if (!this.spotifyProvider) {
|
|
1245
|
+
throw new Error('Spotify provider not initialized');
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
if (options.token) {
|
|
1249
|
+
this.spotifyProvider.setAccessToken(options.token);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
return await this.spotifyProvider.searchTracks(query, options);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* Connect to Lavalink server
|
|
1257
|
+
* @param {Object} options - Lavalink connection options
|
|
1258
|
+
* @returns {Promise<void>}
|
|
1259
|
+
*/
|
|
1260
|
+
async connectLavalink(options = {}) {
|
|
1261
|
+
this.lavalinkProvider = new LavalinkProvider(options);
|
|
1262
|
+
await this.lavalinkProvider.connect();
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
/**
|
|
1266
|
+
* Load Lavalink track and add to queue
|
|
1267
|
+
* @param {string} identifier - Track identifier
|
|
1268
|
+
* @returns {Promise<Track|Array<Track>>} Added track(s)
|
|
1269
|
+
*/
|
|
1270
|
+
async loadLavalinkTrack(identifier) {
|
|
1271
|
+
if (!this.lavalinkProvider) {
|
|
1272
|
+
throw new Error('Lavalink provider not connected');
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
const trackData = await this.lavalinkProvider.loadTrack(identifier);
|
|
1276
|
+
|
|
1277
|
+
if (Array.isArray(trackData)) {
|
|
1278
|
+
const tracks = trackData.map(data => new Track(data.url, data));
|
|
1279
|
+
this.add(tracks);
|
|
1280
|
+
return tracks;
|
|
1281
|
+
} else {
|
|
1282
|
+
const track = new Track(trackData.url, trackData);
|
|
1283
|
+
this.add(track);
|
|
1284
|
+
return track;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* Get Lavalink player for guild/channel
|
|
1290
|
+
* @param {string} guildId - Guild ID
|
|
1291
|
+
* @param {string} channelId - Voice channel ID
|
|
1292
|
+
* @returns {Object} Lavalink player
|
|
1293
|
+
*/
|
|
1294
|
+
getLavalinkPlayer(guildId, channelId) {
|
|
1295
|
+
if (!this.lavalinkProvider) {
|
|
1296
|
+
throw new Error('Lavalink provider not connected');
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
return this.lavalinkProvider.createPlayer(guildId, channelId);
|
|
1300
|
+
}
|
|
1301
|
+
|
|
934
1302
|
/**
|
|
935
1303
|
* Destroy the engine
|
|
936
1304
|
*/
|
|
@@ -940,6 +1308,9 @@ class AudioEngine {
|
|
|
940
1308
|
}
|
|
941
1309
|
this.filters.clear();
|
|
942
1310
|
this.player.stop();
|
|
1311
|
+
if (this.lavalinkProvider) {
|
|
1312
|
+
this.lavalinkProvider.disconnect();
|
|
1313
|
+
}
|
|
943
1314
|
}
|
|
944
1315
|
}
|
|
945
1316
|
|
|
@@ -1473,5 +1844,5 @@ class LocalProvider {
|
|
|
1473
1844
|
}
|
|
1474
1845
|
}
|
|
1475
1846
|
|
|
1476
|
-
export { AudioEngine, EVENTS, EventBus, FILTER_TYPES, LOOP_MODES
|
|
1847
|
+
export { AudioEngine, EVENTS, EventBus, FILTER_TYPES, LOOP_MODES, LavalinkProvider, LocalProvider, Logger, MetadataUtils, PluginManager, ProbeUtils, Queue, REPEAT_MODES, SoundCloudProvider, SpotifyProvider, TimeUtils, Track, YouTubeProvider };
|
|
1477
1848
|
//# sourceMappingURL=index.js.map
|