streamify-audio 2.2.8 → 2.2.10

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
@@ -229,12 +229,21 @@ const manager = new Streamify.Manager(client, {
229
229
 
230
230
  ### Search with Filters
231
231
 
232
- Filter for live streams or sort results by popularity/date.
232
+ Filter for live streams, sort results, or filter by artist.
233
233
 
234
234
  ```javascript
235
235
  const results = await manager.search('lofi hip hop', {
236
- type: 'live',
237
- sort: 'popularity'
236
+ source: 'youtube', // youtube, spotify, soundcloud
237
+ type: 'live', // video, live, playlist
238
+ sort: 'popularity', // relevance, popular, latest
239
+ limit: 10
240
+ });
241
+
242
+ // Artist search - filters results to only include tracks by that artist
243
+ const artistTracks = await manager.search('Drake', {
244
+ source: 'spotify',
245
+ artist: 'Drake', // filters results to tracks BY this artist
246
+ limit: 5
238
247
  });
239
248
  ```
240
249
 
@@ -76,6 +76,7 @@ Searches for tracks.
76
76
  - `limit` (number) - Number of results (default: 10)
77
77
  - `type` (string) - `video`, `live`, or `all` (YouTube only)
78
78
  - `sort` (string) - `relevance`, `popularity`, `date`, or `rating` (YouTube only)
79
+ - `artist` (string) - Filter results to only include tracks by this artist
79
80
 
80
81
  **Example:**
81
82
 
@@ -85,6 +86,13 @@ const result = await manager.search('lofi hip hop', {
85
86
  type: 'live',
86
87
  sort: 'popularity'
87
88
  });
89
+
90
+ // Artist search - filters results to tracks BY the artist
91
+ const artistTracks = await manager.search('Drake', {
92
+ source: 'spotify',
93
+ artist: 'Drake',
94
+ limit: 5
95
+ });
88
96
  ```
89
97
 
90
98
  // Result
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "streamify-audio",
3
- "version": "2.2.8",
3
+ "version": "2.2.10",
4
4
  "description": "Dual-mode audio library: HTTP streaming proxy + Discord player (Lavalink alternative). Supports YouTube, Spotify, SoundCloud with audio filters.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -39,12 +39,18 @@ function checkDependencies() {
39
39
  }
40
40
  }
41
41
 
42
+ let managerInitLogged = false;
43
+
42
44
  class Manager extends EventEmitter {
43
45
  constructor(client, options = {}) {
44
46
  super();
45
47
 
46
48
  checkDependencies();
47
49
 
50
+ if (options.logLevel !== undefined) {
51
+ log.setLevel(options.logLevel);
52
+ }
53
+
48
54
  this.client = client;
49
55
  this.players = new Map();
50
56
 
@@ -85,7 +91,10 @@ class Manager extends EventEmitter {
85
91
 
86
92
  this._setupVoiceStateListener();
87
93
 
88
- log.info('MANAGER', 'Streamify Manager initialized');
94
+ if (!managerInitLogged) {
95
+ managerInitLogged = true;
96
+ log.info('MANAGER', 'Streamify Manager initialized');
97
+ }
89
98
  }
90
99
 
91
100
  _setupVoiceStateListener() {
@@ -261,7 +270,7 @@ class Manager extends EventEmitter {
261
270
  if (!this.config.spotify?.clientId) {
262
271
  throw new Error('Spotify credentials not configured');
263
272
  }
264
- result = await spotify.search(query, limit, this.config);
273
+ result = await spotify.search(query, limit, this.config, options);
265
274
  break;
266
275
 
267
276
  case 'soundcloud':
@@ -269,7 +278,7 @@ class Manager extends EventEmitter {
269
278
  if (!this._isProviderEnabled('soundcloud')) {
270
279
  throw new Error('SoundCloud provider is disabled');
271
280
  }
272
- result = await soundcloud.search(query, limit, this.config);
281
+ result = await soundcloud.search(query, limit, this.config, options);
273
282
  break;
274
283
 
275
284
  case 'twitch':
@@ -3,9 +3,11 @@ const { buildFfmpegArgs } = require('../filters/ffmpeg');
3
3
  const { registerStream, unregisterStream } = require('../utils/stream');
4
4
  const log = require('../utils/logger');
5
5
 
6
- async function search(query, limit, config) {
6
+ async function search(query, limit, config, options = {}) {
7
7
  const startTime = Date.now();
8
- log.info('SOUNDCLOUD', `Searching: "${query}" (limit: ${limit})`);
8
+ const artistFilter = options.artist ? options.artist.toLowerCase() : null;
9
+ const searchLimit = artistFilter ? Math.min(limit * 3, 25) : limit;
10
+ log.info('SOUNDCLOUD', `Searching: "${query}" (limit: ${limit}${artistFilter ? `, artist: ${options.artist}` : ''})`);
9
11
 
10
12
  return new Promise((resolve, reject) => {
11
13
  const args = [
@@ -13,7 +15,7 @@ async function search(query, limit, config) {
13
15
  '--flat-playlist',
14
16
  '--skip-download',
15
17
  '-J',
16
- `scsearch${limit}:${query}`
18
+ `scsearch${searchLimit}:${query}`
17
19
  ];
18
20
 
19
21
  const proc = spawn(config.ytdlpPath, args, {
@@ -32,7 +34,7 @@ async function search(query, limit, config) {
32
34
  }
33
35
  try {
34
36
  const data = JSON.parse(stdout);
35
- const tracks = (data.entries || []).map(entry => ({
37
+ let tracks = (data.entries || []).map(entry => ({
36
38
  id: entry.id,
37
39
  title: entry.title,
38
40
  duration: entry.duration,
@@ -43,6 +45,16 @@ async function search(query, limit, config) {
43
45
  streamUrl: `/soundcloud/stream/${entry.id}`,
44
46
  source: 'soundcloud'
45
47
  }));
48
+
49
+ if (artistFilter) {
50
+ tracks = tracks.filter(t => {
51
+ const author = (t.author || '').toLowerCase();
52
+ const title = (t.title || '').toLowerCase();
53
+ return author.includes(artistFilter) || title.includes(artistFilter);
54
+ }).slice(0, limit);
55
+ log.info('SOUNDCLOUD', `Filtered to ${tracks.length} tracks by artist "${options.artist}"`);
56
+ }
57
+
46
58
  const elapsed = Date.now() - startTime;
47
59
  log.info('SOUNDCLOUD', `Found ${tracks.length} results (${elapsed}ms)`);
48
60
  resolve({ tracks, source: 'soundcloud', searchTime: elapsed });
@@ -52,13 +52,15 @@ async function spotifyApi(endpoint, config) {
52
52
  return response.json();
53
53
  }
54
54
 
55
- async function search(query, limit, config) {
55
+ async function search(query, limit, config, options = {}) {
56
56
  const startTime = Date.now();
57
- log.info('SPOTIFY', `Searching: "${query}" (limit: ${limit})`);
57
+ const artistFilter = options.artist ? options.artist.toLowerCase() : null;
58
+ const searchLimit = artistFilter ? Math.min(limit * 2, 50) : limit;
59
+ log.info('SPOTIFY', `Searching: "${query}" (limit: ${limit}${artistFilter ? `, artist: ${options.artist}` : ''})`);
58
60
 
59
- const data = await spotifyApi(`/search?q=${encodeURIComponent(query)}&type=track&limit=${limit}`, config);
61
+ const data = await spotifyApi(`/search?q=${encodeURIComponent(query)}&type=track&limit=${searchLimit}`, config);
60
62
 
61
- const tracks = (data.tracks?.items || []).map(track => ({
63
+ let tracks = (data.tracks?.items || []).map(track => ({
62
64
  id: track.id,
63
65
  title: track.name,
64
66
  author: track.artists.map(a => a.name).join(', '),
@@ -71,6 +73,14 @@ async function search(query, limit, config) {
71
73
  source: 'spotify'
72
74
  }));
73
75
 
76
+ if (artistFilter) {
77
+ tracks = tracks.filter(t => {
78
+ const author = (t.author || '').toLowerCase();
79
+ return author.includes(artistFilter);
80
+ }).slice(0, limit);
81
+ log.info('SPOTIFY', `Filtered to ${tracks.length} tracks by artist "${options.artist}"`);
82
+ }
83
+
74
84
  const elapsed = Date.now() - startTime;
75
85
  log.info('SPOTIFY', `Found ${tracks.length} results (${elapsed}ms)`);
76
86
  return { tracks, source: 'spotify', searchTime: elapsed };
@@ -5,7 +5,9 @@ const log = require('../utils/logger');
5
5
 
6
6
  async function search(query, limit, config, options = {}) {
7
7
  const startTime = Date.now();
8
- log.info('YOUTUBE', `Searching: "${query}" (limit: ${limit}, type: ${options.type || 'all'}, sort: ${options.sort || 'relevance'})`);
8
+ const artistFilter = options.artist ? options.artist.toLowerCase() : null;
9
+ const searchLimit = artistFilter ? Math.min(limit * 3, 25) : limit;
10
+ log.info('YOUTUBE', `Searching: "${query}" (limit: ${limit}, type: ${options.type || 'all'}, sort: ${options.sort || 'relevance'}${artistFilter ? `, artist: ${options.artist}` : ''})`);
9
11
 
10
12
  return new Promise((resolve, reject) => {
11
13
  const args = [
@@ -20,15 +22,13 @@ async function search(query, limit, config, options = {}) {
20
22
  }
21
23
 
22
24
  let searchQuery = query;
23
-
24
- // Handle sorting by modifying search query or using filters
25
+
25
26
  if (options.sort === 'views' || options.sort === 'popular') {
26
27
  searchQuery += ' most viewed';
27
28
  } else if (options.sort === 'date' || options.sort === 'latest') {
28
29
  searchQuery += ' new';
29
30
  }
30
31
 
31
- // Handle type filtering
32
32
  if (options.type === 'live') {
33
33
  args.push('--match-filter', 'live_status = is_live');
34
34
  } else if (options.type === 'playlist') {
@@ -37,7 +37,7 @@ async function search(query, limit, config, options = {}) {
37
37
  args.push('--match-filter', 'live_status != is_live');
38
38
  }
39
39
 
40
- args.push(`ytsearch${limit}:${searchQuery}`);
40
+ args.push(`ytsearch${searchLimit}:${searchQuery}`);
41
41
 
42
42
  const proc = spawn(config.ytdlpPath, args, {
43
43
  env: { ...process.env, PATH: '/usr/local/bin:/root/.deno/bin:' + process.env.PATH }
@@ -59,7 +59,7 @@ async function search(query, limit, config, options = {}) {
59
59
  }
60
60
  try {
61
61
  const data = JSON.parse(stdout);
62
- const tracks = (data.entries || []).map(entry => {
62
+ let tracks = (data.entries || []).map(entry => {
63
63
  const bestAudio = entry.formats
64
64
  ?.filter(f => f.vcodec === 'none' && f.acodec !== 'none')
65
65
  .sort((a, b) => (b.abr || 0) - (a.abr || 0))[0];
@@ -82,6 +82,16 @@ async function search(query, limit, config, options = {}) {
82
82
  _headers: entry.http_headers || data.http_headers || null
83
83
  };
84
84
  });
85
+
86
+ if (artistFilter) {
87
+ tracks = tracks.filter(t => {
88
+ const author = (t.author || '').toLowerCase();
89
+ const title = (t.title || '').toLowerCase();
90
+ return author.includes(artistFilter) || title.includes(artistFilter);
91
+ }).slice(0, limit);
92
+ log.info('YOUTUBE', `Filtered to ${tracks.length} tracks by artist "${options.artist}"`);
93
+ }
94
+
85
95
  const elapsed = Date.now() - startTime;
86
96
  log.info('YOUTUBE', `Found ${tracks.length} results (${elapsed}ms)`);
87
97
  resolve({ tracks, source: 'youtube', searchTime: elapsed });
package/src/server.js CHANGED
@@ -98,14 +98,14 @@ class Server {
98
98
  if (!this._isProviderEnabled('youtube')) {
99
99
  return res.status(400).json({ error: 'YouTube provider is disabled' });
100
100
  }
101
- const { q, limit = 10, type, sort } = req.query;
101
+ const { q, limit = 10, type, sort, artist } = req.query;
102
102
  if (!q) return res.status(400).json({ error: 'Missing query parameter: q' });
103
103
 
104
- const cacheKey = `yt:search:${q}:${limit}:${type}:${sort}`;
104
+ const cacheKey = `yt:search:${q}:${limit}:${type}:${sort}:${artist || ''}`;
105
105
  const cached = cache.get(cacheKey);
106
106
  if (cached) return res.json(cached);
107
107
 
108
- const results = await youtube.search(q, parseInt(limit), this.config, { type, sort });
108
+ const results = await youtube.search(q, parseInt(limit), this.config, { type, sort, artist });
109
109
  cache.set(cacheKey, results, this.config.cache.searchTTL);
110
110
  res.json(results);
111
111
  } catch (error) {
@@ -149,14 +149,14 @@ class Server {
149
149
  if (!this.config.spotify?.clientId) {
150
150
  return res.status(400).json({ error: 'Spotify not configured' });
151
151
  }
152
- const { q, limit = 10 } = req.query;
152
+ const { q, limit = 10, artist } = req.query;
153
153
  if (!q) return res.status(400).json({ error: 'Missing query parameter: q' });
154
154
 
155
- const cacheKey = `sp:search:${q}:${limit}`;
155
+ const cacheKey = `sp:search:${q}:${limit}:${artist || ''}`;
156
156
  const cached = cache.get(cacheKey);
157
157
  if (cached) return res.json(cached);
158
158
 
159
- const results = await spotify.search(q, parseInt(limit), this.config);
159
+ const results = await spotify.search(q, parseInt(limit), this.config, { artist });
160
160
  cache.set(cacheKey, results, this.config.cache.searchTTL);
161
161
  res.json(results);
162
162
  } catch (error) {
@@ -208,14 +208,14 @@ class Server {
208
208
  if (!this._isProviderEnabled('soundcloud')) {
209
209
  return res.status(400).json({ error: 'SoundCloud provider is disabled' });
210
210
  }
211
- const { q, limit = 10 } = req.query;
211
+ const { q, limit = 10, artist } = req.query;
212
212
  if (!q) return res.status(400).json({ error: 'Missing query parameter: q' });
213
213
 
214
- const cacheKey = `sc:search:${q}:${limit}`;
214
+ const cacheKey = `sc:search:${q}:${limit}:${artist || ''}`;
215
215
  const cached = cache.get(cacheKey);
216
216
  if (cached) return res.json(cached);
217
217
 
218
- const results = await soundcloud.search(q, parseInt(limit), this.config);
218
+ const results = await soundcloud.search(q, parseInt(limit), this.config, { artist });
219
219
  cache.set(cacheKey, results, this.config.cache.searchTTL);
220
220
  res.json(results);
221
221
  } catch (error) {