soundcord 1.0.3 → 2.0.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.
Files changed (55) hide show
  1. package/README.md +530 -62
  2. package/dist/chapters.d.ts +11 -0
  3. package/dist/chapters.d.ts.map +1 -0
  4. package/dist/chapters.js +80 -0
  5. package/dist/chapters.js.map +1 -0
  6. package/dist/deezer.d.ts +41 -0
  7. package/dist/deezer.d.ts.map +1 -0
  8. package/dist/deezer.js +205 -0
  9. package/dist/deezer.js.map +1 -0
  10. package/dist/filters.d.ts +10 -0
  11. package/dist/filters.d.ts.map +1 -0
  12. package/dist/filters.js +190 -0
  13. package/dist/filters.js.map +1 -0
  14. package/dist/index.d.ts +10 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +27 -3
  17. package/dist/index.js.map +1 -1
  18. package/dist/live.d.ts +13 -0
  19. package/dist/live.d.ts.map +1 -0
  20. package/dist/live.js +70 -0
  21. package/dist/live.js.map +1 -0
  22. package/dist/lyrics.d.ts +19 -0
  23. package/dist/lyrics.d.ts.map +1 -0
  24. package/dist/lyrics.js +167 -0
  25. package/dist/lyrics.js.map +1 -0
  26. package/dist/persistence.d.ts +61 -0
  27. package/dist/persistence.d.ts.map +1 -0
  28. package/dist/persistence.js +189 -0
  29. package/dist/persistence.js.map +1 -0
  30. package/dist/radio.d.ts +22 -0
  31. package/dist/radio.d.ts.map +1 -0
  32. package/dist/radio.js +275 -0
  33. package/dist/radio.js.map +1 -0
  34. package/dist/setup.js +88 -24
  35. package/dist/setup.js.map +1 -1
  36. package/dist/soundcloud.d.ts +29 -0
  37. package/dist/soundcloud.d.ts.map +1 -0
  38. package/dist/soundcloud.js +203 -0
  39. package/dist/soundcloud.js.map +1 -0
  40. package/dist/spotify.d.ts +16 -0
  41. package/dist/spotify.d.ts.map +1 -0
  42. package/dist/spotify.js +157 -0
  43. package/dist/spotify.js.map +1 -0
  44. package/dist/types.d.ts +14 -4
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/types.js.map +1 -1
  47. package/dist/utils.d.ts +12 -0
  48. package/dist/utils.d.ts.map +1 -0
  49. package/dist/utils.js +41 -0
  50. package/dist/utils.js.map +1 -0
  51. package/dist/youtube.d.ts +33 -2
  52. package/dist/youtube.d.ts.map +1 -1
  53. package/dist/youtube.js +203 -55
  54. package/dist/youtube.js.map +1 -1
  55. package/package.json +8 -2
package/README.md CHANGED
@@ -1,96 +1,530 @@
1
1
  # soundcord
2
2
 
3
- YouTube audio streaming for Discord bots. Simple API, no bloat.
3
+ [![npm version](https://img.shields.io/npm/v/soundcord.svg)](https://www.npmjs.com/package/soundcord)
4
+ [![npm downloads](https://img.shields.io/npm/dw/soundcord.svg)](https://www.npmjs.com/package/soundcord)
5
+ [![GitHub](https://img.shields.io/github/stars/Shuzzo/soundcord?style=social)](https://github.com/Shuzzo/soundcord)
4
6
 
5
- ## Install
7
+ All-in-one audio streaming library for Discord bots. YouTube, Spotify, SoundCloud, Deezer, Radio, Lyrics, and more.
8
+
9
+ ## Features
10
+
11
+ - **Multi-source**: YouTube, Spotify, SoundCloud, Deezer, Radio, Twitch
12
+ - **Lyrics**: Synced lyrics with timestamps (karaoke mode)
13
+ - **YouTube Chapters**: Navigate long videos
14
+ - **Audio Filters**: Bass boost, nightcore, vaporwave, 8D, and more
15
+ - **Quality Selection**: best, medium, low
16
+ - **Live Streams**: YouTube Live & Twitch
17
+ - **Queue Persistence**: Save/restore queues
18
+ - **Zero ytdl-core**: Uses yt-dlp (more reliable)
19
+ - **TypeScript**: Full type support
20
+
21
+ ## Installation
6
22
 
7
23
  ```bash
8
24
  npm install soundcord
9
25
  ```
10
26
 
11
- ### Dependencies
27
+ ### Setup Dependencies
12
28
 
13
- You need `yt-dlp` and `ffmpeg` installed. Run the setup helper:
29
+ Run the setup helper to install yt-dlp and ffmpeg:
14
30
 
15
31
  ```bash
16
32
  npx soundcord-setup
17
33
  ```
18
34
 
19
- Or install manually:
20
- - [yt-dlp](https://github.com/yt-dlp/yt-dlp)
21
- - [ffmpeg](https://ffmpeg.org/download.html)
35
+ This will:
36
+ - Detect your OS and package manager
37
+ - Install yt-dlp and ffmpeg automatically
38
+ - Add to PATH if needed
22
39
 
23
- ## Usage
40
+ ## Quick Start
24
41
 
25
42
  ```typescript
26
43
  import { YouTube } from 'soundcord';
27
44
 
28
45
  const yt = new YouTube();
29
46
 
30
- // Search videos
31
47
  const results = await yt.search('never gonna give you up');
32
- console.log(results[0].title);
48
+ const stream = await yt.getStream(results[0].id);
49
+ console.log(stream.url);
50
+ ```
51
+
52
+ ## YouTube
33
53
 
34
- // Get video info
54
+ ### Search
55
+
56
+ ```typescript
57
+ const results = await yt.search('lofi hip hop', 5);
58
+
59
+ for (const video of results) {
60
+ console.log(`${video.title} - ${video.author} (${video.duration}s)`);
61
+ }
62
+ ```
63
+
64
+ ### Get Video Info
65
+
66
+ ```typescript
35
67
  const info = await yt.getInfo('dQw4w9WgXcQ');
36
- console.log(info.title, info.duration);
68
+ console.log(info.title, info.author, info.duration);
69
+ ```
37
70
 
38
- // Get audio stream URL (for discord.js voice)
71
+ ### Get Stream URL
72
+
73
+ ```typescript
39
74
  const stream = await yt.getStream('dQw4w9WgXcQ');
40
- console.log(stream.url);
75
+ console.log(stream.url, stream.bitrate);
41
76
  ```
42
77
 
43
- ### With discord.js
78
+ ### Quality Selection
44
79
 
45
80
  ```typescript
46
- import { YouTube } from 'soundcord';
47
- import { createAudioResource, createAudioPlayer, joinVoiceChannel } from '@discordjs/voice';
81
+ const stream = await yt.getStreamWithQuality('dQw4w9WgXcQ', 'best');
82
+ const streamLow = await yt.getStreamWithQuality('dQw4w9WgXcQ', 'low');
48
83
 
49
- const yt = new YouTube();
84
+ const qualities = await yt.getAvailableQualities('dQw4w9WgXcQ');
85
+ console.log(qualities);
86
+ ```
87
+
88
+ ### Playlists
89
+
90
+ ```typescript
91
+ if (yt.isPlaylistUrl(url)) {
92
+ const playlist = await yt.getPlaylist(url, 50);
93
+ console.log(`${playlist.title} - ${playlist.videoCount} videos`);
94
+
95
+ for (const video of playlist.videos) {
96
+ console.log(video.title);
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Chapters
102
+
103
+ ```typescript
104
+ const chapters = await yt.getChapters('dQw4w9WgXcQ');
105
+
106
+ for (const chapter of chapters) {
107
+ console.log(`${chapter.title}: ${chapter.start}s - ${chapter.end}s`);
108
+ }
109
+ ```
110
+
111
+ ### Related Videos (Autoplay)
112
+
113
+ ```typescript
114
+ const related = await yt.getRelated('dQw4w9WgXcQ', 5);
115
+ const nextVideo = related[0];
116
+ ```
117
+
118
+ ### Live Streams
119
+
120
+ ```typescript
121
+ if (await yt.isLive('VIDEO_ID')) {
122
+ const stream = await yt.getLiveStream('VIDEO_ID');
123
+ console.log(stream.url);
124
+ }
125
+ ```
126
+
127
+ ## Spotify
128
+
129
+ Play Spotify tracks, playlists, and albums by searching YouTube.
130
+
131
+ ```typescript
132
+ if (yt.isSpotifyUrl(url)) {
133
+ const result = await yt.playSpotify(url);
134
+
135
+ if (Array.isArray(result)) {
136
+ console.log(`Playlist with ${result.length} tracks`);
137
+ } else {
138
+ console.log(`Track: ${result.title}`);
139
+ }
140
+ }
141
+ ```
142
+
143
+ ### Track
144
+
145
+ ```typescript
146
+ const track = await yt.getSpotifyTrack('https://open.spotify.com/track/...');
147
+ console.log(`${track.artist} - ${track.name}`);
148
+ ```
149
+
150
+ ### Playlist
151
+
152
+ ```typescript
153
+ const tracks = await yt.getSpotifyPlaylist('https://open.spotify.com/playlist/...', 50);
154
+ ```
155
+
156
+ ### Album
157
+
158
+ ```typescript
159
+ const tracks = await yt.getSpotifyAlbum('https://open.spotify.com/album/...');
160
+ ```
161
+
162
+ ## SoundCloud
163
+
164
+ ```typescript
165
+ import { SoundCloud } from 'soundcord';
166
+
167
+ const sc = new SoundCloud();
168
+
169
+ if (sc.isSoundCloudUrl(url)) {
170
+ const track = await sc.getTrack(url);
171
+ console.log(track.title, track.author);
172
+
173
+ const streamUrl = await sc.getStream(track.id);
174
+ }
175
+ ```
176
+
177
+ ### Playlists
178
+
179
+ ```typescript
180
+ if (sc.isSoundCloudPlaylistUrl(url)) {
181
+ const playlist = await sc.getPlaylist(url);
182
+
183
+ for (const track of playlist.tracks) {
184
+ console.log(track.title);
185
+ }
186
+ }
187
+ ```
188
+
189
+ ### Search
190
+
191
+ ```typescript
192
+ const results = await sc.search('chill beats', 10);
193
+ ```
194
+
195
+ ## Deezer
196
+
197
+ ```typescript
198
+ import { Deezer } from 'soundcord';
199
+
200
+ const dz = new Deezer();
201
+
202
+ if (dz.isDeezerUrl(url)) {
203
+ const info = dz.extractDeezerInfo(url);
204
+
205
+ if (info.type === 'track') {
206
+ const track = await dz.getTrack(url);
207
+ console.log(`${track.artist} - ${track.title}`);
208
+ }
209
+
210
+ if (info.type === 'album') {
211
+ const album = await dz.getAlbum(url);
212
+ console.log(`${album.title} - ${album.tracks.length} tracks`);
213
+ }
214
+
215
+ if (info.type === 'playlist') {
216
+ const playlist = await dz.getPlaylist(url);
217
+ console.log(`${playlist.title} - ${playlist.tracks.length} tracks`);
218
+ }
219
+ }
220
+ ```
221
+
222
+ ## Radio
223
+
224
+ 15+ preset stations + worldwide search via radio-browser API.
225
+
226
+ ```typescript
227
+ import { Radio, RadioPresets } from 'soundcord';
228
+
229
+ const radio = new Radio();
230
+ ```
231
+
232
+ ### Presets
233
+
234
+ ```typescript
235
+ const presets = radio.getPresets();
236
+
237
+ for (const station of presets) {
238
+ console.log(`${station.name} - ${station.genre} (${station.country})`);
239
+ }
240
+
241
+ const fip = radio.getPreset('fip');
242
+ console.log(fip.url);
243
+ ```
244
+
245
+ ### Available Presets
246
+
247
+ | Name | Genre | Country |
248
+ |------|-------|---------|
249
+ | `franceinter` | Généraliste | France |
250
+ | `franceinfo` | News | France |
251
+ | `fip` | Eclectic | France |
252
+ | `mouv` | Urban/Hip-Hop | France |
253
+ | `francemusique` | Classical | France |
254
+ | `franceculture` | Culture | France |
255
+ | `skyrock` | Hip-Hop/Rap | France |
256
+ | `funradio` | Dance/Electronic | France |
257
+ | `rtl` | Généraliste | France |
258
+ | `rtl2` | Pop/Rock | France |
259
+ | `bbcradio1` | Pop/Rock | UK |
260
+ | `bbcradio2` | Adult Contemporary | UK |
261
+ | `lofi` | Lofi/Chill | International |
262
+ | `jazz` | Jazz | France |
263
+ | `classique` | Classical | France |
264
+
265
+ ### Search Stations
266
+
267
+ ```typescript
268
+ const stations = await radio.search('jazz', 10);
269
+
270
+ for (const station of stations) {
271
+ console.log(`${station.name} - ${station.url}`);
272
+ }
273
+ ```
274
+
275
+ ### Search by Genre
50
276
 
51
- // Join voice channel
52
- const connection = joinVoiceChannel({
53
- channelId: voiceChannel.id,
54
- guildId: guild.id,
55
- adapterCreator: guild.voiceAdapterCreator,
277
+ ```typescript
278
+ const stations = await radio.searchByGenre('rock', 10);
279
+ ```
280
+
281
+ ### Search by Country
282
+
283
+ ```typescript
284
+ const stations = await radio.searchByCountry('France', 10);
285
+ ```
286
+
287
+ ### Popular Stations
288
+
289
+ ```typescript
290
+ const popular = await radio.getPopular(20);
291
+ ```
292
+
293
+ ## Lyrics
294
+
295
+ Get lyrics with optional synchronized timestamps.
296
+
297
+ ```typescript
298
+ const lyrics = await yt.getLyrics('Artist - Song Title');
299
+
300
+ if (lyrics) {
301
+ console.log(`${lyrics.artist} - ${lyrics.title}`);
302
+ console.log(lyrics.lyrics);
303
+
304
+ if (lyrics.synced) {
305
+ console.log('Synced lyrics available');
306
+ }
307
+ }
308
+ ```
309
+
310
+ ### Synced Lyrics (Karaoke)
311
+
312
+ ```typescript
313
+ import { Lyrics } from 'soundcord';
314
+
315
+ const lyricsEngine = new Lyrics(15000);
316
+ const synced = await lyricsEngine.getSynced('Artist - Song');
317
+
318
+ if (synced) {
319
+ for (const line of synced) {
320
+ console.log(`[${line.time}s] ${line.text}`);
321
+ }
322
+ }
323
+ ```
324
+
325
+ ## Twitch
326
+
327
+ ```typescript
328
+ if (yt.isTwitchUrl(url)) {
329
+ const channel = yt.extractTwitchChannel(url);
330
+ const stream = await yt.getTwitchStream(url);
331
+ console.log(stream.url);
332
+ }
333
+ ```
334
+
335
+ ## Audio Filters
336
+
337
+ 15+ built-in audio filters for ffmpeg.
338
+
339
+ ```typescript
340
+ import { getFilter, getFilterNames, combineFilters } from 'soundcord';
341
+
342
+ const filters = getFilterNames();
343
+ console.log(filters);
344
+
345
+ const bass = getFilter('bass');
346
+ console.log(bass.ffmpegArgs);
347
+ ```
348
+
349
+ ### Available Filters
350
+
351
+ | Filter | Description |
352
+ |--------|-------------|
353
+ | `bass` | Bass boost |
354
+ | `nightcore` | Nightcore effect |
355
+ | `vaporwave` | Vaporwave/slowed |
356
+ | `8d` | 8D audio |
357
+ | `karaoke` | Remove vocals |
358
+ | `treble` | Treble boost |
359
+ | `echo` | Echo effect |
360
+ | `flanger` | Flanger effect |
361
+ | `phaser` | Phaser effect |
362
+ | `tremolo` | Tremolo effect |
363
+ | `vibrato` | Vibrato effect |
364
+ | `reverse` | Reverse audio |
365
+ | `pitch_up` | Higher pitch |
366
+ | `pitch_down` | Lower pitch |
367
+ | `speed` | 1.25x speed |
368
+
369
+ ### Using with FFmpeg
370
+
371
+ ```typescript
372
+ const filter = getFilter('bass');
373
+ const ffmpegArgs = ['-i', streamUrl, ...filter.ffmpegArgs, '-f', 's16le', '-'];
374
+ ```
375
+
376
+ ### Combine Filters
377
+
378
+ ```typescript
379
+ const combined = combineFilters(['bass', 'nightcore']);
380
+ console.log(combined);
381
+ ```
382
+
383
+ ## Queue Persistence
384
+
385
+ Save and restore queues across bot restarts.
386
+
387
+ ```typescript
388
+ import { QueuePersistence, BookmarkManager } from 'soundcord';
389
+
390
+ const persistence = new QueuePersistence('./data/queues');
391
+ const bookmarks = new BookmarkManager('./data/bookmarks');
392
+ ```
393
+
394
+ ### Save Queue
395
+
396
+ ```typescript
397
+ await persistence.save({
398
+ guildId: '123456789',
399
+ songs: [
400
+ { id: 'dQw4w9WgXcQ', title: 'Never Gonna Give You Up', author: 'Rick Astley', duration: 213 }
401
+ ],
402
+ currentIndex: 0,
403
+ position: 45,
404
+ volume: 100,
405
+ loop: 'off'
56
406
  });
407
+ ```
57
408
 
58
- // Play audio
59
- const { url } = await yt.getStream('dQw4w9WgXcQ');
60
- const resource = createAudioResource(url);
61
- const player = createAudioPlayer();
409
+ ### Load Queue
62
410
 
63
- player.play(resource);
64
- connection.subscribe(player);
411
+ ```typescript
412
+ const data = await persistence.load('123456789');
413
+
414
+ if (data) {
415
+ console.log(`Restored ${data.songs.length} songs`);
416
+ }
65
417
  ```
66
418
 
67
- ## API
419
+ ### Bookmarks
68
420
 
69
- ### `new YouTube(options?)`
421
+ ```typescript
422
+ await bookmarks.add('123456789', {
423
+ name: 'My Favorite',
424
+ videoId: 'dQw4w9WgXcQ',
425
+ title: 'Never Gonna Give You Up',
426
+ timestamp: 45
427
+ });
70
428
 
71
- | Option | Type | Default | Description |
72
- |--------|------|---------|-------------|
73
- | `timeout` | number | 15000 | Request timeout in ms |
74
- | `minDuration` | number | 0 | Min video duration to include in search |
75
- | `cacheTTL` | number | 300000 | Stream URL cache duration (5min) |
76
- | `ytdlpPath` | string | auto | Custom path to yt-dlp binary |
429
+ const userBookmarks = await bookmarks.list('123456789');
430
+ ```
77
431
 
78
- ### Methods
432
+ ## Discord.js Integration
79
433
 
80
- #### `search(query, limit?): Promise<SearchResult[]>`
81
- Search YouTube videos. Returns up to `limit` results (default 10).
434
+ ```typescript
435
+ import { YouTube } from 'soundcord';
436
+ import {
437
+ createAudioPlayer,
438
+ createAudioResource,
439
+ joinVoiceChannel,
440
+ StreamType
441
+ } from '@discordjs/voice';
442
+ import { spawn } from 'child_process';
82
443
 
83
- #### `getInfo(urlOrId): Promise<VideoInfo>`
84
- Get video metadata by URL or ID.
444
+ const yt = new YouTube();
85
445
 
86
- #### `getStream(urlOrId): Promise<StreamInfo>`
87
- Get audio stream URL for playback.
446
+ async function play(voiceChannel, query) {
447
+ const connection = joinVoiceChannel({
448
+ channelId: voiceChannel.id,
449
+ guildId: voiceChannel.guild.id,
450
+ adapterCreator: voiceChannel.guild.voiceAdapterCreator,
451
+ selfDeaf: true
452
+ });
453
+
454
+ const results = await yt.search(query, 1);
455
+ const stream = await yt.getStream(results[0].id);
456
+
457
+ const ffmpeg = spawn('ffmpeg', [
458
+ '-reconnect', '1',
459
+ '-reconnect_streamed', '1',
460
+ '-reconnect_delay_max', '5',
461
+ '-i', stream.url,
462
+ '-vn',
463
+ '-f', 's16le',
464
+ '-ar', '48000',
465
+ '-ac', '2',
466
+ '-'
467
+ ]);
468
+
469
+ const resource = createAudioResource(ffmpeg.stdout, {
470
+ inputType: StreamType.Raw
471
+ });
472
+
473
+ const player = createAudioPlayer();
474
+ player.play(resource);
475
+ connection.subscribe(player);
476
+
477
+ return results[0];
478
+ }
479
+ ```
88
480
 
89
- #### `isValidUrl(url): boolean`
90
- Check if string is a valid YouTube URL.
481
+ ## Configuration
91
482
 
92
- #### `extractId(url): string | null`
93
- Extract video ID from URL.
483
+ ```typescript
484
+ const yt = new YouTube({
485
+ timeout: 15000,
486
+ minDuration: 0,
487
+ cacheTTL: 30000,
488
+ ytdlpPath: '/custom/path/to/yt-dlp'
489
+ });
490
+ ```
491
+
492
+ | Option | Type | Default | Description |
493
+ |--------|------|---------|-------------|
494
+ | `timeout` | number | 15000 | Request timeout (ms) |
495
+ | `minDuration` | number | 0 | Min video duration for search |
496
+ | `cacheTTL` | number | 30000 | Stream URL cache (ms) |
497
+ | `ytdlpPath` | string | auto | Custom yt-dlp path |
498
+
499
+ ## Error Handling
500
+
501
+ ```typescript
502
+ import { YouTube, YouTubeError } from 'soundcord';
503
+
504
+ try {
505
+ await yt.getStream('invalid');
506
+ } catch (err) {
507
+ if (err instanceof YouTubeError) {
508
+ switch (err.code) {
509
+ case 'NOT_FOUND':
510
+ console.log('Video not found');
511
+ break;
512
+ case 'NETWORK_ERROR':
513
+ console.log('Network error');
514
+ break;
515
+ case 'YTDLP_MISSING':
516
+ console.log('yt-dlp not installed');
517
+ break;
518
+ case 'RATE_LIMITED':
519
+ console.log('Rate limited by YouTube');
520
+ break;
521
+ case 'PARSE_ERROR':
522
+ console.log('Failed to parse response');
523
+ break;
524
+ }
525
+ }
526
+ }
527
+ ```
94
528
 
95
529
  ## Types
96
530
 
@@ -117,35 +551,69 @@ interface StreamInfo {
117
551
  mimeType: string;
118
552
  bitrate: number;
119
553
  }
120
- ```
121
554
 
122
- ## Error Handling
555
+ interface PlaylistInfo {
556
+ id: string;
557
+ title: string;
558
+ author: string;
559
+ videoCount: number;
560
+ videos: SearchResult[];
561
+ }
123
562
 
124
- ```typescript
125
- import { YouTube, YouTubeError } from 'soundcord';
563
+ interface SpotifyTrack {
564
+ name: string;
565
+ artist: string;
566
+ album: string;
567
+ duration: number;
568
+ url: string;
569
+ }
126
570
 
127
- const yt = new YouTube();
571
+ interface RadioStation {
572
+ name: string;
573
+ url: string;
574
+ genre?: string;
575
+ country?: string;
576
+ bitrate?: number;
577
+ }
128
578
 
129
- try {
130
- await yt.getStream('invalid');
131
- } catch (err) {
132
- if (err instanceof YouTubeError) {
133
- console.log(err.code); // 'NOT_FOUND' | 'NETWORK_ERROR' | 'YTDLP_MISSING' | etc.
134
- }
579
+ interface LyricsResult {
580
+ title: string;
581
+ artist: string;
582
+ lyrics: string;
583
+ synced?: string;
584
+ }
585
+
586
+ interface Chapter {
587
+ title: string;
588
+ start: number;
589
+ end: number;
135
590
  }
136
591
  ```
137
592
 
593
+ ## Comparison
594
+
595
+ | Feature | soundcord | discord-player | distube |
596
+ |---------|-----------|----------------|---------|
597
+ | No ytdl-core | ✅ | ❌ | ❌ |
598
+ | Radio streams | ✅ | ❌ | ❌ |
599
+ | Synced lyrics | ✅ | ❌ | Plugin |
600
+ | YouTube chapters | ✅ | ❌ | ❌ |
601
+ | Audio normalization | ✅ | ❌ | ❌ |
602
+ | Quality selection | ✅ | ❌ | ❌ |
603
+ | Queue persistence | ✅ | ❌ | ❌ |
604
+ | Bundle size | ~50KB | ~500KB+ | ~300KB+ |
605
+
138
606
  ## Disclaimer
139
607
 
140
- This library is provided for **educational and personal use only**.
608
+ This library is for **educational and personal use only**.
141
609
 
142
610
  By using this software, you agree to:
143
611
  - Comply with YouTube's [Terms of Service](https://www.youtube.com/t/terms)
612
+ - Comply with Spotify's [Terms of Service](https://www.spotify.com/legal/end-user-agreement/)
144
613
  - Use the library responsibly and ethically
145
- - Not use it for commercial purposes without proper licensing
146
614
  - Respect copyright and intellectual property rights
147
615
 
148
- The authors are not responsible for any misuse of this library or any violations of third-party terms of service. Downloading or streaming copyrighted content without permission may be illegal in your jurisdiction.
616
+ The authors are not responsible for any misuse of this library.
149
617
 
150
618
  ## License
151
619
 
@@ -0,0 +1,11 @@
1
+ export interface Chapter {
2
+ title: string;
3
+ start: number;
4
+ end: number;
5
+ }
6
+ export declare class Chapters {
7
+ private timeout;
8
+ constructor(timeout: number);
9
+ get(videoId: string): Promise<Chapter[]>;
10
+ }
11
+ //# sourceMappingURL=chapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chapters.d.ts","sourceRoot":"","sources":["../src/chapters.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,MAAM;IAIrB,GAAG,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;CA8E/C"}