youtube-transcript-plus 1.0.4 → 1.1.1
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 +23 -3
- package/dist/constants.d.ts +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/types.d.ts +11 -10
- package/dist/utils.d.ts +2 -5
- package/dist/youtube-transcript-plus.js +133 -73
- package/package.json +12 -9
package/README.md
CHANGED
|
@@ -58,19 +58,37 @@ fetchTranscript('videoId_or_URL', {
|
|
|
58
58
|
|
|
59
59
|
### Custom Fetch Functions
|
|
60
60
|
|
|
61
|
-
You can inject custom `videoFetch` and `transcriptFetch` functions to modify the fetch behavior, such as using a proxy or custom headers.
|
|
61
|
+
You can inject custom `videoFetch`, `playerFetch`, and `transcriptFetch` functions to modify the fetch behavior, such as using a proxy or custom headers. The library makes three types of HTTP requests:
|
|
62
|
+
|
|
63
|
+
1. **`videoFetch`**: Fetches the YouTube video page (GET request)
|
|
64
|
+
2. **`playerFetch`**: Calls YouTube's Innertube API to get caption tracks (POST request)
|
|
65
|
+
3. **`transcriptFetch`**: Downloads the actual transcript data (GET request)
|
|
62
66
|
|
|
63
67
|
```javascript
|
|
64
68
|
fetchTranscript('videoId_or_URL', {
|
|
65
69
|
videoFetch: async ({ url, lang, userAgent }) => {
|
|
70
|
+
// Custom logic for video page fetch (GET)
|
|
71
|
+
return fetch(`https://my-proxy-server.com/?url=${encodeURIComponent(url)}`, {
|
|
72
|
+
headers: {
|
|
73
|
+
...(lang && { 'Accept-Language': lang }),
|
|
74
|
+
'User-Agent': userAgent,
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
},
|
|
78
|
+
playerFetch: async ({ url, method, body, headers, lang, userAgent }) => {
|
|
79
|
+
// Custom logic for Innertube API call (POST)
|
|
66
80
|
return fetch(`https://my-proxy-server.com/?url=${encodeURIComponent(url)}`, {
|
|
81
|
+
method,
|
|
67
82
|
headers: {
|
|
68
83
|
...(lang && { 'Accept-Language': lang }),
|
|
69
84
|
'User-Agent': userAgent,
|
|
85
|
+
...headers,
|
|
70
86
|
},
|
|
87
|
+
body,
|
|
71
88
|
});
|
|
72
89
|
},
|
|
73
90
|
transcriptFetch: async ({ url, lang, userAgent }) => {
|
|
91
|
+
// Custom logic for transcript data fetch (GET)
|
|
74
92
|
return fetch(`https://my-proxy-server.com/?url=${encodeURIComponent(url)}`, {
|
|
75
93
|
headers: {
|
|
76
94
|
...(lang && { 'Accept-Language': lang }),
|
|
@@ -187,6 +205,7 @@ The repository includes several example files in the `example/` directory to dem
|
|
|
187
205
|
3. **`fs-caching-usage.js`**: Demonstrates how to use the `FsCache` to cache transcripts on the file system with a 1-day TTL.
|
|
188
206
|
4. **`language-usage.js`**: Shows how to fetch a transcript in a specific language (e.g., French).
|
|
189
207
|
5. **`proxy-usage.js`**: Demonstrates how to use a proxy server to fetch transcripts, which can be useful for bypassing rate limits or accessing restricted content.
|
|
208
|
+
6. **`custom-fetch-usage.js`**: Shows how to use all three custom fetch functions (`videoFetch`, `playerFetch`, `transcriptFetch`) with logging and custom headers.
|
|
190
209
|
|
|
191
210
|
These examples can be found in the `example/` directory of the repository.
|
|
192
211
|
|
|
@@ -203,8 +222,9 @@ Fetches the transcript for a YouTube video.
|
|
|
203
222
|
- **`cache`**: Custom caching strategy.
|
|
204
223
|
- **`cacheTTL`**: Time-to-live for cache entries in milliseconds.
|
|
205
224
|
- **`disableHttps`**: Set to `true` to use HTTP instead of HTTPS for YouTube requests.
|
|
206
|
-
- **`videoFetch`**: Custom fetch function for the video page request.
|
|
207
|
-
- **`
|
|
225
|
+
- **`videoFetch`**: Custom fetch function for the video page request (GET).
|
|
226
|
+
- **`playerFetch`**: Custom fetch function for the YouTube Innertube API request (POST).
|
|
227
|
+
- **`transcriptFetch`**: Custom fetch function for the transcript data request (GET).
|
|
208
228
|
|
|
209
229
|
Returns a `Promise<TranscriptResponse[]>` where each item in the array represents a transcript segment with the following properties:
|
|
210
230
|
|
package/dist/constants.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export declare const DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
2
2
|
export declare const RE_YOUTUBE: RegExp;
|
|
3
3
|
export declare const RE_XML_TRANSCRIPT: RegExp;
|
|
4
|
+
export declare const DEFAULT_CACHE_TTL = 3600000;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { TranscriptConfig, TranscriptResponse } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Implementation notes:
|
|
4
|
+
* - Keeps the public surface identical.
|
|
5
|
+
* - Internals now use YouTube Innertube `player` to discover captionTracks instead of scraping the watch HTML.
|
|
6
|
+
* - Honors `lang`, custom fetch hooks (`videoFetch`, `transcriptFetch`), and optional cache strategy.
|
|
7
|
+
*/
|
|
2
8
|
export declare class YoutubeTranscript {
|
|
3
9
|
private config?;
|
|
4
|
-
constructor(config?: TranscriptConfig
|
|
5
|
-
cacheTTL?: number;
|
|
6
|
-
});
|
|
10
|
+
constructor(config?: TranscriptConfig);
|
|
7
11
|
fetchTranscript(videoId: string): Promise<TranscriptResponse[]>;
|
|
8
12
|
static fetchTranscript(videoId: string, config?: TranscriptConfig): Promise<TranscriptResponse[]>;
|
|
9
13
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -2,22 +2,23 @@ export interface CacheStrategy {
|
|
|
2
2
|
get(key: string): Promise<string | null>;
|
|
3
3
|
set(key: string, value: string, ttl?: number): Promise<void>;
|
|
4
4
|
}
|
|
5
|
+
export interface FetchParams {
|
|
6
|
+
url: string;
|
|
7
|
+
lang?: string;
|
|
8
|
+
userAgent?: string;
|
|
9
|
+
method?: 'GET' | 'POST';
|
|
10
|
+
body?: string;
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
}
|
|
5
13
|
export interface TranscriptConfig {
|
|
6
14
|
lang?: string;
|
|
7
15
|
userAgent?: string;
|
|
8
16
|
cache?: CacheStrategy;
|
|
9
17
|
cacheTTL?: number;
|
|
10
18
|
disableHttps?: boolean;
|
|
11
|
-
videoFetch?: (params:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
userAgent?: string;
|
|
15
|
-
}) => Promise<Response>;
|
|
16
|
-
transcriptFetch?: (params: {
|
|
17
|
-
url: string;
|
|
18
|
-
lang?: string;
|
|
19
|
-
userAgent?: string;
|
|
20
|
-
}) => Promise<Response>;
|
|
19
|
+
videoFetch?: (params: FetchParams) => Promise<Response>;
|
|
20
|
+
transcriptFetch?: (params: FetchParams) => Promise<Response>;
|
|
21
|
+
playerFetch?: (params: FetchParams) => Promise<Response>;
|
|
21
22
|
}
|
|
22
23
|
export interface TranscriptResponse {
|
|
23
24
|
text: string;
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
+
import { FetchParams } from './types';
|
|
1
2
|
export declare function retrieveVideoId(videoId: string): string;
|
|
2
|
-
export declare function defaultFetch(
|
|
3
|
-
url: string;
|
|
4
|
-
lang?: string;
|
|
5
|
-
userAgent?: string;
|
|
6
|
-
}): Promise<Response>;
|
|
3
|
+
export declare function defaultFetch(params: FetchParams): Promise<Response>;
|
|
@@ -36,6 +36,7 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
36
36
|
const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
|
|
37
37
|
const RE_YOUTUBE = /(?:v=|\/|v\/|embed\/|watch\?.*v=|youtu\.be\/|\/v\/|e\/|watch\?.*vi?=|\/embed\/|\/v\/|vi?\/|watch\?.*vi?=|youtu\.be\/|\/vi?\/|\/e\/)([a-zA-Z0-9_-]{11})/i;
|
|
38
38
|
const RE_XML_TRANSCRIPT = /<text start="([^"]*)" dur="([^"]*)">([^<]*)<\/text>/g;
|
|
39
|
+
const DEFAULT_CACHE_TTL = 3600000; // 1 hour in milliseconds
|
|
39
40
|
|
|
40
41
|
class YoutubeTranscriptTooManyRequestError extends Error {
|
|
41
42
|
constructor() {
|
|
@@ -84,16 +85,23 @@ function retrieveVideoId(videoId) {
|
|
|
84
85
|
}
|
|
85
86
|
throw new YoutubeTranscriptInvalidVideoIdError();
|
|
86
87
|
}
|
|
87
|
-
function defaultFetch(
|
|
88
|
-
return __awaiter(this,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
88
|
+
function defaultFetch(params) {
|
|
89
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
90
|
+
const { url, lang, userAgent, method = 'GET', body, headers = {} } = params;
|
|
91
|
+
const fetchHeaders = Object.assign(Object.assign({ 'User-Agent': userAgent || DEFAULT_USER_AGENT }, (lang && { 'Accept-Language': lang })), headers);
|
|
92
|
+
const fetchOptions = {
|
|
93
|
+
method,
|
|
94
|
+
headers: fetchHeaders,
|
|
95
|
+
};
|
|
96
|
+
if (body && method === 'POST') {
|
|
97
|
+
fetchOptions.body = body;
|
|
98
|
+
}
|
|
99
|
+
return fetch(url, fetchOptions);
|
|
92
100
|
});
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
class FsCache {
|
|
96
|
-
constructor(cacheDir = './cache', defaultTTL =
|
|
104
|
+
constructor(cacheDir = './cache', defaultTTL = DEFAULT_CACHE_TTL) {
|
|
97
105
|
this.cacheDir = cacheDir;
|
|
98
106
|
this.defaultTTL = defaultTTL;
|
|
99
107
|
fs.mkdir(cacheDir, { recursive: true }).catch(() => { });
|
|
@@ -123,9 +131,8 @@ class FsCache {
|
|
|
123
131
|
}
|
|
124
132
|
|
|
125
133
|
class InMemoryCache {
|
|
126
|
-
constructor(defaultTTL =
|
|
134
|
+
constructor(defaultTTL = DEFAULT_CACHE_TTL) {
|
|
127
135
|
this.cache = new Map();
|
|
128
|
-
// 1 hour default TTL
|
|
129
136
|
this.defaultTTL = defaultTTL;
|
|
130
137
|
}
|
|
131
138
|
get(key) {
|
|
@@ -146,101 +153,154 @@ class InMemoryCache {
|
|
|
146
153
|
}
|
|
147
154
|
}
|
|
148
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Implementation notes:
|
|
158
|
+
* - Keeps the public surface identical.
|
|
159
|
+
* - Internals now use YouTube Innertube `player` to discover captionTracks instead of scraping the watch HTML.
|
|
160
|
+
* - Honors `lang`, custom fetch hooks (`videoFetch`, `transcriptFetch`), and optional cache strategy.
|
|
161
|
+
*/
|
|
149
162
|
class YoutubeTranscript {
|
|
150
163
|
constructor(config) {
|
|
151
164
|
this.config = config;
|
|
152
165
|
}
|
|
153
166
|
fetchTranscript(videoId) {
|
|
154
167
|
return __awaiter(this, void 0, void 0, function* () {
|
|
155
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o
|
|
168
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
156
169
|
const identifier = retrieveVideoId(videoId);
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
const cacheKey = `transcript:${identifier}:${
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
170
|
+
const lang = (_a = this.config) === null || _a === void 0 ? void 0 : _a.lang;
|
|
171
|
+
const userAgent = (_c = (_b = this.config) === null || _b === void 0 ? void 0 : _b.userAgent) !== null && _c !== void 0 ? _c : DEFAULT_USER_AGENT;
|
|
172
|
+
// Cache lookup (if provided)
|
|
173
|
+
const cache = (_d = this.config) === null || _d === void 0 ? void 0 : _d.cache;
|
|
174
|
+
const cacheTTL = (_e = this.config) === null || _e === void 0 ? void 0 : _e.cacheTTL;
|
|
175
|
+
const cacheKey = `yt:transcript:${identifier}:${lang !== null && lang !== void 0 ? lang : ''}`;
|
|
176
|
+
if (cache) {
|
|
177
|
+
const cached = yield cache.get(cacheKey);
|
|
178
|
+
if (cached) {
|
|
179
|
+
try {
|
|
180
|
+
return JSON.parse(cached);
|
|
181
|
+
}
|
|
182
|
+
catch (_p) {
|
|
183
|
+
// ignore parse errors and continue
|
|
184
|
+
}
|
|
168
185
|
}
|
|
169
186
|
}
|
|
187
|
+
// 1) Fetch the watch page to extract an Innertube API key (no interface change)
|
|
188
|
+
// Decide protocol once and reuse
|
|
170
189
|
const protocol = ((_f = this.config) === null || _f === void 0 ? void 0 : _f.disableHttps) ? 'http' : 'https';
|
|
171
|
-
|
|
172
|
-
const videoPageResponse =
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
userAgent,
|
|
176
|
-
});
|
|
190
|
+
const watchUrl = `${protocol}://www.youtube.com/watch?v=${identifier}`;
|
|
191
|
+
const videoPageResponse = ((_g = this.config) === null || _g === void 0 ? void 0 : _g.videoFetch)
|
|
192
|
+
? yield this.config.videoFetch({ url: watchUrl, lang, userAgent })
|
|
193
|
+
: yield defaultFetch({ url: watchUrl, lang, userAgent });
|
|
177
194
|
if (!videoPageResponse.ok) {
|
|
178
195
|
throw new YoutubeTranscriptVideoUnavailableError(identifier);
|
|
179
196
|
}
|
|
180
197
|
const videoPageBody = yield videoPageResponse.text();
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (videoPageBody.includes('class="g-recaptcha"')) {
|
|
185
|
-
throw new YoutubeTranscriptTooManyRequestError();
|
|
186
|
-
}
|
|
187
|
-
if (!videoPageBody.includes('"playabilityStatus":')) {
|
|
188
|
-
throw new YoutubeTranscriptVideoUnavailableError(identifier);
|
|
189
|
-
}
|
|
190
|
-
throw new YoutubeTranscriptDisabledError(identifier);
|
|
198
|
+
// Basic bot/recaptcha detection preserves old error behavior
|
|
199
|
+
if (videoPageBody.includes('class="g-recaptcha"')) {
|
|
200
|
+
throw new YoutubeTranscriptTooManyRequestError();
|
|
191
201
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
// 2) Extract Innertube API key from the page
|
|
203
|
+
const apiKeyMatch = videoPageBody.match(/"INNERTUBE_API_KEY":"([^"]+)"/) ||
|
|
204
|
+
videoPageBody.match(/INNERTUBE_API_KEY\\":\\"([^\\"]+)\\"/);
|
|
205
|
+
if (!apiKeyMatch) {
|
|
206
|
+
// If captions JSON wasn't present previously and we also can't find an API key,
|
|
207
|
+
// retain the disabled semantics for compatibility.
|
|
208
|
+
throw new YoutubeTranscriptNotAvailableError(identifier);
|
|
209
|
+
}
|
|
210
|
+
const apiKey = apiKeyMatch[1];
|
|
211
|
+
// 3) Call Innertube player as ANDROID client to retrieve captionTracks
|
|
212
|
+
const playerEndpoint = `https://www.youtube.com/youtubei/v1/player?key=${apiKey}`;
|
|
213
|
+
const playerBody = {
|
|
214
|
+
context: {
|
|
215
|
+
client: {
|
|
216
|
+
clientName: 'ANDROID',
|
|
217
|
+
clientVersion: '20.10.38',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
videoId: identifier,
|
|
221
|
+
};
|
|
222
|
+
// Use configurable playerFetch for the POST to allow custom fetch logic.
|
|
223
|
+
const playerFetchParams = {
|
|
224
|
+
url: playerEndpoint,
|
|
225
|
+
method: 'POST',
|
|
226
|
+
lang,
|
|
227
|
+
userAgent,
|
|
228
|
+
headers: { 'Content-Type': 'application/json' },
|
|
229
|
+
body: JSON.stringify(playerBody),
|
|
230
|
+
};
|
|
231
|
+
const playerRes = ((_h = this.config) === null || _h === void 0 ? void 0 : _h.playerFetch)
|
|
232
|
+
? yield this.config.playerFetch(playerFetchParams)
|
|
233
|
+
: yield defaultFetch(playerFetchParams);
|
|
234
|
+
if (!playerRes.ok) {
|
|
235
|
+
throw new YoutubeTranscriptVideoUnavailableError(identifier);
|
|
236
|
+
}
|
|
237
|
+
const playerJson = yield playerRes.json();
|
|
238
|
+
const tracklist = (_k = (_j = playerJson === null || playerJson === void 0 ? void 0 : playerJson.captions) === null || _j === void 0 ? void 0 : _j.playerCaptionsTracklistRenderer) !== null && _k !== void 0 ? _k : playerJson === null || playerJson === void 0 ? void 0 : playerJson.playerCaptionsTracklistRenderer;
|
|
239
|
+
const tracks = tracklist === null || tracklist === void 0 ? void 0 : tracklist.captionTracks;
|
|
240
|
+
const isPlayableOk = ((_l = playerJson === null || playerJson === void 0 ? void 0 : playerJson.playabilityStatus) === null || _l === void 0 ? void 0 : _l.status) === 'OK';
|
|
241
|
+
// If `captions` is entirely missing, treat as "not available"
|
|
242
|
+
if (!(playerJson === null || playerJson === void 0 ? void 0 : playerJson.captions) || !tracklist) {
|
|
243
|
+
// If video is playable but captions aren’t provided, treat as "disabled"
|
|
244
|
+
if (isPlayableOk) {
|
|
245
|
+
throw new YoutubeTranscriptDisabledError(identifier);
|
|
198
246
|
}
|
|
199
|
-
|
|
200
|
-
|
|
247
|
+
// Otherwise we can’t assert they’re disabled; treat as "not available"
|
|
248
|
+
throw new YoutubeTranscriptNotAvailableError(identifier);
|
|
249
|
+
}
|
|
250
|
+
// If `captions` exists but there are zero tracks, treat as "disabled"
|
|
251
|
+
if (!Array.isArray(tracks) || tracks.length === 0) {
|
|
201
252
|
throw new YoutubeTranscriptDisabledError(identifier);
|
|
202
253
|
}
|
|
203
|
-
|
|
254
|
+
// Respect requested language or fallback to first track
|
|
255
|
+
const selectedTrack = lang ? tracks.find((t) => t.languageCode === lang) : tracks[0];
|
|
256
|
+
if (!selectedTrack) {
|
|
257
|
+
const available = tracks.map((t) => t.languageCode).filter(Boolean);
|
|
258
|
+
throw new YoutubeTranscriptNotAvailableLanguageError(lang, available, identifier);
|
|
259
|
+
}
|
|
260
|
+
// 4) Build transcript URL; prefer XML by stripping fmt if present
|
|
261
|
+
let transcriptURL = selectedTrack.baseUrl || selectedTrack.url;
|
|
262
|
+
if (!transcriptURL) {
|
|
204
263
|
throw new YoutubeTranscriptNotAvailableError(identifier);
|
|
205
264
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
265
|
+
transcriptURL = transcriptURL.replace(/&fmt=[^&]+$/, '');
|
|
266
|
+
if ((_m = this.config) === null || _m === void 0 ? void 0 : _m.disableHttps) {
|
|
267
|
+
transcriptURL = transcriptURL.replace(/^https:\/\//, 'http://');
|
|
209
268
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
:
|
|
213
|
-
|
|
214
|
-
? captionURL.replace('https://', 'http://')
|
|
215
|
-
: captionURL;
|
|
216
|
-
// Fetch the transcript
|
|
217
|
-
const transcriptResponse = yield transcriptFetch({
|
|
218
|
-
url: transcriptURL,
|
|
219
|
-
lang: (_o = this.config) === null || _o === void 0 ? void 0 : _o.lang,
|
|
220
|
-
userAgent,
|
|
221
|
-
});
|
|
269
|
+
// 5) Fetch transcript XML using the same hook surface as before
|
|
270
|
+
const transcriptResponse = ((_o = this.config) === null || _o === void 0 ? void 0 : _o.transcriptFetch)
|
|
271
|
+
? yield this.config.transcriptFetch({ url: transcriptURL, lang, userAgent })
|
|
272
|
+
: yield defaultFetch({ url: transcriptURL, lang, userAgent });
|
|
222
273
|
if (!transcriptResponse.ok) {
|
|
274
|
+
// Preserve legacy behavior
|
|
275
|
+
if (transcriptResponse.status === 429) {
|
|
276
|
+
throw new YoutubeTranscriptTooManyRequestError();
|
|
277
|
+
}
|
|
223
278
|
throw new YoutubeTranscriptNotAvailableError(identifier);
|
|
224
279
|
}
|
|
225
280
|
const transcriptBody = yield transcriptResponse.text();
|
|
281
|
+
// 6) Parse XML into the existing TranscriptResponse shape
|
|
226
282
|
const results = [...transcriptBody.matchAll(RE_XML_TRANSCRIPT)];
|
|
227
|
-
const transcript = results.map((
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
//
|
|
237
|
-
if (
|
|
238
|
-
|
|
283
|
+
const transcript = results.map((m) => ({
|
|
284
|
+
text: m[3],
|
|
285
|
+
duration: parseFloat(m[2]),
|
|
286
|
+
offset: parseFloat(m[1]),
|
|
287
|
+
lang: lang !== null && lang !== void 0 ? lang : selectedTrack.languageCode,
|
|
288
|
+
}));
|
|
289
|
+
if (transcript.length === 0) {
|
|
290
|
+
throw new YoutubeTranscriptNotAvailableError(identifier);
|
|
291
|
+
}
|
|
292
|
+
// Cache store
|
|
293
|
+
if (cache) {
|
|
294
|
+
try {
|
|
295
|
+
yield cache.set(cacheKey, JSON.stringify(transcript), cacheTTL);
|
|
296
|
+
}
|
|
297
|
+
catch (_q) {
|
|
298
|
+
// non-fatal
|
|
299
|
+
}
|
|
239
300
|
}
|
|
240
301
|
return transcript;
|
|
241
302
|
});
|
|
242
303
|
}
|
|
243
|
-
// Add static method for new usage pattern
|
|
244
304
|
static fetchTranscript(videoId, config) {
|
|
245
305
|
return __awaiter(this, void 0, void 0, function* () {
|
|
246
306
|
const instance = new YoutubeTranscript(config);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "youtube-transcript-plus",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Fetch transcript from a YouTube video",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/youtube-transcript-plus.js",
|
|
@@ -29,23 +29,26 @@
|
|
|
29
29
|
]
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@types/jest": "^
|
|
32
|
+
"@types/jest": "^30.0.0",
|
|
33
33
|
"https-proxy-agent": "^7.0.6",
|
|
34
34
|
"husky": "^9.1.7",
|
|
35
|
-
"jest": "^
|
|
36
|
-
"lint-staged": "^
|
|
37
|
-
"prettier": "^3.
|
|
38
|
-
"rollup": "^4.
|
|
35
|
+
"jest": "^30.0.5",
|
|
36
|
+
"lint-staged": "^16.1.5",
|
|
37
|
+
"prettier": "^3.6.2",
|
|
38
|
+
"rollup": "^4.46.4",
|
|
39
39
|
"rollup-plugin-typescript": "^1.0.1",
|
|
40
40
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
41
|
-
"ts-jest": "^29.
|
|
41
|
+
"ts-jest": "^29.4.1",
|
|
42
42
|
"tslib": "^2.8.1",
|
|
43
|
-
"typescript": "^5.
|
|
43
|
+
"typescript": "^5.9.2"
|
|
44
44
|
},
|
|
45
45
|
"files": [
|
|
46
46
|
"dist/*"
|
|
47
47
|
],
|
|
48
|
-
"repository":
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/ericmmartin/youtube-transcript-plus.git"
|
|
51
|
+
},
|
|
49
52
|
"publishConfig": {
|
|
50
53
|
"access": "public"
|
|
51
54
|
},
|