@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/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$1 = {
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$1;
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$1.OFF;
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$1.TRACK:
242
+ case LOOP_MODES.TRACK:
241
243
  // Replay current track
242
244
  break;
243
- case LOOP_MODES$1.QUEUE:
245
+ case LOOP_MODES.QUEUE:
244
246
  // Play next in queue
245
247
  this.audioEngine.queue.getNext();
246
248
  break;
247
- case LOOP_MODES$1.OFF:
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$1 as LOOP_MODES, LocalProvider, Logger, MetadataUtils, PluginManager, ProbeUtils, Queue, REPEAT_MODES, SoundCloudProvider, TimeUtils, Track, YouTubeProvider };
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