getraw 0.2.0 → 0.3.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/.github/workflows/release.yml +1 -0
- package/bun.lock +11 -0
- package/package.json +5 -2
- package/src/cli/index.ts +2 -1
- package/src/cli/options.ts +2 -2
- package/src/core/orchestrator.ts +1 -1
- package/src/extractors/base.ts +70 -1
- package/src/extractors/youtube/index.ts +166 -187
- package/src/extractors/youtube/innertube.ts +51 -23
- package/src/extractors/youtube/js-analyzer.ts +798 -0
- package/src/extractors/youtube/player.ts +139 -0
- package/src/extractors/youtube/sabr-download.ts +155 -0
- package/video/.hyperframes/expanded-prompt.md +0 -173
- package/video/design.md +0 -82
- package/video/index.html +0 -684
- package/video/renders/video_2026-06-16_23-50-45.meta.json +0 -1
- package/video/renders/video_2026-06-16_23-50-45.mp4 +0 -0
|
@@ -104,14 +104,14 @@ export interface BrowseResponse {
|
|
|
104
104
|
const CLIENTS: Record<string, ClientContext> = {
|
|
105
105
|
WEB: {
|
|
106
106
|
clientName: "WEB",
|
|
107
|
-
clientVersion: "2.
|
|
108
|
-
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
107
|
+
clientVersion: "2.20250615.01.00",
|
|
108
|
+
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
|
|
109
109
|
apiKey: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8",
|
|
110
110
|
},
|
|
111
111
|
ANDROID: {
|
|
112
112
|
clientName: "ANDROID",
|
|
113
|
-
clientVersion: "19.
|
|
114
|
-
userAgent: "com.google.android.youtube/19.
|
|
113
|
+
clientVersion: "19.44.38",
|
|
114
|
+
userAgent: "com.google.android.youtube/19.44.38 (Linux; U; Android 14) gzip",
|
|
115
115
|
apiKey: "AIzaSyA8eiZmM1FaDVjRy-df2KTyQ_vz_yYM39w",
|
|
116
116
|
clientId: 3,
|
|
117
117
|
},
|
|
@@ -122,6 +122,13 @@ const CLIENTS: Record<string, ClientContext> = {
|
|
|
122
122
|
apiKey: "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8",
|
|
123
123
|
clientId: 85,
|
|
124
124
|
},
|
|
125
|
+
IOS: {
|
|
126
|
+
clientName: "IOS",
|
|
127
|
+
clientVersion: "19.45.4",
|
|
128
|
+
userAgent: "com.google.ios.youtube/19.45.4 (iPhone16,2; U; CPU iOS 17_5_1 like Mac OS X;)",
|
|
129
|
+
apiKey: "AIzaSyB-63vPrdThhKuerbB2N_l7Kwwcxj6yUAc",
|
|
130
|
+
clientId: 5,
|
|
131
|
+
},
|
|
125
132
|
};
|
|
126
133
|
|
|
127
134
|
const PLAYER_ENDPOINT = "https://www.youtube.com/youtubei/v1/player";
|
|
@@ -130,22 +137,28 @@ const BROWSE_ENDPOINT = "https://www.youtube.com/youtubei/v1/browse";
|
|
|
130
137
|
export class InnerTubeClient {
|
|
131
138
|
private clientName: string;
|
|
132
139
|
private context: ClientContext;
|
|
140
|
+
private signatureTimestamp: number;
|
|
133
141
|
|
|
134
|
-
constructor(clientName: "WEB" | "ANDROID" | "TVHTML5_EMBED" = "WEB") {
|
|
142
|
+
constructor(clientName: "WEB" | "ANDROID" | "TVHTML5_EMBED" | "IOS" = "WEB", signatureTimestamp = 20073) {
|
|
135
143
|
this.clientName = clientName;
|
|
136
144
|
this.context = CLIENTS[clientName];
|
|
145
|
+
this.signatureTimestamp = signatureTimestamp;
|
|
137
146
|
}
|
|
138
147
|
|
|
139
|
-
async getPlayerResponse(videoId: string, embedUrl?: string): Promise<PlayerResponse> {
|
|
140
|
-
const body = this.buildPlayerBody(videoId, embedUrl);
|
|
148
|
+
async getPlayerResponse(videoId: string, embedUrl?: string, visitorData?: string): Promise<PlayerResponse> {
|
|
149
|
+
const body = this.buildPlayerBody(videoId, embedUrl, visitorData);
|
|
150
|
+
const headers: Record<string, string> = {
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
"User-Agent": this.context.userAgent,
|
|
153
|
+
"X-YouTube-Client-Name": String(this.context.clientId ?? 1),
|
|
154
|
+
"X-YouTube-Client-Version": this.context.clientVersion,
|
|
155
|
+
};
|
|
156
|
+
if (visitorData) {
|
|
157
|
+
headers["X-Goog-Visitor-Id"] = visitorData;
|
|
158
|
+
}
|
|
141
159
|
const response = await fetch(`${PLAYER_ENDPOINT}?key=${this.context.apiKey}&prettyPrint=false`, {
|
|
142
160
|
method: "POST",
|
|
143
|
-
headers
|
|
144
|
-
"Content-Type": "application/json",
|
|
145
|
-
"User-Agent": this.context.userAgent,
|
|
146
|
-
"X-YouTube-Client-Name": String(this.context.clientId ?? 1),
|
|
147
|
-
"X-YouTube-Client-Version": this.context.clientVersion,
|
|
148
|
-
},
|
|
161
|
+
headers,
|
|
149
162
|
body: JSON.stringify(body),
|
|
150
163
|
});
|
|
151
164
|
|
|
@@ -193,26 +206,37 @@ export class InnerTubeClient {
|
|
|
193
206
|
return response.json() as Promise<BrowseResponse>;
|
|
194
207
|
}
|
|
195
208
|
|
|
196
|
-
private buildPlayerBody(videoId: string, embedUrl?: string): Record<string, unknown> {
|
|
209
|
+
private buildPlayerBody(videoId: string, embedUrl?: string, visitorData?: string): Record<string, unknown> {
|
|
210
|
+
const clientContext: Record<string, unknown> = {
|
|
211
|
+
clientName: this.context.clientName,
|
|
212
|
+
clientVersion: this.context.clientVersion,
|
|
213
|
+
hl: "en",
|
|
214
|
+
gl: "US",
|
|
215
|
+
};
|
|
216
|
+
if (visitorData) {
|
|
217
|
+
clientContext.visitorData = visitorData;
|
|
218
|
+
}
|
|
197
219
|
const body: Record<string, unknown> = {
|
|
198
220
|
videoId,
|
|
199
221
|
context: {
|
|
200
|
-
client:
|
|
201
|
-
clientName: this.context.clientName,
|
|
202
|
-
clientVersion: this.context.clientVersion,
|
|
203
|
-
hl: "en",
|
|
204
|
-
gl: "US",
|
|
205
|
-
},
|
|
222
|
+
client: clientContext,
|
|
206
223
|
},
|
|
207
224
|
playbackContext: {
|
|
208
225
|
contentPlaybackContext: {
|
|
209
|
-
signatureTimestamp:
|
|
226
|
+
signatureTimestamp: this.signatureTimestamp,
|
|
210
227
|
},
|
|
211
228
|
},
|
|
212
229
|
contentCheckOk: true,
|
|
213
230
|
racyCheckOk: true,
|
|
214
231
|
};
|
|
215
232
|
|
|
233
|
+
if (this.clientName === "IOS") {
|
|
234
|
+
clientContext.deviceMake = "Apple";
|
|
235
|
+
clientContext.deviceModel = "iPhone16,2";
|
|
236
|
+
clientContext.osName = "iPhone";
|
|
237
|
+
clientContext.osVersion = "17.5.1.21F90";
|
|
238
|
+
}
|
|
239
|
+
|
|
216
240
|
if (this.clientName === "TVHTML5_EMBED" && embedUrl) {
|
|
217
241
|
(body.context as Record<string, unknown>).thirdParty = {
|
|
218
242
|
embedUrl,
|
|
@@ -310,8 +334,12 @@ export class InnerTubeClient {
|
|
|
310
334
|
return { subtitles, automatic_captions };
|
|
311
335
|
}
|
|
312
336
|
|
|
313
|
-
|
|
314
|
-
|
|
337
|
+
setSignatureTimestamp(sts: number): void {
|
|
338
|
+
this.signatureTimestamp = sts;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
static withClient(clientName: "WEB" | "ANDROID" | "TVHTML5_EMBED" | "IOS", signatureTimestamp?: number): InnerTubeClient {
|
|
342
|
+
return new InnerTubeClient(clientName, signatureTimestamp);
|
|
315
343
|
}
|
|
316
344
|
}
|
|
317
345
|
|