streamify-audio 2.3.0 → 2.3.2
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/index.d.ts +9 -0
- package/package.json +1 -1
- package/src/config.js +11 -0
- package/src/discord/Manager.js +1 -1
- package/src/discord/Stream.js +9 -12
- package/src/providers/spotify.js +2 -2
- package/docs/README.md +0 -31
- package/docs/automation.md +0 -186
- package/docs/configuration.md +0 -198
- package/docs/discord/events.md +0 -206
- package/docs/discord/manager.md +0 -195
- package/docs/discord/player.md +0 -263
- package/docs/discord/queue.md +0 -197
- package/docs/examples/advanced-bot.md +0 -391
- package/docs/examples/basic-bot.md +0 -182
- package/docs/examples/lavalink.md +0 -156
- package/docs/filters.md +0 -347
- package/docs/http/endpoints.md +0 -224
- package/docs/http/server.md +0 -174
- package/docs/plans/2026-02-22-stream-revamp-design.md +0 -88
- package/docs/plans/2026-02-22-stream-revamp-plan.md +0 -814
- package/docs/quick-start.md +0 -92
- package/docs/sources.md +0 -189
- package/docs/sponsorblock.md +0 -95
- package/tests/cache.test.js +0 -234
- package/tests/config.test.js +0 -44
- package/tests/error-handling.test.js +0 -318
- package/tests/ffmpeg.test.js +0 -66
- package/tests/filters-edge.test.js +0 -333
- package/tests/http.test.js +0 -24
- package/tests/integration.test.js +0 -325
- package/tests/local.test.js +0 -37
- package/tests/queue.test.js +0 -94
- package/tests/spotify.test.js +0 -238
- package/tests/stream.test.js +0 -217
- package/tests/twitch.test.js +0 -42
- package/tests/utils.test.js +0 -60
- package/tests/youtube.test.js +0 -219
- package/youtube-cookies.txt +0 -26
|
@@ -1,318 +0,0 @@
|
|
|
1
|
-
const { describe, it, beforeEach } = require('node:test');
|
|
2
|
-
const assert = require('node:assert');
|
|
3
|
-
|
|
4
|
-
describe('Error Handling', () => {
|
|
5
|
-
describe('YouTube Provider Errors', () => {
|
|
6
|
-
const youtube = require('../src/providers/youtube');
|
|
7
|
-
const mockConfig = {
|
|
8
|
-
ytdlpPath: 'yt-dlp',
|
|
9
|
-
ffmpegPath: 'ffmpeg',
|
|
10
|
-
cookiesPath: null
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
it('should handle missing binary gracefully', async () => {
|
|
14
|
-
const badConfig = { ...mockConfig, ytdlpPath: 'nonexistent-binary' };
|
|
15
|
-
|
|
16
|
-
await assert.rejects(
|
|
17
|
-
() => youtube.search('test', 1, badConfig),
|
|
18
|
-
Error
|
|
19
|
-
);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should handle malformed video ID', async () => {
|
|
23
|
-
await assert.rejects(
|
|
24
|
-
() => youtube.getInfo('!!!invalid!!!', mockConfig),
|
|
25
|
-
Error
|
|
26
|
-
);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it('should handle empty search results', async () => {
|
|
30
|
-
const results = await youtube.search('xyznonexistent123456789abcdef', 1, mockConfig);
|
|
31
|
-
assert(Array.isArray(results.tracks));
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe('Spotify Provider Errors', () => {
|
|
36
|
-
const spotify = require('../src/providers/spotify');
|
|
37
|
-
|
|
38
|
-
it('should throw clear error without credentials', async () => {
|
|
39
|
-
const badConfig = { spotify: {} };
|
|
40
|
-
|
|
41
|
-
await assert.rejects(
|
|
42
|
-
() => spotify.search('test', 1, badConfig),
|
|
43
|
-
/credentials/i
|
|
44
|
-
);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('should throw clear error with invalid credentials', async () => {
|
|
48
|
-
const badConfig = {
|
|
49
|
-
spotify: {
|
|
50
|
-
clientId: 'invalid',
|
|
51
|
-
clientSecret: 'invalid'
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
await assert.rejects(
|
|
56
|
-
() => spotify.search('test', 1, badConfig),
|
|
57
|
-
Error
|
|
58
|
-
);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe('SoundCloud Provider Errors', () => {
|
|
63
|
-
const soundcloud = require('../src/providers/soundcloud');
|
|
64
|
-
const mockConfig = {
|
|
65
|
-
ytdlpPath: 'yt-dlp'
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
it('should return results array for search', async () => {
|
|
69
|
-
const results = await soundcloud.search('test', 1, mockConfig);
|
|
70
|
-
assert(results.tracks, 'Should have tracks array');
|
|
71
|
-
assert(Array.isArray(results.tracks));
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
describe('Local Provider Errors', () => {
|
|
76
|
-
const local = require('../src/providers/local');
|
|
77
|
-
|
|
78
|
-
it('should throw for non-existent files', async () => {
|
|
79
|
-
await assert.rejects(
|
|
80
|
-
() => local.getInfo('/nonexistent/path/file.mp3', {}),
|
|
81
|
-
/not found/i
|
|
82
|
-
);
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it('should throw for directories', async () => {
|
|
86
|
-
await assert.rejects(
|
|
87
|
-
() => local.getInfo('/tmp', {}),
|
|
88
|
-
/directory/i
|
|
89
|
-
);
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
describe('HTTP Provider Errors', () => {
|
|
94
|
-
const http = require('../src/providers/http');
|
|
95
|
-
|
|
96
|
-
it('should handle invalid URLs', async () => {
|
|
97
|
-
await assert.rejects(
|
|
98
|
-
() => http.getInfo('not-a-valid-url', {}),
|
|
99
|
-
Error
|
|
100
|
-
);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('should return track info for valid URL format', async () => {
|
|
104
|
-
const info = await http.getInfo('http://example.com/audio.mp3', {});
|
|
105
|
-
assert(info.id, 'Should have id');
|
|
106
|
-
assert(info.uri, 'Should have uri');
|
|
107
|
-
assert.strictEqual(info.source, 'http');
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe('Stream Controller Errors', () => {
|
|
112
|
-
const { createStream } = require('../src/discord/Stream');
|
|
113
|
-
const mockConfig = {
|
|
114
|
-
ytdlpPath: 'yt-dlp',
|
|
115
|
-
ffmpegPath: 'ffmpeg',
|
|
116
|
-
ytdlp: { format: 'bestaudio/best', additionalArgs: [] },
|
|
117
|
-
audio: { bitrate: '128k', format: 'opus' }
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
it('should handle destroyed stream gracefully', async () => {
|
|
121
|
-
const track = { id: 'test', title: 'Test', source: 'youtube' };
|
|
122
|
-
const stream = createStream(track, {}, mockConfig);
|
|
123
|
-
|
|
124
|
-
stream.destroy();
|
|
125
|
-
|
|
126
|
-
await assert.rejects(
|
|
127
|
-
() => stream.create(),
|
|
128
|
-
/destroyed/
|
|
129
|
-
);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it('should handle double destroy', () => {
|
|
133
|
-
const track = { id: 'test', title: 'Test', source: 'youtube' };
|
|
134
|
-
const stream = createStream(track, {}, mockConfig);
|
|
135
|
-
|
|
136
|
-
assert.doesNotThrow(() => {
|
|
137
|
-
stream.destroy();
|
|
138
|
-
stream.destroy();
|
|
139
|
-
stream.destroy();
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it('should handle missing track ID', async () => {
|
|
144
|
-
const track = { title: 'Test', source: 'youtube' };
|
|
145
|
-
const stream = createStream(track, {}, mockConfig);
|
|
146
|
-
|
|
147
|
-
await assert.rejects(
|
|
148
|
-
() => stream.create(),
|
|
149
|
-
/Invalid track ID/
|
|
150
|
-
);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
describe('Queue Errors', () => {
|
|
155
|
-
const Queue = require('../src/discord/Queue');
|
|
156
|
-
|
|
157
|
-
it('should handle negative index in remove', () => {
|
|
158
|
-
const queue = new Queue();
|
|
159
|
-
queue.add({ id: '1', title: 'Track 1' });
|
|
160
|
-
|
|
161
|
-
const removed = queue.remove(-1);
|
|
162
|
-
assert.strictEqual(removed, null);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it('should handle out of bounds index in remove', () => {
|
|
166
|
-
const queue = new Queue();
|
|
167
|
-
queue.add({ id: '1', title: 'Track 1' });
|
|
168
|
-
|
|
169
|
-
const removed = queue.remove(100);
|
|
170
|
-
assert.strictEqual(removed, null);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('should handle move with invalid indices', () => {
|
|
174
|
-
const queue = new Queue();
|
|
175
|
-
queue.add({ id: '1', title: 'Track 1' });
|
|
176
|
-
queue.add({ id: '2', title: 'Track 2' });
|
|
177
|
-
|
|
178
|
-
assert.strictEqual(queue.move(-1, 0), false);
|
|
179
|
-
assert.strictEqual(queue.move(0, 100), false);
|
|
180
|
-
assert.strictEqual(queue.move(100, 0), false);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('should handle shift on empty queue', () => {
|
|
184
|
-
const queue = new Queue();
|
|
185
|
-
const result = queue.shift();
|
|
186
|
-
assert.strictEqual(result, null);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
it('should handle unshift on empty history', () => {
|
|
190
|
-
const queue = new Queue();
|
|
191
|
-
const result = queue.unshift();
|
|
192
|
-
assert.strictEqual(result, null);
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
describe('Config Errors', () => {
|
|
197
|
-
const config = require('../src/config');
|
|
198
|
-
|
|
199
|
-
it('should use defaults for missing options', () => {
|
|
200
|
-
const loaded = config.load({});
|
|
201
|
-
|
|
202
|
-
assert(loaded.port);
|
|
203
|
-
assert(loaded.host);
|
|
204
|
-
assert(loaded.ytdlpPath);
|
|
205
|
-
assert(loaded.ffmpegPath);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it('should handle null options', () => {
|
|
209
|
-
assert.doesNotThrow(() => {
|
|
210
|
-
config.load(null);
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('should handle undefined options', () => {
|
|
215
|
-
assert.doesNotThrow(() => {
|
|
216
|
-
config.load(undefined);
|
|
217
|
-
});
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
describe('Filter Errors', () => {
|
|
222
|
-
const { buildFfmpegArgs, applyEffectPreset } = require('../src/filters/ffmpeg');
|
|
223
|
-
const mockConfig = { audio: { bitrate: '128k', format: 'opus' } };
|
|
224
|
-
|
|
225
|
-
it('should handle empty filters', () => {
|
|
226
|
-
assert.doesNotThrow(() => {
|
|
227
|
-
buildFfmpegArgs({}, mockConfig);
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('should handle null filters', () => {
|
|
232
|
-
assert.doesNotThrow(() => {
|
|
233
|
-
buildFfmpegArgs(null, mockConfig);
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
it('should handle undefined filters', () => {
|
|
238
|
-
assert.doesNotThrow(() => {
|
|
239
|
-
buildFfmpegArgs(undefined, mockConfig);
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it('should return null for unknown preset', () => {
|
|
244
|
-
const result = applyEffectPreset('nonexistent');
|
|
245
|
-
assert.strictEqual(result, null);
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
it('should handle invalid equalizer values', () => {
|
|
249
|
-
assert.doesNotThrow(() => {
|
|
250
|
-
buildFfmpegArgs({ equalizer: 'not an array' }, mockConfig);
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
it('should handle invalid tremolo object', () => {
|
|
255
|
-
assert.doesNotThrow(() => {
|
|
256
|
-
buildFfmpegArgs({ tremolo: 'not an object' }, mockConfig);
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
describe('Cache Edge Cases', () => {
|
|
262
|
-
const cache = require('../src/cache');
|
|
263
|
-
|
|
264
|
-
beforeEach(() => {
|
|
265
|
-
cache.clear();
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('should handle getting undefined key', () => {
|
|
269
|
-
assert.doesNotThrow(() => {
|
|
270
|
-
cache.get(undefined);
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
it('should handle setting undefined value', () => {
|
|
275
|
-
assert.doesNotThrow(() => {
|
|
276
|
-
cache.set('key', undefined);
|
|
277
|
-
});
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
it('should handle deleting non-existent key', () => {
|
|
281
|
-
assert.doesNotThrow(() => {
|
|
282
|
-
cache.del('nonexistent');
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it('should handle clearing already empty cache', () => {
|
|
287
|
-
assert.doesNotThrow(() => {
|
|
288
|
-
cache.clear();
|
|
289
|
-
cache.clear();
|
|
290
|
-
});
|
|
291
|
-
});
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
describe('Stream Utils Errors', () => {
|
|
295
|
-
const streamUtils = require('../src/utils/stream');
|
|
296
|
-
|
|
297
|
-
it('should handle unregistering non-existent stream', () => {
|
|
298
|
-
assert.doesNotThrow(() => {
|
|
299
|
-
streamUtils.unregisterStream('nonexistent');
|
|
300
|
-
});
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
it('should return null for non-existent stream position', () => {
|
|
304
|
-
const position = streamUtils.getStreamPosition('nonexistent');
|
|
305
|
-
assert.strictEqual(position, null);
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it('should return undefined for non-existent stream by ID', () => {
|
|
309
|
-
const stream = streamUtils.getStreamById('nonexistent');
|
|
310
|
-
assert.strictEqual(stream, undefined);
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
it('should return null when updating non-existent stream', () => {
|
|
314
|
-
const result = streamUtils.updateStreamFilters('nonexistent', { bass: 10 });
|
|
315
|
-
assert.strictEqual(result, null);
|
|
316
|
-
});
|
|
317
|
-
});
|
|
318
|
-
});
|
package/tests/ffmpeg.test.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
const test = require('node:test');
|
|
2
|
-
const assert = require('node:assert');
|
|
3
|
-
const { buildFfmpegArgs, applyEffectPreset } = require('../src/filters/ffmpeg');
|
|
4
|
-
|
|
5
|
-
test('ffmpeg filters: buildFfmpegArgs', async (t) => {
|
|
6
|
-
await t.test('should return default args with no filters', () => {
|
|
7
|
-
const args = buildFfmpegArgs();
|
|
8
|
-
assert.ok(args.includes('-acodec'));
|
|
9
|
-
assert.ok(args.includes('libopus'));
|
|
10
|
-
assert.ok(args.includes('-f'));
|
|
11
|
-
assert.ok(args.includes('ogg'));
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
await t.test('should include volume filter', () => {
|
|
15
|
-
const args = buildFfmpegArgs({ volume: 50 });
|
|
16
|
-
const afIndex = args.indexOf('-af');
|
|
17
|
-
assert.ok(afIndex !== -1);
|
|
18
|
-
assert.ok(args[afIndex + 1].includes('volume=0.5'));
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
await t.test('should include bass filter', () => {
|
|
22
|
-
const args = buildFfmpegArgs({ bass: 10 });
|
|
23
|
-
const afIndex = args.indexOf('-af');
|
|
24
|
-
assert.ok(args[afIndex + 1].includes('bass=g=10'));
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
await t.test('should include multiple filters', () => {
|
|
28
|
-
const args = buildFfmpegArgs({ volume: 150, speed: 1.5 });
|
|
29
|
-
const afIndex = args.indexOf('-af');
|
|
30
|
-
assert.ok(args[afIndex + 1].includes('volume=1.5'));
|
|
31
|
-
assert.ok(args[afIndex + 1].includes('atempo=1.5'));
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
await t.test('should handle nightcore preset', () => {
|
|
35
|
-
const args = buildFfmpegArgs({ nightcore: true });
|
|
36
|
-
const afIndex = args.indexOf('-af');
|
|
37
|
-
assert.ok(args[afIndex + 1].includes('atempo=1.25'));
|
|
38
|
-
assert.ok(args[afIndex + 1].includes('asetrate=48000*1.25'));
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
await t.test('should respect audio config', () => {
|
|
42
|
-
const config = { audio: { bitrate: '192k', format: 'mp3' } };
|
|
43
|
-
const args = buildFfmpegArgs({}, config);
|
|
44
|
-
assert.ok(args.includes('192k'));
|
|
45
|
-
assert.ok(args.includes('libmp3lame'));
|
|
46
|
-
assert.ok(args.includes('mp3'));
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test('ffmpeg filters: applyEffectPreset', async (t) => {
|
|
51
|
-
await t.test('should return correct filters for bassboost', () => {
|
|
52
|
-
const effect = applyEffectPreset('bassboost');
|
|
53
|
-
assert.strictEqual(effect.bass, 12);
|
|
54
|
-
assert.ok(Array.isArray(effect.equalizer));
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
await t.test('should scale with intensity', () => {
|
|
58
|
-
const effect = applyEffectPreset('bassboost', 0.5);
|
|
59
|
-
assert.strictEqual(effect.bass, 6);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
await t.test('should return null for unknown preset', () => {
|
|
63
|
-
const effect = applyEffectPreset('nonexistent');
|
|
64
|
-
assert.strictEqual(effect, null);
|
|
65
|
-
});
|
|
66
|
-
});
|