/***************************************************************************** * VLCPlexParser.m * VLC for iOS ***************************************************************************** * Copyright (c) 2014-2019 VideoLAN. All rights reserved. * * Authors: Pierre Sagaspe * * Refer to the COPYING file of the official project for license. *****************************************************************************/ #import "VLCPlexParser.h" #import "VLCPlexWebAPI.h" #import static NSString *const kPlexMediaServerDirInit = @"/library/sections"; static NSString *const kPlexVLCDeviceName = @"VLC for iOS"; @interface VLCPlexParser () { NSMutableArray *_containerInfo; NSMutableDictionary *_dicoInfo; NSString *_PlexMediaServerUrl; } @end @implementation VLCPlexParser - (NSArray *)PlexMediaServerParser:(NSString *)address port:(NSNumber *)port navigationPath:(NSString *)path authentification:(NSString *)auth error:(NSError *__autoreleasing *)outError { _containerInfo = [[NSMutableArray alloc] init]; _dicoInfo = [[NSMutableDictionary alloc] init]; NSURLComponents *urlComponents = [[NSURLComponents alloc] init]; urlComponents.scheme = @"http"; urlComponents.host = address; urlComponents.port = port; _PlexMediaServerUrl = [urlComponents URL].absoluteString; NSString *mediaServerUrl; if ([path length] == 0) { urlComponents.path = kPlexMediaServerDirInit; } else { if ([path rangeOfString:@"library"].location != NSNotFound) { urlComponents.path = [@"/" stringByAppendingPathComponent:path]; } else { urlComponents.path = [kPlexMediaServerDirInit stringByAppendingPathComponent:path]; } } mediaServerUrl = [[urlComponents URL].absoluteString stringByRemovingPercentEncoding]; VLCPlexWebAPI *PlexWebAPI = [[VLCPlexWebAPI alloc] init]; NSURL *url = [[NSURL alloc] initWithString:[PlexWebAPI urlAuth:mediaServerUrl authentification:auth]]; NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0]; NSHTTPURLResponse *response = nil; NSError *error = nil; NSData *data = [self sendSynchronousRequest:request returningResponse:&response error:&error]; if ([response statusCode] != 200) { NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; if([responseString rangeOfString:@"Unauthorized"].location != NSNotFound) { NSString *serviceString = [NSString stringWithFormat:@"plex://%@:%@", address, port]; XKKeychainGenericPasswordItem *keychainItem = [XKKeychainGenericPasswordItem itemsForService:serviceString error:nil].firstObject; if (!keychainItem) { serviceString = [NSString stringWithFormat:@"plex://Account:%@", port]; keychainItem = [XKKeychainGenericPasswordItem itemsForService:serviceString error:nil].firstObject; } if (keychainItem) { NSString *username = keychainItem.account; NSString *password = keychainItem.secret.stringValue; auth = [PlexWebAPI PlexAuthentification:username password:password]; url = [NSURL URLWithString:[PlexWebAPI urlAuth:mediaServerUrl authentification:auth]]; request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0]; response = nil; error = nil; data = [self sendSynchronousRequest:request returningResponse:&response error:&error]; if ([response statusCode] != 200) { if (outError) { NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary: @{ NSLocalizedDescriptionKey : NSLocalizedString(@"PLEX_ERROR_ACCOUNT", nil), NSLocalizedFailureReasonErrorKey : NSLocalizedString(@"PLEX_CHECK_ACCOUNT", nil), }]; if (error) { userInfo[NSUnderlyingErrorKey] = error; } *outError = [NSError errorWithDomain:@"org.videolan.vlc-ios.plexParser" code:response.statusCode userInfo:userInfo]; } } [_containerInfo removeAllObjects]; [_dicoInfo removeAllObjects]; } else { if (outError) { NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithDictionary: @{ NSLocalizedDescriptionKey : NSLocalizedString(@"UNAUTHORIZED", nil), NSLocalizedFailureReasonErrorKey : NSLocalizedString(@"PLEX_CHECK_ACCOUNT", nil), }]; if (error) { userInfo[NSUnderlyingErrorKey] = error; } *outError = [NSError errorWithDomain:@"org.videolan.vlc-ios.plexParser" code:response.statusCode userInfo:userInfo]; } } } else APLog(@"PlexParser url Errors : %ld", (long)[response statusCode]); } if (error) { APLog(@"PlexParser url Error: %@", error); } NSXMLParser *xmlparser = [[NSXMLParser alloc] initWithData:data]; [xmlparser setDelegate:self]; if (![xmlparser parse]) APLog(@"PlexParser url Errors : %@", url); [_containerInfo setValue:auth forKey:@"authentification"]; return [NSArray arrayWithArray:_containerInfo]; } - (NSArray *)PlexExtractDeviceInfo:(NSData *)data { _containerInfo = [[NSMutableArray alloc] init]; _dicoInfo = [[NSMutableDictionary alloc] init]; NSXMLParser *xmlparser = [[NSXMLParser alloc] initWithData:data]; [xmlparser setDelegate:self]; if (![xmlparser parse]) APLog(@"PlexParser data Errors : %@", data); return [NSArray arrayWithArray:_containerInfo]; } - (NSString *)PlexExtractToken:(NSData *)data { NSString *token = @""; _containerInfo = [[NSMutableArray alloc] init]; _dicoInfo = [[NSMutableDictionary alloc] init]; NSXMLParser *xmlparser = [[NSXMLParser alloc] initWithData:data]; [xmlparser setDelegate:self]; if (![xmlparser parse]) { NSLog(@"PlexParser data Errors : %@", data); } else { token = [[NSArray arrayWithArray:_containerInfo] firstObject][@"token"]; } return token ? token : @""; } #pragma mark - Parser - (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict { if ([elementName isEqualToString:@"MediaContainer"]) { if ([attributeDict objectForKey:@"friendlyName"]) [_dicoInfo setObject:[attributeDict objectForKey:@"friendlyName"] forKey:@"libTitle"]; else if ([attributeDict objectForKey:@"title1"]) [_dicoInfo setObject:[attributeDict objectForKey:@"title1"] forKey:@"libTitle"]; if ([attributeDict objectForKey:@"title2"]) [_dicoInfo setObject:[attributeDict objectForKey:@"title2"] forKey:@"libTitle"]; if ([attributeDict objectForKey:@"grandparentTitle"]) [_dicoInfo setObject:[attributeDict objectForKey:@"grandparentTitle"] forKey:@"grandparentTitle"]; } else if ([elementName isEqualToString:@"Directory"]) { [_dicoInfo setObject:@"directory" forKey:@"container"]; [_dicoInfo setObject:[attributeDict objectForKey:@"key"] forKey:@"key"]; [_dicoInfo setObject:[attributeDict objectForKey:@"title"] forKey:@"title"]; } else if ([elementName isEqualToString:@"Video"] || [elementName isEqualToString:@"Track"]) { [_dicoInfo setObject:@"item" forKey:@"container"]; [_dicoInfo setObject:[attributeDict objectForKey:@"key"] forKey:@"key"]; [_dicoInfo setObject:[attributeDict objectForKey:@"title"] forKey:@"title"]; [_dicoInfo setObject:[attributeDict objectForKey:@"ratingKey"] forKey:@"ratingKey"]; if ([attributeDict objectForKey:@"summary"]) [_dicoInfo setObject:[attributeDict objectForKey:@"summary"] forKey:@"summary"]; if ([attributeDict objectForKey:@"viewCount"]) [_dicoInfo setObject:@"watched" forKey:@"state"]; else [_dicoInfo setObject:@"unwatched" forKey:@"state"]; } else if ([elementName isEqualToString:@"Media"]) { if ([attributeDict objectForKey:@"audioCodec"]) [_dicoInfo setObject:[attributeDict objectForKey:@"audioCodec"] forKey:@"audioCodec"]; if ([attributeDict objectForKey:@"videoCodec"]) [_dicoInfo setObject:[attributeDict objectForKey:@"videoCodec"] forKey:@"videoCodec"]; } else if ([elementName isEqualToString:@"Part"]) { [_dicoInfo setObject:[NSString stringWithFormat:@"%@%@",_PlexMediaServerUrl, [attributeDict objectForKey:@"key"]] forKey:@"keyMedia"]; if([attributeDict objectForKey:@"file"]) [_dicoInfo setObject:[[attributeDict objectForKey:@"file"] lastPathComponent] forKey:@"namefile"]; NSString *duration = [[VLCTime timeWithNumber:[attributeDict objectForKey:@"duration"]] stringValue]; [_dicoInfo setObject:duration forKey:@"duration"]; NSString *sizeFile = (NSString *)[attributeDict objectForKey:@"size"]; if (sizeFile) [_dicoInfo setObject:sizeFile forKey:@"size"]; } else if ([elementName isEqualToString:@"Stream"]) { if ([attributeDict objectForKey:@"key"]) [_dicoInfo setObject:[NSString stringWithFormat:@"%@%@",_PlexMediaServerUrl, [attributeDict objectForKey:@"key"]] forKey:@"keySubtitle"]; if ([attributeDict objectForKey:@"codec"]) [_dicoInfo setObject:[attributeDict objectForKey:@"codec"] forKey:@"codecSubtitle"]; if ([attributeDict objectForKey:@"language"]) [_dicoInfo setObject:[attributeDict objectForKey:@"language"] forKey:@"languageSubtitle"]; if ([attributeDict objectForKey:@"languageCode"]) [_dicoInfo setObject:[attributeDict objectForKey:@"languageCode"] forKey:@"languageCode"]; } else if ([elementName isEqualToString:@"Device"] && [[attributeDict objectForKey:@"name"] isEqualToString:kPlexVLCDeviceName]) { if ([attributeDict objectForKey:@"name"]) [_dicoInfo setObject:[attributeDict objectForKey:@"name"] forKey:@"name"]; if ([attributeDict objectForKey:@"product"]) [_dicoInfo setObject:[attributeDict objectForKey:@"product"] forKey:@"product"]; if ([attributeDict objectForKey:@"productVersion"]) [_dicoInfo setObject:[attributeDict objectForKey:@"productVersion"] forKey:@"productVersion"]; if ([attributeDict objectForKey:@"platformVersion"]) [_dicoInfo setObject:[attributeDict objectForKey:@"platformVersion"] forKey:@"platformVersion"]; if ([attributeDict objectForKey:@"token"]) [_dicoInfo setObject:[attributeDict objectForKey:@"token"] forKey:@"token"]; if ([attributeDict objectForKey:@"clientIdentifier"]) [_dicoInfo setObject:[attributeDict objectForKey:@"clientIdentifier"] forKey:@"clientIdentifier"]; if ([attributeDict objectForKey:@"id"]) [_dicoInfo setObject:[attributeDict objectForKey:@"id"] forKey:@"id"]; } else if ([elementName isEqualToString:@"user"]) { if ([attributeDict objectForKey:@"authToken"]) [_dicoInfo setObject:[attributeDict objectForKey:@"authToken"] forKey:@"token"]; if ([attributeDict objectForKey:@"authenticationToken"]) [_dicoInfo setObject:[attributeDict objectForKey:@"authenticationToken"] forKey:@"token"]; } if ([attributeDict objectForKey:@"thumb"] && ([elementName isEqualToString:@"Video"] || [elementName isEqualToString:@"Directory"] || [elementName isEqualToString:@"Part"] || [elementName isEqualToString:@"Track"])) [_dicoInfo setObject:[NSString stringWithFormat:@"%@%@", _PlexMediaServerUrl, [attributeDict objectForKey:@"thumb"]] forKey:@"thumb"]; } - (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName { if (([elementName isEqualToString:@"Video"] || [elementName isEqualToString:@"Track"] || [elementName isEqualToString:@"Directory"] || [elementName isEqualToString:@"MediaContainer"] || [elementName isEqualToString:@"Device"] || [elementName isEqualToString:@"user"]) && [_dicoInfo count] > 0) { [_containerInfo addObject:_dicoInfo]; _dicoInfo = [[NSMutableDictionary alloc] init]; } } - (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error { NSError __block *erreur = NULL; NSData __block *data; BOOL __block reqProcessed = false; NSURLResponse __block *urlResponse; [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable _data, NSURLResponse * _Nullable _response, NSError * _Nullable _error) { urlResponse = _response; erreur = _error; data = _data; reqProcessed = true; }] resume]; while (!reqProcessed) { [NSThread sleepForTimeInterval:0]; } *response = urlResponse; *error = erreur; return data; } @end