aqualink 2.9.13 → 2.10.0

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 CHANGED
@@ -87,67 +87,65 @@
87
87
 
88
88
  ## đŸ’ģ Quick Start
89
89
 
90
- ```javascript
91
- npm install aqualink
92
-
93
- // If you're using Module, use this:
94
- // import { createRequire } from 'module';
95
- // const require = createRequire(import.meta.url);
96
-
97
- //const { Aqua } = require('aqualink');
98
-
99
-
90
+ ```bash
91
+ npm install aqualink discord.js
92
+ ```
100
93
 
94
+ ```javascript
101
95
  const { Aqua } = require("aqualink");
102
- const { Client, Collection, GatewayDispatchEvents } = require("discord.js");
96
+ const { Client, GatewayIntentBits, Events } = require("discord.js");
103
97
 
104
98
  const client = new Client({
105
99
  intents: [
106
- "Guilds",
107
- "GuildMembers",
108
- "GuildMessages",
109
- "MessageContent",
110
- "GuildVoiceStates"
100
+ GatewayIntentBits.Guilds,
101
+ GatewayIntentBits.GuildMembers,
102
+ GatewayIntentBits.GuildMessages,
103
+ GatewayIntentBits.MessageContent,
104
+ GatewayIntentBits.GuildVoiceStates
111
105
  ]
112
106
  });
113
107
 
114
108
  const nodes = [
115
109
  {
116
110
  host: "127.0.0.1",
117
- password: "yourpass",
118
- port: 233,
111
+ password: "your_password",
112
+ port: 2333,
119
113
  secure: false,
120
- name: "localhost"
114
+ name: "main-node"
121
115
  }
122
116
  ];
123
117
 
124
- const aqua = Aqua(client, nodes, {
125
- defaultSearchPlatform: "ytsearch",
126
- restVersion: "v4",
127
- autoResume: false,
128
- infiniteReconnects: true,
118
+ const aqua = new Aqua(client, nodes, {
119
+ defaultSearchPlatform: "ytsearch",
120
+ restVersion: "v4",
121
+ autoResume: true,
122
+ infiniteReconnects: true,
123
+ autoplayPlatform: ['spsearch', 'ytsearch', 'scsearch'],
124
+ nodeResolver: 'LeastLoad'
129
125
  });
130
126
 
131
127
  client.aqua = aqua;
132
128
 
133
-
134
- client.once("ready", () => {
129
+ client.once(Events.Ready, () => {
135
130
  client.aqua.init(client.user.id);
136
- console.log("Ready!");
131
+ console.log(`Logged in as ${client.user.tag}`);
137
132
  });
138
133
 
139
-
140
- client.on("raw", (d) => {
141
- if (![GatewayDispatchEvents.VoiceStateUpdate, GatewayDispatchEvents.VoiceServerUpdate,].includes(d.t)) return;
134
+ client.on(Events.Raw, (d) => {
135
+ if (![Events.VoiceStateUpdate, Events.VoiceServerUpdate].includes(d.t)) return;
142
136
  client.aqua.updateVoiceState(d);
143
137
  });
144
138
 
145
- client.on("messageCreate", async (message) => {
146
- if (message.author.bot) return;
139
+ client.on(Events.MessageCreate, async (message) => {
140
+ if (message.author.bot || !message.content.startsWith("!play")) return;
147
141
 
148
- if (!message.content.startsWith("!play")) return;
142
+ const query = message.content.slice(6).trim();
143
+ if (!query) return message.channel.send("Please provide a song to play.");
149
144
 
150
- const query = message.content.slice(6);
145
+ // Check if user is in a voice channel
146
+ if (!message.member.voice.channel) {
147
+ return message.channel.send("You need to be in a voice channel to play music!");
148
+ }
151
149
 
152
150
  const player = client.aqua.createConnection({
153
151
  guildId: message.guild.id,
@@ -156,38 +154,76 @@ client.on("messageCreate", async (message) => {
156
154
  deaf: true,
157
155
  });
158
156
 
159
- const resolve = await client.aqua.resolve({ query, requester: message.member });
160
-
161
- if (resolve.loadType === 'playlist') {
162
- await message.channel.send(`Added ${resolve.tracks.length} songs from ${resolve.playlistInfo.name} playlist.`);
163
- for (const track of resolve.tracks) {
164
- player.queue.add(track)
157
+ try {
158
+ const resolve = await client.aqua.resolve({ query, requester: message.member });
159
+ const { loadType, tracks, playlistInfo } = resolve;
160
+
161
+ if (loadType === 'playlist') {
162
+ for (const track of tracks) {
163
+ player.queue.add(track);
164
+ }
165
+ message.channel.send(`Added ${tracks.length} songs from ${playlistInfo.name}.`);
166
+ } else if (loadType === 'search' || loadType === 'track') {
167
+ const track = tracks[0];
168
+ player.queue.add(track);
169
+ message.channel.send(`Added **${track.title}** to the queue.`);
170
+ } else {
171
+ return message.channel.send("No results found.");
165
172
  }
166
- if (!player.playing && !player.paused) return player.play();
167
-
168
- } else if (resolve.loadType === 'search' || resolve.loadType === 'track') {
169
- const track = resolve.tracks.shift();
170
- track.info.requester = message.member;
171
173
 
172
- player.queue.add(track);
173
-
174
- await message.channel.send(`Added **${track.info.title}** to the queue.`);
175
-
176
- if (!player.playing && !player.paused) return player.play();
177
-
178
- } else {
179
- return message.channel.send(`There were no results found for your query.`);
174
+ if (!player.playing && !player.paused) {
175
+ player.play();
176
+ }
177
+ } catch (error) {
178
+ console.error("Playback error:", error);
179
+ message.channel.send("An error occurred while trying to play the song.");
180
180
  }
181
181
  });
182
182
 
183
183
  client.aqua.on("nodeConnect", (node) => {
184
184
  console.log(`Node connected: ${node.name}`);
185
185
  });
186
+
186
187
  client.aqua.on("nodeError", (node, error) => {
187
188
  console.log(`Node "${node.name}" encountered an error: ${error.message}.`);
188
189
  });
189
190
 
190
- client.login("Yourtokenhere");
191
+ client.aqua.on('trackStart', (player, track) => {
192
+ const channel = client.channels.cache.get(player.textChannel);
193
+ if (channel) channel.send(`Now playing: **${track.title}**`);
194
+ });
195
+
196
+ client.aqua.on('queueEnd', (player) => {
197
+ const channel = client.channels.cache.get(player.textChannel);
198
+ if (channel) channel.send('The queue has ended.');
199
+ player.destroy();
200
+ });
201
+
202
+ client.login("YOUR_DISCORD_BOT_TOKEN");
203
+ ```
204
+
205
+ ### Additional Commands You Can Add:
206
+
207
+ ```javascript
208
+ client.on(Events.MessageCreate, async (message) => {
209
+ if (message.content === "!skip") {
210
+ const player = client.aqua.players.get(message.guild.id);
211
+ if (player) {
212
+ player.skip();
213
+ message.channel.send("â­ī¸ Skipped current track!");
214
+ }
215
+ }
216
+ });
217
+
218
+ client.on(Events.MessageCreate, async (message) => {
219
+ if (message.content === "!stop") {
220
+ const player = client.aqua.players.get(message.guild.id);
221
+ if (player) {
222
+ player.destroy();
223
+ message.channel.send("âšī¸ Stopped playback and cleared queue!");
224
+ }
225
+ }
226
+ });
191
227
  ```
192
228
 
193
229
  ## 🌟 Featured Projects
@@ -223,7 +259,7 @@ client.login("Yourtokenhere");
223
259
 
224
260
  For detailed usage, API references, and examples, check out our official documentation:
225
261
 
226
- [![Docs](https://img.shields.io/badge/Documentation-0099FF?style=for-the-badge&logo=readthedocs&logoColor=white)](https://toddythenoobdud.github.io/aqualink.github.io)
262
+ [![Docs](https://img.shields.io/badge/Documentation-0099FF?style=for-the-badge&logo=readthedocs&logoColor=white)](https://roddynnn.github.io/docs/)
227
263
 
228
264
  📌 **Get Started Quickly**
229
265
  - Installation guide
@@ -231,7 +267,7 @@ For detailed usage, API references, and examples, check out our official documen
231
267
  - Advanced features
232
268
  - Troubleshooting
233
269
 
234
- 🔗 Visit: **[Aqualink Docs](https://toddythenoobdud.github.io/aqualink.github.io)**
270
+ 🔗 Visit: **[Aqualink Docs](https://roddynnn.github.io/docs)**
235
271
 
236
272
  ## 👑 Premium Bots Using Aqualink
237
273
 
@@ -290,7 +326,7 @@ For detailed usage, API references, and examples, check out our official documen
290
326
  <a href="https://github.com/pomicee">
291
327
  <img src="https://avatars.githubusercontent.com/u/134554554?v=4?s=100" width="100px;" alt="pomicee"/>
292
328
  <br />
293
- <sub><b>pomicee</b></sub>
329
+ <sub><b>asynico</b></sub>
294
330
  </a>
295
331
  <br />
296
332
  <a href="#code-pomicee" title="Code">đŸ’ģ</a>
@@ -313,7 +349,7 @@ For detailed usage, API references, and examples, check out our official documen
313
349
  <sub><b>SoulDevs</b></sub>
314
350
  </a>
315
351
  <br />
316
- <a href="#code-SoulDevs title="Code">đŸ’ģ</a>
352
+ <a href="#code-SoulDevs" title="Code">đŸ’ģ</a>
317
353
  </td>
318
354
  </tr>
319
355
  </tbody>
@@ -9,7 +9,6 @@ const agent = new https.Agent({
9
9
  freeSocketTimeout: 4000
10
10
  });
11
11
 
12
- const TOTP_SECRET = Buffer.from("5507145853487499592248630329347", 'utf8');
13
12
 
14
13
  const SOUNDCLOUD_REGEX = /<a\s+itemprop="url"\s+href="(\/[^"]+)"/g;
15
14
 
@@ -48,7 +47,7 @@ const fastFetch = (url, options = {}) => {
48
47
  const soundAutoPlay = async (baseUrl) => {
49
48
  try {
50
49
  const html = await fastFetch(`${baseUrl}/recommended`);
51
-
50
+
52
51
  const links = [];
53
52
  let match;
54
53
  while ((match = SOUNDCLOUD_REGEX.exec(html)) && links.length < 50) {
@@ -64,51 +63,47 @@ const soundAutoPlay = async (baseUrl) => {
64
63
  }
65
64
  };
66
65
 
67
- const generateToken = () => {
68
- const timeStep = (Date.now() / 30000) | 0;
69
- const counter = Buffer.allocUnsafe(8);
70
- counter.writeBigUInt64BE(BigInt(timeStep), 0);
71
-
72
- const hash = crypto.createHmac('sha1', TOTP_SECRET).update(counter).digest();
73
- const offset = hash[19] & 0x0f;
74
-
75
- const binCode = (
76
- (hash[offset] & 0x7f) << 24 |
77
- hash[offset + 1] << 16 |
78
- hash[offset + 2] << 8 |
79
- hash[offset + 3]
80
- );
81
-
82
- return [
83
- (binCode % 1000000).toString().padStart(6, '0'),
84
- timeStep * 30000
85
- ];
86
- };
66
+ const spotifyAutoPlay = async (seed, player, requester, excludedIdentifiers = []) => {
67
+ try {
68
+ const { trackId, artistIds } = seed
69
+ if (!trackId) return null
70
+
71
+ const prevIdentifier = player.current?.identifier
72
+ let seedQuery = `seed_tracks=${trackId}`
73
+ if (artistIds) seedQuery += `&seed_artists=${artistIds}`
74
+ console.log('Seed query:', seedQuery)
75
+
76
+ const response = await player.aqua.resolve({
77
+ query: seedQuery,
78
+ source: 'spsearch',
79
+ requester
80
+ })
81
+ const candidates = response?.tracks || []
82
+
83
+ const seenIds = new Set(excludedIdentifiers)
84
+ if (prevIdentifier) seenIds.add(prevIdentifier)
85
+
86
+ const result = []
87
+ for (const track of candidates) {
88
+ const { identifier } = track
89
+ if (seenIds.has(identifier)) continue
90
+ seenIds.add(identifier)
91
+ track.pluginInfo = {
92
+ ...(track.pluginInfo || {}),
93
+ clientData: { fromAutoplay: true }
94
+ }
95
+ result.push(track)
96
+ if (result.length === 5) break
97
+ }
87
98
 
88
- const spotifyAutoPlay = async (seedTrackId) => {
89
- const [totp, ts] = generateToken();
90
-
91
- try {
92
- const tokenUrl = `https://open.spotify.com/api/token?reason=init&productType=embed&totp=${totp}&totpVer=5&ts=${ts}`;
93
- const tokenResponse = await fastFetch(tokenUrl);
94
- const { accessToken } = JSON.parse(tokenResponse);
95
-
96
- if (!accessToken) throw new Error("No access token");
97
-
98
- const recUrl = `https://api.spotify.com/v1/recommendations?limit=10&seed_tracks=${seedTrackId}`;
99
- const recResponse = await fastFetch(recUrl, {
100
- headers: { Authorization: `Bearer ${accessToken}` }
101
- });
99
+ return result
102
100
 
103
- const { tracks } = JSON.parse(recResponse);
104
- if (!tracks?.length) throw new Error("No tracks");
101
+ } catch (err) {
102
+ console.error('Spotify autoplay error:', err)
103
+ return null
104
+ }
105
+ }
105
106
 
106
- return tracks[Math.random() * tracks.length | 0].id;
107
- } catch (err) {
108
- console.error("Spotify error:", err.message);
109
- throw err;
110
- }
111
- };
112
107
 
113
108
  module.exports = {
114
109
  scAutoPlay: soundAutoPlay,