Example #1
0
    def metadataForInappPurchase(self, htmlTree):
        InappMetadata = namedtuple('InappMetadata', ['refname', 'cleared', 'languages', 'textid', 'numericid', 'price_tier', 'reviewnotes', 'hosted'])

        inappReferenceName = htmlTree.xpath('//span[@id="iapReferenceNameUpdateContainer"]//span/text()')[0].strip()
        textId = htmlTree.xpath('//div[@id="productIdText"]//span/text()')[0].strip()
        numericId = htmlTree.xpath('//label[.="Apple ID: "]/following-sibling::span/text()')[0].strip()
        hostedContent = len(htmlTree.xpath('//div[contains(@class,"hosted-content")]/following-sibling::p')) > 0
        reviewNotes = htmlTree.xpath('//div[@class="hosted-review-notes"]//span/text()')[0].strip()

        clearedForSaleText = htmlTree.xpath('//div[contains(@class,"cleared-for-sale")]//span/text()')[0]
        clearedForSale = False
        if clearedForSaleText == 'Yes':
            clearedForSale = True

        inapptype = htmlTree.xpath('//div[@class="status-label"]//span/text()')[0].strip()
        priceTier = None

        if inapptype != "Free Subscription":
            priceTier = htmlTree.xpath('//tr[@id="interval-row-0"]//a/text()')[0].strip().split(' ')
            priceTier = int(priceTier[-1])

        idAddon = "autoRenewableL" if (inapptype == "Free Subscription") else "l"
        languagesSpan = htmlTree.xpath('//span[@id="0' + idAddon + 'ocalizationListListRefreshContainerId"]')[0]
        activatedLanguages = languagesSpan.xpath('.//li[starts-with(@id, "0' + idAddon + 'ocalizationListRow")]/div[starts-with(@class, "ajaxListRowDiv")]/@itemid')
        activatedLangsIds = [languages.langCodeForLanguage(lang) for lang in activatedLanguages]
        languageAction = htmlTree.xpath('//div[@id="0' + idAddon + 'ocalizationListLightbox"]/@action')[0]

        # logging.info('Activated languages for inapp ' + self.numericId + ': ' + ', '.join(activatedLanguages))
        logging.debug('Activated languages ids: ' + ', '.join(activatedLangsIds))
        metadataLanguages = {}

        for langId in activatedLangsIds:
            metadataLanguages[langId] = {}
            languageParamStr = "&itemID=" + languages.appleLangIdForLanguage(langId)
            localizationTree = self.parseTreeForURL(languageAction + "?open=true" + languageParamStr)
            metadataLanguages[langId]['name'] = localizationTree.xpath('//div[@id="proposedDisplayName"]//input/@value')[0]
            metadataLanguages[langId]['description'] = localizationTree.xpath('//div[@id="proposedDescription"]//textarea/text()')[0].strip()
    
            localizedPublicationName = localizationTree.xpath('//div[@id="proposedPublicationName"]//input/@value')
            if len(localizedPublicationName) > 0:
                metadataLanguages[langId]['publication name'] = localizedPublicationName[0]

        return InappMetadata(refname=inappReferenceName
                            , cleared=clearedForSale
                            , languages=metadataLanguages
                            , price_tier=priceTier
                            , textid=textId
                            , numericid=int(numericId)
                            , hosted=hostedContent
                            , reviewnotes=reviewNotes)
Example #2
0
    def editVersion(self, dataDict, lang=None, versionString=None, filename_format=None):
        if dataDict == None or len(dataDict) == 0: # nothing to change
            return

        if len(self.versions) == 0:
            self.getAppInfo()
        if len(self.versions) == 0:
            raise 'Can\'t get application versions'
        if versionString == None: # Suppose there's one or less editable versions
            versionString = next((versionString for versionString, version in self.versions.items() if version['editable']), None)
        if versionString == None: # Suppose there's one or less editable versions
            raise 'No editable version found'
            
        version = self.versions[versionString]
        if not version['editable']:
            raise 'Version ' + versionString + ' is not editable'

        languageId = languages.appleLangIdForLanguage(lang)
        languageCode = languages.langCodeForLanguage(lang)

        metadata = self.__parseAppVersionMetadata(version, lang)
        # activatedLanguages = metadata.activatedLanguages
        # nonactivatedLanguages = metadata.nonactivatedLanguages
        formData = {} #metadata.formData[languageId]
        formNames = metadata.formNames[languageId]
        submitAction = metadata.submitActions[languageId]
        
        formData["save"] = "true"

        formData[formNames['appNameName']]      = dataDict.get('name', metadata.formData[languageId]['appNameValue'])
        formData[formNames['descriptionName']]  = dataFromStringOrFile(dataDict.get('description', metadata.formData[languageId]['descriptionValue']), languageCode)
        if 'whatsNewName' in formNames:
            formData[formNames['whatsNewName']] = dataFromStringOrFile(dataDict.get('whats new', metadata.formData[languageId]['whatsNewValue']), languageCode)
        formData[formNames['keywordsName']]     = dataFromStringOrFile(dataDict.get('keywords', metadata.formData[languageId]['keywordsValue']), languageCode)
        formData[formNames['supportURLName']]   = dataDict.get('support url', metadata.formData[languageId]['supportURLValue'])
        formData[formNames['marketingURLName']] = dataDict.get('marketing url', metadata.formData[languageId]['marketingURLValue'])
        formData[formNames['pPolicyURLName']]   = dataDict.get('privacy policy url', metadata.formData[languageId]['pPolicyURLValue'])

        iphoneUploadScreenshotForm  = formNames['iphoneUploadScreenshotForm'] 
        iphone5UploadScreenshotForm = formNames['iphone5UploadScreenshotForm']
        ipadUploadScreenshotForm    = formNames['ipadUploadScreenshotForm']

        iphoneUploadScreenshotJS = iphoneUploadScreenshotForm.xpath('../following-sibling::script/text()')[0]
        iphone5UploadScreenshotJS = iphone5UploadScreenshotForm.xpath('../following-sibling::script/text()')[0]
        ipadUploadScreenshotJS = ipadUploadScreenshotForm.xpath('../following-sibling::script/text()')[0]

        self._uploadSessionData[DEVICE_TYPE.iPhone] = dict({'action': iphoneUploadScreenshotForm.attrib['action']
                                                        , 'key': iphoneUploadScreenshotForm.xpath(".//input[@name='uploadKey']/@value")[0]
                                                      }, **self.parseURLSFromScript(iphoneUploadScreenshotJS))
        self._uploadSessionData[DEVICE_TYPE.iPhone5] = dict({'action': iphone5UploadScreenshotForm.attrib['action']
                                                         , 'key': iphone5UploadScreenshotForm.xpath(".//input[@name='uploadKey']/@value")[0]
                                                       }, **self.parseURLSFromScript(iphone5UploadScreenshotJS))
        self._uploadSessionData[DEVICE_TYPE.iPad] = dict({'action': ipadUploadScreenshotForm.attrib['action']
                                                      , 'key': ipadUploadScreenshotForm.xpath(".//input[@name='uploadKey']/@value")[0]
                                                    }, **self.parseURLSFromScript(ipadUploadScreenshotJS))

        self._uploadSessionId = iphoneUploadScreenshotForm.xpath('.//input[@name="uploadSessionID"]/@value')[0]

        # get all images
        for device_type in [DEVICE_TYPE.iPhone, DEVICE_TYPE.iPhone5, DEVICE_TYPE.iPad]:
            self._images[device_type] = self.imagesForDevice(device_type)

        logging.debug(self._images)
        # logging.debug(formData)

        if 'images' in dataDict:
            imagesActions = dataDict['images']
            languageCode = languages.langCodeForLanguage(lang)

            for dType in imagesActions:
                device_type = None
                if dType.lower() == 'iphone':
                    device_type = DEVICE_TYPE.iPhone
                elif dType.lower() == 'iphone 5':
                    device_type = DEVICE_TYPE.iPhone5
                elif dType.lower() == 'ipad':
                    device_type = DEVICE_TYPE.iPad
                else:
                    continue

                deviceImagesActions = imagesActions[dType]
                if deviceImagesActions == "":
                    continue

                for imageAction in deviceImagesActions:
                    imageAction.setdefault('cmd')
                    imageAction.setdefault('indexes')
                    cmd = imageAction['cmd']
                    indexes = imageAction['indexes']
                    replace_language = ALIASES.language_aliases.get(languageCode, languageCode)
                    replace_device = ALIASES.device_type_aliases.get(dType.lower(), DEVICE_TYPE.deviceStrings[device_type])

                    imagePath = filename_format.replace('{language}', replace_language) \
                           .replace('{device_type}', replace_device)
                    logging.debug('Looking for images at ' + imagePath)

                    if (indexes == None) and ((cmd == 'u') or (cmd == 'r')):
                        indexes = []
                        for i in range(0, 5):
                            realImagePath = imagePath.replace("{index}", str(i + 1))
                            logging.debug('img path: ' + realImagePath)
                            if os.path.exists(realImagePath):
                                indexes.append(i + 1)

                    logging.debug('indexes ' + indexes.__str__())
                    logging.debug('Processing command ' + imageAction.__str__())

                    if (cmd == 'd') or (cmd == 'r'): # delete or replace. To perform replace we need to delete images first
                        deleteIndexes = [img['id'] for img in self._images[device_type]]
                        if indexes != None:
                            deleteIndexes = [deleteIndexes[idx - 1] for idx in indexes]

                        logging.debug('deleting images ' + deleteIndexes.__str__())
                        
                        for imageIndexToDelete in deleteIndexes:
                            img = next(im for im in self._images[device_type] if im['id'] == imageIndexToDelete)
                            self.deleteScreenshot(device_type, img['id'])

                        self._images[device_type] = self.imagesForDevice(device_type)
                    
                    if (cmd == 'u') or (cmd == 'r'): # upload or replace
                        currentIndexes = [img['id'] for img in self._images[device_type]]

                        if indexes == None:
                            continue

                        indexes = sorted(indexes)
                        for i in indexes:
                            realImagePath = imagePath.replace("{index}", str(i))
                            if os.path.exists(realImagePath):
                                self.uploadScreenshot(device_type, realImagePath)

                        self._images[device_type] = self.imagesForDevice(device_type)

                        if cmd == 'r':
                            newIndexes = [img['id'] for img in self._images[device_type]][len(currentIndexes):]

                            if len(newIndexes) == 0:
                                continue

                            for i in indexes:
                                currentIndexes.insert(i - 1, newIndexes.pop(0))

                            self.sortScreenshots(device_type, currentIndexes)
                            self._images[device_type] = self.imagesForDevice(device_type)

                    if (cmd == 's'): # sort
                        if indexes == None or len(indexes) != len(self._images[device_type]):
                            continue
                        newIndexes = [self._images[device_type][i - 1]['id'] for i in indexes]

                        self.sortScreenshots(device_type, newIndexes)
                        self._images[device_type] = self.imagesForDevice(device_type)

        formData['uploadSessionID'] = self._uploadSessionId
        logging.debug(formData)
        # formData['uploadKey'] = self._uploadSessionData[DEVICE_TYPE.iPhone5]['key']

        postFormResponse = self._parser.requests_session.post(ITUNESCONNECT_URL + submitAction, data = formData, cookies=cookie_jar)

        if postFormResponse.status_code != 200:
            raise 'Wrong response from iTunesConnect. Status code: ' + str(postFormResponse.status_code)

        if len(postFormResponse.text) > 0:
            logging.error("Save information failed. " + postFormResponse.text)
Example #3
0
    def editVersion(self,
                    dataDict,
                    lang=None,
                    versionString=None,
                    filename_format=None):
        if dataDict == None or len(dataDict) == 0:  # nothing to change
            return

        if len(self.versions) == 0:
            self.getAppInfo()
        if len(self.versions) == 0:
            raise 'Can\'t get application versions'
        if versionString == None:  # Suppose there's one or less editable versions
            versionString = next(
                (versionString
                 for versionString, version in self.versions.items()
                 if version['editable']), None)
        if versionString == None:  # Suppose there's one or less editable versions
            raise 'No editable version found'

        version = self.versions[versionString]
        if not version['editable']:
            raise 'Version ' + versionString + ' is not editable'

        languageId = languages.appleLangIdForLanguage(lang)
        languageCode = languages.langCodeForLanguage(lang)

        metadata = self.__parseAppVersionMetadata(version, lang)
        # activatedLanguages = metadata.activatedLanguages
        # nonactivatedLanguages = metadata.nonactivatedLanguages
        formData = {}  #metadata.formData[languageId]
        formNames = metadata.formNames[languageId]
        submitAction = metadata.submitActions[languageId]

        formData["save"] = "true"

        formData[formNames['appNameName']] = dataDict.get(
            'name', metadata.formData[languageId]['appNameValue'])
        formData[formNames['descriptionName']] = dataFromStringOrFile(
            dataDict.get('description',
                         metadata.formData[languageId]['descriptionValue']),
            languageCode)
        if 'whatsNewName' in formNames:
            formData[formNames['whatsNewName']] = dataFromStringOrFile(
                dataDict.get('whats new',
                             metadata.formData[languageId]['whatsNewValue']),
                languageCode)
        formData[formNames['keywordsName']] = dataFromStringOrFile(
            dataDict.get('keywords',
                         metadata.formData[languageId]['keywordsValue']),
            languageCode)
        formData[formNames['supportURLName']] = dataDict.get(
            'support url', metadata.formData[languageId]['supportURLValue'])
        formData[formNames['marketingURLName']] = dataDict.get(
            'marketing url',
            metadata.formData[languageId]['marketingURLValue'])
        formData[formNames['pPolicyURLName']] = dataDict.get(
            'privacy policy url',
            metadata.formData[languageId]['pPolicyURLValue'])

        iphoneUploadScreenshotForm = formNames['iphoneUploadScreenshotForm']
        iphone5UploadScreenshotForm = formNames['iphone5UploadScreenshotForm']
        ipadUploadScreenshotForm = formNames['ipadUploadScreenshotForm']

        iphoneUploadScreenshotJS = iphoneUploadScreenshotForm.xpath(
            '../following-sibling::script/text()')[0]
        iphone5UploadScreenshotJS = iphone5UploadScreenshotForm.xpath(
            '../following-sibling::script/text()')[0]
        ipadUploadScreenshotJS = ipadUploadScreenshotForm.xpath(
            '../following-sibling::script/text()')[0]

        self._uploadSessionData[DEVICE_TYPE.iPhone] = dict(
            {
                'action':
                iphoneUploadScreenshotForm.attrib['action'],
                'key':
                iphoneUploadScreenshotForm.xpath(
                    ".//input[@name='uploadKey']/@value")[0]
            }, **self.parseURLSFromScript(iphoneUploadScreenshotJS))
        self._uploadSessionData[DEVICE_TYPE.iPhone5] = dict(
            {
                'action':
                iphone5UploadScreenshotForm.attrib['action'],
                'key':
                iphone5UploadScreenshotForm.xpath(
                    ".//input[@name='uploadKey']/@value")[0]
            }, **self.parseURLSFromScript(iphone5UploadScreenshotJS))
        self._uploadSessionData[DEVICE_TYPE.iPad] = dict(
            {
                'action':
                ipadUploadScreenshotForm.attrib['action'],
                'key':
                ipadUploadScreenshotForm.xpath(
                    ".//input[@name='uploadKey']/@value")[0]
            }, **self.parseURLSFromScript(ipadUploadScreenshotJS))

        self._uploadSessionId = iphoneUploadScreenshotForm.xpath(
            './/input[@name="uploadSessionID"]/@value')[0]

        # get all images
        for device_type in [
                DEVICE_TYPE.iPhone, DEVICE_TYPE.iPhone5, DEVICE_TYPE.iPad
        ]:
            self._images[device_type] = self.imagesForDevice(device_type)

        logging.debug(self._images)
        # logging.debug(formData)

        if 'images' in dataDict:
            imagesActions = dataDict['images']
            languageCode = languages.langCodeForLanguage(lang)

            for dType in imagesActions:
                device_type = None
                if dType.lower() == 'iphone':
                    device_type = DEVICE_TYPE.iPhone
                elif dType.lower() == 'iphone 5':
                    device_type = DEVICE_TYPE.iPhone5
                elif dType.lower() == 'ipad':
                    device_type = DEVICE_TYPE.iPad
                else:
                    continue

                deviceImagesActions = imagesActions[dType]
                if deviceImagesActions == "":
                    continue

                for imageAction in deviceImagesActions:
                    imageAction.setdefault('cmd')
                    imageAction.setdefault('indexes')
                    cmd = imageAction['cmd']
                    indexes = imageAction['indexes']
                    replace_language = ALIASES.language_aliases.get(
                        languageCode, languageCode)
                    replace_device = ALIASES.device_type_aliases.get(
                        dType.lower(), DEVICE_TYPE.deviceStrings[device_type])

                    imagePath = filename_format.replace('{language}', replace_language) \
                           .replace('{device_type}', replace_device)
                    logging.debug('Looking for images at ' + imagePath)

                    if (indexes == None) and ((cmd == 'u') or (cmd == 'r')):
                        indexes = []
                        for i in range(0, 5):
                            realImagePath = imagePath.replace(
                                "{index}", str(i + 1))
                            logging.debug('img path: ' + realImagePath)
                            if os.path.exists(realImagePath):
                                indexes.append(i + 1)

                    logging.debug('indexes ' + indexes.__str__())
                    logging.debug('Processing command ' +
                                  imageAction.__str__())

                    if (cmd == 'd') or (
                            cmd == 'r'
                    ):  # delete or replace. To perform replace we need to delete images first
                        deleteIndexes = [
                            img['id'] for img in self._images[device_type]
                        ]
                        if indexes != None:
                            deleteIndexes = [
                                deleteIndexes[idx - 1] for idx in indexes
                            ]

                        logging.debug('deleting images ' +
                                      deleteIndexes.__str__())

                        for imageIndexToDelete in deleteIndexes:
                            img = next(im for im in self._images[device_type]
                                       if im['id'] == imageIndexToDelete)
                            self.deleteScreenshot(device_type, img['id'])

                        self._images[device_type] = self.imagesForDevice(
                            device_type)

                    if (cmd == 'u') or (cmd == 'r'):  # upload or replace
                        currentIndexes = [
                            img['id'] for img in self._images[device_type]
                        ]

                        if indexes == None:
                            continue

                        indexes = sorted(indexes)
                        for i in indexes:
                            realImagePath = imagePath.replace(
                                "{index}", str(i))
                            if os.path.exists(realImagePath):
                                self.uploadScreenshot(device_type,
                                                      realImagePath)

                        self._images[device_type] = self.imagesForDevice(
                            device_type)

                        if cmd == 'r':
                            newIndexes = [
                                img['id'] for img in self._images[device_type]
                            ][len(currentIndexes):]

                            if len(newIndexes) == 0:
                                continue

                            for i in indexes:
                                currentIndexes.insert(i - 1, newIndexes.pop(0))

                            self.sortScreenshots(device_type, currentIndexes)
                            self._images[device_type] = self.imagesForDevice(
                                device_type)

                    if (cmd == 's'):  # sort
                        if indexes == None or len(indexes) != len(
                                self._images[device_type]):
                            continue
                        newIndexes = [
                            self._images[device_type][i - 1]['id']
                            for i in indexes
                        ]

                        self.sortScreenshots(device_type, newIndexes)
                        self._images[device_type] = self.imagesForDevice(
                            device_type)

        formData['uploadSessionID'] = self._uploadSessionId
        logging.debug(formData)
        # formData['uploadKey'] = self._uploadSessionData[DEVICE_TYPE.iPhone5]['key']

        postFormResponse = self._parser.requests_session.post(
            ITUNESCONNECT_URL + submitAction,
            data=formData,
            cookies=cookie_jar)

        if postFormResponse.status_code != 200:
            raise 'Wrong response from iTunesConnect. Status code: ' + str(
                postFormResponse.status_code)

        if len(postFormResponse.text) > 0:
            logging.error("Save information failed. " + postFormResponse.text)
Example #4
0
    def parseCreateOrEditPage(self, htmlTree, version, language=None):
        tree = htmlTree

        AppMetadata = namedtuple('AppMetadata', ['activatedLanguages', 'nonactivatedLanguages'
                                                , 'formData', 'formNames', 'submitActions'])

        localizationLightboxAction = tree.xpath("//div[@id='localizationLightbox']/@action")[0] # if no lang provided, edit default
        #localizationLightboxUpdateAction = tree.xpath("//span[@id='localizationLightboxUpdate']/@action")[0] 

        activatedLanguages    = tree.xpath('//div[@id="modules-dropdown"] \
                                    /ul/li[count(preceding-sibling::li[@class="heading"])=1]/a/text()')
        nonactivatedLanguages = tree.xpath('//div[@id="modules-dropdown"] \
                                    /ul/li[count(preceding-sibling::li[@class="heading"])=2]/a/text()')
        
        activatedLanguages = [lng.replace("(Default)", "").strip() for lng in activatedLanguages]

        logging.info('Activated languages: ' + ', '.join(activatedLanguages))
        logging.debug('Nonactivated languages: ' + ', '.join(nonactivatedLanguages))

        langs = activatedLanguages

        if language != None:
            langs = [language]

        formData = {}
        formNames = {}
        submitActions = {}
        versionString = version['versionString']

        for lang in langs:
            logging.info('Processing language: ' + lang)
            languageId = languages.appleLangIdForLanguage(lang)
            logging.debug('Apple language id: ' + languageId)

            if lang in activatedLanguages:
                logging.info('Getting metadata for ' + lang + '. Version: ' + versionString)
            elif lang in nonactivatedLanguages:
                logging.info('Add ' + lang + ' for version ' + versionString)

            editTree = self.parseTreeForURL(localizationLightboxAction + "?open=true" 
                                                    + ("&language=" + languageId if (languageId != None) else ""))
            hasWhatsNew = False

            formDataForLang = {}
            formNamesForLang = {}

            submitActionForLang = editTree.xpath("//div[@class='lcAjaxLightboxContentsWrapper']/div[@class='lcAjaxLightboxContents']/@action")[0]

            formNamesForLang['appNameName'] = editTree.xpath("//div[@id='appNameUpdateContainerId']//input/@name")[0]
            formNamesForLang['descriptionName'] = editTree.xpath("//div[@id='descriptionUpdateContainerId']//textarea/@name")[0]
            whatsNewName = editTree.xpath("//div[@id='whatsNewinthisVersionUpdateContainerId']//textarea/@name")

            if len(whatsNewName) > 0: # there's no what's new section for first version
                hasWhatsNew = True
                formNamesForLang['whatsNewName'] = whatsNewName[0]

            formNamesForLang['keywordsName']     = editTree.xpath("//div/label[.='Keywords']/..//input/@name")[0]
            formNamesForLang['supportURLName']   = editTree.xpath("//div/label[.='Support URL']/..//input/@name")[0]
            formNamesForLang['marketingURLName'] = editTree.xpath("//div/label[contains(., 'Marketing URL')]/..//input/@name")[0]
            formNamesForLang['pPolicyURLName']   = editTree.xpath("//div/label[contains(., 'Privacy Policy URL')]/..//input/@name")[0]

            formDataForLang['appNameValue']     = editTree.xpath("//div[@id='appNameUpdateContainerId']//input/@value")[0]
            formDataForLang['descriptionValue'] = getElement(editTree.xpath("//div[@id='descriptionUpdateContainerId']//textarea/text()"), 0)
            whatsNewValue    = editTree.xpath("//div[@id='whatsNewinthisVersionUpdateContainerId']//textarea/text()")

            if len(whatsNewValue) > 0 and hasWhatsNew:
                formDataForLang['whatsNewValue'] = getElement(whatsNewValue, 0)

            formDataForLang['keywordsValue']     = getElement(editTree.xpath("//div/label[.='Keywords']/..//input/@value"), 0)
            formDataForLang['supportURLValue']   = getElement(editTree.xpath("//div/label[.='Support URL']/..//input/@value"), 0)
            formDataForLang['marketingURLValue'] = getElement(editTree.xpath("//div/label[contains(., 'Marketing URL')]/..//input/@value"), 0)
            formDataForLang['pPolicyURLValue']   = getElement(editTree.xpath("//div/label[contains(., 'Privacy Policy URL')]/..//input/@value"), 0)

            logging.debug("Old values:")
            logging.debug(formDataForLang)

            iphoneUploadScreenshotForm = editTree.xpath("//form[@name='FileUploadForm_35InchRetinaDisplayScreenshots']")[0]
            iphone5UploadScreenshotForm = editTree.xpath("//form[@name='FileUploadForm_iPhone5']")[0]
            ipadUploadScreenshotForm = editTree.xpath("//form[@name='FileUploadForm_iPadScreenshots']")[0]

            formNamesForLang['iphoneUploadScreenshotForm'] = iphoneUploadScreenshotForm
            formNamesForLang['iphone5UploadScreenshotForm'] = iphone5UploadScreenshotForm
            formNamesForLang['ipadUploadScreenshotForm'] = ipadUploadScreenshotForm

            formData[languageId] = formDataForLang
            formNames[languageId] = formNamesForLang
            submitActions[languageId] = submitActionForLang

        metadata = AppMetadata(activatedLanguages=activatedLanguages
                             , nonactivatedLanguages=nonactivatedLanguages
                             , formData=formData
                             , formNames=formNames
                             , submitActions=submitActions)

        return metadata
    def parseCreateOrEditPage(self, htmlTree, version, language=None):
        tree = htmlTree

        AppMetadata = namedtuple('AppMetadata', [
            'activatedLanguages', 'nonactivatedLanguages', 'formData',
            'formNames', 'submitActions'
        ])

        localizationLightboxAction = tree.xpath(
            "//div[@id='localizationLightbox']/@action")[
                0]  # if no lang provided, edit default
        #localizationLightboxUpdateAction = tree.xpath("//span[@id='localizationLightboxUpdate']/@action")[0]

        activatedLanguages = tree.xpath('//div[@id="modules-dropdown"] \
                                    /ul/li[count(preceding-sibling::li[@class="heading"])=1]/a/text()'
                                        )
        nonactivatedLanguages = tree.xpath('//div[@id="modules-dropdown"] \
                                    /ul/li[count(preceding-sibling::li[@class="heading"])=2]/a/text()'
                                           )

        activatedLanguages = [
            lng.replace("(Default)", "").strip() for lng in activatedLanguages
        ]

        logging.info('Activated languages: ' + ', '.join(activatedLanguages))
        logging.debug('Nonactivated languages: ' +
                      ', '.join(nonactivatedLanguages))

        langs = activatedLanguages

        if language != None:
            langs = [language]

        formData = {}
        formNames = {}
        submitActions = {}
        versionString = version['versionString']

        for lang in langs:
            logging.info('Processing language: ' + lang)
            languageId = languages.appleLangIdForLanguage(lang)
            logging.debug('Apple language id: ' + languageId)

            if lang in activatedLanguages:
                logging.info('Getting metadata for ' + lang + '. Version: ' +
                             versionString)
            elif lang in nonactivatedLanguages:
                logging.info('Add ' + lang + ' for version ' + versionString)

            editTree = self.parseTreeForURL(
                localizationLightboxAction + "?open=true" +
                ("&language=" + languageId if (languageId != None) else ""))
            hasWhatsNew = False

            formDataForLang = {}
            formNamesForLang = {}

            submitActionForLang = editTree.xpath(
                "//div[@class='lcAjaxLightboxContentsWrapper']/div[@class='lcAjaxLightboxContents']/@action"
            )[0]

            formNamesForLang['appNameName'] = editTree.xpath(
                "//div[@id='appNameUpdateContainerId']//input/@name")[0]
            formNamesForLang['descriptionName'] = editTree.xpath(
                "//div[@id='descriptionUpdateContainerId']//textarea/@name")[0]
            whatsNewName = editTree.xpath(
                "//div[@id='whatsNewinthisVersionUpdateContainerId']//textarea/@name"
            )

            if len(whatsNewName
                   ) > 0:  # there's no what's new section for first version
                hasWhatsNew = True
                formNamesForLang['whatsNewName'] = whatsNewName[0]

            formNamesForLang['keywordsName'] = editTree.xpath(
                "//div/label[.='Keywords']/..//input/@name")[0]
            formNamesForLang['supportURLName'] = editTree.xpath(
                "//div/label[.='Support URL']/..//input/@name")[0]
            formNamesForLang['marketingURLName'] = editTree.xpath(
                "//div/label[contains(., 'Marketing URL')]/..//input/@name")[0]
            formNamesForLang['pPolicyURLName'] = editTree.xpath(
                "//div/label[contains(., 'Privacy Policy URL')]/..//input/@name"
            )[0]

            formDataForLang['appNameValue'] = editTree.xpath(
                "//div[@id='appNameUpdateContainerId']//input/@value")[0]
            formDataForLang['descriptionValue'] = getElement(
                editTree.xpath(
                    "//div[@id='descriptionUpdateContainerId']//textarea/text()"
                ), 0)
            whatsNewValue = editTree.xpath(
                "//div[@id='whatsNewinthisVersionUpdateContainerId']//textarea/text()"
            )

            if len(whatsNewValue) > 0 and hasWhatsNew:
                formDataForLang['whatsNewValue'] = getElement(whatsNewValue, 0)

            formDataForLang['keywordsValue'] = getElement(
                editTree.xpath("//div/label[.='Keywords']/..//input/@value"),
                0)
            formDataForLang['supportURLValue'] = getElement(
                editTree.xpath(
                    "//div/label[.='Support URL']/..//input/@value"), 0)
            formDataForLang['marketingURLValue'] = getElement(
                editTree.xpath(
                    "//div/label[contains(., 'Marketing URL')]/..//input/@value"
                ), 0)
            formDataForLang['pPolicyURLValue'] = getElement(
                editTree.xpath(
                    "//div/label[contains(., 'Privacy Policy URL')]/..//input/@value"
                ), 0)

            logging.debug("Old values:")
            logging.debug(formDataForLang)

            iphoneUploadScreenshotForm = editTree.xpath(
                "//form[@name='FileUploadForm_35InchRetinaDisplayScreenshots']"
            )[0]
            iphone5UploadScreenshotForm = editTree.xpath(
                "//form[@name='FileUploadForm_iPhone5']")[0]
            ipadUploadScreenshotForm = editTree.xpath(
                "//form[@name='FileUploadForm_iPadScreenshots']")[0]

            formNamesForLang[
                'iphoneUploadScreenshotForm'] = iphoneUploadScreenshotForm
            formNamesForLang[
                'iphone5UploadScreenshotForm'] = iphone5UploadScreenshotForm
            formNamesForLang[
                'ipadUploadScreenshotForm'] = ipadUploadScreenshotForm

            formData[languageId] = formDataForLang
            formNames[languageId] = formNamesForLang
            submitActions[languageId] = submitActionForLang

        metadata = AppMetadata(activatedLanguages=activatedLanguages,
                               nonactivatedLanguages=nonactivatedLanguages,
                               formData=formData,
                               formNames=formNames,
                               submitActions=submitActions)

        return metadata
Example #6
0
    def update(self, inappDict):
        tree = self._parser.parseTreeForURL(ITCInappPurchase.actionURLs['itemActionUrl'] + "?itemID=" + self.numericId)

        # for non-consumable iap we can change name, cleared-for-sale and pricing. Check if we need to:
        inappReferenceName = tree.xpath('//span[@id="iapReferenceNameUpdateContainer"]//span/text()')[0]
        clearedForSaleText = tree.xpath('//div[contains(@class,"cleared-for-sale")]//span/text()')[0]
        clearedForSale = False
        if clearedForSaleText == 'Yes':
            clearedForSale = True

        logging.debug('Updating inapp: ' + inappDict.__str__())

        self.name = inappDict.get('name', self.name)
        self.clearedForSale = inappDict.get('cleared', self.clearedForSale)
        self.hostingContentWithApple = inappDict.get('hosting content with apple', self.hostingContentWithApple)
        self.reviewNotes = inappDict.get('review notes', self.reviewNotes)

        # TODO: change price tier
        if (inappReferenceName != self.name) \
            or (clearedForSale != self.clearedForSale):
            editAction = tree.xpath('//div[@id="singleAddonPricingLightbox"]/@action')[0]

            inappTree = self._parser.parseTreeForURL(editAction)

            inappReferenceNameName = inappTree.xpath('//div[@id="referenceNameTooltipId"]/..//input/@name')[0]
            clearedForSaleName = inappTree.xpath('//div[contains(@class,"cleared-for-sale")]//input[@classname="radioTrue"]/@name')[0]
            clearedForSaleNames = {}
            clearedForSaleNames["true"] = inappTree.xpath('//div[contains(@class,"cleared-for-sale")]//input[@classname="radioTrue"]/@value')[0]
            clearedForSaleNames["false"] = inappTree.xpath('//div[contains(@class,"cleared-for-sale")]//input[@classname="radioFalse"]/@value')[0]
            inappPriceTierName = inappTree.xpath('//select[@id="price_tier_popup"]/@name')[0]

            dateComponentsNames = inappTree.xpath('//select[contains(@id, "_day")]/@name')
            dateComponentsNames.extend(inappTree.xpath('//select[contains(@id, "_month")]/@name'))
            dateComponentsNames.extend(inappTree.xpath('//select[contains(@id, "_year")]/@name'))

            postAction = inappTree.xpath('//div[@class="lcAjaxLightboxContents"]/@action')[0]

            formData = {}
            formData[inappReferenceNameName] = self.name
            formData[clearedForSaleName] = clearedForSaleNames["true" if self.clearedForSale else "false"]
            formData[inappPriceTierName] = 'WONoSelectionString'
            for dcn in dateComponentsNames:
                formData[dcn] = 'WONoSelectionString'
            formData['save'] = "true"

            postFormResponse = requests.post(ITUNESCONNECT_URL + postAction, data = formData, cookies=cookie_jar)

            if postFormResponse.status_code != 200:
                raise 'Wrong response from iTunesConnect. Status code: ' + str(postFormResponse.status_code)


        idAddon = "autoRenewableL" if (inapptype == "Free Subscription") else "l"
        languagesSpan = inappTree.xpath('//span[@id="0' + idAddon + 'ocalizationListListRefreshContainerId"]')[0]
        activatedLanguages = languagesSpan.xpath('.//li[starts-with(@id, "0' + idAddon + 'ocalizationListRow")]/div[starts-with(@class, "ajaxListRowDiv")]/@itemid')
        activatedLangsIds = [languages.langCodeForLanguage(lang) for lang in activatedLanguages]
        languageAction = tree.xpath('//div[@id="0' + idAddon + 'ocalizationListLightbox"]/@action')[0]

        logging.info('Activated languages for inapp ' + self.numericId + ': ' + ', '.join(activatedLanguages))
        logging.debug('Activated languages ids: ' + ', '.join(activatedLangsIds))

        langDict = inappDict.get('languages', {})
        for langId, langVal in langDict.items():
            if type(langVal) is str:
                if langId in activatedLangsIds and langVal == 'd': # TODO: delete lang
                    pass
                return
            
            languageParamStr = ""
            isEdit = False

            if langId in activatedLangsIds: # edit
                languageParamStr = "&itemID=" + languages.appleLangIdForLanguage(langId)
                isEdit = True

            localizationTree = self._parser.parseTreeForURL(languageAction + "?open=true" + languageParamStr)
            self.__createUpdateLanguage(localizationTree, langId, langVal, isEdit=isEdit)

        # upload screenshot, edit review notes, hosting content with apple, etc
        formData = {"save":"true"}
        editHostedContentAction = tree.xpath('//div[@id="versionLightboxId0"]/@action')[0]
        hostedContentTree = self._parser.parseTreeForURL(editHostedContentAction + "?open=true")
        saveEditHostedContentAction = hostedContentTree.xpath('//div[@class="lcAjaxLightboxContents"]/@action')[0]

        if (self.type == "Non-Consumable"):
            hostingContentName = hostedContentTree.xpath('//div[contains(@class,"hosting-on-apple")]//input[@classname="radioTrue"]/@name')[0]
            hostingContentNames = {}
            hostingContentNames["true"] = hostedContentTree.xpath('//div[contains(@class,"hosting-on-apple")]//input[@classname="radioTrue"]/@value')[0]
            hostingContentNames["false"] = hostedContentTree.xpath('//div[contains(@class,"hosting-on-apple")]//input[@classname="radioFalse"]/@value')[0]
            formData[hostingContentName] = hostingContentNames["true" if self.hostingContentWithApple else "false"]

        if inappDict['review screenshot'] != None:
            uploadForm = hostedContentTree.xpath('//form[@name="FileUploadForm__screenshotId"]')[0]
            self._uploadScreenshotAction = uploadForm.xpath('./@action')[0]
            self._uploadSessionId = uploadForm.xpath('.//input[@id="uploadSessionID"]/@value')[0]
            self._uploadScreenshotKey = uploadForm.xpath('.//input[@id="uploadKey"]/@value')[0]
            statusURLScript = hostedContentTree.xpath('//script[contains(., "var uploader_screenshotId")]/text()')[0]
            matches = re.findall('statusURL:\s\'([^\']+)\'', statusURLScript)
            self._statusURL = matches[0]
            self.__uploadScreenshot(inappDict['review screenshot'])
            requests.get(ITUNESCONNECT_URL + self._statusURL, cookies=cookie_jar)

            formData["uploadSessionID"] = self._uploadSessionId
            formData["uploadKey"] = self._uploadScreenshotKey
            formData["filename"] = inappDict['review screenshot']

 
        reviewNotesName = hostedContentTree.xpath('//div[@class="hosted-review-notes"]//textarea/@name')[0]
        formData[reviewNotesName] = self.reviewNotes
        self._parser.parseTreeForURL(saveEditHostedContentAction, method="POST", payload=formData)
Example #7
0
    def update(self, inappDict):
        tree = self._parser.parseTreeForURL(
            ITCInappPurchase.actionURLs['itemActionUrl'] + "?itemID=" +
            self.numericId)

        # for non-consumable iap we can change name, cleared-for-sale and pricing. Check if we need to:
        inappReferenceName = tree.xpath(
            '//span[@id="iapReferenceNameUpdateContainer"]//span/text()')[0]
        clearedForSaleText = tree.xpath(
            '//div[contains(@class,"cleared-for-sale")]//span/text()')[0]
        clearedForSale = False
        if clearedForSaleText == 'Yes':
            clearedForSale = True

        logging.debug('Updating inapp: ' + inappDict.__str__())

        self.name = inappDict.get('name', self.name)
        self.clearedForSale = inappDict.get('cleared', self.clearedForSale)
        self.hostingContentWithApple = inappDict.get(
            'hosting content with apple', self.hostingContentWithApple)
        self.reviewNotes = inappDict.get('review notes', self.reviewNotes)

        # TODO: change price tier
        if (inappReferenceName != self.name) \
            or (clearedForSale != self.clearedForSale):
            editAction = tree.xpath(
                '//div[@id="singleAddonPricingLightbox"]/@action')[0]

            inappTree = self._parser.parseTreeForURL(editAction)

            inappReferenceNameName = inappTree.xpath(
                '//div[@id="referenceNameTooltipId"]/..//input/@name')[0]
            clearedForSaleName = inappTree.xpath(
                '//div[contains(@class,"cleared-for-sale")]//input[@classname="radioTrue"]/@name'
            )[0]
            clearedForSaleNames = {}
            clearedForSaleNames["true"] = inappTree.xpath(
                '//div[contains(@class,"cleared-for-sale")]//input[@classname="radioTrue"]/@value'
            )[0]
            clearedForSaleNames["false"] = inappTree.xpath(
                '//div[contains(@class,"cleared-for-sale")]//input[@classname="radioFalse"]/@value'
            )[0]
            inappPriceTierName = inappTree.xpath(
                '//select[@id="price_tier_popup"]/@name')[0]

            dateComponentsNames = inappTree.xpath(
                '//select[contains(@id, "_day")]/@name')
            dateComponentsNames.extend(
                inappTree.xpath('//select[contains(@id, "_month")]/@name'))
            dateComponentsNames.extend(
                inappTree.xpath('//select[contains(@id, "_year")]/@name'))

            postAction = inappTree.xpath(
                '//div[@class="lcAjaxLightboxContents"]/@action')[0]

            formData = {}
            formData[inappReferenceNameName] = self.name
            formData[clearedForSaleName] = clearedForSaleNames[
                "true" if self.clearedForSale else "false"]
            formData[inappPriceTierName] = 'WONoSelectionString'
            for dcn in dateComponentsNames:
                formData[dcn] = 'WONoSelectionString'
            formData['save'] = "true"

            postFormResponse = self._parser.requests_session.post(
                ITUNESCONNECT_URL + postAction,
                data=formData,
                cookies=cookie_jar)

            if postFormResponse.status_code != 200:
                raise 'Wrong response from iTunesConnect. Status code: ' + str(
                    postFormResponse.status_code)

        idAddon = "autoRenewableL" if (inapptype
                                       == "Free Subscription") else "l"
        languagesSpan = inappTree.xpath(
            '//span[@id="0' + idAddon +
            'ocalizationListListRefreshContainerId"]')[0]
        activatedLanguages = languagesSpan.xpath(
            './/li[starts-with(@id, "0' + idAddon +
            'ocalizationListRow")]/div[starts-with(@class, "ajaxListRowDiv")]/@itemid'
        )
        activatedLangsIds = [
            languages.langCodeForLanguage(lang) for lang in activatedLanguages
        ]
        languageAction = tree.xpath('//div[@id="0' + idAddon +
                                    'ocalizationListLightbox"]/@action')[0]

        logging.info('Activated languages for inapp ' + self.numericId + ': ' +
                     ', '.join(activatedLanguages))
        logging.debug('Activated languages ids: ' +
                      ', '.join(activatedLangsIds))

        langDict = inappDict.get('languages', {})
        for langId, langVal in langDict.items():
            if type(langVal) is str:
                if langId in activatedLangsIds and langVal == 'd':  # TODO: delete lang
                    pass
                return

            languageParamStr = ""
            isEdit = False

            if langId in activatedLangsIds:  # edit
                languageParamStr = "&itemID=" + languages.appleLangIdForLanguage(
                    langId)
                isEdit = True

            localizationTree = self._parser.parseTreeForURL(languageAction +
                                                            "?open=true" +
                                                            languageParamStr)
            self.__createUpdateLanguage(localizationTree,
                                        langId,
                                        langVal,
                                        isEdit=isEdit)

        # upload screenshot, edit review notes, hosting content with apple, etc
        formData = {"save": "true"}
        editHostedContentAction = tree.xpath(
            '//div[@id="versionLightboxId0"]/@action')[0]
        hostedContentTree = self._parser.parseTreeForURL(
            editHostedContentAction + "?open=true")
        saveEditHostedContentAction = hostedContentTree.xpath(
            '//div[@class="lcAjaxLightboxContents"]/@action')[0]

        if (self.type == "Non-Consumable"):
            hostingContentName = hostedContentTree.xpath(
                '//div[contains(@class,"hosting-on-apple")]//input[@classname="radioTrue"]/@name'
            )[0]
            hostingContentNames = {}
            hostingContentNames["true"] = hostedContentTree.xpath(
                '//div[contains(@class,"hosting-on-apple")]//input[@classname="radioTrue"]/@value'
            )[0]
            hostingContentNames["false"] = hostedContentTree.xpath(
                '//div[contains(@class,"hosting-on-apple")]//input[@classname="radioFalse"]/@value'
            )[0]
            formData[hostingContentName] = hostingContentNames[
                "true" if self.hostingContentWithApple else "false"]

        if inappDict['review screenshot'] != None:
            uploadForm = hostedContentTree.xpath(
                '//form[@name="FileUploadForm__screenshotId"]')[0]
            self._uploadScreenshotAction = uploadForm.xpath('./@action')[0]
            self._uploadSessionId = uploadForm.xpath(
                './/input[@id="uploadSessionID"]/@value')[0]
            self._uploadScreenshotKey = uploadForm.xpath(
                './/input[@id="uploadKey"]/@value')[0]
            statusURLScript = hostedContentTree.xpath(
                '//script[contains(., "var uploader_screenshotId")]/text()')[0]
            matches = re.findall('statusURL:\s\'([^\']+)\'', statusURLScript)
            self._statusURL = matches[0]
            self.__uploadScreenshot(inappDict['review screenshot'])
            self._parser.requests_session.get(ITUNESCONNECT_URL +
                                              self._statusURL,
                                              cookies=cookie_jar)

            formData["uploadSessionID"] = self._uploadSessionId
            formData["uploadKey"] = self._uploadScreenshotKey
            formData["filename"] = inappDict['review screenshot']

        reviewNotesName = hostedContentTree.xpath(
            '//div[@class="hosted-review-notes"]//textarea/@name')[0]
        formData[reviewNotesName] = self.reviewNotes
        self._parser.parseTreeForURL(saveEditHostedContentAction,
                                     method="POST",
                                     payload=formData)
Example #8
0
    def metadataForInappPurchase(self, htmlTree):
        InappMetadata = namedtuple('InappMetadata', [
            'refname', 'cleared', 'languages', 'textid', 'numericid',
            'price_tier', 'reviewnotes', 'hosted'
        ])

        inappReferenceName = htmlTree.xpath(
            '//span[@id="iapReferenceNameUpdateContainer"]//span/text()'
        )[0].strip()
        textId = htmlTree.xpath(
            '//div[@id="productIdText"]//span/text()')[0].strip()
        numericId = htmlTree.xpath(
            '//label[.="Apple ID: "]/following-sibling::span/text()')[0].strip(
            )
        hostedContent = len(
            htmlTree.xpath(
                '//div[contains(@class,"hosted-content")]/following-sibling::p'
            )) > 0
        reviewNotes = htmlTree.xpath(
            '//div[@class="hosted-review-notes"]//span/text()')[0].strip()

        clearedForSaleText = htmlTree.xpath(
            '//div[contains(@class,"cleared-for-sale")]//span/text()')[0]
        clearedForSale = False
        if clearedForSaleText == 'Yes':
            clearedForSale = True

        inapptype = htmlTree.xpath(
            '//div[@class="status-label"]//span/text()')[0].strip()
        priceTier = None

        if inapptype != "Free Subscription":
            priceTier = htmlTree.xpath(
                '//tr[@id="interval-row-0"]//a/text()')[0].strip().split(' ')
            priceTier = int(priceTier[-1])

        idAddon = "autoRenewableL" if (inapptype
                                       == "Free Subscription") else "l"
        languagesSpan = htmlTree.xpath(
            '//span[@id="0' + idAddon +
            'ocalizationListListRefreshContainerId"]')[0]
        activatedLanguages = languagesSpan.xpath(
            './/li[starts-with(@id, "0' + idAddon +
            'ocalizationListRow")]/div[starts-with(@class, "ajaxListRowDiv")]/@itemid'
        )
        activatedLangsIds = [
            languages.langCodeForLanguage(lang) for lang in activatedLanguages
        ]
        languageAction = htmlTree.xpath('//div[@id="0' + idAddon +
                                        'ocalizationListLightbox"]/@action')[0]

        # logging.info('Activated languages for inapp ' + self.numericId + ': ' + ', '.join(activatedLanguages))
        logging.debug('Activated languages ids: ' +
                      ', '.join(activatedLangsIds))
        metadataLanguages = {}

        for langId in activatedLangsIds:
            metadataLanguages[langId] = {}
            languageParamStr = "&itemID=" + languages.appleLangIdForLanguage(
                langId)
            localizationTree = self.parseTreeForURL(languageAction +
                                                    "?open=true" +
                                                    languageParamStr)
            metadataLanguages[langId]['name'] = localizationTree.xpath(
                '//div[@id="proposedDisplayName"]//input/@value')[0]
            metadataLanguages[langId]['description'] = localizationTree.xpath(
                '//div[@id="proposedDescription"]//textarea/text()')[0].strip(
                )

            localizedPublicationName = localizationTree.xpath(
                '//div[@id="proposedPublicationName"]//input/@value')
            if len(localizedPublicationName) > 0:
                metadataLanguages[langId][
                    'publication name'] = localizedPublicationName[0]

        return InappMetadata(refname=inappReferenceName,
                             cleared=clearedForSale,
                             languages=metadataLanguages,
                             price_tier=priceTier,
                             textid=textId,
                             numericid=int(numericId),
                             hosted=hostedContent,
                             reviewnotes=reviewNotes)
Example #9
0
    def editVersion(self, dataDict, lang=None, versionString=None, filename_format=None):
        if dataDict == None or len(dataDict) == 0:  # nothing to change
            return

        if len(self.versions) == 0:
            self.getAppInfo()
        if len(self.versions) == 0:
            raise "Can't get application versions"
        if versionString == None:  # Suppose there's one or less editable versions
            versionString = next(
                (versionString for versionString, version in self.versions.items() if version["editable"]), None
            )
        if versionString == None:  # Suppose there's one or less editable versions
            raise "No editable version found"

        version = self.versions[versionString]
        if not version["editable"]:
            raise "Version " + versionString + " is not editable"

        languageId = languages.appleLangIdForLanguage(lang)
        languageCode = languages.langCodeForLanguage(lang)

        metadata = self.__parseAppVersionMetadata(version, lang)
        # activatedLanguages = metadata.activatedLanguages
        # nonactivatedLanguages = metadata.nonactivatedLanguages
        formData = {}  # metadata.formData[languageId]
        formNames = metadata.formNames[languageId]
        submitAction = metadata.submitActions[languageId]

        formData["save"] = "true"

        if "name" in dataDict:
            formData[formNames["appNameName"]] = dataDict["name"]

        if "description" in dataDict:
            if isinstance(dataDict["description"], basestring):
                formData[formNames["descriptionName"]] = dataDict["description"]
            elif isinstance(dataDict["description"], dict):
                if "file name format" in dataDict["description"]:
                    desc_filename_format = dataDict["description"]["file name format"]
                    replace_language = ALIASES.language_aliases.get(languageCode, languageCode)
                    descriptionFilePath = desc_filename_format.replace("{language}", replace_language)
                    formData[formNames["descriptionName"]] = open(descriptionFilePath, "r").read()

        if ("whatsNewName" in formNames) and ("whats new" in dataDict):
            if isinstance(dataDict["whats new"], basestring):
                formData[formNames["whatsNewName"]] = dataDict["whats new"]
            elif isinstance(dataDict["whats new"], dict):
                if "file name format" in dataDict["whats new"]:
                    desc_filename_format = dataDict["whats new"]["file name format"]
                    replace_language = ALIASES.language_aliases.get(languageCode, languageCode)
                    descriptionFilePath = desc_filename_format.replace("{language}", replace_language)
                    formData[formNames["whatsNewName"]] = open(descriptionFilePath, "r").read()

        if "keywords" in dataDict:
            if isinstance(dataDict["keywords"], basestring):
                formData[formNames["keywordsName"]] = dataDict["keywords"]
            elif isinstance(dataDict["keywords"], dict):
                if "file name format" in dataDict["keywords"]:
                    desc_filename_format = dataDict["keywords"]["file name format"]
                    replace_language = ALIASES.language_aliases.get(languageCode, languageCode)
                    descriptionFilePath = desc_filename_format.replace("{language}", replace_language)
                    formData[formNames["keywordsName"]] = open(descriptionFilePath, "r").read()

        if "support url" in dataDict:
            formData[formNames["supportURLName"]] = dataDict["support url"]

        if "marketing url" in dataDict:
            formData[formNames["marketingURLName"]] = dataDict["marketing url"]

        if "privacy policy url" in dataDict:
            formData[formNames["pPolicyURLName"]] = dataDict["privacy policy url"]

        iphoneUploadScreenshotForm = formNames["iphoneUploadScreenshotForm"]
        iphone5UploadScreenshotForm = formNames["iphone5UploadScreenshotForm"]
        ipadUploadScreenshotForm = formNames["ipadUploadScreenshotForm"]

        iphoneUploadScreenshotJS = iphoneUploadScreenshotForm.xpath("../following-sibling::script/text()")[0]
        iphone5UploadScreenshotJS = iphone5UploadScreenshotForm.xpath("../following-sibling::script/text()")[0]
        ipadUploadScreenshotJS = ipadUploadScreenshotForm.xpath("../following-sibling::script/text()")[0]

        self._uploadSessionData[DEVICE_TYPE.iPhone] = dict(
            {
                "action": iphoneUploadScreenshotForm.attrib["action"],
                "key": iphoneUploadScreenshotForm.xpath(".//input[@name='uploadKey']/@value")[0],
            },
            **self.__parseURLSFromScript(iphoneUploadScreenshotJS)
        )
        self._uploadSessionData[DEVICE_TYPE.iPhone5] = dict(
            {
                "action": iphone5UploadScreenshotForm.attrib["action"],
                "key": iphone5UploadScreenshotForm.xpath(".//input[@name='uploadKey']/@value")[0],
            },
            **self.__parseURLSFromScript(iphone5UploadScreenshotJS)
        )
        self._uploadSessionData[DEVICE_TYPE.iPad] = dict(
            {
                "action": ipadUploadScreenshotForm.attrib["action"],
                "key": ipadUploadScreenshotForm.xpath(".//input[@name='uploadKey']/@value")[0],
            },
            **self.__parseURLSFromScript(ipadUploadScreenshotJS)
        )

        self._uploadSessionId = iphoneUploadScreenshotForm.xpath('.//input[@name="uploadSessionID"]/@value')[0]

        # get all images
        for device_type in [DEVICE_TYPE.iPhone, DEVICE_TYPE.iPhone5, DEVICE_TYPE.iPad]:
            self._images[device_type] = self.__imagesForDevice(device_type)

        logging.debug(self._images)
        # logging.debug(formData)

        if "images" in dataDict:
            imagesActions = dataDict["images"]
            languageCode = languages.langCodeForLanguage(lang)

            for dType in imagesActions:
                device_type = None
                if dType.lower() == "iphone":
                    device_type = DEVICE_TYPE.iPhone
                elif dType.lower() == "iphone 5":
                    device_type = DEVICE_TYPE.iPhone5
                elif dType.lower() == "ipad":
                    device_type = DEVICE_TYPE.iPad
                else:
                    continue

                logging.debug("\n\n" + DEVICE_TYPE.deviceStrings[device_type] + "\n")

                deviceImagesActions = imagesActions[dType]
                if deviceImagesActions == "":
                    continue

                for imageAction in deviceImagesActions:
                    imageAction.setdefault("cmd")
                    imageAction.setdefault("indexes")
                    cmd = imageAction["cmd"]
                    indexes = imageAction["indexes"]
                    replace_language = ALIASES.language_aliases.get(languageCode, languageCode)
                    replace_device = ALIASES.device_type_aliases.get(
                        dType.lower(), DEVICE_TYPE.deviceStrings[device_type]
                    )

                    imagePath = filename_format.replace("{language}", replace_language).replace(
                        "{device_type}", replace_device
                    )
                    logging.debug("Looking for images at " + imagePath)

                    if (indexes == None) and ((cmd == "u") or (cmd == "r")):
                        indexes = []
                        for i in range(0, 5):
                            realImagePath = imagePath.replace("{index}", str(i + 1))
                            logging.debug("img path: " + realImagePath)
                            if os.path.exists(realImagePath):
                                indexes.append(i + 1)

                    logging.debug("indexes " + indexes.__str__())
                    logging.debug("Processing command " + imageAction.__str__())

                    if (cmd == "d") or (
                        cmd == "r"
                    ):  # delete or replace. To perform replace we need to delete images first
                        deleteIndexes = [img["id"] for img in self._images[device_type]]
                        if indexes != None:
                            deleteIndexes = [deleteIndexes[idx - 1] for idx in indexes]

                        for imageIndexToDelete in deleteIndexes:
                            img = next(im for im in self._images[device_type] if im["id"] == imageIndexToDelete)
                            self.__deleteScreenshot(device_type, img["id"])

                        self._images[device_type] = self.__imagesForDevice(device_type)

                    if (cmd == "u") or (cmd == "r"):  # upload or replace
                        currentIndexes = [img["id"] for img in self._images[device_type]]

                        if indexes == None:
                            continue

                        indexes = sorted(indexes)
                        for i in indexes:
                            realImagePath = imagePath.replace("{index}", str(i))
                            if os.path.exists(realImagePath):
                                self.__uploadScreenshot(device_type, realImagePath)

                        self._images[device_type] = self.__imagesForDevice(device_type)

                        if cmd == "r":
                            newIndexes = [img["id"] for img in self._images[device_type]][len(currentIndexes) :]

                            if len(newIndexes) == 0:
                                continue

                            for i in indexes:
                                currentIndexes.insert(i - 1, newIndexes.pop(0))

                            self.__sortScreenshots(device_type, currentIndexes)
                            self._images[device_type] = self.__imagesForDevice(device_type)

                    if cmd == "s":  # sort
                        if indexes == None or len(indexes) != len(self._images[device_type]):
                            continue
                        newIndexes = [self._images[device_type][i - 1]["id"] for i in indexes]

                        self.__sortScreenshots(device_type, newIndexes)
                        self._images[device_type] = self.__imagesForDevice(device_type)

        formData["uploadSessionID"] = self._uploadSessionId
        # formData['uploadKey'] = self._uploadSessionData[DEVICE_TYPE.iPhone5]['key']

        postFormResponse = requests.post(ITUNESCONNECT_URL + submitAction, data=formData, cookies=cookie_jar)

        if postFormResponse.status_code != 200:
            raise "Wrong response from iTunesConnect. Status code: " + str(postFormResponse.status_code)

        if len(postFormResponse.text) > 0:
            logging.error("Save information failed. " + postFormResponse.text)