diff --git a/XCDYouTubeKit/XCDYouTubeClient.h b/XCDYouTubeKit/XCDYouTubeClient.h index 573a9715..1fba33ca 100644 --- a/XCDYouTubeKit/XCDYouTubeClient.h +++ b/XCDYouTubeKit/XCDYouTubeClient.h @@ -25,6 +25,9 @@ NS_ASSUME_NONNULL_BEGIN */ @interface XCDYouTubeClient : NSObject ++ (NSString *)innertubeApiKey; ++ (void)setInnertubeApiKey:(NSString *)key; + /** * ------------------ * @name Initializing diff --git a/XCDYouTubeKit/XCDYouTubeClient.m b/XCDYouTubeKit/XCDYouTubeClient.m index 5305d1ae..2ae8e2bd 100644 --- a/XCDYouTubeKit/XCDYouTubeClient.m +++ b/XCDYouTubeKit/XCDYouTubeClient.m @@ -14,6 +14,8 @@ @implementation XCDYouTubeClient @synthesize languageIdentifier = _languageIdentifier; +static NSString * _innertubeApiKey = @"AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8"; + + (instancetype) defaultClient { static XCDYouTubeClient *defaultClient; @@ -29,6 +31,15 @@ - (instancetype) init return [self initWithLanguageIdentifier:nil]; } + ++ (NSString *)innertubeApiKey { + return _innertubeApiKey; +} + ++ (void)setInnertubeApiKey:(NSString *)key { + _innertubeApiKey = key; +} + - (instancetype) initWithLanguageIdentifier:(NSString *)languageIdentifier { if (!(self = [super init])) diff --git a/XCDYouTubeKit/XCDYouTubeVideoOperation.m b/XCDYouTubeKit/XCDYouTubeVideoOperation.m index be4ce9ee..b3d42648 100644 --- a/XCDYouTubeKit/XCDYouTubeVideoOperation.m +++ b/XCDYouTubeKit/XCDYouTubeVideoOperation.m @@ -12,6 +12,7 @@ #import "XCDYouTubeDashManifestXML.h" #import "XCDYouTubePlayerScript.h" #import "XCDYouTubeLogger+Private.h" +#import "XCDYouTubeClient.h" typedef NS_ENUM(NSUInteger, XCDYouTubeRequestType) { XCDYouTubeRequestTypeGetVideoInfo = 1, @@ -146,13 +147,24 @@ - (void) startNextRequest } else { - NSString *eventLabel = [self.eventLabels objectAtIndex:0]; [self.eventLabels removeObjectAtIndex:0]; - NSDictionary *query = @{ @"video_id": self.videoIdentifier, @"hl": self.languageIdentifier, @"el": eventLabel, @"ps": @"default" }; - NSString *queryString = XCDQueryStringWithDictionary(query); - NSURL *videoInfoURL = [NSURL URLWithString:[@"https://www.youtube.com/get_video_info?" stringByAppendingString:queryString]]; - [self startRequestWithURL:videoInfoURL type:XCDYouTubeRequestTypeGetVideoInfo]; + NSString *urlString = [NSString stringWithFormat:@"https://youtubei.googleapis.com/youtubei/v1/player?key=%@", XCDYouTubeClient.innertubeApiKey]; + NSURL *url = [NSURL URLWithString:urlString]; + + NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10]; + + [request setHTTPMethod:@"POST"]; + + NSString *string = [NSString stringWithFormat:@"{'context': {'client': {'hl': 'en','clientName': 'WEB','clientVersion': '2.20210721.00.00','mainAppWebInfo': {'graftUrl': '/watch?v=%@'}}},'videoId': '%@'}", self.videoIdentifier, self.videoIdentifier]; + + NSData *postData = [string dataUsingEncoding:NSASCIIStringEncoding]; + + [request setHTTPBody:postData]; + + [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; + + [self startRequestWith:request type:XCDYouTubeRequestTypeGetVideoInfo]; } } @@ -206,6 +218,39 @@ - (void) startRequestWithURL:(NSURL *)url type:(XCDYouTubeRequestType)requestTyp self.requestType = requestType; } +- (void) startRequestWith:(NSMutableURLRequest *)request type:(XCDYouTubeRequestType)requestType +{ + if (self.isCancelled) + return; + + // Max (age-restricted VEVO) = 2×GetVideoInfo + 1×WatchPage + 2×EmbedPage + 1×JavaScriptPlayer + 1×GetVideoInfo + 1xDashManifest + if (++self.requestCount > 8) + { + // This condition should never happen but the request flow is quite complex so better abort here than go into an infinite loop of requests + [self finishWithError]; + return; + } + + XCDYouTubeLogDebug(@"Starting request: %@", [request URL]); + + [request setValue:self.languageIdentifier forHTTPHeaderField:@"Accept-Language"]; + [request setValue:[NSString stringWithFormat:@"https://youtube.com/watch?v=%@", self.videoIdentifier] forHTTPHeaderField:@"Referer"]; + + self.dataTask = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) + { + if (self.isCancelled) + return; + + if (error) + [self handleConnectionError:error requestType:requestType]; + else + [self handleConnectionSuccessWithData:data response:response requestType:requestType]; + }]; + [self.dataTask resume]; + + self.requestType = requestType; +} + #pragma mark - Response Dispatch - (void) handleConnectionSuccessWithData:(NSData *)data response:(NSURLResponse *)response requestType:(XCDYouTubeRequestType)requestType