@@ -5,20 +5,22 @@ import {
55 buildTrack ,
66 checkURLType
77} from '../common.js'
8+ import { PoTokenManager } from '../potoke.js'
89
910export default class Web extends BaseClient {
1011 constructor ( nodelink , oauth ) {
1112 super ( nodelink , 'WEB' , oauth )
13+ this . poTokenManager = new PoTokenManager ( )
1214 }
1315
1416 getClient ( context ) {
1517 return {
1618 client : {
1719 clientName : 'WEB' ,
18- clientVersion : '2.20260101.00 .00' ,
20+ clientVersion : '2.20260107.01 .00' ,
1921 platform : 'DESKTOP' ,
2022 userAgent :
21- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36,gzip(gfe) ' ,
23+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36' ,
2224 hl : context . client . hl ,
2325 gl : context . client . gl
2426 } ,
@@ -248,33 +250,120 @@ export default class Web extends BaseClient {
248250 }
249251
250252 async getTrackUrl ( decodedTrack , context , cipherManager , itag ) {
251- const sourceName = decodedTrack . sourceName || 'youtube'
252- logger (
253- 'debug' ,
254- 'youtube-web' ,
255- `Getting stream URL for: ${ decodedTrack . title } (ID: ${ decodedTrack . identifier } ) on ${ sourceName } `
256- )
253+ if ( this . oauth && this . oauth . accessToken ) {
254+ await this . oauth . getAccessToken ( )
255+ }
257256
258- const { body : playerResponse , statusCode } = await this . _makePlayerRequest (
259- decodedTrack . identifier ,
260- context ,
261- { } ,
262- cipherManager
263- )
257+ const { poToken, visitorData } = await this . poTokenManager . generate ( decodedTrack . identifier )
258+
259+ if ( poToken ) {
260+ const client = this . getClient ( context )
261+ client . client . visitorData = visitorData
262+
263+ let signatureTimestamp = null
264+ try {
265+ const playerScript = await cipherManager . getCachedPlayerScript ( )
266+ if ( playerScript ) {
267+ signatureTimestamp = await cipherManager . getTimestamp ( playerScript . url )
268+ }
269+ } catch ( e ) {
270+ logger ( 'warn' , 'YouTube-Web' , `Failed to get STS: ${ e . message } ` )
271+ }
272+
273+ const requestBody = {
274+ context : client ,
275+ videoId : decodedTrack . identifier ,
276+ contentCheckOk : true ,
277+ racyCheckOk : true ,
278+ serviceIntegrityDimensions : { poToken }
279+ }
264280
265- if ( statusCode !== 200 ) {
266- const message = `Failed to get player data for stream. Status: ${ statusCode } `
267- logger ( 'error' , 'youtube-web' , message )
268- return { exception : { message, severity : 'common' , cause : 'Upstream' } }
281+ if ( signatureTimestamp ) {
282+ requestBody . playbackContext = {
283+ contentPlaybackContext : {
284+ signatureTimestamp
285+ }
286+ }
287+ }
288+
289+ try {
290+ const { body : playerResponse } = await makeRequest (
291+ 'https://youtubei.googleapis.com/youtubei/v1/player?prettyPrint=false' ,
292+ {
293+ method : 'POST' ,
294+ headers : {
295+ 'User-Agent' : client . client . userAgent ,
296+ 'X-Goog-Visitor-Id' : visitorData ,
297+ 'X-Youtube-Client-Name' : '1' ,
298+ 'X-Youtube-Client-Version' : client . client . clientVersion ,
299+ 'Origin' : 'https://www.youtube.com' ,
300+ 'Referer' : `https://www.youtube.com/watch?v=${ decodedTrack . identifier } `
301+ } ,
302+ body : requestBody ,
303+ disableBodyCompression : true
304+ }
305+ )
306+
307+ const streamingData = playerResponse . streamingData || playerResponse . streaming_data
308+ const serverAbrUrl = streamingData ?. serverAbrStreamingUrl || streamingData ?. server_abr_streaming_url
309+ const ustreamerConfig = playerResponse . playerConfig ?. mediaCommonConfig ?. mediaUstreamerRequestConfig ?. videoPlaybackUstreamerConfig
310+
311+ if ( serverAbrUrl ) {
312+ const playerScript = await cipherManager . getCachedPlayerScript ( )
313+
314+ let resolvedUrl = serverAbrUrl
315+ if ( playerScript ) {
316+ try {
317+ resolvedUrl = await cipherManager . resolveUrl (
318+ serverAbrUrl ,
319+ null ,
320+ null ,
321+ null ,
322+ playerScript ,
323+ context
324+ )
325+ } catch ( e ) {
326+ logger ( 'warn' , 'YouTube-Web' , `Failed to resolve SABR URL via cipher server: ${ e . message } ` )
327+ }
328+ }
329+
330+ const formats = [ ...( streamingData . formats || [ ] ) , ...( streamingData . adaptiveFormats || streamingData . adaptive_formats || [ ] ) ] . map ( f => ( {
331+ itag : f . itag ,
332+ lastModified : f . lastModified || f . last_modified_ms ,
333+ xtags : f . xtags ,
334+ width : f . width ,
335+ height : f . height ,
336+ mimeType : f . mimeType || f . mime_type ,
337+ audioQuality : f . audioQuality || f . audio_quality ,
338+ bitrate : f . bitrate ,
339+ averageBitrate : f . averageBitrate || f . average_bitrate ,
340+ quality : f . quality ,
341+ qualityLabel : f . qualityLabel || f . quality_label ,
342+ audioTrackId : f . audioTrack ?. id ,
343+ approxDurationMs : f . approxDurationMs || f . approx_duration_ms ,
344+ contentLength : f . contentLength || f . content_length ,
345+ isDrc : ! ! f . isDrc
346+ } ) )
347+
348+ return {
349+ protocol : 'sabr' ,
350+ url : resolvedUrl ,
351+ additionalData : {
352+ serverAbrStreamingUrl : resolvedUrl ,
353+ videoPlaybackUstreamerConfig : ustreamerConfig ,
354+ poToken,
355+ visitorData,
356+ clientInfo : { clientName : 1 , clientVersion : client . client . clientVersion } ,
357+ formats,
358+ accessToken : null ,
359+ userAgent : client . client . userAgent
360+ }
361+ }
362+ }
363+ } catch ( e ) { }
269364 }
270365
271- return await this . _extractStreamData (
272- playerResponse ,
273- decodedTrack ,
274- context ,
275- cipherManager ,
276- itag
277- )
366+ return super . getTrackUrl ( decodedTrack , context , cipherManager , itag )
278367 }
279368
280369 async getChapters ( trackInfo , context ) {
0 commit comments