""" This is the Adobe Offline Package downloader for Windows. Download package only! (except acrobat pro) Installer not included Based on https://github.com/Drovosek01/adobe-packager """ import os import string import random import argparse import json import locale import ctypes import sys import operator from collections import OrderedDict from xml.etree import ElementTree as ET try: import requests except ImportError: sys.exit("""You need requests module! install it from https://pypi.org/project/requests/ or run: pip3 install requests.""") try: from tqdm.auto import tqdm except ImportError: sys.exit("""You need tqdm module! install it from https://pypi.org/project/tqdm/ or run: pip3 install tqdm.""") VERSION = 1 VERSION_STR = '0.0.3' CODE_QUALITY = 'Really_AWFUL' ADOBE_PRODUCTS_XML_URL = 'https://prod-rel-ffc-ccm.oobesaas.adobe.com/adobe-ffc-external/core/v{urlVersion}/products/all?_type=xml&channel=ccm&channel=sti&platform={installPlatform}&productType=Desktop' ADOBE_APPLICATION_JSON_URL = 'https://cdn-ffc.oobesaas.adobe.com/core/v3/applications' ADOBE_REQ_HEADERS = { 'X-Adobe-App-Id': 'accc-apps-panel-desktop', 'User-Agent': 'Adobe Application Manager 2.0', 'X-Api-Key': 'CC_HD_ESD_1_0', 'Cookie': 'fg=' + ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(26)) + '======' } ADOBE_DL_HEADERS = { 'User-Agent': 'Creative Cloud' } session = requests.sessions.Session() def show_version(): ye = int((32 - len(VERSION_STR)) / 2) print('=================================') print('= Adobe CC Package Downloader =') print('{} {} {}\n'.format('=' * ye, VERSION_STR, '=' * (31 - len(VERSION_STR) - ye))) def get_install_dir(): return os.path.join(os.environ["PROGRAMFILES"], "Adobe") def questiony(question: str) -> bool: """Question prompt default Y.""" reply = None while reply not in ("", "y", "n"): reply = input(f"{question} (Y/n): ").lower() return (reply in ("", "y")) def url_version(): urlVersion = None if args.urlVersion: if args.urlVersion.lower() == "v4" or args.urlVersion == "4": urlVersion = 4 elif args.urlVersion.lower() == "v5" or args.urlVersion == "5": urlVersion = 5 elif args.urlVersion.lower() == "v6" or args.urlVersion == "6": urlVersion = 6 else: print('Invalid argument "{}" for {}! Please select from version list below\n'.format(args.urlVersion, 'URL version')) urlVersion = None while not urlVersion: versions = { 'v4' : 'URL version 4', 'v5' : 'URL version 5', 'v6' : 'URL version 6', } print('Available URL versions') for p, v in versions.items(): print('[{}]{}{}'.format(p, (12 - len(p)) * ' ', v)) val = input('\nPlease enter the URL version(v4/v5/v6) for downloading products.xml, or nothing for v6: ') or 'v6' if val == 'v4' or val == '4': urlVersion = 4 elif val == 'v5' or val == '5': urlVersion = 5 elif val == 'v6' or val == '6': urlVersion = 6 else: print('Invalid URL version: {}\n'.format(val)) return urlVersion def app_platform(): winPlatforms = { 'win32' : '32 Bit Windows', 'win64' : '64 Bit Windows', 'winarm64' : 'Windows ARM', } appPlatform = None if args.appPlatform: if args.appPlatform in winPlatforms: appPlatform = args.appPlatform print('\nUsing provided version: ' + appPlatform) else: print('Invalid argument "{}" for {}! Please select form list below\n'.format(args.appPlatform, 'application platform')) appPlatform = None while not appPlatform: print('Available Platforms') for p, v in winPlatforms.items(): print('[{}]{}{}'.format(p, (12 - len(p)) * ' ', v)) val = input('\nPlease enter application platform (eg win32), or nothing for win64: ') or 'win64' if val in winPlatforms: appPlatform = val else: print('Invalid platform: {}\n'.format(val)) return appPlatform def product_code(sapCodes): selectedCode = args.sapCode if not selectedCode: for s, d in sapCodes.items(): print('[{}]{}{}'.format(s, (10 - len(s)) * ' ', d)) while selectedCode is None: val = input('\nPlease enter the SAP Code of the desired product from the list above: ').upper() if products.get(val): selectedCode = val elif val == '': print('No product selected! Please use a value from the list above.') else: print('{} is not available! Please use a value from the list above.'.format(val)) return selectedCode def product_version(product, versions): version = None if (args.version): if versions.get(args.version): print('\nUsing provided version: ' + args.version) version = args.version else: print('\nProvided version not found: ' + args.version) print('') if not version: lastVersion = None for v in reversed(versions.values()): if v['buildGuid'] or v['manifestURL']: print('{} Platform: {} - {}'.format(product['displayName'], v['appPlatform'], v['productVersion'])) lastVersion = v['productVersion'] while version is None: val = input('\nPlease enter the desired version. Nothing for ' + lastVersion + ': ') or lastVersion if versions.get(val): version = val else: print('{} is not a valid version. Please use a value from the list above.'.format(val)) print('\nPrepare to download Adobe {}, version {}'.format(product['displayName'], version)) return version def install_language(supportedLangs): if 'mul' in supportedLangs: supportedLangs[supportedLangs.index('mul')] = 'All' else: if 'All' not in supportedLangs: supportedLangs.append('All') supportedLangs.sort() # Detecting Current set default Os language. Fixed. windll = ctypes.windll.kernel32 osLang = locale.windows_locale[ windll.GetUserDefaultUILanguage() ] if args.osLanguage: osLang = args.osLanguage defLang = 'All' if osLang in supportedLangs: defLang = osLang installLanguage = None if args.installLanguage: if args.installLanguage in supportedLangs: print('\nUsing provided language: ' + args.installLanguage) installLanguage = args.installLanguage else: print('\nProvided language not available: ' + args.installLanguage) if not installLanguage: print('\nAvailable languages: {}'.format(', '.join(supportedLangs))) while installLanguage is None: val = input( f'\nPlease enter the desired install language, or nothing for [{defLang}]: ') or defLang if len(val) == 5: val = val[0:2].lower() + val[2] + val[3:5].upper() elif len(val) == 3: val = val[0].upper() + val[1:].lower() if val in supportedLangs: installLanguage = val else: print( '{} is not available. Please use a value from the list above.'.format(val)) if osLang != installLanguage: if installLanguage != 'All': while osLang not in supportedLangs: print('Could not detect your default Language.') osLang = input( f'\nPlease enter the your OS Language, or nothing for [{installLanguage}]: ') or installLanguage if osLang not in supportedLangs: print( '{} is not available. Please use a value from the list above.'.format(osLang)) print('\nDownloading packages with {} language'.format(installLanguage)) return installLanguage def product_icons(elem): productIcons = [] if len(elem.find('productIcons')): for icon in elem.findall('productIcons/icon'): productIcons.append(icon.text) return productIcons def product_languages(elem): supportedLang = [] if len(elem.find('locales')): for locale in elem.findall('locales/locale'): supportedLang.append(locale.get('name')) return supportedLang def parse_products_xml(products_url, url_version, allowed_platform, selected_platform): # get xml data #print('Using existing products.xml\n') #products_xml = ET.parse('products.xml') print('Downloading product data\n') response = session.get(products_url, stream=True, headers=ADOBE_REQ_HEADERS) response.encoding = 'utf-8' products_xml = ET.fromstring(response.content) #to study #with open('products.xml', 'wb+') as f: # f.write(response.content) """Parsing the XML data.""" cdn = products_xml.find('.//*/cdn/secure').text sti_products = products_xml.findall(".//*[@name='sti']/products/product") ccm_products = products_xml.findall(".//*[@name='ccm']/products/product") allProducts = {} for product in sti_products: sapCode = product.get('id') displayName = product.find('displayName').text if not allProducts.get(sapCode): allProducts[sapCode] = { 'hidden' : True, 'displayName': displayName, 'sapCode': sapCode, 'versions': OrderedDict() } platforms = product.findall("platforms/platform") if platforms is None: allProducts.pop(sapCode, None) continue for platform in platforms: appPlatform = platform.attrib['id'] # filtered by allowed platforms if appPlatform in allowed_platform: languageSet = platform.findall("languageSet") if len(languageSet): for ls in languageSet: productInfo = ls.attrib productVersion = productInfo.get('productVersion') if productVersion is not None: allProducts[sapCode]['versions'][productVersion] = { 'sapCode': sapCode, 'displayName': displayName, 'appPlatform' : appPlatform, 'productVersion' : productInfo.get('productVersion'), 'supportedLanguages' : product_languages(ls), 'productIcons' : product_icons(product), 'buildGuid' : productInfo.get('buildGuid'), } else: allProducts.pop(sapCode, None) else: allProducts.pop(sapCode, None) for product in ccm_products: sapCode = product.get('id') displayName = product.find('displayName').text if not allProducts.get(sapCode): allProducts[sapCode] = { 'hidden' : False, 'displayName': displayName, 'sapCode': sapCode, 'versions': OrderedDict() } # filtered by selected platform languageSet = product.findall("platforms/platform[@id='" + selected_platform + "']/languageSet") if len(languageSet): for ls in languageSet: manifestURL = ls.find('urls/manifestURL') if manifestURL is not None: manifestURL = manifestURL.text productInfo = ls.attrib productVersion = productInfo.get('productVersion') if productVersion is not None: allProducts[sapCode]['versions'][productVersion] = { 'sapCode': sapCode, 'displayName': displayName, 'appPlatform' : selected_platform, 'productVersion' : productInfo.get('productVersion'), 'supportedLanguages' : product_languages(ls), 'productIcons' : product_icons(product), 'buildGuid' : productInfo.get('buildGuid'), 'manifestURL' : manifestURL } else: if url_version >= 5: allProducts.pop(sapCode, None) else: if url_version >= 5: allProducts.pop(sapCode, None) return allProducts, cdn def get_products(): selectedVersion = url_version() print('\nUsing URL version {}\n'.format(selectedVersion)) selectedPlatform = app_platform() print('\nGetting {} products\n'.format(selectedPlatform)) if args.Auth: ADOBE_REQ_HEADERS['Authorization'] = args.Auth allowedPlatforms = [selectedPlatform] productsPlatform = selectedPlatform if selectedPlatform == 'win64': allowedPlatforms.append('win32') productsPlatform += ',win32' products_xml_url = ADOBE_PRODUCTS_XML_URL.format(urlVersion=selectedVersion, installPlatform=productsPlatform) # download and parse product xml products, cdn = parse_products_xml(products_xml_url, selectedVersion, allowedPlatforms, selectedPlatform) sapCodes = {} for p in products.values(): if not p['hidden']: versions = p['versions'] lastVersion = None for v in reversed(versions.values()): if v['buildGuid'] or v['manifestURL']: lastVersion = v['productVersion'] if lastVersion: sapCodes[p['sapCode']] = p['displayName'] print(str(len(sapCodes)) + ' products found:') if args.sapCode and products.get(args.sapCode.upper()) is None: print('\nProvided SAP Code not found in products: ' + args.sapCode) exit(1) return products, cdn, sapCodes, selectedPlatform def get_download_path(): """Ask for desired download folder""" if (args.destination): print('\nUsing provided destination: ' + args.destination + '\n') dest = args.destination else: print('\nDownloaded file will be put on current location.\n') dest = os.path.dirname(os.path.realpath(__name__)) return dest def download_progress(url, dest_dir, prefix=''): response = session.get( url, stream=True, headers=ADOBE_REQ_HEADERS) total_size_in_bytes = int( response.headers.get('content-length', 0)) if (total_size_in_bytes > 0): block_size = 1024 # 1 Kibibyte progress_bar = tqdm(total=total_size_in_bytes, unit='iB', unit_scale=True) if prefix: name = prefix + os.path.basename(url) dest_dir = dest_dir + '\\' + name with open(dest_dir, 'wb') as file: for data in response.iter_content(block_size): progress_bar.update(len(data)) file.write(data) progress_bar.close() if total_size_in_bytes != 0 and progress_bar.n != total_size_in_bytes: print("ERROR, something went wrong") def icons_download(product, dest_dir): """Download product icons""" print('Downloading product icons\n') icon_dir = os.path.join(dest_dir, 'icons') os.makedirs(icon_dir, exist_ok=True) icons = product['productIcons'] prefix = product['sapCode'].lower() for url in icons: download_progress(url, icon_dir, prefix) def file_download(url, dest_dir, s, v, name=None): """Download a product package""" if not name: name = os.path.basename(url) print('[{}_{}] Downloading {}'.format(s, v, name)) file_path = os.path.join(dest_dir, name) response = session.head(url, stream=True, headers=ADOBE_DL_HEADERS) total_size_in_bytes = int( response.headers.get('content-length', 0)) if (args.skipExisting and os.path.isfile(file_path) and os.path.getsize(file_path) == total_size_in_bytes): print('[{}_{}] {} already exists, skipping\n'.format(s, v, name)) else: download_progress(url, file_path) def download_acrobat(appInfo, cdn): """Download manifest.xml""" response = session.get(cdn + appInfo['manifestURL'], stream=True, headers=ADOBE_REQ_HEADERS) response.encoding = 'utf-8' # to study with open('manifest.xml', 'wb+') as f: f.write(response.content) manifest = ET.fromstring(response.text) # for full app assetList = manifest.find('app_modes/app_mode/asset_list') for asset in assetList: if asset.find('asset_type').text == 'EXE': downloadURL = asset.find('asset_path').text # for update #downloadURL = manifest.find('asset_list/asset/asset_path').text dest = get_download_path() sapCode = appInfo['sapCode'] version = appInfo['productVersion'] name = os.path.basename(downloadURL) print('') print('dest: ' + os.path.join(dest, name)) print('\nDownloading...\n') print('[{}_{}] Selected 1 package'.format(sapCode, version)) file_download(downloadURL, dest, sapCode, version, name) print('\nInstaller successfully downloaded.') return def get_application_json(buildGuid): """Retrieve JSON.""" headers = ADOBE_REQ_HEADERS.copy() headers['x-adobe-build-guid'] = buildGuid response = session.get(ADOBE_APPLICATION_JSON_URL, headers=headers) response.encoding = 'utf-8' return json.loads(response.content) def save_application_json(package_dir, json_data): app_json_path = os.path.join(package_dir, 'application.json') with open(app_json_path, 'w+') as file: json.dump(json_data, file, separators=(',', ':')) def write_driver_xml(jsonData, products_dir, prefix=""): driver = ET.Element('DriverInfo') product = ET.SubElement(driver, 'ProductInfo') prd_name = ET.SubElement(product, 'Name') prd_name.text = 'Adobe ' + jsonData['Name'] sap_code = ET.SubElement(product, 'SAPCode') sap_code.text = jsonData['SAPCode'] cod_ver = ET.SubElement(product, 'CodexVersion') cod_ver.text = jsonData['CodexVersion'] base_ver = ET.SubElement(product, 'BaseVersion') base_ver.text = jsonData['BaseVersion'] platform = ET.SubElement(product, 'Platform') platform.text = jsonData['Platform'] esd_dir = ET.SubElement(product, 'EsdDirectory') esd_dir.text = './' + jsonData['SAPCode'] if 'Dependencies' in jsonData: dep_top = ET.SubElement(product, 'Dependencies') for dependency in jsonData['Dependencies']['Dependency']: dep_sub = ET.SubElement(dep_top, 'Dependency') dep_code = ET.SubElement(dep_sub, 'SAPCode') dep_code.text = dependency['SAPCode'] dep_base = ET.SubElement(dep_sub, 'BaseVersion') dep_base.text = dependency['BaseVersion'] dep_dir = ET.SubElement(dep_sub, 'EsdDirectory') dep_dir.text = './' + dependency['SAPCode'] tree = ET.ElementTree(driver) ET.indent(tree, space=" ", level=0) xml_file = os.path.join(products_dir, prefix + 'Driver.xml') tree.write(xml_file, encoding='utf-8', xml_declaration=True) def condition_test(package, language, ProcessorFamily, condition): packageType = ProcessorFamily if 'ProcessorFamily' in package: packageType = package['ProcessorFamily'] if '==' in condition: testSubject, testValue = condition.split('==') testOption = operator.eq elif '!=' in condition: testSubject, testValue = condition.split('!=') testOption = operator.ne elif '<=' in condition: testSubject, testValue = condition.split('<=') testOption = operator.le elif '>=' in condition: testSubject, testValue = condition.split('>=') testOption = operator.ge elif '>' in condition: testSubject, testValue = condition.split('>') testOption = operator.gt elif '<' in condition: testSubject, testValue = condition.split('<') testOption = operator.lt # start testing if testSubject.strip() == '[OSProcessorFamily]': return testOption(testValue.strip(), ProcessorFamily) elif testSubject.strip() == '[OSVersion]': getWinVer = sys.getwindowsversion() winVer = '{}.{}'.format(getWinVer.major, getWinVer.minor) return testOption(testValue.strip(), winVer) elif testSubject.strip() == '[installLanguage]' and packageType == ProcessorFamily: if language.lower() == 'all' or language.lower() == 'mul': return True else: return testOption(testValue.strip(), language) else: # alway true on unset return True def condition_filter(package, language, ProcessorFamily): conditions = [] conditionString = package['Condition'].strip() result = False if '&&' in conditionString: conditions = conditionString.split('&&') for condition in conditions: result = True if condition_test(package, language, ProcessorFamily, condition) == False: result = False break elif '||' in conditionString: conditions = conditionString.split('||') for condition in conditions: result = False if condition_test(package, language, ProcessorFamily, condition) == True: result = True break else: result = condition_test(package, language, ProcessorFamily, conditionString) return result def language_for_premiere(language): if '_' in language: main, locale = language.split('_') return '-esl_lp_' + main return language def package_filter(package, language, platform): coreCount = 0 noneCoreCount = 0 urlPath = [] newPackage = [] ProcessorFamily = '64-bit' if platform == 'win32': ProcessorFamily = '32-bit' for pkg in package: if pkg.get('Type') and pkg['Type'] == 'core': if 'Condition' in pkg: if condition_filter(pkg, language, ProcessorFamily) == True: newPackage.append(pkg) urlPath.append(pkg['Path']) coreCount += 1 elif 'ProcessorFamily' in pkg: if platform == 'win32': if pkg['ProcessorFamily'] == ProcessorFamily: newPackage.append(pkg) urlPath.append(pkg['Path']) coreCount += 1 else: newPackage.append(pkg) urlPath.append(pkg['Path']) coreCount += 1 else: newPackage.append(pkg) urlPath.append(pkg['Path']) coreCount += 1 else: if 'Condition' in pkg: if condition_filter(pkg, language, ProcessorFamily) == True: newPackage.append(pkg) urlPath.append(pkg['Path']) noneCoreCount += 1 elif 'ProcessorFamily' in pkg: if platform == 'win32': if pkg['ProcessorFamily'] == ProcessorFamily: newPackage.append(pkg) urlPath.append(pkg['Path']) noneCoreCount += 1 else: newPackage.append(pkg) urlPath.append(pkg['Path']) noneCoreCount += 1 else: # for premiere pro if '-esl_lp_' in pkg['PackageName']: if language_for_premiere(language) in pkg['PackageName']: newPackage.append(pkg) urlPath.append(pkg['Path']) noneCoreCount += 1 else: newPackage.append(pkg) urlPath.append(pkg['Path']) noneCoreCount += 1 print('Selected {} core packages and {} non-core packages'.format(coreCount, noneCoreCount)) return urlPath, newPackage def dependencies_download(dep_data, products_dir, language, selectedPlatform): dep_dir = os.path.join(products_dir, dep_data['sapCode']) os.makedirs(dep_dir, exist_ok=True) dep_data = list(dep_data['versions'].values()) firstItem = dep_data[0] dep_json = get_application_json(firstItem['buildGuid']) package_download(dep_json, dep_dir, language, selectedPlatform) def package_download(app_json, package_dir, language, selectedPlatform): allPackages = app_json['Packages']['Package'] cdn = app_json['Cdn']['Secure'] sapCode = app_json['SAPCode'] version = app_json['ProductVersion'] urls, package = package_filter(allPackages, language, selectedPlatform) # module filter if 'Modules' in app_json: packageNames = [] for names in package: packageNames.append(names['PackageName']) allModules = app_json['Modules']['Module'] newModules = [] for module in allModules: refPackage = module['ReferencePackages']['ReferencePackage'] for names in refPackage: if names in packageNames: newModules.append(module) app_json['Modules']['Module'] = newModules # filtered json data app_json['Packages']['Package'] = package print('\nCreate application.json\n') save_application_json(package_dir, app_json) for url in urls: file_download(cdn + url, package_dir, sapCode, version) def run_ccdl(products, cdn, sapCodes, selectedPlatform): """Run Main execution.""" # get product list sapCode = product_code(sapCodes) product = products.get(sapCode) # version select versions = product['versions'] selectedVersion = product_version(product, versions) # product to download prodInfo = versions[selectedVersion] # language select supportedLangs = prodInfo['supportedLanguages'] installLanguage = install_language(supportedLangs) # download by manifest url if sapCode == 'APRO': download_acrobat(prodInfo, cdn) return # main product print('\nPrepare to download Adobe {}-{}-{}-{}'.format(prodInfo['displayName'], prodInfo['productVersion'], installLanguage, prodInfo['appPlatform'])) dest = get_download_path() #download icons icons_download(prodInfo, dest) # create products directory products_dir = os.path.join(dest, 'products') os.makedirs(products_dir, exist_ok=True) package_dir = os.path.join(products_dir, sapCode) os.makedirs(package_dir, exist_ok=True) app_json = get_application_json(prodInfo['buildGuid']) package_download(app_json, package_dir, installLanguage, selectedPlatform) if 'Dependencies' in app_json: for dependency in app_json['Dependencies']['Dependency']: depSap = dependency['SAPCode'] depPackage = products.get(depSap) dependencies_download(depPackage, products_dir, installLanguage, selectedPlatform) print('\nGenerating driver.xml') prefix = app_json['SAPCode'] + '-' write_driver_xml(app_json, products_dir, prefix) return if __name__ == '__main__': show_version() parser = argparse.ArgumentParser() parser.add_argument('-l', '--installLanguage', help='Language code (eg. en_US)', action='store') parser.add_argument('-o', '--osLanguage', help='OS Language code (eg. en_US)', action='store') parser.add_argument('-p', '--appPlatform', help='Application platform (eg. win64)', action='store') parser.add_argument('-s', '--sapCode', help='SAP code for desired product (eg. PHSP)', action='store') parser.add_argument('-v', '--version', help='Version of desired product (eg. 21.0.3)', action='store') parser.add_argument('-d', '--destination', help='Directory to download installation files to', action='store') parser.add_argument('-u', '--urlVersion', help="Get app info from v4/v5/v6 url (eg. v6)", action='store') parser.add_argument('-A', '--Auth', help='Add a bearer_token to to authenticate your account, e.g. downloading Xd', action='store') parser.add_argument('--noRepeatPrompt', help="Don't prompt for additional downloads", action='store_true') parser.add_argument('-x', '--skipExisting', help="Skip existing files, e.g. resuming failed downloads", action='store_true') args = parser.parse_args() products, cdn, sapCodes, selectedPlatform = get_products() while True: run_ccdl(products, cdn, sapCodes, selectedPlatform) if args.noRepeatPrompt or not questiony('\n\nDo you want to download another package'): break