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.
Files changed (39) hide show
  1. package/index.d.ts +9 -0
  2. package/package.json +1 -1
  3. package/src/config.js +11 -0
  4. package/src/discord/Manager.js +1 -1
  5. package/src/discord/Stream.js +9 -12
  6. package/src/providers/spotify.js +2 -2
  7. package/docs/README.md +0 -31
  8. package/docs/automation.md +0 -186
  9. package/docs/configuration.md +0 -198
  10. package/docs/discord/events.md +0 -206
  11. package/docs/discord/manager.md +0 -195
  12. package/docs/discord/player.md +0 -263
  13. package/docs/discord/queue.md +0 -197
  14. package/docs/examples/advanced-bot.md +0 -391
  15. package/docs/examples/basic-bot.md +0 -182
  16. package/docs/examples/lavalink.md +0 -156
  17. package/docs/filters.md +0 -347
  18. package/docs/http/endpoints.md +0 -224
  19. package/docs/http/server.md +0 -174
  20. package/docs/plans/2026-02-22-stream-revamp-design.md +0 -88
  21. package/docs/plans/2026-02-22-stream-revamp-plan.md +0 -814
  22. package/docs/quick-start.md +0 -92
  23. package/docs/sources.md +0 -189
  24. package/docs/sponsorblock.md +0 -95
  25. package/tests/cache.test.js +0 -234
  26. package/tests/config.test.js +0 -44
  27. package/tests/error-handling.test.js +0 -318
  28. package/tests/ffmpeg.test.js +0 -66
  29. package/tests/filters-edge.test.js +0 -333
  30. package/tests/http.test.js +0 -24
  31. package/tests/integration.test.js +0 -325
  32. package/tests/local.test.js +0 -37
  33. package/tests/queue.test.js +0 -94
  34. package/tests/spotify.test.js +0 -238
  35. package/tests/stream.test.js +0 -217
  36. package/tests/twitch.test.js +0 -42
  37. package/tests/utils.test.js +0 -60
  38. package/tests/youtube.test.js +0 -219
  39. package/youtube-cookies.txt +0 -26
@@ -1,37 +0,0 @@
1
- const test = require('node:test');
2
- const assert = require('node:assert');
3
- const path = require('node:path');
4
- const fs = require('node:fs');
5
- const local = require('../src/providers/local');
6
- const { loadConfig } = require('../src/config');
7
-
8
- test('local provider: getInfo', async (t) => {
9
- const config = loadConfig();
10
- const testFile = path.resolve(__dirname, 'test-audio.mp3');
11
-
12
- // Create a dummy file for testing
13
- fs.writeFileSync(testFile, 'dummy audio content');
14
-
15
- try {
16
- await t.test('should return correct info for a local file', async () => {
17
- const info = await local.getInfo(testFile, config);
18
-
19
- assert.strictEqual(info.source, 'local');
20
- assert.strictEqual(info.title, 'test-audio.mp3');
21
- assert.ok(info.absolutePath.endsWith('tests/test-audio.mp3'));
22
- assert.strictEqual(info.uri, `file://${info.absolutePath}`);
23
- });
24
-
25
- await t.test('should throw error if file does not exist', async () => {
26
- await assert.rejects(
27
- local.getInfo('non-existent-file.mp3', config),
28
- { message: /File not found/ }
29
- );
30
- });
31
- } finally {
32
- // Cleanup
33
- if (fs.existsSync(testFile)) {
34
- fs.unlinkSync(testFile);
35
- }
36
- }
37
- });
@@ -1,94 +0,0 @@
1
- const test = require('node:test');
2
- const assert = require('node:assert');
3
- const Queue = require('../src/discord/Queue');
4
-
5
- test('Queue: management', async (t) => {
6
- await t.test('should add tracks', () => {
7
- const queue = new Queue();
8
- queue.add({ id: '1', title: 'Track 1' });
9
- queue.add({ id: '2', title: 'Track 2' });
10
- assert.strictEqual(queue.size, 2);
11
- assert.strictEqual(queue.tracks[0].id, '1');
12
- });
13
-
14
- await t.test('should add many tracks', () => {
15
- const queue = new Queue();
16
- queue.addMany([{ id: '1' }, { id: '2' }]);
17
- assert.strictEqual(queue.size, 2);
18
- });
19
-
20
- await t.test('should remove tracks', () => {
21
- const queue = new Queue();
22
- queue.addMany([{ id: '1' }, { id: '2' }]);
23
- const removed = queue.remove(0);
24
- assert.strictEqual(removed.id, '1');
25
- assert.strictEqual(queue.size, 1);
26
- });
27
-
28
- await t.test('should move tracks', () => {
29
- const queue = new Queue();
30
- queue.addMany([{ id: '1' }, { id: '2' }, { id: '3' }]);
31
- queue.move(2, 0); // Move '3' to front
32
- assert.strictEqual(queue.tracks[0].id, '3');
33
- assert.strictEqual(queue.tracks[1].id, '1');
34
- });
35
-
36
- await t.test('should shift tracks and track history', () => {
37
- const queue = new Queue({ maxPreviousTracks: 2 });
38
- const t1 = { id: '1' };
39
- const t2 = { id: '2' };
40
- const t3 = { id: '3' };
41
-
42
- queue.addMany([t1, t2, t3]);
43
-
44
- assert.strictEqual(queue.shift().id, '1');
45
- assert.strictEqual(queue.current.id, '1');
46
-
47
- assert.strictEqual(queue.shift().id, '2');
48
- assert.strictEqual(queue.previous[0].id, '1');
49
-
50
- assert.strictEqual(queue.shift().id, '3');
51
- assert.strictEqual(queue.previous[0].id, '2');
52
- assert.strictEqual(queue.previous[1].id, '1');
53
- });
54
-
55
- await t.test('should handle repeat track mode', () => {
56
- const queue = new Queue();
57
- const t1 = { id: '1' };
58
- queue.add(t1);
59
- queue.shift(); // current = t1
60
-
61
- queue.setRepeatMode('track');
62
- assert.strictEqual(queue.shift().id, '1');
63
- assert.strictEqual(queue.shift().id, '1');
64
- });
65
-
66
- await t.test('should handle repeat queue mode', () => {
67
- const queue = new Queue();
68
- const t1 = { id: '1' };
69
- const t2 = { id: '2' };
70
- queue.addMany([t1, t2]);
71
-
72
- queue.setRepeatMode('queue');
73
- queue.shift(); // current = t1
74
- queue.shift(); // current = t2
75
-
76
- assert.strictEqual(queue.shift().id, '1');
77
- assert.strictEqual(queue.current.id, '1');
78
- });
79
-
80
- await t.test('should unshift (previous)', () => {
81
- const queue = new Queue();
82
- const t1 = { id: '1' };
83
- const t2 = { id: '2' };
84
- queue.addMany([t1, t2]);
85
-
86
- queue.shift(); // current = t1
87
- queue.shift(); // current = t2
88
-
89
- const prev = queue.unshift();
90
- assert.strictEqual(prev.id, '1');
91
- assert.strictEqual(queue.current.id, '1');
92
- assert.strictEqual(queue.tracks[0].id, '2');
93
- });
94
- });
@@ -1,238 +0,0 @@
1
- const { describe, it, beforeEach } = require('node:test');
2
- const assert = require('node:assert');
3
-
4
- const spotify = require('../src/providers/spotify');
5
-
6
- describe('Spotify Provider', () => {
7
- const mockConfig = {
8
- ytdlpPath: 'yt-dlp',
9
- ffmpegPath: 'ffmpeg',
10
- spotify: {
11
- clientId: process.env.SPOTIFY_CLIENT_ID || null,
12
- clientSecret: process.env.SPOTIFY_CLIENT_SECRET || null
13
- },
14
- cache: {
15
- searchTTL: 300,
16
- infoTTL: 600
17
- }
18
- };
19
-
20
- const hasCredentials = mockConfig.spotify.clientId && mockConfig.spotify.clientSecret;
21
-
22
- describe('search', () => {
23
- it('should throw without credentials', async function() {
24
- if (hasCredentials) this.skip();
25
-
26
- const badConfig = { ...mockConfig, spotify: {} };
27
- await assert.rejects(
28
- () => spotify.search('test', 1, badConfig),
29
- /credentials/i
30
- );
31
- });
32
-
33
- it('should return tracks for valid query', async function() {
34
- if (!hasCredentials) this.skip();
35
-
36
- const results = await spotify.search('never gonna give you up', 1, mockConfig);
37
-
38
- assert(results.tracks, 'Should have tracks');
39
- assert(Array.isArray(results.tracks), 'Tracks should be array');
40
- assert.strictEqual(results.source, 'spotify');
41
- });
42
-
43
- it('should respect limit', async function() {
44
- if (!hasCredentials) this.skip();
45
-
46
- const results = await spotify.search('music', 5, mockConfig);
47
- assert(results.tracks.length <= 5, 'Should not exceed limit');
48
- });
49
-
50
- it('should return track with required fields', async function() {
51
- if (!hasCredentials) this.skip();
52
-
53
- const results = await spotify.search('test', 1, mockConfig);
54
-
55
- if (results.tracks.length > 0) {
56
- const track = results.tracks[0];
57
- assert(track.id, 'Should have id');
58
- assert(track.title, 'Should have title');
59
- assert(track.author, 'Should have author');
60
- assert(track.album, 'Should have album');
61
- assert(typeof track.duration === 'number', 'Should have numeric duration');
62
- assert(track.uri, 'Should have uri');
63
- assert(track.streamUrl, 'Should have streamUrl');
64
- assert.strictEqual(track.source, 'spotify');
65
- }
66
- });
67
-
68
- it('should handle empty results', async function() {
69
- if (!hasCredentials) this.skip();
70
-
71
- const results = await spotify.search('xyznonexistenttrack12345', 1, mockConfig);
72
- assert(Array.isArray(results.tracks), 'Should return empty array');
73
- });
74
-
75
- it('should handle special characters', async function() {
76
- if (!hasCredentials) this.skip();
77
-
78
- const results = await spotify.search('rock & roll "classics"', 1, mockConfig);
79
- assert(results.tracks, 'Should handle special chars');
80
- });
81
- });
82
-
83
- describe('getInfo', () => {
84
- it('should throw without credentials', async function() {
85
- if (hasCredentials) this.skip();
86
-
87
- const badConfig = { ...mockConfig, spotify: {} };
88
- await assert.rejects(
89
- () => spotify.getInfo('4uLU6hMCjMI75M1A2tKUQC', badConfig),
90
- /credentials/i
91
- );
92
- });
93
-
94
- it('should return info for valid track ID', async function() {
95
- if (!hasCredentials) this.skip();
96
-
97
- const info = await spotify.getInfo('4uLU6hMCjMI75M1A2tKUQC', mockConfig);
98
-
99
- assert(info.id, 'Should have id');
100
- assert(info.title, 'Should have title');
101
- assert(info.author, 'Should have author');
102
- assert(info.album, 'Should have album');
103
- assert.strictEqual(info.source, 'spotify');
104
- });
105
-
106
- it('should throw for invalid track ID', async function() {
107
- if (!hasCredentials) this.skip();
108
-
109
- await assert.rejects(
110
- () => spotify.getInfo('invalidtrackid', mockConfig),
111
- /Spotify API error/
112
- );
113
- });
114
-
115
- it('should include duration in seconds', async function() {
116
- if (!hasCredentials) this.skip();
117
-
118
- const info = await spotify.getInfo('4uLU6hMCjMI75M1A2tKUQC', mockConfig);
119
- assert(typeof info.duration === 'number', 'Duration should be number');
120
- assert(info.duration > 0, 'Duration should be positive');
121
- assert(info.duration < 3600, 'Duration should be in seconds not ms');
122
- });
123
- });
124
-
125
- describe('resolveToYouTube', () => {
126
- it('should resolve Spotify track to YouTube ID', async function() {
127
- if (!hasCredentials) this.skip();
128
-
129
- const youtubeId = await spotify.resolveToYouTube('4uLU6hMCjMI75M1A2tKUQC', mockConfig);
130
-
131
- assert(youtubeId, 'Should return YouTube ID');
132
- assert(typeof youtubeId === 'string', 'Should be string');
133
- assert(youtubeId.length === 11, 'YouTube IDs are 11 characters');
134
- });
135
-
136
- it('should cache resolved IDs', async function() {
137
- if (!hasCredentials) this.skip();
138
-
139
- const trackId = '4uLU6hMCjMI75M1A2tKUQC';
140
-
141
- const start1 = Date.now();
142
- const id1 = await spotify.resolveToYouTube(trackId, mockConfig);
143
- const time1 = Date.now() - start1;
144
-
145
- const start2 = Date.now();
146
- const id2 = await spotify.resolveToYouTube(trackId, mockConfig);
147
- const time2 = Date.now() - start2;
148
-
149
- assert.strictEqual(id1, id2, 'Should return same ID');
150
- assert(time2 < time1 / 2, 'Cached lookup should be faster');
151
- });
152
-
153
- it('should throw for invalid track', async function() {
154
- if (!hasCredentials) this.skip();
155
-
156
- await assert.rejects(
157
- () => spotify.resolveToYouTube('invalidtrack', mockConfig),
158
- /Spotify API error/
159
- );
160
- });
161
- });
162
-
163
- describe('getPlaylist', () => {
164
- it('should return playlist tracks', async function() {
165
- if (!hasCredentials) this.skip();
166
-
167
- const playlist = await spotify.getPlaylist('37i9dQZF1DXcBWIGoYBM5M', mockConfig);
168
-
169
- assert(playlist.id, 'Should have id');
170
- assert(playlist.title, 'Should have title');
171
- assert(playlist.tracks, 'Should have tracks');
172
- assert(Array.isArray(playlist.tracks), 'Tracks should be array');
173
- assert.strictEqual(playlist.source, 'spotify');
174
- });
175
-
176
- it('should throw for invalid playlist', async function() {
177
- if (!hasCredentials) this.skip();
178
-
179
- await assert.rejects(
180
- () => spotify.getPlaylist('invalidplaylist', mockConfig),
181
- /Spotify API error/
182
- );
183
- });
184
- });
185
-
186
- describe('getAlbum', () => {
187
- it('should return album tracks', async function() {
188
- if (!hasCredentials) this.skip();
189
-
190
- const album = await spotify.getAlbum('4aawyAB9vmqN3uQ7FjRGTy', mockConfig);
191
-
192
- assert(album.id, 'Should have id');
193
- assert(album.title, 'Should have title');
194
- assert(album.author, 'Should have author');
195
- assert(album.tracks, 'Should have tracks');
196
- assert(Array.isArray(album.tracks), 'Tracks should be array');
197
- assert.strictEqual(album.source, 'spotify');
198
- });
199
-
200
- it('should throw for invalid album', async function() {
201
- if (!hasCredentials) this.skip();
202
-
203
- await assert.rejects(
204
- () => spotify.getAlbum('invalidalbum', mockConfig),
205
- /Spotify API error/
206
- );
207
- });
208
- });
209
-
210
- describe('getRecommendations', () => {
211
- it('should return recommended tracks', async function() {
212
- if (!hasCredentials) this.skip();
213
-
214
- const recs = await spotify.getRecommendations('4uLU6hMCjMI75M1A2tKUQC', 5, mockConfig);
215
-
216
- assert(recs.tracks, 'Should have tracks');
217
- assert(Array.isArray(recs.tracks), 'Tracks should be array');
218
- assert.strictEqual(recs.source, 'spotify');
219
- });
220
-
221
- it('should mark tracks as autoplay', async function() {
222
- if (!hasCredentials) this.skip();
223
-
224
- const recs = await spotify.getRecommendations('4uLU6hMCjMI75M1A2tKUQC', 3, mockConfig);
225
-
226
- if (recs.tracks.length > 0) {
227
- assert.strictEqual(recs.tracks[0].isAutoplay, true);
228
- }
229
- });
230
-
231
- it('should respect limit', async function() {
232
- if (!hasCredentials) this.skip();
233
-
234
- const recs = await spotify.getRecommendations('4uLU6hMCjMI75M1A2tKUQC', 3, mockConfig);
235
- assert(recs.tracks.length <= 3, 'Should not exceed limit');
236
- });
237
- });
238
- });
@@ -1,217 +0,0 @@
1
- const { describe, it, beforeEach, afterEach, mock } = require('node:test');
2
- const assert = require('node:assert');
3
- const { EventEmitter } = require('events');
4
-
5
- const { createStream, StreamController } = require('../src/discord/Stream');
6
-
7
- describe('StreamController', () => {
8
- const mockConfig = {
9
- ytdlpPath: 'yt-dlp',
10
- ffmpegPath: 'ffmpeg',
11
- cookiesPath: null,
12
- ytdlp: {
13
- format: 'bestaudio/best',
14
- additionalArgs: []
15
- },
16
- audio: {
17
- bitrate: '128k',
18
- format: 'opus'
19
- },
20
- sponsorblock: {
21
- enabled: false
22
- }
23
- };
24
-
25
- describe('constructor', () => {
26
- it('should initialize with default values', () => {
27
- const track = { id: 'test123', title: 'Test Track', source: 'youtube' };
28
- const stream = createStream(track, {}, mockConfig);
29
-
30
- assert.strictEqual(stream.track, track);
31
- assert.deepStrictEqual(stream.filters, {});
32
- assert.strictEqual(stream.destroyed, false);
33
- assert.strictEqual(stream.ytdlp, null);
34
- assert.strictEqual(stream.ffmpeg, null);
35
- });
36
-
37
- it('should accept filters', () => {
38
- const track = { id: 'test123', title: 'Test Track', source: 'youtube' };
39
- const filters = { bass: 10, speed: 1.25 };
40
- const stream = createStream(track, filters, mockConfig);
41
-
42
- assert.deepStrictEqual(stream.filters, filters);
43
- });
44
-
45
- it('should handle null filters', () => {
46
- const track = { id: 'test123', title: 'Test Track', source: 'youtube' };
47
- const stream = createStream(track, null, mockConfig);
48
-
49
- assert.deepStrictEqual(stream.filters, {});
50
- });
51
- });
52
-
53
- describe('destroy', () => {
54
- it('should set destroyed flag', () => {
55
- const track = { id: 'test123', title: 'Test Track', source: 'youtube' };
56
- const stream = createStream(track, {}, mockConfig);
57
-
58
- stream.destroy();
59
-
60
- assert.strictEqual(stream.destroyed, true);
61
- });
62
-
63
- it('should be idempotent', () => {
64
- const track = { id: 'test123', title: 'Test Track', source: 'youtube' };
65
- const stream = createStream(track, {}, mockConfig);
66
-
67
- stream.destroy();
68
- stream.destroy();
69
- stream.destroy();
70
-
71
- assert.strictEqual(stream.destroyed, true);
72
- });
73
-
74
- it('should nullify resources', () => {
75
- const track = { id: 'test123', title: 'Test Track', source: 'youtube' };
76
- const stream = createStream(track, {}, mockConfig);
77
-
78
- stream.destroy();
79
-
80
- assert.strictEqual(stream.ytdlp, null);
81
- assert.strictEqual(stream.ffmpeg, null);
82
- assert.strictEqual(stream.resource, null);
83
- });
84
- });
85
-
86
- describe('create - edge cases', () => {
87
- it('should throw if already destroyed', async () => {
88
- const track = { id: 'test123', title: 'Test Track', source: 'youtube' };
89
- const stream = createStream(track, {}, mockConfig);
90
-
91
- stream.destroy();
92
-
93
- await assert.rejects(
94
- () => stream.create(),
95
- { message: 'Stream already destroyed' }
96
- );
97
- });
98
-
99
- it('should throw for invalid track ID', async () => {
100
- const track = { id: undefined, title: 'Test Track', source: 'youtube' };
101
- const stream = createStream(track, {}, mockConfig);
102
-
103
- await assert.rejects(
104
- () => stream.create(),
105
- /Invalid track ID/
106
- );
107
- });
108
-
109
- it('should throw for null track ID', async () => {
110
- const track = { id: null, title: 'Test Track', source: 'youtube' };
111
- const stream = createStream(track, {}, mockConfig);
112
-
113
- await assert.rejects(
114
- () => stream.create(),
115
- /Invalid track ID/
116
- );
117
- });
118
-
119
- it('should throw for "undefined" string track ID', async () => {
120
- const track = { id: 'undefined', title: 'Test Track', source: 'youtube' };
121
- const stream = createStream(track, {}, mockConfig);
122
-
123
- await assert.rejects(
124
- () => stream.create(),
125
- /Invalid track ID/
126
- );
127
- });
128
- });
129
-
130
- describe('source detection', () => {
131
- it('should handle youtube source', () => {
132
- const track = { id: 'dQw4w9WgXcQ', title: 'Test', source: 'youtube' };
133
- const stream = createStream(track, {}, mockConfig);
134
- assert.strictEqual(stream.track.source, 'youtube');
135
- });
136
-
137
- it('should handle spotify source', () => {
138
- const track = { id: '4uLU6hMCjMI75M1A2tKUQC', title: 'Test', source: 'spotify' };
139
- const stream = createStream(track, {}, mockConfig);
140
- assert.strictEqual(stream.track.source, 'spotify');
141
- });
142
-
143
- it('should handle soundcloud source', () => {
144
- const track = { id: '123456', title: 'Test', source: 'soundcloud', uri: 'https://soundcloud.com/test/track' };
145
- const stream = createStream(track, {}, mockConfig);
146
- assert.strictEqual(stream.track.source, 'soundcloud');
147
- });
148
-
149
- it('should default to youtube for unknown source', () => {
150
- const track = { id: 'test123', title: 'Test' };
151
- const stream = createStream(track, {}, mockConfig);
152
- assert.strictEqual(stream.track.source || 'youtube', 'youtube');
153
- });
154
- });
155
-
156
- describe('metrics tracking', () => {
157
- it('should initialize metrics to zero', () => {
158
- const track = { id: 'test123', title: 'Test', source: 'youtube' };
159
- const stream = createStream(track, {}, mockConfig);
160
-
161
- assert.deepStrictEqual(stream.metrics, {
162
- metadata: 0,
163
- spawn: 0,
164
- firstByte: 0,
165
- total: 0
166
- });
167
- });
168
- });
169
-
170
- describe('resolved ID handling', () => {
171
- it('should use _resolvedId if available', () => {
172
- const track = {
173
- id: 'spotify123',
174
- title: 'Test',
175
- source: 'spotify',
176
- _resolvedId: 'youtube456'
177
- };
178
- const stream = createStream(track, {}, mockConfig);
179
- assert.strictEqual(stream.track._resolvedId, 'youtube456');
180
- });
181
- });
182
-
183
- describe('live stream handling', () => {
184
- it('should detect live stream from isLive flag', () => {
185
- const track = { id: 'live123', title: 'Live Stream', source: 'youtube', isLive: true };
186
- const stream = createStream(track, {}, mockConfig);
187
- assert.strictEqual(stream.track.isLive, true);
188
- });
189
-
190
- it('should detect live stream from zero duration', () => {
191
- const track = { id: 'live123', title: 'Live Stream', source: 'youtube', duration: 0 };
192
- const stream = createStream(track, {}, mockConfig);
193
- assert.strictEqual(stream.track.duration, 0);
194
- });
195
- });
196
- });
197
-
198
- describe('createStream factory', () => {
199
- const mockConfig = {
200
- ytdlpPath: 'yt-dlp',
201
- ffmpegPath: 'ffmpeg',
202
- ytdlp: { format: 'bestaudio/best', additionalArgs: [] },
203
- audio: { bitrate: '128k', format: 'opus' }
204
- };
205
-
206
- it('should return a StreamController instance', () => {
207
- const track = { id: 'test', title: 'Test', source: 'youtube' };
208
- const stream = createStream(track, {}, mockConfig);
209
-
210
- assert(stream instanceof StreamController);
211
- });
212
-
213
- it('should handle empty track object', () => {
214
- const stream = createStream({}, {}, mockConfig);
215
- assert(stream instanceof StreamController);
216
- });
217
- });
@@ -1,42 +0,0 @@
1
- const test = require('node:test');
2
- const assert = require('node:assert');
3
- const twitch = require('../src/providers/twitch');
4
- const { loadConfig } = require('../src/config');
5
-
6
- test('twitch provider: getInfo', async (t) => {
7
- const config = loadConfig();
8
-
9
- // We'll use a real URL but it might fail if yt-dlp has issues or network is restricted.
10
- // However, it's better to test the real integration if possible.
11
- const url = 'https://www.twitch.tv/monstercat';
12
-
13
- await t.test('should return info for a twitch stream', async () => {
14
- try {
15
- const info = await twitch.getInfo(url, config);
16
-
17
- assert.strictEqual(info.source, 'twitch');
18
- assert.ok(info.title);
19
- assert.ok(info.author);
20
- assert.strictEqual(info.isLive, true);
21
- } catch (error) {
22
- // If it fails because channel is not live, that's expected for this test URL sometimes
23
- if (error.message.includes('The channel is not currently live')) {
24
- console.warn('Twitch test: channel is offline, skipping validation');
25
- return;
26
- }
27
- // If it fails due to network/yt-dlp issues, we skip it but log it
28
- if (error.message.includes('yt-dlp exited with code') || error.message.includes('ERROR:')) {
29
- console.warn('Twitch test failed (likely network/yt-dlp issue):', error.message);
30
- return;
31
- }
32
- throw error;
33
- }
34
- });
35
-
36
- await t.test('should reject on invalid URL', async () => {
37
- await assert.rejects(
38
- twitch.getInfo('https://not-a-twitch-url.com/abc', config),
39
- { message: /ERROR:|yt-dlp exited with code/ }
40
- );
41
- });
42
- });
@@ -1,60 +0,0 @@
1
- const test = require('node:test');
2
- const assert = require('node:assert');
3
- const {
4
- registerStream,
5
- unregisterStream,
6
- getActiveStreams,
7
- getStreamPosition,
8
- updateStreamFilters,
9
- setEventEmitter
10
- } = require('../src/utils/stream');
11
- const EventEmitter = require('events');
12
-
13
- test('Utils: Stream Management', async (t) => {
14
- const emitter = new EventEmitter();
15
- setEventEmitter(emitter);
16
-
17
- await t.test('should register a stream and emit event', () => {
18
- let eventEmitted = false;
19
- emitter.once('streamStart', (data) => {
20
- assert.strictEqual(data.id, 'test-stream');
21
- eventEmitted = true;
22
- });
23
-
24
- registerStream('test-stream', { source: 'youtube', videoId: 'abc' });
25
-
26
- const active = getActiveStreams();
27
- assert.ok(active.has('test-stream'));
28
- assert.ok(eventEmitted);
29
- });
30
-
31
- await t.test('should update stream filters', () => {
32
- updateStreamFilters('test-stream', { volume: 80 });
33
- const stream = getActiveStreams().get('test-stream');
34
- assert.strictEqual(stream.filters.volume, 80);
35
- });
36
-
37
- await t.test('should get stream position', async () => {
38
- const pos1 = getStreamPosition('test-stream');
39
- assert.ok(typeof pos1 === 'number');
40
-
41
- await new Promise(r => setTimeout(r, 100));
42
-
43
- const pos2 = getStreamPosition('test-stream');
44
- assert.ok(pos2 > pos1);
45
- });
46
-
47
- await t.test('should unregister a stream and emit event', () => {
48
- let eventEmitted = false;
49
- emitter.once('streamEnd', (data) => {
50
- assert.strictEqual(data.id, 'test-stream');
51
- eventEmitted = true;
52
- });
53
-
54
- unregisterStream('test-stream');
55
-
56
- const active = getActiveStreams();
57
- assert.strictEqual(active.has('test-stream'), false);
58
- assert.ok(eventEmitted);
59
- });
60
- });