youtube-transcript-plus 1.1.2 → 1.2.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
@@ -169,7 +169,7 @@ fetchTranscript('videoId_or_URL', {
169
169
 
170
170
  ### Error Handling
171
171
 
172
- The library throws specific errors for different failure scenarios. Make sure to handle them appropriately.
172
+ The library throws specific errors for different failure scenarios. Each error includes a `videoId` property for programmatic handling.
173
173
 
174
174
  ```javascript
175
175
  import {
@@ -183,13 +183,13 @@ fetchTranscript('videoId_or_URL')
183
183
  .then(console.log)
184
184
  .catch((error) => {
185
185
  if (error instanceof YoutubeTranscriptVideoUnavailableError) {
186
- console.error('Video is unavailable:', error.message);
186
+ console.error('Video is unavailable:', error.videoId);
187
187
  } else if (error instanceof YoutubeTranscriptDisabledError) {
188
- console.error('Transcripts are disabled for this video:', error.message);
188
+ console.error('Transcripts are disabled:', error.videoId);
189
189
  } else if (error instanceof YoutubeTranscriptNotAvailableError) {
190
- console.error('No transcript available:', error.message);
190
+ console.error('No transcript available:', error.videoId);
191
191
  } else if (error instanceof YoutubeTranscriptNotAvailableLanguageError) {
192
- console.error('Transcript not available in the specified language:', error.message);
192
+ console.error('Language not available:', error.lang, error.availableLangs);
193
193
  } else {
194
194
  console.error('An unexpected error occurred:', error.message);
195
195
  }
@@ -209,6 +209,14 @@ The repository includes several example files in the `example/` directory to dem
209
209
 
210
210
  These examples can be found in the `example/` directory of the repository.
211
211
 
212
+ ### TypeScript Types
213
+
214
+ All types are exported for TypeScript consumers:
215
+
216
+ ```typescript
217
+ import type { TranscriptConfig, TranscriptResponse, FetchParams, CacheStrategy } from 'youtube-transcript-plus';
218
+ ```
219
+
212
220
  ### API
213
221
 
214
222
  ### `fetchTranscript(videoId: string, config?: TranscriptConfig)`
@@ -237,10 +245,11 @@ Returns a `Promise<TranscriptResponse[]>` where each item in the array represent
237
245
 
238
246
  The library throws the following errors:
239
247
 
240
- - **`YoutubeTranscriptVideoUnavailableError`**: The video is unavailable or has been removed.
241
- - **`YoutubeTranscriptDisabledError`**: Transcripts are disabled for the video.
242
- - **`YoutubeTranscriptNotAvailableError`**: No transcript is available for the video.
243
- - **`YoutubeTranscriptNotAvailableLanguageError`**: The transcript is not available in the specified language.
248
+ - **`YoutubeTranscriptVideoUnavailableError`**: The video is unavailable or has been removed. Properties: `videoId`.
249
+ - **`YoutubeTranscriptDisabledError`**: Transcripts are disabled for the video. Properties: `videoId`.
250
+ - **`YoutubeTranscriptNotAvailableError`**: No transcript is available for the video. Properties: `videoId`.
251
+ - **`YoutubeTranscriptNotAvailableLanguageError`**: The transcript is not available in the specified language. Properties: `videoId`, `lang`, `availableLangs`.
252
+ - **`YoutubeTranscriptTooManyRequestError`**: YouTube is rate-limiting requests from your IP.
244
253
  - **`YoutubeTranscriptInvalidVideoIdError`**: The provided video ID or URL is invalid.
245
254
 
246
255
  ## License
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -2,6 +2,7 @@ import { CacheStrategy } from '../types';
2
2
  export declare class FsCache implements CacheStrategy {
3
3
  private cacheDir;
4
4
  private defaultTTL;
5
+ private ready;
5
6
  constructor(cacheDir?: string, defaultTTL?: number);
6
7
  get(key: string): Promise<string | null>;
7
8
  set(key: string, value: string, ttl?: number): Promise<void>;
package/dist/errors.d.ts CHANGED
@@ -2,15 +2,21 @@ export declare class YoutubeTranscriptTooManyRequestError extends Error {
2
2
  constructor();
3
3
  }
4
4
  export declare class YoutubeTranscriptVideoUnavailableError extends Error {
5
+ readonly videoId: string;
5
6
  constructor(videoId: string);
6
7
  }
7
8
  export declare class YoutubeTranscriptDisabledError extends Error {
9
+ readonly videoId: string;
8
10
  constructor(videoId: string);
9
11
  }
10
12
  export declare class YoutubeTranscriptNotAvailableError extends Error {
13
+ readonly videoId: string;
11
14
  constructor(videoId: string);
12
15
  }
13
16
  export declare class YoutubeTranscriptNotAvailableLanguageError extends Error {
17
+ readonly videoId: string;
18
+ readonly lang: string;
19
+ readonly availableLangs: string[];
14
20
  constructor(lang: string, availableLangs: string[], videoId: string);
15
21
  }
16
22
  export declare class YoutubeTranscriptInvalidVideoIdError extends Error {
package/dist/index.d.ts CHANGED
@@ -11,7 +11,7 @@ export declare class YoutubeTranscript {
11
11
  fetchTranscript(videoId: string): Promise<TranscriptResponse[]>;
12
12
  static fetchTranscript(videoId: string, config?: TranscriptConfig): Promise<TranscriptResponse[]>;
13
13
  }
14
- export type { CacheStrategy } from './types';
14
+ export type { CacheStrategy, TranscriptConfig, TranscriptResponse, FetchParams } from './types';
15
15
  export { InMemoryCache, FsCache } from './cache';
16
16
  export * from './errors';
17
17
  export declare const fetchTranscript: typeof YoutubeTranscript.fetchTranscript;
package/dist/types.d.ts CHANGED
@@ -24,5 +24,5 @@ export interface TranscriptResponse {
24
24
  text: string;
25
25
  duration: number;
26
26
  offset: number;
27
- lang?: string;
27
+ lang: string;
28
28
  }
package/dist/utils.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  import { FetchParams } from './types';
2
+ export declare function decodeXmlEntities(text: string): string;
2
3
  export declare function retrieveVideoId(videoId: string): string;
3
4
  export declare function defaultFetch(params: FetchParams): Promise<Response>;
@@ -48,24 +48,30 @@ class YoutubeTranscriptVideoUnavailableError extends Error {
48
48
  constructor(videoId) {
49
49
  super(`The video with ID "${videoId}" is no longer available or has been removed. Please check the video URL or ID and try again.`);
50
50
  this.name = 'YoutubeTranscriptVideoUnavailableError';
51
+ this.videoId = videoId;
51
52
  }
52
53
  }
53
54
  class YoutubeTranscriptDisabledError extends Error {
54
55
  constructor(videoId) {
55
56
  super(`Transcripts are disabled for the video with ID "${videoId}". This may be due to the video owner disabling captions or the video not supporting transcripts.`);
56
57
  this.name = 'YoutubeTranscriptDisabledError';
58
+ this.videoId = videoId;
57
59
  }
58
60
  }
59
61
  class YoutubeTranscriptNotAvailableError extends Error {
60
62
  constructor(videoId) {
61
63
  super(`No transcripts are available for the video with ID "${videoId}". This may be because the video does not have captions or the captions are not accessible.`);
62
64
  this.name = 'YoutubeTranscriptNotAvailableError';
65
+ this.videoId = videoId;
63
66
  }
64
67
  }
65
68
  class YoutubeTranscriptNotAvailableLanguageError extends Error {
66
69
  constructor(lang, availableLangs, videoId) {
67
70
  super(`No transcripts are available in "${lang}" for the video with ID "${videoId}". Available languages: ${availableLangs.join(', ')}. Please try a different language.`);
68
71
  this.name = 'YoutubeTranscriptNotAvailableLanguageError';
72
+ this.videoId = videoId;
73
+ this.lang = lang;
74
+ this.availableLangs = availableLangs;
69
75
  }
70
76
  }
71
77
  class YoutubeTranscriptInvalidVideoIdError extends Error {
@@ -75,8 +81,21 @@ class YoutubeTranscriptInvalidVideoIdError extends Error {
75
81
  }
76
82
  }
77
83
 
84
+ const RE_VIDEO_ID = /^[a-zA-Z0-9_-]{11}$/;
85
+ const XML_ENTITIES = {
86
+ '&amp;': '&',
87
+ '&lt;': '<',
88
+ '&gt;': '>',
89
+ '&quot;': '"',
90
+ '&#39;': "'",
91
+ '&apos;': "'",
92
+ };
93
+ const RE_XML_ENTITY = /&(?:amp|lt|gt|quot|apos|#39);/g;
94
+ function decodeXmlEntities(text) {
95
+ return text.replace(RE_XML_ENTITY, (match) => { var _a; return (_a = XML_ENTITIES[match]) !== null && _a !== void 0 ? _a : match; });
96
+ }
78
97
  function retrieveVideoId(videoId) {
79
- if (videoId.length === 11) {
98
+ if (RE_VIDEO_ID.test(videoId)) {
80
99
  return videoId;
81
100
  }
82
101
  const matchId = videoId.match(RE_YOUTUBE);
@@ -100,15 +119,19 @@ function defaultFetch(params) {
100
119
  });
101
120
  }
102
121
 
122
+ function sanitizeKey(key) {
123
+ return key.replace(/[^a-zA-Z0-9_-]/g, '_');
124
+ }
103
125
  class FsCache {
104
126
  constructor(cacheDir = './cache', defaultTTL = DEFAULT_CACHE_TTL) {
105
127
  this.cacheDir = cacheDir;
106
128
  this.defaultTTL = defaultTTL;
107
- fs.mkdir(cacheDir, { recursive: true }).catch(() => { });
129
+ this.ready = fs.mkdir(cacheDir, { recursive: true }).then(() => { });
108
130
  }
109
131
  get(key) {
110
132
  return __awaiter(this, void 0, void 0, function* () {
111
- const filePath = path.join(this.cacheDir, key);
133
+ yield this.ready;
134
+ const filePath = path.join(this.cacheDir, sanitizeKey(key));
112
135
  try {
113
136
  const data = yield fs.readFile(filePath, 'utf-8');
114
137
  const { value, expires } = JSON.parse(data);
@@ -123,7 +146,8 @@ class FsCache {
123
146
  }
124
147
  set(key, value, ttl) {
125
148
  return __awaiter(this, void 0, void 0, function* () {
126
- const filePath = path.join(this.cacheDir, key);
149
+ yield this.ready;
150
+ const filePath = path.join(this.cacheDir, sanitizeKey(key));
127
151
  const expires = Date.now() + (ttl !== null && ttl !== void 0 ? ttl : this.defaultTTL);
128
152
  yield fs.writeFile(filePath, JSON.stringify({ value, expires }), 'utf-8');
129
153
  });
@@ -209,7 +233,7 @@ class YoutubeTranscript {
209
233
  }
210
234
  const apiKey = apiKeyMatch[1];
211
235
  // 3) Call Innertube player as ANDROID client to retrieve captionTracks
212
- const playerEndpoint = `https://www.youtube.com/youtubei/v1/player?key=${apiKey}`;
236
+ const playerEndpoint = `${protocol}://www.youtube.com/youtubei/v1/player?key=${apiKey}`;
213
237
  const playerBody = {
214
238
  context: {
215
239
  client: {
@@ -262,7 +286,7 @@ class YoutubeTranscript {
262
286
  if (!transcriptURL) {
263
287
  throw new YoutubeTranscriptNotAvailableError(identifier);
264
288
  }
265
- transcriptURL = transcriptURL.replace(/&fmt=[^&]+$/, '');
289
+ transcriptURL = transcriptURL.replace(/&fmt=[^&]+/, '');
266
290
  if ((_m = this.config) === null || _m === void 0 ? void 0 : _m.disableHttps) {
267
291
  transcriptURL = transcriptURL.replace(/^https:\/\//, 'http://');
268
292
  }
@@ -281,7 +305,7 @@ class YoutubeTranscript {
281
305
  // 6) Parse XML into the existing TranscriptResponse shape
282
306
  const results = [...transcriptBody.matchAll(RE_XML_TRANSCRIPT)];
283
307
  const transcript = results.map((m) => ({
284
- text: m[3],
308
+ text: decodeXmlEntities(m[3]),
285
309
  duration: parseFloat(m[2]),
286
310
  offset: parseFloat(m[1]),
287
311
  lang: lang !== null && lang !== void 0 ? lang : selectedTrack.languageCode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "youtube-transcript-plus",
3
- "version": "1.1.2",
3
+ "version": "1.2.0",
4
4
  "description": "Fetch transcript from a YouTube video",
5
5
  "type": "module",
6
6
  "main": "dist/youtube-transcript-plus.js",
@@ -43,7 +43,6 @@
43
43
  "nock": "^14.0.10",
44
44
  "prettier": "^3.6.2",
45
45
  "rollup": "^4.46.4",
46
- "rollup-plugin-typescript": "^1.0.1",
47
46
  "rollup-plugin-typescript2": "^0.36.0",
48
47
  "ts-jest": "^29.4.1",
49
48
  "tslib": "^2.8.1",