From 6ea1caf4f55a25c799a63c7e4c685012cf08e077 Mon Sep 17 00:00:00 2001 From: Kamte <43906188+ckamte@users.noreply.github.com> Date: Tue, 27 Feb 2024 20:14:11 +0630 Subject: [PATCH] Create ccdl-win.py Download adobe application --- ccdl-win.py | 632 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 632 insertions(+) create mode 100644 ccdl-win.py diff --git a/ccdl-win.py b/ccdl-win.py new file mode 100644 index 0000000..7b38e7e --- /dev/null +++ b/ccdl-win.py @@ -0,0 +1,632 @@ +#!/usr/bin/env python3 +""" +This is the Adobe Offline Package downloader for Windows. + +""" + +import argparse +import json +import locale +import os +import random +import string +import sys +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.""") + +session = requests.sessions.Session() + +VERSION = 4 +VERSION_STR = '0.2.0' +CODE_QUALITY = 'Mildly_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' + +DRIVER_XML = ''' + + Adobe {name} + {sapCode} + {version} + {baseVersion} + {installPlatform} + ./{sapCode} + +{dependencies} + + + + {installDir}\Adobe + {language} + + +''' + +DRIVER_XML_DEPENDENCY = ''' + {sapCode} + {version} + ./{sapCode} + ''' + +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' +} + +def get_install_dir(): + return os.environ["ProgramFiles"] + +def r(url, headers=ADOBE_REQ_HEADERS): + """Retrieve a from a url as a string.""" + req = session.get(url, headers=headers) + req.encoding = 'utf-8' + #with open('1.xml', 'wb+') as f: + # f.write(req.content) + return req.text + + +def get_products_xml(adobeurl): + """First stage of parsing the XML.""" + print('Source URL is: ' + adobeurl) + return ET.fromstring(r(adobeurl)) + + +def parse_products_xml(products_xml, urlVersion): + """2nd stage of parsing the XML.""" + if urlVersion == 6: + prefix = 'channels/' + else: + prefix = '' + cdn = products_xml.find(prefix + 'channel/cdn/secure').text + products = {} + parent_map = {c: p for p in products_xml.iter() for c in p} + for p in products_xml.findall(prefix + 'channel/products/product'): + sap = p.get('id') + hidden = parent_map[parent_map[p]].get('name') != 'ccm' + displayName = p.find('displayName').text + productVersion = p.get('version') + if not products.get(sap): + products[sap] = { + 'hidden': hidden, + 'displayName': displayName, + 'sapCode': sap, + 'versions': OrderedDict() + } + + icons = [] + if p.find('productIcons'): + for icon in p.findall('productIcons/icon'): + icons.append(icon.text) + + for pf in p.findall('platforms/platform'): + baseVersion = pf.find('languageSet').get('baseVersion') + buildGuid = pf.find('languageSet').get('buildGuid') + appplatform = pf.get('id') + dependencies = list(pf.findall('languageSet/dependencies/dependency')) + + if sap == 'APRO': + baseVersion = productVersion + if urlVersion == 4 or urlVersion == 5: + productVersion = pf.find('languageSet/nglLicensingInfo/appVersion').text + if urlVersion == 6: + for b in products_xml.findall('builds/build'): + if b.get("id") == sap and b.get("version") == baseVersion: + productVersion = b.find('nglLicensingInfo/appVersion').text + break + buildGuid = pf.find('languageSet/urls/manifestURL').text + # This is actually manifest URL + + products[sap]['versions'][productVersion] = { + 'sapCode': sap, + 'baseVersion': baseVersion, + 'productVersion': productVersion, + 'productIcons': icons, + 'apPlatform': appplatform, + 'dependencies': [{ + 'sapCode': d.find('sapCode').text, 'version': d.find('baseVersion').text + } for d in dependencies], + 'buildGuid': buildGuid + } + return products, cdn + + +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 questionn(question: str) -> bool: + """Question prompt default N.""" + reply = None + while reply not in ("", "y", "n"): + reply = input(f"{question} (y/N): ").lower() + return (reply in ("y", "Y")) + + +def get_application_json(buildGuid): + """Retrieve JSON.""" + headers = ADOBE_REQ_HEADERS.copy() + headers['x-adobe-build-guid'] = buildGuid + return json.loads(r(ADOBE_APPLICATION_JSON_URL, headers)) + + +def get_download_path(): + """Ask for desired download folder""" + if (args.destination): + print('\nUsing provided destination: ' + args.destination) + dest = args.destination + else: + print('\nDownloaded file will be put on current location.') + dest = os.path.dirname(os.path.realpath(__name__)) + return dest + + +def download_icons(icons, icon_dir): + print('Downloading application icons') + if not os.path.exists(icon_dir): + os.makedirs(icon_dir) + for url in icons: + 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) + name = url.split('/')[-1].split('?')[0] + file_path = os.path.join(icon_dir, name) + with open(file_path, 'wb') as file: + for data in response.iter_content(block_size): + progress_bar.update(len(data)) + file.write(data) + progress_bar.close() + + +def download_file(url, product_dir, s, v, name=None): + """Download a file""" + if not name: + name = url.split('/')[-1].split('?')[0] + print('Url is: ' + url) + print('[{}_{}] Downloading {}'.format(s, v, name)) + file_path = os.path.join(product_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'.format(s, v, name)) + else: + response = session.get( + url, stream=True, headers=ADOBE_REQ_HEADERS) + total_size_in_bytes = int( + response.headers.get('content-length', 0)) + block_size = 1024 # 1 Kibibyte + progress_bar = tqdm(total=total_size_in_bytes, + unit='iB', unit_scale=True) + with open(file_path, '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 download_APRO(appInfo, cdn): + """Download APRO""" + manifest = get_products_xml(cdn + appInfo['buildGuid']) + downloadURL = manifest.find('asset_list/asset/asset_path').text + dest = get_download_path() + sapCode = appInfo['sapCode'] + version = appInfo['productVersion'] + name = '{}_{}_{}.exe'.format(sapCode, version, appInfo['apPlatform']) + print('') + print('sapCode: ' + sapCode) + print('version: ' + version) + print('installLanguage: ' + 'ALL') + print('dest: ' + os.path.join(dest, name)) + + print('\nDownloading...\n') + + print('[{}_{}] Selected 1 package'.format(sapCode, version)) + download_file(downloadURL, dest, sapCode, version, name) + + print('\nInstaller successfully downloaded.') + return + + +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_products(): + selectedVersion = None + if args.urlVersion: + if args.urlVersion.lower() == "v4" or args.urlVersion == "4": + selectedVersion = 4 + elif args.urlVersion.lower() == "v5" or args.urlVersion == "5": + selectedVersion = 5 + elif args.urlVersion.lower() == "v6" or args.urlVersion == "6": + selectedVersion = 6 + else: + print('Invalid argument "{}" for {}'.format(args.urlVersion, 'URL version')) + exit(1) + + while not selectedVersion: + 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': + selectedVersion = 4 + elif val == 'v5' or val == '5': + selectedVersion = 5 + elif val == 'v6' or val == '6': + selectedVersion = 6 + else: + print('Invalid URL version: {}'.format(val)) + print('') + + if args.Auth: + ADOBE_REQ_HEADERS['Authorization'] = args.Auth + + allowedPlatforms = ['win64', 'win32'] + + productsPlatform = 'win32,win64' + adobeurl = ADOBE_PRODUCTS_XML_URL.format(urlVersion=selectedVersion, installPlatform=productsPlatform) + + print('\nDownloading products.xml\n') + products_xml = get_products_xml(adobeurl) + + print('\nParsing products.xml\n') + products, cdn = parse_products_xml(products_xml, selectedVersion) + + print('CDN: ' + cdn) + sapCodes = {} + for p in products.values(): + if not p['hidden']: + versions = p['versions'] + lastv = None + for v in reversed(versions.values()): + if v['buildGuid'] and v['apPlatform'] in allowedPlatforms: + lastv = v['productVersion'] + if lastv: + 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, allowedPlatforms + +''' +def condition_check(condition, language): + if 'installLanguage' in condition: + if language == 'ALL': + return True + else: + if language in condition: + return True + + if 'OSProcessorFamily' in condition: + # lock on 64 bit os + processor = '64-bit' + if processor in condition: + return True + + return False + +def language_for_premiere(language): + if '_' in language: + main, locale = language.split('_') + return '-esl_lp_' + main + +def package_filter(package, language): + newPackage = [] + + for pkg in package: + if pkg.get('Type') and pkg['Type'] == 'core': + if 'Condition' in pkg: + condition = pkg['Condition'] + if condition_check(condition, language): + newPackage.append(pkg) + else: + newPackage.append(pkg) + + else: + if 'Condition' in pkg: + condition = pkg['Condition'] + if condition_check(condition, language): + newPackage.append(pkg) + else: + # for premiere pro + if '-esl_lp_' in pkg['PackageName']: + if language_for_premiere(language) in pkg['PackageName']: + newPackage.append(pkg) + + else: + newPackage.append(pkg) + return newPackage +''' + +def run_ccdl(products, cdn, sapCodes, allowedPlatforms): + """Run Main execution.""" + sapCode = args.sapCode + if not sapCode: + for s, d in sapCodes.items(): + print('[{}]{}{}'.format(s, (10 - len(s)) * ' ', d)) + + while sapCode is None: + val = input( + '\nPlease enter the SAP Code of the desired product (eg. PHSP for Photoshop): ').upper() or 'PHSP' + if products.get(val): + sapCode = val + else: + print( + '{} is not a valid SAP Code. Please use a value from the list above.'.format(val)) + + product = products.get(sapCode) + versions = 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: + lastv = None + for v in reversed(versions.values()): + + if v['buildGuid'] and v['apPlatform'] in allowedPlatforms: + print('{} Platform: {} - {}'.format(product['displayName'], v['apPlatform'], v['productVersion'])) + lastv = v['productVersion'] + + while version is None: + val = input('\nPlease enter the desired version. Nothing for ' + lastv + ': ') or lastv + if versions.get(val): + version = val + else: + print('{} is not a valid version. Please use a value from the list above.'.format(val)) + print('') + + if sapCode == 'APRO': + download_APRO(versions[version], cdn) + return + + # TODO: Parase languages in the xml + langs = ['en_US', 'en_GB', 'en_IL', 'en_AE', 'es_ES', 'es_MX', 'pt_BR', 'fr_FR', 'fr_CA', 'fr_MA', 'it_IT', 'de_DE', 'nl_NL', + 'ru_RU', 'uk_UA', 'zh_TW', 'zh_CN', 'ja_JP', 'ko_KR', 'pl_PL', 'hu_HU', 'cs_CZ', 'tr_TR', 'sv_SE', 'nb_NO', 'fi_FI', 'da_DK', 'ALL'] + # Detecting Current set default Os language. Fixed. + defLang = locale.getlocale()[0] + if not defLang: + defLang = 'en_US' + + oslang = None + if args.osLanguage: + oslang = args.osLanguage + elif defLang: + oslang = defLang + + if oslang in langs: + deflang = oslang + else: + deflang = 'en_US' + + installLanguage = None + if args.installLanguage: + if args.installLanguage in langs: + print('\nUsing provided language: ' + args.installLanguage) + installLanguage = args.installLanguage + else: + print('\nProvided language not available: ' + args.installLanguage) + + if not installLanguage: + print('Available languages: {}'.format(', '.join(langs))) + 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.upper() + if val in langs: + 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 langs: + 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 langs: + print( + '{} is not available. Please use a value from the list above.'.format(oslang)) + + dest = get_download_path() + + print('') + + prodInfo = versions[version] + prods_to_download = [] + dependencies = prodInfo['dependencies'] + for d in dependencies: + firstGuid = buildGuid = None + for p in products[d['sapCode']]['versions']: + if products[d['sapCode']]['versions'][p]['baseVersion'] == d['version']: + if not firstGuid: + firstGuid = products[d['sapCode']]['versions'][p]['buildGuid'] + if products[d['sapCode']]['versions'][p]['apPlatform'] in allowedPlatforms: + buildGuid = products[d['sapCode']]['versions'][p]['buildGuid'] + break + if not buildGuid: + buildGuid = firstGuid + prods_to_download.append({'sapCode': d['sapCode'], 'version': d['version'], + 'buildGuid': buildGuid}) + + prods_to_download.insert( + 0, {'sapCode': prodInfo['sapCode'], 'version': prodInfo['productVersion'], 'buildGuid': prodInfo['buildGuid'], 'productIcons': prodInfo['productIcons']}) + apPlatform = prodInfo['apPlatform'] + install_app_name = '{}_{}-{}-{}'.format( + sapCode, version, installLanguage, apPlatform) + install_app_path = os.path.join(dest, install_app_name) + print('sapCode: ' + sapCode) + print('version: ' + version) + print('installLanguage: ' + installLanguage) + print('dest: ' + install_app_path) + + print('\nCreating {}'.format(install_app_name)) + + products_dir = os.path.join(dest, 'products') + + print('\nPreparing...\n') + + for p in prods_to_download: + s, v = p['sapCode'], p['version'] + product_dir = os.path.join(products_dir, s) + app_json_path = os.path.join(product_dir, 'application.json') + + print('[{}_{}] Downloading application.json'.format(s, v)) + app_json = get_application_json(p['buildGuid']) + + ''' + #replace filtered result for testing + to_filter = app_json['Packages']['Package'] + app_json['Packages']['Package'] = package_filter(to_filter, installLanguage) + ''' + + p['application_json'] = app_json + + print('[{}_{}] Creating folder for product'.format(s, v)) + os.makedirs(product_dir, exist_ok=True) + + print('[{}_{}] Saving application.json'.format(s, v)) + with open(app_json_path, 'w') as file: + json.dump(app_json, file, separators=(',', ':')) + + print('') + + print('Downloading...\n') + + for p in prods_to_download: + s, v = p['sapCode'], p['version'] + app_json = p['application_json'] + #icons directory + icon_dir = os.path.join(products_dir, 'icons') + product_dir = os.path.join(products_dir, s) + + print('[{}_{}] Parsing available packages'.format(s, v)) + core_pkg_count = 0 + noncore_pkg_count = 0 + packages = app_json['Packages']['Package'] + download_urls = [] + for pkg in packages: + if pkg.get('Type') and pkg['Type'] == 'core': + core_pkg_count += 1 + download_urls.append(cdn + pkg['Path']) + else: + # TODO: actually parse `Condition` and check it properly (and maybe look for & add support for conditions other than installLanguage) + if installLanguage == "ALL": + noncore_pkg_count += 1 + download_urls.append(cdn + pkg['Path']) + else: + if (not pkg.get('Condition')) or installLanguage in pkg['Condition'] or oslang in pkg['Condition']: + noncore_pkg_count += 1 + download_urls.append(cdn + pkg['Path']) + print('[{}_{}] Selected {} core packages and {} non-core packages'.format(s, + v, core_pkg_count, noncore_pkg_count)) + + # get app icons if exist + if 'productIcons' in p: + productIcons = p['productIcons'] + download_icons(productIcons, icon_dir) + + for url in download_urls: + download_file(url, product_dir, s, v) + + print('\nGenerating driver.xml') + + driver = DRIVER_XML.format( + name=product['displayName'], + sapCode=prodInfo['sapCode'], + version=prodInfo['productVersion'], + baseVersion=prodInfo['baseVersion'], + installPlatform=apPlatform, + dependencies='\n'.join([DRIVER_XML_DEPENDENCY.format( + sapCode=d['sapCode'], + version=d['version'] + ) for d in prodInfo['dependencies']]), + installDir=get_install_dir(), + language=installLanguage + ) + + with open(os.path.join(products_dir, f'{sapCode}-driver.xml'), 'w') as f: + f.write(driver) + f.close() + + print('\nPackage successfully downloaded.') + 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('-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('-a', '--arch', + help='Set the architecture to download', 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, allowedPlatforms = get_products() + + while True: + run_ccdl(products, cdn, sapCodes, allowedPlatforms) + if args.noRepeatPrompt or not questiony('\n\nDo you want to create another package'): + break