├── .gitignore ├── README.md ├── main.py ├── reqs ├── __init__.py ├── itunes.py ├── schemas │ ├── README.md │ ├── __init__.py │ ├── itunes_lookup_resp.py │ ├── schema_defs │ │ ├── __init__.py │ │ ├── __main__.py │ │ ├── itunes_lookup_resp.json │ │ ├── store_authenticate_req.json │ │ ├── store_authenticate_resp.json │ │ ├── store_buyproduct_req.json │ │ ├── store_buyproduct_resp.json │ │ ├── store_download_req.json │ │ └── store_download_resp.json │ ├── schema_examples │ │ ├── itunes_lookup_resp.log │ │ ├── store_buyproduct_req.log │ │ └── store_buyproduct_resp.log │ ├── store_authenticate_req.py │ ├── store_authenticate_resp.py │ ├── store_buyproduct_req.py │ ├── store_buyproduct_resp.py │ ├── store_download_req.py │ └── store_download_resp.py └── store.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | __pycache__ 3 | venv/ 4 | downloaded/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IPATool-py 2 | 3 | Python version of IPATool! 4 | 5 | **Now Supports**: 6 | - **Purchasing App via `--purchase`** 7 | - **Downloading Old IPA via iTunes Server** 8 | 9 | ## Installation 10 | 11 | ``` 12 | pip3 install -r requirements.txt 13 | ``` 14 | 15 | ## Usage 16 | 17 | There're three commands: lookup, historyver, download. Each command's usage can be found by `-h` option. 18 | 19 | You can also chain multiple command, last command's output will be passed to next command (so you don't need to supply some arguments like appVerId) 20 | 21 | ### Download Newest Version 22 | 23 | Download an app using bundleID (-o can be omited) 24 | ``` 25 | python3 main.py lookup -b com.touchingapp.potatsolite -c JP download -e APPLE_EMAIL -p APPLE_PWD -o DIR 26 | ``` 27 | 28 | Or appID (`XXXXX` in the `apps.apple.com/app/xxxx/idXXXXXX`) 29 | ``` 30 | python3 main.py download -i XXXXX 31 | ``` 32 | 33 | You can also purchase apps when downloading using `--purchase`: 34 | ``` 35 | python3 main.py lookup -b com.touchingapp.potatsolite -c JP download --purchase -e APPLE_EMAIL -p APPLE_PWD -o DIR 36 | ``` 37 | 38 | ### Download OLD Version 39 | 40 | Old versions must be downloaded together with `iTunes Server`. You can get `iTunes Server` in several ways: 41 | - Using [action-ipadown](https://github.com/NyaMisty/action-ipadown) directly, which integrated this tool 42 | - NOTE: this method does not support 2FA 43 | - Manually way with Windows: (supports 2FA) 44 | - download this repo: https://github.com/NyaMisty/actions-iTunes-header 45 | - install iTunes 12.6.5.3, from https://secure-appldnld.apple.com/itunes12/091-87819-20180912-69177170-B085-11E8-B6AB-C1D03409AD2A6/iTunes64Setup.exe 46 | - patch the iTunes using `actions-iTunes-header/workflow_helper/iTunesInstall/patch_itunes.py` 47 | - install frida: `pip3 install frida` 48 | - open iTunes, sign out & re-login your account 49 | - run: `actions-iTunes-header/workflow_helper/iTunesDownload/get_header.py` 50 | - Manually way with jailbroken iOS device: (supports 2FA) 51 | - download [KbsyncTool](https://github.com/Lessica/KbsyncTool/releases) 52 | - install `com.darwindev.kbsync_XXX.deb` on your jailbroken device 53 | - exec `kbsynctool -s 9000` on your jailbroken device 54 | - you will find log `Using -s http://192.168.100.227:9000/...`, use it as server address in next step 55 | 56 | After setting up the server, you can run this tool to download a specific version 57 | ``` 58 | python3 main.py lookup -b com.touchingapp.potatsolite -c JP download -s http://127.0.0.1:9000 --appVerId 833889087 59 | ``` 60 | 61 | NOTE: Some users are reporting that you need to **authorize computer** and make first purchase in iTunes with marked "do not ask for password" before using the iTunes server. (See #26) 62 | 63 | ### Get History Ver (usually used together with JSON) 64 | 65 | Get all appVerId of app from Apple 66 | ``` 67 | python3 main.py lookup -b com.touchingapp.potatsolite -c JP historyver -e APPLE_EMAIL -p APPLE_PWD 68 | ``` 69 | 70 | ### Lookup (usually used together with JSON) 71 | 72 | Query app basic information: 73 | - By bundleID: `python3 main.py lookup -b com.touchingapp.potatsolite -c JP` 74 | - By appID: `python3 main.py lookup -i 1239860606 -c JP` 75 | 76 | Query appVerId: 77 | ``` 78 | python3 main.py lookup -b com.touchingapp.potatsolite -c JP --get-verid 79 | ``` 80 | 81 | ### Headless Usage 82 | 83 | For each command you can use `--json` switch to get result of command in JSON 84 | 85 | ``` 86 | python3 main.py --json lookup -b com.touchingapp.potatsolite -c JP --get-verid 87 | python3 main.py --json lookup -b com.touchingapp.potatsolite -c JP historyver -e APPLE_EMAIL -p APPLE_PWD 88 | ``` 89 | 90 | ### For Large Scale Scraping 91 | 92 | You can download all versions of an app like this: 93 | ``` 94 | python3 main.py --json download --itunes-server http://XXX.XXX.XXX.XXX:9000 --appId 414478124 --purchase --downloadAllVersion 95 | ``` 96 | 97 | - In this mode, errors will only be logged instead of interrupting the whole process 98 | - For each downloaded app version, it will output a line of json in stdout like this: 99 | ``` 100 | {"appName": "WeChat", "appBundleId": "com.tencent.xin", "appVer": "6.5.13.34", "appId": 414478124, "appVerId": 822899148, "downloadedIPA": "wechat\\com.tencent.xin-6.5.13.34-414478124-822899148.ipa", "downloadedVerId": 822899148} 101 | ``` 102 | Logs will only be printed to stderr, so you can parse this line for automation. 103 | 104 | 105 | ## Development 106 | 107 | - All requests' reqBody and respBody are modeled using modified JSONSchema2PoPo2 (see my NyaMisty/JSONSchema2PoPo2), you can regenerate the binding by cd into `reqs/schemas` and execute `python3 -m schema_defs` 108 | - See more information on how to generate schema in reqs/schemas directory 109 | 110 | ## Credit 111 | 112 | - Thanks @majd's ipatool, which is written in swift 113 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | import pickle 4 | import sys 5 | import time 6 | import zipfile 7 | 8 | from requests.adapters import HTTPAdapter 9 | from urllib3 import Retry 10 | 11 | from reqs.itunes import * 12 | from reqs.store import * 13 | import reprlib 14 | 15 | reprlib.aRepr.maxstring = 200 16 | 17 | import argparse 18 | 19 | import logging 20 | from rich.logging import RichHandler 21 | from rich.console import Console 22 | import rich 23 | 24 | rich.get_console().file = sys.stderr 25 | if rich.get_console().width < 100: 26 | rich.get_console().width = 100 27 | 28 | logging_handler = RichHandler(rich_tracebacks=True) 29 | logging.basicConfig( 30 | level="INFO", 31 | format="%(message)s", 32 | datefmt="[%X]", 33 | handlers=[logging_handler] 34 | ) 35 | logging.getLogger('urllib3').setLevel(logging.WARNING) 36 | retryLogger = logging.getLogger('urllib3.util.retry') 37 | retryLogger.setLevel(logging.DEBUG) 38 | retryLogger.handlers = [logging_handler] 39 | retryLogger.propagate = False 40 | 41 | logger = logging.getLogger('main') 42 | 43 | import requests 44 | 45 | def get_zipinfo_datetime(timestamp=None): 46 | # Some applications need reproducible .whl files, but they can't do this without forcing 47 | # the timestamp of the individual ZipInfo objects. See issue #143. 48 | timestamp = int(timestamp or time.time()) 49 | return time.gmtime(timestamp)[0:6] 50 | 51 | def downloadFile(url, outfile, retries=10): 52 | for retry in range(retries): 53 | try: 54 | _downloadFile(url, outfile) 55 | break 56 | except Exception as e: 57 | logger.info("Error during downloading %s (retry %d/%d), error %s", url, retry, retries, e) 58 | os.remove(outfile) 59 | logger.info("Download success in retry %d", retry) 60 | 61 | download_sess = requests.Session() 62 | download_sess.headers = {"User-Agent": CONFIGURATOR_UA} 63 | DOWNLOAD_READ_TIMEOUT = 25.0 64 | def _downloadFile(url, outfile): 65 | with download_sess.get(url, stream=True, timeout=DOWNLOAD_READ_TIMEOUT) as r: 66 | if not r.headers.get('Content-Length'): 67 | raise Exception("server is not returning Content-Length!") 68 | totalLen = int(r.headers.get('Content-Length', '0')) 69 | downLen = 0 70 | r.raise_for_status() 71 | try: 72 | with open(outfile, 'wb') as f: 73 | lastLen = 0 74 | for chunk in r.iter_content(chunk_size=1 * 1024 * 1024): 75 | # If you have chunk encoded response uncomment if 76 | # and set chunk_size parameter to None. 77 | # if chunk: 78 | f.write(chunk) 79 | downLen += len(chunk) 80 | if totalLen and downLen - lastLen > totalLen * 0.05: 81 | logger.info("Download progress: %3.2f%% (%5.1fM /%5.1fM)" % ( 82 | downLen / totalLen * 100, downLen / 1024 / 1024, totalLen / 1024 / 1024)) 83 | lastLen = downLen 84 | finally: 85 | if downLen != totalLen: # ensure no partial downloaded files exists 86 | os.unlink(outfile) 87 | if downLen != totalLen: 88 | raise Exception("failed to completely download the IPA file") 89 | 90 | return outfile 91 | 92 | class IPATool(object): 93 | def __init__(self): 94 | self.sess = requests.Session() 95 | 96 | retry_strategy = Retry( 97 | connect=4, 98 | read=4, 99 | # total=8, 100 | status=20, 101 | allowed_methods=None, 102 | status_forcelist=[429, 502, 503], 103 | backoff_factor=1.0, 104 | respect_retry_after_header=False, 105 | ) 106 | self.sess.mount("https://", HTTPAdapter(max_retries=retry_strategy)) 107 | self.sess.mount("http://", HTTPAdapter(max_retries=retry_strategy)) 108 | IPATOOL_PROXY = os.getenv("IPATOOL_PROXY") 109 | if IPATOOL_PROXY is not None: 110 | self.sess.proxies.update( 111 | {'http': IPATOOL_PROXY, 'https': IPATOOL_PROXY}) 112 | # self.sess.headers = {} 113 | self.sess.headers = {"Connection": "close"} 114 | self.sess.keep_alive = False 115 | 116 | self.appId = None 117 | # self.appInfo = None 118 | self.appVerId = None 119 | self.appVerIds = None 120 | 121 | self.jsonOut = None 122 | 123 | def tool_main(self): 124 | commparser = argparse.ArgumentParser(description='IPATool-Python Commands.', add_help=False) 125 | subp = commparser.add_subparsers(dest='command', required=True) 126 | lookup_p = subp.add_parser('lookup') 127 | id_group = lookup_p.add_mutually_exclusive_group(required=True) 128 | id_group.add_argument('--bundle-id', '-b', dest='bundle_id') 129 | id_group.add_argument('--appId', '-i', dest='appId') 130 | lookup_p.add_argument('--country', '-c', dest='country', required=True) 131 | lookup_p.add_argument('--get-verid', dest='get_verid', action='store_true') 132 | lookup_p.set_defaults(func=self.handleLookup) 133 | 134 | def add_auth_options(p): 135 | auth_p = p.add_argument_group('Auth Options', 'Must specify either Apple ID & Password, or iTunes Server URL') 136 | appleid = auth_p.add_argument('--appleid', '-e') 137 | passwd = auth_p.add_argument('--password', '-p') 138 | sessdir = auth_p.add_argument('--session-dir', dest='session_dir', default=None) 139 | 140 | itunessrv = auth_p.add_argument('--itunes-server', '-s', dest='itunes_server') 141 | 142 | ## Multiple hack here just to achieve (appleid & password) || itunes_server 143 | # p._optionals.conflict_handler = 'ignore' 144 | # p._optionals._handle_conflict_ignore = lambda *args: None 145 | auth_p = p.add_mutually_exclusive_group(required=True) 146 | auth_p._group_actions.append(appleid) 147 | auth_p._group_actions.append(itunessrv) 148 | # auth_p._group_actions.append(sessdir) 149 | 150 | auth_p = p.add_mutually_exclusive_group(required=True) 151 | auth_p._group_actions.append(passwd) 152 | auth_p._group_actions.append(itunessrv) 153 | 154 | purchase_p = subp.add_parser('purchase') 155 | add_auth_options(purchase_p) 156 | purchase_p.add_argument('--appId', '-i', dest='appId') 157 | purchase_p.set_defaults(func=self.handlePurchase) 158 | 159 | down_p = subp.add_parser('download') 160 | add_auth_options(down_p) 161 | down_p.add_argument('--appId', '-i', dest='appId') 162 | down_p.add_argument('--appVerId', dest='appVerId') 163 | down_p.add_argument('--purchase', action='store_true') 164 | down_p.add_argument('--downloadAllVersion', action='store_true') 165 | down_p.add_argument('--output-dir', '-o', dest='output_dir', default='.') 166 | down_p.set_defaults(func=self.handleDownload) 167 | 168 | his_p = subp.add_parser('historyver') 169 | his_p.add_argument('--appId', '-i', dest='appId') 170 | his_p.add_argument('--purchase', action='store_true') 171 | his_p.add_argument('--output-dir', '-o', dest='output_dir', default='.') 172 | add_auth_options(his_p) 173 | his_p.set_defaults(func=self.handleHistoryVersion) 174 | 175 | parser = argparse.ArgumentParser(description='IPATool-Python.', parents=[commparser]) 176 | parser.add_argument('--log-level', '-l', dest='log_level', default='info', 177 | help='output level (default: info)') 178 | parser.add_argument('--json', dest='out_json', action='store_true', 179 | help='output json in stdout (log will always be put into stderr)') 180 | 181 | # parse global flags & first comm's arguments 182 | args, rest = parser.parse_known_args() 183 | logging.getLogger().setLevel(args.log_level.upper()) 184 | outJson = args.out_json 185 | 186 | while True: 187 | args.func(args) 188 | if not rest: 189 | break 190 | args, rest = commparser.parse_known_args(rest) 191 | 192 | if outJson and self.jsonOut: 193 | print(json.dumps(self.jsonOut, ensure_ascii=False)) 194 | 195 | def _outputJson(self, obj): 196 | self.jsonOut = obj 197 | 198 | def handleLookup(self, args): 199 | if args.bundle_id: 200 | s = 'BundleID %s' % args.bundle_id 201 | else: 202 | s = 'AppID %s' % args.appId 203 | logger.info('Looking up app in country %s with BundleID %s' % (args.country, s)) 204 | iTunes = iTunesClient(self.sess) 205 | appInfos = iTunes.lookup(bundleId=args.bundle_id, appId=args.appId, country=args.country) 206 | if appInfos.resultCount != 1: 207 | logger.fatal("Failed to find app in country %s with %s" % (args.country, s)) 208 | return 209 | appInfo = appInfos.results[0] 210 | logger.info("Found app:\n\tName: %s\n\tVersion: %s\n\tbundleId: %s\n\tappId: %s" % (appInfo.trackName, appInfo.version, appInfo.bundleId, appInfo.trackId)) 211 | self.appId = appInfo.trackId 212 | # self.appInfo = appInfo 213 | 214 | ret = { 215 | "name": appInfo.trackName, 216 | "version": appInfo.version, 217 | "appId": appInfo.trackId, 218 | "bundleId": appInfo.bundleId, 219 | } 220 | 221 | if args.get_verid: 222 | logger.info("Retrieving verId using iTunes webpage...") 223 | verId = iTunes.getAppVerId(self.appId, args.country) 224 | logger.info("Got current verId using iTunes webpage: %s" % verId) 225 | ret["appVerId"] = verId 226 | 227 | self._outputJson(ret) 228 | 229 | storeClientCache = {} 230 | def _get_StoreClient(self, args): 231 | to_delete = [] 232 | for k, v in self.storeClientCache.items(): 233 | if time.time() - v < 30.0: 234 | return k 235 | else: 236 | to_delete.append(k) 237 | for k in to_delete: 238 | self.storeClientCache.pop(k) 239 | newSess = pickle.loads(pickle.dumps(self.sess)) 240 | Store = StoreClient(newSess) 241 | 242 | if args.itunes_server: 243 | logger.info("Using iTunes interface %s to download app!" % args.itunes_server) 244 | servUrl = args.itunes_server 245 | def handle_iTunes_provider(url): 246 | startTime = time.time() 247 | r = requests.get(servUrl, params={ 248 | 'url': url 249 | }) 250 | logger.debug("got itunes header in %.2f seconds", time.time() - startTime) 251 | 252 | ret = r.json() 253 | kbsync = bytes.fromhex(ret.pop('kbsync')) 254 | guid = ret.pop('guid') 255 | retHdrs = ret.pop('headers') 256 | handled = { 257 | 'headers': retHdrs, 258 | 'kbsync': kbsync, 259 | 'guid': guid, 260 | } 261 | if 'sbsync' in ret: 262 | handled['sbsync'] = bytes.fromhex(ret.pop('sbsync')) 263 | if 'afds' in ret: 264 | handled['afds'] = ret.pop('afds') 265 | return handled 266 | Store.iTunes_provider = handle_iTunes_provider 267 | else: 268 | appleid = args.appleid 269 | applepass = args.password 270 | 271 | needLogin = True 272 | session_cache = os.path.join(args.session_dir, args.appleid) if args.session_dir else None 273 | if session_cache and os.path.exists(session_cache): 274 | needLogin = False 275 | try: 276 | # inside try in case the file format changed 277 | with open(session_cache, "r") as f: 278 | content = f.read() 279 | Store.authenticate_load_session(content) 280 | except Exception as e: 281 | logger.warning(f"Error loading session {session_cache}") 282 | os.unlink(session_cache) 283 | needLogin = True 284 | else: 285 | logger.info('Loaded session for %s' % (str(Store.authInfo))) 286 | if needLogin: 287 | logger.info("Logging into iTunes as %s ..." % appleid) 288 | 289 | Store.authenticate(appleid, applepass) 290 | logger.info('Logged in as %s' % (str(Store.authInfo))) 291 | 292 | if session_cache: 293 | with open(session_cache, "w") as f: 294 | f.write(Store.authenticate_save_session()) 295 | 296 | def authedPost(*args, **kwargs): 297 | if 'MZFinance.woa/wa/authenticate' in args[0]: 298 | return Store.sess.original_post(*args, **kwargs) 299 | for i in range(3): 300 | r = Store.sess.original_post(*args, **kwargs) 301 | isAuthFail = False 302 | try: 303 | d = plistlib.loads(r.content) 304 | if str(d['failureType']) in ("2034", "1008"): 305 | isAuthFail = True 306 | except: 307 | return r 308 | if not isAuthFail: 309 | return r 310 | Store.authenticate(appleid, applepass) 311 | if session_cache: 312 | with open(session_cache, "w") as f: 313 | f.write(Store.authenticate_save_session()) 314 | continue 315 | Store.sess.original_post = Store.sess.post 316 | Store.sess.post = authedPost 317 | 318 | self.storeClientCache[Store] = time.time() 319 | return Store 320 | 321 | def _handleStoreException(self, _e): 322 | e = _e # type: StoreException 323 | logger.fatal("Store %s failed! Message: %s%s" % (e.req, e.errMsg, " (errorType %s)" % e.errType if e.errType else '')) 324 | logger.fatal(" Raw Response: %s" % (e.resp)) 325 | 326 | def handlePurchase(self, args): 327 | Store = self._get_StoreClient(args) 328 | logger.info('Try to purchase appId %s' % (self.appId)) 329 | try: 330 | Store.purchase(self.appId) 331 | except StoreException as e: 332 | if e.errMsg == 'purchased_before': 333 | logger.warning('You have already purchased appId %s before' % (self.appId)) 334 | else: 335 | raise 336 | 337 | def handleHistoryVersion(self, args, caches=True): 338 | if args.appId: 339 | self.appId = args.appId 340 | 341 | if not self.appId: 342 | logger.fatal("appId not supplied!") 343 | return 344 | 345 | versionsJsonPath = args.output_dir + f"/historyver_{self.appId}.json" 346 | if caches: 347 | if os.path.exists(versionsJsonPath): 348 | cacheContent = None 349 | try: 350 | with open(versionsJsonPath) as f: 351 | cacheContent = json.load(f) 352 | except: 353 | pass 354 | if cacheContent is not None: 355 | logger.info('Loaded history version cache for appId %s' % self.appId) 356 | self.appVerIds = cacheContent['appVerIds'] 357 | return 358 | 359 | logger.info('Retrieving history version for appId %s' % self.appId) 360 | 361 | try: 362 | Store = self._get_StoreClient(args) 363 | 364 | logger.info('Retrieving download info for appId %s' % (self.appId)) 365 | if args.purchase: 366 | logger.info('Purchasing appId %s' % (self.appId)) 367 | # We have already successfully purchased, so don't purchase again :) 368 | self.handlePurchase(args) 369 | args.purchase = False 370 | 371 | downResp = Store.download(self.appId, '', isRedownload=not args.purchase) 372 | logger.debug('Got download info: %s', downResp) 373 | if args.purchase: 374 | # We have already successfully purchased, so don't purchase again :) 375 | args.purchase = False 376 | 377 | if not downResp.songList: 378 | logger.fatal("failed to get app download info!") 379 | raise StoreException('download', downResp, 'no songList') 380 | downInfo = downResp.songList[0] 381 | logger.info('Got available version ids %s', downInfo.metadata.softwareVersionExternalIdentifiers) 382 | self._outputJson({ 383 | "appVerIds": downInfo.metadata.softwareVersionExternalIdentifiers 384 | }) 385 | self.appVerIds = downInfo.metadata.softwareVersionExternalIdentifiers 386 | if caches: 387 | with open(versionsJsonPath, 'w') as f: 388 | json.dump({ 389 | 'appVerIds': self.appVerIds, 390 | }, f) 391 | except StoreException as e: 392 | self._handleStoreException(e) 393 | if not e.errMsg.startswith('http error status') and not e.errMsg.startswith( 394 | 'We are temporarily unable to process your request') and not e.errMsg.startswith( 395 | "License not found"): 396 | # this error is persistent (e.g. app deleted) 397 | self.appVerIds = [] 398 | if caches: 399 | with open(versionsJsonPath, 'w') as f: 400 | json.dump({ 401 | 'appVerIds': self.appVerIds, 402 | 'error': str(e), 403 | 'errorResp': str(e.resp), 404 | }, f) 405 | 406 | def handleDownload(self, args): 407 | os.makedirs(args.output_dir, exist_ok=True) 408 | if args.downloadAllVersion: 409 | if os.path.exists(args.output_dir + "/all_done"): 410 | logger.info('Already fully finished, skipping!') 411 | return 412 | self.handleHistoryVersion(args, caches=True) 413 | if not self.appVerIds: 414 | logger.fatal('failed to retrive history versions for appId %s', args.appId) 415 | return 416 | everything_succ = True 417 | for appVerId in self.appVerIds: 418 | self.jsonOut = None 419 | stateFile = args.output_dir + '/' + str(appVerId) + '.finish' 420 | if os.path.exists(stateFile): 421 | logger.info('Skipping already downloaded') 422 | continue 423 | try: 424 | self.appVerId = appVerId 425 | self.downloadOne(args) 426 | if args.out_json and self.jsonOut: 427 | print(json.dumps(self.jsonOut, ensure_ascii=False)) 428 | if self.jsonOut is not None: # successfully finished 429 | with open(stateFile, 'w') as f: 430 | f.write('1') 431 | except Exception as e: 432 | logger.fatal("error during downloading appVerId %s", appVerId, exc_info=1) 433 | everything_succ = False 434 | finally: 435 | self.jsonOut = None 436 | if everything_succ: 437 | with open(args.output_dir + "/all_done", 'w') as f: 438 | f.write("1") 439 | else: 440 | self.downloadOne(args) 441 | 442 | def downloadOne(self, args): 443 | if args.appId: 444 | self.appId = args.appId 445 | if args.appVerId: 446 | self.appVerId = args.appVerId 447 | 448 | if not self.appId: 449 | logger.fatal("appId not supplied!") 450 | return 451 | 452 | logger.info("Downloading appId %s appVerId %s", self.appId, self.appVerId) 453 | try: 454 | appleid = args.appleid 455 | Store = self._get_StoreClient(args) 456 | 457 | if args.purchase: 458 | self.handlePurchase(args) 459 | 460 | logger.info('Retrieving download info for appId %s%s' % (self.appId, " with versionId %s" % self.appVerId if self.appVerId else "")) 461 | 462 | downResp = Store.download(self.appId, self.appVerId, isRedownload=not args.purchase) 463 | logger.debug('Got download info: %s', downResp.as_dict()) 464 | 465 | if not downResp.songList: 466 | logger.fatal("failed to get app download info!") 467 | raise StoreException('download', downResp, 'no songList') 468 | downInfo = downResp.songList[0] 469 | 470 | appName = downInfo.metadata.bundleDisplayName 471 | appId = downInfo.songId 472 | appBundleId = downInfo.metadata.softwareVersionBundleId 473 | appVerId = downInfo.metadata.softwareVersionExternalIdentifier 474 | # when downloading history versions, bundleShortVersionString will always give a wrong version number (the newest one) 475 | # should use bundleVersion in these cases 476 | appVer = downInfo.metadata.bundleShortVersionString if not self.appVerId else downInfo.metadata.bundleVersion 477 | 478 | logger.info(f'Downloading app {appName} ({appBundleId}) with appId {appId} (version {appVer}, versionId {appVerId})') 479 | 480 | # if self.appInfo: 481 | filename = '%s-%s-%s-%s.ipa' % (appBundleId, 482 | appVer, 483 | appId, 484 | appVerId) 485 | # else: 486 | # filename = '%s-%s.ipa' % (self.appId, appVerId) 487 | 488 | filepath = os.path.join(args.output_dir, filename) 489 | logger.info("Downloading ipa to %s" % filepath) 490 | downloadFile(downInfo.URL, filepath) 491 | metadata = downInfo.metadata.as_dict() 492 | if appleid: 493 | metadata["apple-id"] = appleid 494 | metadata["userName"] = appleid 495 | logger.info("Writing out iTunesMetadata.plist...") 496 | if zipfile.is_zipfile(filepath): 497 | with zipfile.ZipFile(filepath, 'a') as ipaFile: 498 | logger.debug("Writing iTunesMetadata.plist") 499 | ipaFile.writestr(zipfile.ZipInfo("iTunesMetadata.plist", get_zipinfo_datetime()), plistlib.dumps(metadata)) 500 | logger.debug("Writing IPAToolInfo.plist") 501 | ipaFile.writestr(zipfile.ZipInfo("IPAToolInfo.plist", get_zipinfo_datetime()), plistlib.dumps(downResp.as_dict())) 502 | 503 | def findAppContentPath(c): 504 | if not c.startswith('Payload/'): 505 | return False 506 | pathparts = c.strip('/').split('/') 507 | if len(pathparts) != 2: 508 | return False 509 | if not pathparts[1].endswith(".app"): 510 | return False 511 | return True 512 | appContentDirChoices = [c for c in ipaFile.namelist() if findAppContentPath(c)] 513 | if len(appContentDirChoices) != 1: 514 | raise Exception("failed to find appContentDir, choices %s", appContentDirChoices) 515 | appContentDir = appContentDirChoices[0].rstrip('/') 516 | 517 | processedSinf = False 518 | if (appContentDir + '/SC_Info/Manifest.plist') in ipaFile.namelist(): 519 | #Try to get the Manifest.plist file, since it doesn't always exist. 520 | scManifestData = ipaFile.read(appContentDir + '/SC_Info/Manifest.plist') 521 | logger.debug("Got SC_Info/Manifest.plist: %s", scManifestData) 522 | scManifest = plistlib.loads(scManifestData) 523 | sinfs = {c.id: c.sinf for c in downInfo.sinfs} 524 | if 'SinfPaths' in scManifest: 525 | for i, sinfPath in enumerate(scManifest['SinfPaths']): 526 | logger.debug("Writing sinf to %s", sinfPath) 527 | ipaFile.writestr(appContentDir + '/' + sinfPath, sinfs[i]) 528 | processedSinf = True 529 | if not processedSinf: 530 | logger.info('Manifest.plist does not exist! Assuming it is an old app without one...') 531 | infoListData = ipaFile.read(appContentDir + '/Info.plist') #Is this not loaded anywhere yet? 532 | infoList = plistlib.loads(infoListData) 533 | sinfPath = appContentDir + '/SC_Info/'+infoList['CFBundleExecutable']+".sinf" 534 | logger.debug("Writing sinf to %s", sinfPath) 535 | #Assuming there is only one .sinf file, hence the 0 536 | ipaFile.writestr(sinfPath, downInfo.sinfs[0].sinf) 537 | processedSinf = True 538 | 539 | logger.info("Downloaded ipa to %s" % filename) 540 | else: 541 | plist = filepath[:-4]+".info.plist" 542 | with open(plist, "wb") as f: 543 | f.write(plistlib.dumps(downResp.as_dict())) 544 | plist = filepath[:-4]+".plist" 545 | with open(plist, "wb") as f: 546 | f.write(plistlib.dumps(metadata)) 547 | logger.info("Downloaded ipa to %s and plist to %s" % (filename, plist)) 548 | 549 | self._outputJson({ 550 | "appName": appName, 551 | "appBundleId": appBundleId, 552 | "appVer": appVer, 553 | "appId": appId, 554 | "appVerId": appVerId, 555 | 556 | "downloadedIPA": filepath, 557 | "downloadedVerId": appVerId, 558 | "downloadURL": downInfo.URL, 559 | }) 560 | except StoreException as e: 561 | self._handleStoreException(e) 562 | 563 | def main(): 564 | tool = IPATool() 565 | tool.tool_main() 566 | 567 | if __name__ == '__main__': 568 | main() -------------------------------------------------------------------------------- /reqs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NyaMisty/ipatool-py/d4ff581f110d184229aa92477a5e3b4e81099b9a/reqs/__init__.py -------------------------------------------------------------------------------- /reqs/itunes.py: -------------------------------------------------------------------------------- 1 | __all__ = ['iTunesClient'] 2 | 3 | import json 4 | import re 5 | 6 | from reqs.schemas.itunes_lookup_resp import ItunesLookupResp 7 | import requests 8 | 9 | STORE_TABLE = {"AE":"143481-2,32","AG":"143540-2,32","AI":"143538-2,32","AL":"143575-2,32","AM":"143524-2,32","AO":"143564-2,32","AR":"143505-28,32","AT":"143445-4,32","AU":"143460-27,32","AZ":"143568-2,32","BB":"143541-2,32","BE":"143446-2,32","BF":"143578-2,32","BG":"143526-2,32","BH":"143559-2,32","BJ":"143576-2,32","BM":"143542-2,32","BN":"143560-2,32","BO":"143556-28,32","BR":"143503-15,32","BS":"143539-2,32","BT":"143577-2,32","BW":"143525-2,32","BY":"143565-2,32","BZ":"143555-2,32","CA":"143455-6,32","CG":"143582-2,32","CH":"143459-57,32","CL":"143483-28,32","CN":"143465-19,32","CO":"143501-28,32","CR":"143495-28,32","CV":"143580-2,32","CY":"143557-2,32","CZ":"143489-2,32","DE":"143443-4,32","DK":"143458-2,32","DM":"143545-2,32","DO":"143508-28,32","DZ":"143563-2,32","EC":"143509-28,32","EE":"143518-2,32","EG":"143516-2,32","ES":"143454-8,32","FI":"143447-2,32","FJ":"143583-2,32","FM":"143591-2,32","FR":"143442-3,32","GB":"143444-2,32","GD":"143546-2,32","GH":"143573-2,32","GM":"143584-2,32","GR":"143448-2,32","GT":"143504-28,32","GW":"143585-2,32","GY":"143553-2,32","HK":"143463-45,32","HN":"143510-28,32","HR":"143494-2,32","HU":"143482-2,32","ID":"143476-2,32","IE":"143449-2,32","IL":"143491-2,32","IN":"143467-2,32","IS":"143558-2,32","IT":"143450-7,32","JM":"143511-2,32","JO":"143528-2,32","JP":"143462-9,32","KE":"143529-2,32","KG":"143586-2,32","KH":"143579-2,32","KN":"143548-2,32","KR":"143466-13,32","KW":"143493-2,32","KY":"143544-2,32","KZ":"143517-2,32","LA":"143587-2,32","LB":"143497-2,32","LC":"143549-2,32","LK":"143486-2,32","LR":"143588-2,32","LT":"143520-2,32","LU":"143451-2,32","LV":"143519-2,32","MD":"143523-2,32","MG":"143531-2,32","MK":"143530-2,32","ML":"143532-2,32","MN":"143592-2,32","MO":"143515-45,32","MR":"143590-2,32","MS":"143547-2,32","MT":"143521-2,32","MU":"143533-2,32","MW":"143589-2,32","MX":"143468-28,32","MY":"143473-2,32","MZ":"143593-2,32","NA":"143594-2,32","NE":"143534-2,32","NG":"143561-2,32","NI":"143512-28,32","NL":"143452-10,32","NO":"143457-2,32","NP":"143484-2,32","NZ":"143461-27,32","OM":"143562-2,32","PA":"143485-28,32","PE":"143507-28,32","PG":"143597-2,32","PH":"143474-2,32","PK":"143477-2,32","PL":"143478-2,32","PT":"143453-24,32","PW":"143595-2,32","PY":"143513-28,32","QA":"143498-2,32","RO":"143487-2,32","RU":"143469-16,32","SA":"143479-2,32","SB":"143601-2,32","SC":"143599-2,32","SE":"143456-17,32","SG":"143464-19,32","SI":"143499-2,32","SK":"143496-2,32","SL":"143600-2,32","SN":"143535-2,32","SR":"143554-2,32","ST":"143598-2,32","SV":"143506-28,32","SZ":"143602-2,32","TC":"143552-2,32","TD":"143581-2,32","TH":"143475-2,32","TJ":"143603-2,32","TM":"143604-2,32","TN":"143536-2,32","TR":"143480-2,32","TT":"143551-2,32","TW":"143470-18,32","TZ":"143572-2,32","UA":"143492-2,32","UG":"143537-2,32","US":"143441-1,32","UY":"143514-2,32","UZ":"143566-2,32","VC":"143550-2,32","VE":"143502-28,32","VG":"143543-2,32","VN":"143471-2,32","YE":"143571-2,32","ZA":"143472-2,32","ZW":"143605-2,32"} 10 | 11 | 12 | class iTunesClient(object): 13 | def __init__(self, sess: requests.Session): 14 | self.sess = sess 15 | 16 | # curl -k -X GET \ 17 | # -H "Content-Type: application/x-www-form-urlencoded" \ 18 | # https://itunes.apple.com/lookup?bundleId=com.touchingapp.potatsolite&limit=1&media=software 19 | def lookup(self, bundleId=None, appId=None, term=None, country="US", limit=1, media="software") -> ItunesLookupResp: 20 | r = self.sess.get("https://itunes.apple.com/lookup?", 21 | params={ 22 | "bundleId": bundleId, 23 | "id": appId, 24 | "term": term, 25 | "country": country, 26 | "limit": limit, 27 | "media": media, 28 | }, 29 | headers={ 30 | "Content-Type": "application/x-www-form-urlencoded", 31 | }) 32 | return ItunesLookupResp.from_dict(r.json()) 33 | 34 | def getAppVerId(self, appId, country): 35 | if not ',' in country: 36 | storeFront = STORE_TABLE[country.upper()] 37 | else: 38 | storeFront = country 39 | appInfo = requests.get("https://apps.apple.com/app/id%s" % appId, headers={"X-Apple-Store-Front": storeFront}).text 40 | try: 41 | appParam = re.findall(r'"buyParams":"(.*?)"', appInfo)[0] 42 | except: 43 | appParam = re.findall(r'buy-params="(.*?)"', appInfo)[0] 44 | appParam = appParam.replace('&', '&') 45 | appParamDict = dict((c.split('=') for c in json.loads('"%s"' % appParam).split('&'))) 46 | appVer = appParamDict['appExtVrsId'] 47 | return appVer -------------------------------------------------------------------------------- /reqs/schemas/README.md: -------------------------------------------------------------------------------- 1 | # Schema Definitions for ipatool-py 2 | 3 | In this directory, JSON Schema Definition files are stored in `schema_defs`, with corresponding plist examples in `schema_examples` 4 | 5 | ## How to generate schema from plist 6 | 7 | 1. Convert plist to corresponding JSON, using either `plistlib` or online tools 8 | 2. Use https://www.liquid-technologies.com/online-json-to-schema-converter to convert JSON to schema 9 | - You have to switch the `Array Rules` option to `List Validation` 10 | 3. Merge different request / response body's schema manually 11 | - Usually the "required" field can help you merging the json schema 12 | 13 | ## Regenerate schema bindings 14 | 15 | Run this in current folder: 16 | ``` 17 | python3 -m schema_defs 18 | ``` -------------------------------------------------------------------------------- /reqs/schemas/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NyaMisty/ipatool-py/d4ff581f110d184229aa92477a5e3b4e81099b9a/reqs/schemas/__init__.py -------------------------------------------------------------------------------- /reqs/schemas/schema_defs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NyaMisty/ipatool-py/d4ff581f110d184229aa92477a5e3b4e81099b9a/reqs/schemas/schema_defs/__init__.py -------------------------------------------------------------------------------- /reqs/schemas/schema_defs/__main__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import os.path as path 3 | import subprocess 4 | 5 | curpath = path.dirname(__file__) 6 | outpath = path.dirname(curpath) 7 | for p in os.listdir(curpath): 8 | if not p.endswith('.json'): 9 | continue 10 | #subprocess.call(["jsonschema2popo2", "--translate-properties", "--use-types", "-o", path.join(outpath, p.split('.')[0] + '.py'), path.join(curpath, p)]) 11 | print("Converting %s" % p) 12 | subprocess.call(["jsonschema2popo2", "--use-types", "-o", path.join(outpath, p.split('.')[0] + '.py'), path.join(curpath, p)]) -------------------------------------------------------------------------------- /reqs/schemas/schema_defs/itunes_lookup_resp.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "iTunes Lookup Resp", 3 | "type": "object", 4 | "properties": { 5 | "resultCount": { 6 | "type": "integer" 7 | }, 8 | "results": { 9 | "type": "array", 10 | "items": { 11 | "type": "object", 12 | "properties": { 13 | "appletvScreenshotUrls": { 14 | "type": "array", 15 | "items": { 16 | "type": ["number","string","boolean","object","array", "null"] 17 | } 18 | }, 19 | "screenshotUrls": { 20 | "type": "array", 21 | "items": { 22 | "type": "string" 23 | } 24 | }, 25 | "ipadScreenshotUrls": { 26 | "type": "array", 27 | "items": { 28 | "type": "string" 29 | } 30 | }, 31 | "artworkUrl60": { 32 | "type": "string" 33 | }, 34 | "artworkUrl512": { 35 | "type": "string" 36 | }, 37 | "artworkUrl100": { 38 | "type": "string" 39 | }, 40 | "artistViewUrl": { 41 | "type": "string" 42 | }, 43 | "supportedDevices": { 44 | "type": "array", 45 | "items": { 46 | "type": "string" 47 | } 48 | }, 49 | "advisories": { 50 | "type": "array", 51 | "items": { 52 | "type": ["number","string","boolean","object","array", "null"] 53 | } 54 | }, 55 | "isGameCenterEnabled": { 56 | "type": "boolean" 57 | }, 58 | "kind": { 59 | "type": "string" 60 | }, 61 | "features": { 62 | "type": "array", 63 | "items": { 64 | "type": "string" 65 | } 66 | }, 67 | "minimumOsVersion": { 68 | "type": "string" 69 | }, 70 | "trackCensoredName": { 71 | "type": "string" 72 | }, 73 | "languageCodesISO2A": { 74 | "type": "array", 75 | "items": { 76 | "type": "string" 77 | } 78 | }, 79 | "fileSizeBytes": { 80 | "type": "string" 81 | }, 82 | "formattedPrice": { 83 | "type": "string" 84 | }, 85 | "contentAdvisoryRating": { 86 | "type": "string" 87 | }, 88 | "averageUserRatingForCurrentVersion": { 89 | "type": "number" 90 | }, 91 | "userRatingCountForCurrentVersion": { 92 | "type": "integer" 93 | }, 94 | "averageUserRating": { 95 | "type": "number" 96 | }, 97 | "trackViewUrl": { 98 | "type": "string" 99 | }, 100 | "trackContentRating": { 101 | "type": "string" 102 | }, 103 | "releaseDate": { 104 | "type": "string" 105 | }, 106 | "genreIds": { 107 | "type": "array", 108 | "items": { 109 | "type": "string" 110 | } 111 | }, 112 | "primaryGenreName": { 113 | "type": "string" 114 | }, 115 | "trackId": { 116 | "type": "integer" 117 | }, 118 | "trackName": { 119 | "type": "string" 120 | }, 121 | "sellerName": { 122 | "type": "string" 123 | }, 124 | "isVppDeviceBasedLicensingEnabled": { 125 | "type": "boolean" 126 | }, 127 | "currentVersionReleaseDate": { 128 | "type": "string" 129 | }, 130 | "releaseNotes": { 131 | "type": "string" 132 | }, 133 | "primaryGenreId": { 134 | "type": "integer" 135 | }, 136 | "currency": { 137 | "type": "string" 138 | }, 139 | "version": { 140 | "type": "string" 141 | }, 142 | "wrapperType": { 143 | "type": "string" 144 | }, 145 | "artistId": { 146 | "type": "integer" 147 | }, 148 | "artistName": { 149 | "type": "string" 150 | }, 151 | "genres": { 152 | "type": "array", 153 | "items": { 154 | "type": "string" 155 | } 156 | }, 157 | "price": { 158 | "type": "number" 159 | }, 160 | "description": { 161 | "type": "string" 162 | }, 163 | "bundleId": { 164 | "type": "string" 165 | }, 166 | "userRatingCount": { 167 | "type": "integer" 168 | } 169 | }, 170 | "required": [ 171 | "appletvScreenshotUrls", 172 | "screenshotUrls", 173 | "ipadScreenshotUrls", 174 | "artworkUrl60", 175 | "artworkUrl512", 176 | "artworkUrl100", 177 | "artistViewUrl", 178 | "supportedDevices", 179 | "advisories", 180 | "isGameCenterEnabled", 181 | "kind", 182 | "features", 183 | "minimumOsVersion", 184 | "trackCensoredName", 185 | "languageCodesISO2A", 186 | "fileSizeBytes", 187 | "formattedPrice", 188 | "contentAdvisoryRating", 189 | "averageUserRatingForCurrentVersion", 190 | "userRatingCountForCurrentVersion", 191 | "averageUserRating", 192 | "trackViewUrl", 193 | "trackContentRating", 194 | "releaseDate", 195 | "genreIds", 196 | "primaryGenreName", 197 | "trackId", 198 | "trackName", 199 | "sellerName", 200 | "isVppDeviceBasedLicensingEnabled", 201 | "currentVersionReleaseDate", 202 | "releaseNotes", 203 | "primaryGenreId", 204 | "currency", 205 | "version", 206 | "wrapperType", 207 | "artistId", 208 | "artistName", 209 | "genres", 210 | "price", 211 | "description", 212 | "bundleId", 213 | "userRatingCount" 214 | ] 215 | } 216 | } 217 | }, 218 | "required": [ 219 | "resultCount", 220 | "results" 221 | ] 222 | } -------------------------------------------------------------------------------- /reqs/schemas/schema_defs/store_authenticate_req.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Store Authenticate Req", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "type": "object", 5 | "properties": { 6 | "appleId": { 7 | "type": "string" 8 | }, 9 | "attempt": { 10 | "type": "string" 11 | }, 12 | "createSession": { 13 | "type": "string" 14 | }, 15 | "guid": { 16 | "type": "string" 17 | }, 18 | "password": { 19 | "type": "string" 20 | }, 21 | "rmp": { 22 | "type": "string" 23 | }, 24 | "why": { 25 | "type": "string" 26 | } 27 | }, 28 | "required": [ 29 | "appleId", 30 | "attempt", 31 | "createSession", 32 | "guid", 33 | "password", 34 | "rmp", 35 | "why" 36 | ] 37 | } -------------------------------------------------------------------------------- /reqs/schemas/schema_defs/store_authenticate_resp.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Store Authenticate Resp", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "type": "object", 5 | "properties": { 6 | "pings": { 7 | "type": "array", 8 | "items": { 9 | "type": ["number","string","boolean","object","array", "null"] 10 | } 11 | }, 12 | "cancel-purchase-batch": { 13 | "type": "boolean" 14 | }, 15 | "customerMessage": { 16 | "type": "string" 17 | }, 18 | "failureType": { 19 | "type": "string" 20 | }, 21 | "accountInfo": { 22 | "type": "object", 23 | "properties": { 24 | "appleId": { 25 | "type": "string" 26 | }, 27 | "address": { 28 | "type": "object", 29 | "properties": { 30 | "firstName": { 31 | "type": "string" 32 | }, 33 | "lastName": { 34 | "type": "string" 35 | } 36 | }, 37 | "required": [ 38 | "firstName", 39 | "lastName" 40 | ] 41 | } 42 | }, 43 | "required": [ 44 | "appleId", 45 | "address" 46 | ] 47 | }, 48 | "passwordToken": { 49 | "type": "string" 50 | }, 51 | "clearToken": { 52 | "type": "string" 53 | }, 54 | "m-allowed": { 55 | "type": "boolean" 56 | }, 57 | "is-cloud-enabled": { 58 | "type": "string" 59 | }, 60 | "dsPersonId": { 61 | "type": "string" 62 | }, 63 | "creditDisplay": { 64 | "type": "string" 65 | }, 66 | "creditBalance": { 67 | "type": "string" 68 | }, 69 | "freeSongBalance": { 70 | "type": "string" 71 | }, 72 | "isManagedStudent": { 73 | "type": "boolean" 74 | }, 75 | "action": { 76 | "type": "object", 77 | "properties": { 78 | "kind": { 79 | "type": "string" 80 | } 81 | }, 82 | "required": [ 83 | "kind" 84 | ] 85 | }, 86 | "subscriptionStatus": { 87 | "type": "object", 88 | "properties": { 89 | "terms": { 90 | "type": "array", 91 | "items": { 92 | "type": "object", 93 | "properties": { 94 | "type": { 95 | "type": "string" 96 | }, 97 | "latestTerms": { 98 | "type": "integer" 99 | }, 100 | "agreedToTerms": { 101 | "type": "integer" 102 | }, 103 | "source": { 104 | "type": "string" 105 | } 106 | }, 107 | "required": [ 108 | "type", 109 | "latestTerms", 110 | "agreedToTerms", 111 | "source" 112 | ] 113 | } 114 | }, 115 | "account": { 116 | "type": "object", 117 | "properties": { 118 | "isMinor": { 119 | "type": "boolean" 120 | }, 121 | "suspectUnderage": { 122 | "type": "boolean" 123 | } 124 | }, 125 | "required": [ 126 | "isMinor", 127 | "suspectUnderage" 128 | ] 129 | }, 130 | "family": { 131 | "type": "object", 132 | "properties": { 133 | "hasFamily": { 134 | "type": "boolean" 135 | } 136 | }, 137 | "required": [ 138 | "hasFamily" 139 | ] 140 | } 141 | }, 142 | "required": [ 143 | "terms", 144 | "account", 145 | "family" 146 | ] 147 | }, 148 | "accountFlags": { 149 | "type": "object", 150 | "properties": { 151 | "personalization": { 152 | "type": "boolean" 153 | }, 154 | "underThirteen": { 155 | "type": "boolean" 156 | }, 157 | "identityLastVerified": { 158 | "type": "integer" 159 | }, 160 | "verifiedExpirationDate": { 161 | "type": "integer" 162 | }, 163 | "retailDemo": { 164 | "type": "boolean" 165 | }, 166 | "autoPlay": { 167 | "type": "boolean" 168 | }, 169 | "isDisabledAccount": { 170 | "type": "boolean" 171 | }, 172 | "isRestrictedAccount": { 173 | "type": "boolean" 174 | }, 175 | "isManagedAccount": { 176 | "type": "boolean" 177 | }, 178 | "isInRestrictedRegion": { 179 | "type": "boolean" 180 | }, 181 | "accountFlagsVersion": { 182 | "type": "integer" 183 | }, 184 | "hasAgreedToTerms": { 185 | "type": "boolean" 186 | }, 187 | "hasAgreedToAppClipTerms": { 188 | "type": "boolean" 189 | }, 190 | "hasWatchHardwareOffer": { 191 | "type": "boolean" 192 | }, 193 | "isInFamily": { 194 | "type": "boolean" 195 | }, 196 | "hasSubscriptionFamilySharingEnabled": { 197 | "type": "boolean" 198 | } 199 | }, 200 | "required": [ 201 | "personalization", 202 | "underThirteen", 203 | "identityLastVerified", 204 | "verifiedExpirationDate", 205 | "retailDemo", 206 | "autoPlay", 207 | "isDisabledAccount", 208 | "isRestrictedAccount", 209 | "isManagedAccount", 210 | "isInRestrictedRegion", 211 | "accountFlagsVersion", 212 | "hasAgreedToTerms", 213 | "hasAgreedToAppClipTerms", 214 | "hasWatchHardwareOffer", 215 | "isInFamily", 216 | "hasSubscriptionFamilySharingEnabled" 217 | ] 218 | }, 219 | "status": { 220 | "type": "integer" 221 | }, 222 | "download-queue-info": { 223 | "type": "object", 224 | "properties": { 225 | "download-queue-item-count": { 226 | "type": "integer" 227 | }, 228 | "dsid": { 229 | "type": "integer" 230 | }, 231 | "is-auto-download-machine": { 232 | "type": "boolean" 233 | } 234 | }, 235 | "required": [ 236 | "download-queue-item-count", 237 | "dsid", 238 | "is-auto-download-machine" 239 | ] 240 | }, 241 | "privacyAcknowledgement": { 242 | "type": "object", 243 | "properties": { 244 | "com.apple.onboarding.appstore": { 245 | "type": "integer" 246 | }, 247 | "com.apple.onboarding.applemusic": { 248 | "type": "integer" 249 | }, 250 | "com.apple.onboarding.videos": { 251 | "type": "integer" 252 | }, 253 | "com.apple.onboarding.itunesstore": { 254 | "type": "integer" 255 | }, 256 | "com.apple.onboarding.itunesu": { 257 | "type": "integer" 258 | }, 259 | "com.apple.onboarding.applearcade": { 260 | "type": "integer" 261 | } 262 | }, 263 | "required": [ 264 | "com.apple.onboarding.appstore", 265 | "com.apple.onboarding.applemusic", 266 | "com.apple.onboarding.videos", 267 | "com.apple.onboarding.itunesstore", 268 | "com.apple.onboarding.itunesu", 269 | "com.apple.onboarding.applearcade" 270 | ] 271 | }, 272 | "dialog": { 273 | "type": "object", 274 | "properties": { 275 | "m-allowed": { 276 | "type": "boolean" 277 | }, 278 | "message": { 279 | "type": "string" 280 | }, 281 | "explanation": { 282 | "type": "string" 283 | }, 284 | "defaultButton": { 285 | "type": "string" 286 | }, 287 | "okButtonString": { 288 | "type": "string" 289 | }, 290 | "initialCheckboxValue": { 291 | "type": "boolean" 292 | } 293 | }, 294 | "required": [ 295 | "m-allowed", 296 | "message", 297 | "explanation", 298 | "defaultButton", 299 | "okButtonString", 300 | "initialCheckboxValue" 301 | ] 302 | } 303 | }, 304 | "required": [ 305 | "pings", 306 | "accountInfo", 307 | "passwordToken", 308 | "clearToken", 309 | "m-allowed", 310 | "is-cloud-enabled", 311 | "dsPersonId", 312 | "creditDisplay", 313 | "creditBalance", 314 | "freeSongBalance", 315 | "isManagedStudent", 316 | "action", 317 | "subscriptionStatus", 318 | "accountFlags", 319 | "status", 320 | "download-queue-info", 321 | "privacyAcknowledgement", 322 | "dialog" 323 | ] 324 | } -------------------------------------------------------------------------------- /reqs/schemas/schema_defs/store_buyproduct_req.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Store BuyProduct Req", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "type": "object", 5 | "properties": { 6 | "ageCheck": { 7 | "type": "string" 8 | }, 9 | "appExtVrsId": { 10 | "type": "string" 11 | }, 12 | "guid": { 13 | "type": "string" 14 | }, 15 | "hasBeenAuthedForBuy": { 16 | "type": "string" 17 | }, 18 | "isInApp": { 19 | "type": "string" 20 | }, 21 | "kbsync": { 22 | "type": "string" 23 | }, 24 | "sbsync": { 25 | "type": "string" 26 | }, 27 | "afds": { 28 | "type": "string" 29 | }, 30 | "machineName": { 31 | "type": "string" 32 | }, 33 | "mtApp": { 34 | "type": "string" 35 | }, 36 | "mtClientId": { 37 | "type": "string" 38 | }, 39 | "mtEventTime": { 40 | "type": "string" 41 | }, 42 | "mtPageId": { 43 | "type": "string" 44 | }, 45 | "mtPageType": { 46 | "type": "string" 47 | }, 48 | "mtPrevPage": { 49 | "type": "string" 50 | }, 51 | "mtRequestId": { 52 | "type": "string" 53 | }, 54 | "mtTopic": { 55 | "type": "string" 56 | }, 57 | "needDiv": { 58 | "type": "string" 59 | }, 60 | "pg": { 61 | "type": "string" 62 | }, 63 | "price": { 64 | "type": "string" 65 | }, 66 | "pricingParameters": { 67 | "type": "string" 68 | }, 69 | "productType": { 70 | "type": "string" 71 | }, 72 | "salableAdamId": { 73 | "type": "string" 74 | }, 75 | 76 | "hasAskedToFulfillPreorder": { 77 | "type": "string" 78 | }, 79 | "buyWithoutAuthorization": { 80 | "type": "string" 81 | }, 82 | "hasDoneAgeCheck": { 83 | "type": "string" 84 | }, 85 | "hasConfirmedPaymentSheet": { 86 | "type": "string" 87 | }, 88 | "asn": { 89 | "type": "string" 90 | } 91 | }, 92 | "required": [ 93 | "guid", 94 | "kbsync", 95 | "price", 96 | "pricingParameters", 97 | "productType", 98 | "salableAdamId", 99 | "appExtVrsId" 100 | ], 101 | "optional": [ 102 | "ageCheck", 103 | "hasBeenAuthedForBuy", 104 | "hasConfirmedPaymentSheet", 105 | "asn", 106 | "isInApp", 107 | 108 | "machineName", 109 | "mtApp", 110 | "mtClientId", 111 | "mtEventTime", 112 | "mtPageId", 113 | "mtPageType", 114 | "mtPrevPage", 115 | "mtRequestId", 116 | "mtTopic", 117 | "needDiv", 118 | "pg" 119 | ] 120 | } -------------------------------------------------------------------------------- /reqs/schemas/schema_defs/store_buyproduct_resp.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Store BuyProduct Resp", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "type": "object", 5 | "properties": { 6 | "pings": { 7 | "type": "array", 8 | "items": { 9 | "type": ["number","string","boolean","object","array", "null"] 10 | } 11 | }, 12 | "jingleDocType": { 13 | "type": "string" 14 | }, 15 | "jingleAction": { 16 | "type": "string" 17 | }, 18 | "status": { 19 | "type": "integer" 20 | }, 21 | "dsPersonId": { 22 | "type": "string" 23 | }, 24 | "creditDisplay": { 25 | "type": "string" 26 | }, 27 | "creditBalance": { 28 | "type": "string" 29 | }, 30 | "freeSongBalance": { 31 | "type": "string" 32 | }, 33 | "creditDisplayInternal": { 34 | "type": "string" 35 | }, 36 | "authorized": { 37 | "type": "boolean" 38 | }, 39 | "download-queue-item-count": { 40 | "type": "integer" 41 | }, 42 | "songList": { 43 | "type": "array", 44 | "items": { 45 | "type": "object", 46 | "properties": { 47 | "songId": { 48 | "type": "integer" 49 | }, 50 | "URL": { 51 | "type": "string" 52 | }, 53 | "downloadKey": { 54 | "type": "string" 55 | }, 56 | "artworkURL": { 57 | "type": "string" 58 | }, 59 | "artwork-urls": { 60 | "type": "object", 61 | "properties": { 62 | "image-type": { 63 | "type": "string" 64 | }, 65 | "default": { 66 | "type": "object", 67 | "properties": { 68 | "url": { 69 | "type": "string" 70 | } 71 | }, 72 | "required": [ 73 | "url" 74 | ] 75 | } 76 | }, 77 | "required": [ 78 | "image-type", 79 | "default" 80 | ] 81 | }, 82 | "md5": { 83 | "type": "string" 84 | }, 85 | "chunks": { 86 | "type": "object", 87 | "properties": { 88 | "chunkSize": { 89 | "type": "integer" 90 | }, 91 | "hashes": { 92 | "type": "array", 93 | "items": { 94 | "type": "string" 95 | } 96 | } 97 | }, 98 | "required": [ 99 | "chunkSize", 100 | "hashes" 101 | ] 102 | }, 103 | "isStreamable": { 104 | "type": "boolean" 105 | }, 106 | "uncompressedSize": { 107 | "type": "integer" 108 | }, 109 | "sinfs": { 110 | "type": "array", 111 | "items": { 112 | "type": "object", 113 | "properties": { 114 | "id": { 115 | "type": "integer" 116 | }, 117 | "sinf": { 118 | "type": "string" 119 | } 120 | }, 121 | "required": [ 122 | "id", 123 | "sinf" 124 | ] 125 | } 126 | }, 127 | "purchaseDate": { 128 | "type": "string" 129 | }, 130 | "download-id": { 131 | "type": "string" 132 | }, 133 | "is-in-queue": { 134 | "type": "boolean" 135 | }, 136 | "asset-info": { 137 | "type": "object", 138 | "properties": { 139 | "file-size": { 140 | "type": "integer" 141 | }, 142 | "flavor": { 143 | "type": "string" 144 | } 145 | }, 146 | "required": [ 147 | "file-size", 148 | "flavor" 149 | ] 150 | }, 151 | "metadata": { 152 | "type": "object", 153 | "properties": { 154 | "MacUIRequiredDeviceCapabilities": { 155 | "type": "object", 156 | "properties": { 157 | "arm64": { 158 | "type": "boolean" 159 | } 160 | }, 161 | "required": [ 162 | "arm64" 163 | ] 164 | }, 165 | "UIRequiredDeviceCapabilities": { 166 | "type": "object", 167 | "properties": { 168 | "arm64": { 169 | "type": "boolean" 170 | } 171 | }, 172 | "required": [ 173 | "arm64" 174 | ] 175 | }, 176 | "WKRunsIndependentlyOfCompanionApp": { 177 | "type": "boolean" 178 | }, 179 | "WKWatchOnly": { 180 | "type": "boolean" 181 | }, 182 | "appleWatchEnabled": { 183 | "type": "boolean" 184 | }, 185 | "artistId": { 186 | "type": "integer" 187 | }, 188 | "artistName": { 189 | "type": "string" 190 | }, 191 | "bundleDisplayName": { 192 | "type": "string" 193 | }, 194 | "bundleShortVersionString": { 195 | "type": "string" 196 | }, 197 | "bundleVersion": { 198 | "type": "string" 199 | }, 200 | "copyright": { 201 | "type": "string" 202 | }, 203 | "fileExtension": { 204 | "type": "string" 205 | }, 206 | "gameCenterEnabled": { 207 | "type": "boolean" 208 | }, 209 | "gameCenterEverEnabled": { 210 | "type": "boolean" 211 | }, 212 | "genre": { 213 | "type": "string" 214 | }, 215 | "genreId": { 216 | "type": "integer" 217 | }, 218 | "itemId": { 219 | "type": "integer" 220 | }, 221 | "itemName": { 222 | "type": "string" 223 | }, 224 | "kind": { 225 | "type": "string" 226 | }, 227 | "nameTranscriptions": { 228 | "type": "object", 229 | "properties": { 230 | "zh-Hans-CN": { 231 | "type": "array", 232 | "items": { 233 | "type": "string" 234 | } 235 | } 236 | }, 237 | "required": [ 238 | "zh-Hans-CN" 239 | ] 240 | }, 241 | "playlistName": { 242 | "type": "string" 243 | }, 244 | "product-type": { 245 | "type": "string" 246 | }, 247 | "rating": { 248 | "type": "object", 249 | "properties": { 250 | "content": { 251 | "type": "string" 252 | }, 253 | "label": { 254 | "type": "string" 255 | }, 256 | "rank": { 257 | "type": "integer" 258 | }, 259 | "system": { 260 | "type": "string" 261 | } 262 | }, 263 | "required": [ 264 | "content", 265 | "label", 266 | "rank", 267 | "system" 268 | ] 269 | }, 270 | "releaseDate": { 271 | "type": "string" 272 | }, 273 | "requiresRosetta": { 274 | "type": "boolean" 275 | }, 276 | "runsOnAppleSilicon": { 277 | "type": "boolean" 278 | }, 279 | "runsOnIntel": { 280 | "type": "boolean" 281 | }, 282 | "s": { 283 | "type": "integer" 284 | }, 285 | "software-platform": { 286 | "type": "string" 287 | }, 288 | "softwareIcon57x57URL": { 289 | "type": "string" 290 | }, 291 | "softwareIconNeedsShine": { 292 | "type": "boolean" 293 | }, 294 | "softwareSupportedDeviceIds": { 295 | "type": "array", 296 | "items": { 297 | "type": "integer" 298 | } 299 | }, 300 | "softwareVersionBundleId": { 301 | "type": "string" 302 | }, 303 | "softwareVersionExternalIdentifier": { 304 | "type": "integer" 305 | }, 306 | "softwareVersionExternalIdentifiers": { 307 | "type": "array", 308 | "items": { 309 | "type": "integer" 310 | } 311 | }, 312 | "vendorId": { 313 | "type": "integer" 314 | }, 315 | "drmVersionNumber": { 316 | "type": "integer" 317 | }, 318 | "versionRestrictions": { 319 | "type": "integer" 320 | }, 321 | "storeCohort": { 322 | "type": "string" 323 | }, 324 | "hasOrEverHasHadIAP": { 325 | "type": "boolean" 326 | } 327 | }, 328 | "required": [ 329 | "MacUIRequiredDeviceCapabilities", 330 | "UIRequiredDeviceCapabilities", 331 | "WKRunsIndependentlyOfCompanionApp", 332 | "WKWatchOnly", 333 | "appleWatchEnabled", 334 | "artistId", 335 | "artistName", 336 | "bundleDisplayName", 337 | "bundleShortVersionString", 338 | "bundleVersion", 339 | "copyright", 340 | "fileExtension", 341 | "gameCenterEnabled", 342 | "gameCenterEverEnabled", 343 | "genre", 344 | "genreId", 345 | "itemId", 346 | "itemName", 347 | "kind", 348 | "nameTranscriptions", 349 | "playlistName", 350 | "product-type", 351 | "rating", 352 | "releaseDate", 353 | "requiresRosetta", 354 | "runsOnAppleSilicon", 355 | "runsOnIntel", 356 | "s", 357 | "software-platform", 358 | "softwareIcon57x57URL", 359 | "softwareIconNeedsShine", 360 | "softwareSupportedDeviceIds", 361 | "softwareVersionBundleId", 362 | "softwareVersionExternalIdentifier", 363 | "softwareVersionExternalIdentifiers", 364 | "vendorId", 365 | "drmVersionNumber", 366 | "versionRestrictions", 367 | "storeCohort", 368 | "hasOrEverHasHadIAP" 369 | ] 370 | } 371 | }, 372 | "required": [ 373 | "songId", 374 | "URL", 375 | "downloadKey", 376 | "artworkURL", 377 | "artwork-urls", 378 | "md5", 379 | "chunks", 380 | "isStreamable", 381 | "uncompressedSize", 382 | "sinfs", 383 | "purchaseDate", 384 | "download-id", 385 | "is-in-queue", 386 | "asset-info", 387 | "metadata" 388 | ] 389 | } 390 | }, 391 | "download-queue-info": { 392 | "type": "object", 393 | "properties": { 394 | "download-queue-item-count": { 395 | "type": "integer" 396 | }, 397 | "dsid": { 398 | "type": "integer" 399 | }, 400 | "is-auto-download-machine": { 401 | "type": "boolean" 402 | } 403 | }, 404 | "required": [ 405 | "download-queue-item-count", 406 | "dsid", 407 | "is-auto-download-machine" 408 | ] 409 | }, 410 | "metrics": { 411 | "type": "object", 412 | "properties": { 413 | "itemIds": { 414 | "type": "array", 415 | "items": { 416 | "type": "integer" 417 | } 418 | }, 419 | "price": { 420 | "type": "integer" 421 | }, 422 | "priceType": { 423 | "type": "string" 424 | }, 425 | "productTypes": { 426 | "type": "array", 427 | "items": { 428 | "type": "string" 429 | } 430 | }, 431 | "mtApp": { 432 | "type": "string" 433 | }, 434 | "mtClientId": { 435 | "type": "string" 436 | }, 437 | "mtEventTime": { 438 | "type": "string" 439 | }, 440 | "mtPageId": { 441 | "type": "string" 442 | }, 443 | "mtPageType": { 444 | "type": "string" 445 | }, 446 | "mtPrevPage": { 447 | "type": "string" 448 | }, 449 | "mtRequestId": { 450 | "type": "string" 451 | }, 452 | "mtTopic": { 453 | "type": "string" 454 | }, 455 | "currency": { 456 | "type": "string" 457 | }, 458 | "exchangeRateToUSD": { 459 | "type": "number" 460 | }, 461 | "commerceEvent_purchase_priceType": { 462 | "type": "string" 463 | }, 464 | "commerceEvent_storeFrontId": { 465 | "type": "string" 466 | }, 467 | "commerceEvent_result_resultType": { 468 | "type": "integer" 469 | }, 470 | "commerceEvent_flowType": { 471 | "type": "integer" 472 | }, 473 | "commerceEvent_flowStep": { 474 | "type": "integer" 475 | }, 476 | 477 | "dialogId": { 478 | "type": "string" 479 | }, 480 | "message": { 481 | "type": "string" 482 | }, 483 | "messageCode": { 484 | "type": "string" 485 | }, 486 | "options": { 487 | "type": "array", 488 | "items": { 489 | "type": "string" 490 | } 491 | }, 492 | "actionUrl": { 493 | "type": "string" 494 | }, 495 | "asnState": { 496 | "type": "integer" 497 | }, 498 | "eventType": { 499 | "type": "string" 500 | } 501 | }, 502 | "required": [ 503 | "mtApp", 504 | "mtClientId", 505 | "mtEventTime", 506 | "mtPageId", 507 | "mtPageType", 508 | "mtPrevPage", 509 | "mtRequestId", 510 | "mtTopic" 511 | ], 512 | "optional": [ 513 | "itemIds", 514 | "price", 515 | "priceType", 516 | "productTypes", 517 | "currency", 518 | "exchangeRateToUSD", 519 | "commerceEvent_purchase_priceType", 520 | "commerceEvent_storeFrontId", 521 | "commerceEvent_result_resultType", 522 | "commerceEvent_flowType", 523 | "commerceEvent_flowStep", 524 | 525 | "dialogId", 526 | "message", 527 | "messageCode", 528 | "options", 529 | "actionUrl", 530 | "asnState", 531 | "eventType" 532 | ] 533 | }, 534 | "duAnonymousPings": { 535 | "type": "array", 536 | "items": { 537 | "type": "string" 538 | } 539 | }, 540 | "subscriptionStatus": { 541 | "type": "object", 542 | "properties": { 543 | "music": { 544 | "type": "object", 545 | "properties": { 546 | "status": { 547 | "type": "string" 548 | }, 549 | "reason": { 550 | "type": "string" 551 | }, 552 | "isAdmin": { 553 | "type": "boolean" 554 | }, 555 | "isNotEligibleForFreeTrial": { 556 | "type": "boolean" 557 | } 558 | }, 559 | "required": [ 560 | "status", 561 | "reason", 562 | "isAdmin", 563 | "isNotEligibleForFreeTrial" 564 | ] 565 | }, 566 | "terms": { 567 | "type": "array", 568 | "items": { 569 | "type": "object", 570 | "properties": { 571 | "type": { 572 | "type": "string" 573 | }, 574 | "latestTerms": { 575 | "type": "integer" 576 | }, 577 | "agreedToTerms": { 578 | "type": "integer" 579 | }, 580 | "source": { 581 | "type": "string" 582 | } 583 | }, 584 | "required": [ 585 | "type", 586 | "latestTerms", 587 | "agreedToTerms", 588 | "source" 589 | ] 590 | } 591 | }, 592 | "account": { 593 | "type": "object", 594 | "properties": { 595 | "isMinor": { 596 | "type": "boolean" 597 | }, 598 | "suspectUnderage": { 599 | "type": "boolean" 600 | } 601 | }, 602 | "required": [ 603 | "isMinor", 604 | "suspectUnderage" 605 | ] 606 | }, 607 | "family": { 608 | "type": "object", 609 | "properties": { 610 | "hasFamily": { 611 | "type": "boolean" 612 | } 613 | }, 614 | "required": [ 615 | "hasFamily" 616 | ] 617 | } 618 | }, 619 | "required": [ 620 | "music", 621 | "terms", 622 | "account", 623 | "family" 624 | ] 625 | }, 626 | "cancel-purchase-batch": { 627 | "type": "boolean" 628 | }, 629 | "failureType": { 630 | "type": "string" 631 | }, 632 | "customerMessage": { 633 | "type": "string" 634 | }, 635 | "m-allowed": { 636 | "type": "boolean" 637 | }, 638 | "dialog": { 639 | "type": "object", 640 | "properties": { 641 | "kind": { 642 | "type": "string" 643 | }, 644 | "m-allowed": { 645 | "type": "boolean" 646 | }, 647 | "use-keychain": { 648 | "type": "boolean" 649 | }, 650 | "isFree": { 651 | "type": "boolean" 652 | }, 653 | "message": { 654 | "type": "string" 655 | }, 656 | "explanation": { 657 | "type": "string" 658 | }, 659 | "defaultButton": { 660 | "type": "string" 661 | }, 662 | "okButtonString": { 663 | "type": "string" 664 | }, 665 | "okButtonAction": { 666 | "type": "object", 667 | "properties": { 668 | "kind": { 669 | "type": "string" 670 | }, 671 | "buyParams": { 672 | "type": "string" 673 | }, 674 | "itemName": { 675 | "type": "string" 676 | } 677 | }, 678 | "required": [ 679 | "kind", 680 | "buyParams", 681 | "itemName" 682 | ] 683 | }, 684 | "cancelButtonString": { 685 | "type": "string" 686 | }, 687 | "initialCheckboxValue": { 688 | "type": "boolean" 689 | } 690 | }, 691 | "required": [ 692 | "kind", 693 | "m-allowed", 694 | "use-keychain", 695 | "isFree", 696 | "message", 697 | "explanation", 698 | "defaultButton", 699 | "okButtonString", 700 | "okButtonAction", 701 | "cancelButtonString", 702 | "initialCheckboxValue" 703 | ] 704 | } 705 | }, 706 | "required": [ 707 | "pings", 708 | "metrics" 709 | ], 710 | "optional": [ 711 | "jingleDocType", 712 | "jingleAction", 713 | "status", 714 | "dsPersonId", 715 | "creditDisplay", 716 | "creditBalance", 717 | "freeSongBalance", 718 | "creditDisplayInternal", 719 | "authorized", 720 | "download-queue-item-count", 721 | "songList", 722 | "download-queue-info", 723 | "duAnonymousPings", 724 | "subscriptionStatus", 725 | 726 | "failureType", 727 | "customerMessage", 728 | "m-allowed", 729 | "dialog", 730 | "cancel-purchase-batch" 731 | ] 732 | } -------------------------------------------------------------------------------- /reqs/schemas/schema_defs/store_download_req.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Store Download Req", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "type": "object", 5 | "properties": { 6 | "creditDisplay": { 7 | "type": "string" 8 | }, 9 | "guid": { 10 | "type": "string" 11 | }, 12 | "salableAdamId": { 13 | "type": "string" 14 | }, 15 | "externalVersionId": { 16 | "type": "string" 17 | } 18 | }, 19 | "required": [ 20 | "creditDisplay", 21 | "guid", 22 | "salableAdamId" 23 | ] 24 | } -------------------------------------------------------------------------------- /reqs/schemas/schema_defs/store_download_resp.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Store Download Resp", 3 | "$schema": "http://json-schema.org/draft-04/schema#", 4 | "type": "object", 5 | "properties": { 6 | "pings": { 7 | "type": "array", 8 | "items": { 9 | "type": ["number","string","boolean","object","array", "null"] 10 | } 11 | }, 12 | "cancel-purchase-batch": { 13 | "type": "boolean" 14 | }, 15 | "customerMessage": { 16 | "type": "string" 17 | }, 18 | "failureType": { 19 | "type": "string" 20 | }, 21 | "jingleDocType": { 22 | "type": "string" 23 | }, 24 | "jingleAction": { 25 | "type": "string" 26 | }, 27 | "status": { 28 | "type": "integer" 29 | }, 30 | "dsPersonId": { 31 | "type": "string" 32 | }, 33 | "creditDisplay": { 34 | "type": "string" 35 | }, 36 | "creditBalance": { 37 | "type": "string" 38 | }, 39 | "freeSongBalance": { 40 | "type": "string" 41 | }, 42 | "authorized": { 43 | "type": "boolean" 44 | }, 45 | "download-queue-item-count": { 46 | "type": "integer" 47 | }, 48 | "songList": { 49 | "type": "array", 50 | "items": { 51 | "type": "object", 52 | "properties": { 53 | "songId": { 54 | "type": "integer" 55 | }, 56 | "URL": { 57 | "type": "string" 58 | }, 59 | "downloadKey": { 60 | "type": "string" 61 | }, 62 | "artworkURL": { 63 | "type": "string" 64 | }, 65 | "artwork-urls": { 66 | "type": "object", 67 | "properties": { 68 | "image-type": { 69 | "type": "string" 70 | }, 71 | "default": { 72 | "type": "object", 73 | "properties": { 74 | "url": { 75 | "type": "string" 76 | } 77 | }, 78 | "required": [ 79 | "url" 80 | ] 81 | } 82 | }, 83 | "required": [ 84 | "image-type", 85 | "default" 86 | ] 87 | }, 88 | "md5": { 89 | "type": "string" 90 | }, 91 | "chunks": { 92 | "type": "object", 93 | "properties": { 94 | "chunkSize": { 95 | "type": "integer" 96 | }, 97 | "hashes": { 98 | "type": "array", 99 | "items": { 100 | "type": "string" 101 | } 102 | } 103 | }, 104 | "required": [ 105 | "chunkSize", 106 | "hashes" 107 | ] 108 | }, 109 | "isStreamable": { 110 | "type": "boolean" 111 | }, 112 | "uncompressedSize": { 113 | "type": "string" 114 | }, 115 | "sinfs": { 116 | "type": "array", 117 | "items": { 118 | "type": "object", 119 | "properties": { 120 | "id": { 121 | "type": "integer" 122 | }, 123 | "sinf": { 124 | "type": "string" 125 | } 126 | }, 127 | "required": [ 128 | "id", 129 | "sinf" 130 | ] 131 | } 132 | }, 133 | "purchaseDate": { 134 | "type": "string" 135 | }, 136 | "download-id": { 137 | "type": "string" 138 | }, 139 | "is-in-queue": { 140 | "type": "boolean" 141 | }, 142 | "asset-info": { 143 | "type": "object", 144 | "properties": { 145 | "file-size": { 146 | "type": "integer" 147 | }, 148 | "flavor": { 149 | "type": "string" 150 | } 151 | }, 152 | "required": [ 153 | "file-size", 154 | "flavor" 155 | ] 156 | }, 157 | "metadata": { 158 | "type": "object", 159 | "properties": { 160 | "MacUIRequiredDeviceCapabilities": { 161 | "type": "object", 162 | "properties": { 163 | "arm64": { 164 | "type": "boolean" 165 | }, 166 | "gamekit": { 167 | "type": "boolean" 168 | }, 169 | "metal": { 170 | "type": "boolean" 171 | } 172 | }, 173 | "required": [ 174 | "arm64", 175 | "gamekit", 176 | "metal" 177 | ] 178 | }, 179 | "UIRequiredDeviceCapabilities": { 180 | "type": "object", 181 | "properties": { 182 | "arm64": { 183 | "type": "boolean" 184 | }, 185 | "gamekit": { 186 | "type": "boolean" 187 | }, 188 | "metal": { 189 | "type": "boolean" 190 | } 191 | }, 192 | "required": [ 193 | "arm64", 194 | "gamekit", 195 | "metal" 196 | ] 197 | }, 198 | "artistId": { 199 | "type": "integer" 200 | }, 201 | "artistName": { 202 | "type": "string" 203 | }, 204 | "bundleDisplayName": { 205 | "type": "string" 206 | }, 207 | "bundleShortVersionString": { 208 | "type": "string" 209 | }, 210 | "bundleVersion": { 211 | "type": "string" 212 | }, 213 | "copyright": { 214 | "type": "string" 215 | }, 216 | "fileExtension": { 217 | "type": "string" 218 | }, 219 | "gameCenterEnabled": { 220 | "type": "boolean" 221 | }, 222 | "gameCenterEverEnabled": { 223 | "type": "boolean" 224 | }, 225 | "genre": { 226 | "type": "string" 227 | }, 228 | "genreId": { 229 | "type": "integer" 230 | }, 231 | "itemId": { 232 | "type": "integer" 233 | }, 234 | "itemName": { 235 | "type": "string" 236 | }, 237 | "kind": { 238 | "type": "string" 239 | }, 240 | "playlistName": { 241 | "type": "string" 242 | }, 243 | "product-type": { 244 | "type": "string" 245 | }, 246 | "rating": { 247 | "type": "object", 248 | "properties": { 249 | "content": { 250 | "type": "string" 251 | }, 252 | "label": { 253 | "type": "string" 254 | }, 255 | "rank": { 256 | "type": "integer" 257 | }, 258 | "system": { 259 | "type": "string" 260 | } 261 | }, 262 | "required": [ 263 | "content", 264 | "label", 265 | "rank", 266 | "system" 267 | ] 268 | }, 269 | "releaseDate": { 270 | "type": "string" 271 | }, 272 | "requiresRosetta": { 273 | "type": "boolean" 274 | }, 275 | "runsOnAppleSilicon": { 276 | "type": "boolean" 277 | }, 278 | "runsOnIntel": { 279 | "type": "boolean" 280 | }, 281 | "s": { 282 | "type": "integer" 283 | }, 284 | "software-platform": { 285 | "type": "string" 286 | }, 287 | "softwareIcon57x57URL": { 288 | "type": "string" 289 | }, 290 | "softwareIconNeedsShine": { 291 | "type": "boolean" 292 | }, 293 | "softwareSupportedDeviceIds": { 294 | "type": "array", 295 | "items": { 296 | "type": "integer" 297 | } 298 | }, 299 | "softwareVersionBundleId": { 300 | "type": "string" 301 | }, 302 | "softwareVersionExternalIdentifier": { 303 | "type": "integer" 304 | }, 305 | "softwareVersionExternalIdentifiers": { 306 | "type": "array", 307 | "items": { 308 | "type": "integer" 309 | } 310 | }, 311 | "subgenres": { 312 | "type": "array", 313 | "items": { 314 | "type": "object", 315 | "properties": { 316 | "genre": { 317 | "type": "string" 318 | }, 319 | "genreId": { 320 | "type": "integer" 321 | } 322 | }, 323 | "required": [ 324 | "genre", 325 | "genreId" 326 | ] 327 | } 328 | }, 329 | "vendorId": { 330 | "type": "integer" 331 | }, 332 | "drmVersionNumber": { 333 | "type": "integer" 334 | }, 335 | "versionRestrictions": { 336 | "type": "integer" 337 | } 338 | }, 339 | "required": [ 340 | "MacUIRequiredDeviceCapabilities", 341 | "UIRequiredDeviceCapabilities", 342 | "artistId", 343 | "artistName", 344 | "bundleDisplayName", 345 | "bundleShortVersionString", 346 | "bundleVersion", 347 | "copyright", 348 | "fileExtension", 349 | "gameCenterEnabled", 350 | "gameCenterEverEnabled", 351 | "genre", 352 | "genreId", 353 | "itemId", 354 | "itemName", 355 | "kind", 356 | "playlistName", 357 | "product-type", 358 | "rating", 359 | "releaseDate", 360 | "requiresRosetta", 361 | "runsOnAppleSilicon", 362 | "runsOnIntel", 363 | "s", 364 | "software-platform", 365 | "softwareIcon57x57URL", 366 | "softwareIconNeedsShine", 367 | "softwareSupportedDeviceIds", 368 | "softwareVersionBundleId", 369 | "softwareVersionExternalIdentifier", 370 | "softwareVersionExternalIdentifiers", 371 | "subgenres", 372 | "vendorId", 373 | "drmVersionNumber", 374 | "versionRestrictions" 375 | ] 376 | } 377 | }, 378 | "required": [ 379 | "songId", 380 | "URL", 381 | "downloadKey", 382 | "artworkURL", 383 | "artwork-urls", 384 | "md5", 385 | "chunks", 386 | "isStreamable", 387 | "uncompressedSize", 388 | "sinfs", 389 | "purchaseDate", 390 | "download-id", 391 | "is-in-queue", 392 | "asset-info", 393 | "metadata" 394 | ] 395 | } 396 | }, 397 | "metrics": { 398 | "type": "object", 399 | "properties": { 400 | "itemIds": { 401 | "type": "array", 402 | "items": { 403 | "type": "integer" 404 | } 405 | }, 406 | "currency": { 407 | "type": "string" 408 | }, 409 | "exchangeRateToUSD": { 410 | "type": "number" 411 | } 412 | }, 413 | "required": [ 414 | "itemIds", 415 | "currency", 416 | "exchangeRateToUSD" 417 | ] 418 | }, 419 | "subscriptionStatus": { 420 | "type": "object", 421 | "properties": { 422 | "terms": { 423 | "type": "array", 424 | "items": { 425 | "type": "object", 426 | "properties": { 427 | "type": { 428 | "type": "string" 429 | }, 430 | "latestTerms": { 431 | "type": "integer" 432 | }, 433 | "agreedToTerms": { 434 | "type": "integer" 435 | }, 436 | "source": { 437 | "type": "string" 438 | } 439 | }, 440 | "required": [ 441 | "type", 442 | "latestTerms", 443 | "agreedToTerms", 444 | "source" 445 | ] 446 | } 447 | }, 448 | "account": { 449 | "type": "object", 450 | "properties": { 451 | "isMinor": { 452 | "type": "boolean" 453 | }, 454 | "suspectUnderage": { 455 | "type": "boolean" 456 | } 457 | }, 458 | "required": [ 459 | "isMinor", 460 | "suspectUnderage" 461 | ] 462 | }, 463 | "family": { 464 | "type": "object", 465 | "properties": { 466 | "hasFamily": { 467 | "type": "boolean" 468 | } 469 | }, 470 | "required": [ 471 | "hasFamily" 472 | ] 473 | } 474 | }, 475 | "required": [ 476 | "terms", 477 | "account", 478 | "family" 479 | ] 480 | } 481 | }, 482 | "required": [ 483 | "pings", 484 | "jingleDocType", 485 | "jingleAction", 486 | "status", 487 | "dsPersonId", 488 | "creditDisplay", 489 | "creditBalance", 490 | "freeSongBalance", 491 | "authorized", 492 | "download-queue-item-count", 493 | "songList", 494 | "metrics", 495 | "subscriptionStatus" 496 | ] 497 | } -------------------------------------------------------------------------------- /reqs/schemas/schema_examples/itunes_lookup_resp.log: -------------------------------------------------------------------------------- 1 | { 2 | "resultCount":1, 3 | "results": [ 4 | { 5 | "screenshotUrls":["https://is1-ssl.mzstatic.com/image/thumb/Purple123/v4/c7/9d/5a/c79d5ac9-4543-e9d7-acb3-4753704f1488/pr_source.png/392x696bb.png", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/e4/38/3a/e4383af3-d9ed-3d0c-9c80-ca0d413c0f06/pr_source.png/392x696bb.png", "https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/08/b2/39/08b23995-3c76-3e74-5af4-3edb4914dc4c/pr_source.png/392x696bb.png"], 6 | "ipadScreenshotUrls":["https://is5-ssl.mzstatic.com/image/thumb/Purple113/v4/67/74/39/6774390f-7a12-3d9f-ded7-392b9af90663/pr_source.png/576x768bb.png", "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/88/62/85/886285f0-b1c2-ad6b-3ab3-2a5ee70d7c9d/pr_source.png/576x768bb.png", "https://is4-ssl.mzstatic.com/image/thumb/Purple123/v4/29/39/68/29396836-1b94-1561-4bd1-cde929ae5baa/pr_source.png/576x768bb.png"], "appletvScreenshotUrls":[], 7 | "artworkUrl60":"https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/6b/ed/31/6bed31bd-42d1-5ae9-04fd-a04c865af27d/AppIcon-0-0-1x_U007emarketing-0-0-0-7-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/60x60bb.jpg", 8 | "artworkUrl512":"https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/6b/ed/31/6bed31bd-42d1-5ae9-04fd-a04c865af27d/AppIcon-0-0-1x_U007emarketing-0-0-0-7-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/512x512bb.jpg", 9 | "artworkUrl100":"https://is3-ssl.mzstatic.com/image/thumb/Purple123/v4/6b/ed/31/6bed31bd-42d1-5ae9-04fd-a04c865af27d/AppIcon-0-0-1x_U007emarketing-0-0-0-7-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/100x100bb.jpg", "artistViewUrl":"https://apps.apple.com/us/developer/potatso-lab-ltd/id1267906737?uo=4", 10 | "supportedDevices":["iPhone5s-iPhone5s", "iPadAir-iPadAir", "iPadAirCellular-iPadAirCellular", "iPadMiniRetina-iPadMiniRetina", "iPadMiniRetinaCellular-iPadMiniRetinaCellular", "iPhone6-iPhone6", "iPhone6Plus-iPhone6Plus", "iPadAir2-iPadAir2", "iPadAir2Cellular-iPadAir2Cellular", "iPadMini3-iPadMini3", "iPadMini3Cellular-iPadMini3Cellular", "iPodTouchSixthGen-iPodTouchSixthGen", "iPhone6s-iPhone6s", "iPhone6sPlus-iPhone6sPlus", "iPadMini4-iPadMini4", "iPadMini4Cellular-iPadMini4Cellular", "iPadPro-iPadPro", "iPadProCellular-iPadProCellular", "iPadPro97-iPadPro97", "iPadPro97Cellular-iPadPro97Cellular", "iPhoneSE-iPhoneSE", "iPhone7-iPhone7", "iPhone7Plus-iPhone7Plus", "iPad611-iPad611", "iPad612-iPad612", "iPad71-iPad71", "iPad72-iPad72", "iPad73-iPad73", "iPad74-iPad74", "iPhone8-iPhone8", "iPhone8Plus-iPhone8Plus", "iPhoneX-iPhoneX", "iPad75-iPad75", "iPad76-iPad76", "iPhoneXS-iPhoneXS", "iPhoneXSMax-iPhoneXSMax", "iPhoneXR-iPhoneXR", "iPad812-iPad812", "iPad834-iPad834", "iPad856-iPad856", "iPad878-iPad878", "iPadMini5-iPadMini5", "iPadMini5Cellular-iPadMini5Cellular", "iPadAir3-iPadAir3", "iPadAir3Cellular-iPadAir3Cellular", "iPodTouchSeventhGen-iPodTouchSeventhGen", "iPhone11-iPhone11", "iPhone11Pro-iPhone11Pro", "iPadSeventhGen-iPadSeventhGen", "iPadSeventhGenCellular-iPadSeventhGenCellular", "iPhone11ProMax-iPhone11ProMax", "iPhoneSESecondGen-iPhoneSESecondGen", "iPadProSecondGen-iPadProSecondGen", "iPadProSecondGenCellular-iPadProSecondGenCellular", "iPadProFourthGen-iPadProFourthGen", "iPadProFourthGenCellular-iPadProFourthGenCellular", "iPhone12Mini-iPhone12Mini", "iPhone12-iPhone12", "iPhone12Pro-iPhone12Pro", "iPhone12ProMax-iPhone12ProMax", "iPadAir4-iPadAir4", "iPadAir4Cellular-iPadAir4Cellular", "iPadEighthGen-iPadEighthGen", "iPadEighthGenCellular-iPadEighthGenCellular", "iPadProThirdGen-iPadProThirdGen", "iPadProThirdGenCellular-iPadProThirdGenCellular", "iPadProFifthGen-iPadProFifthGen", "iPadProFifthGenCellular-iPadProFifthGenCellular", "iPhone13Pro-iPhone13Pro", "iPhone13ProMax-iPhone13ProMax", "iPhone13Mini-iPhone13Mini", "iPhone13-iPhone13", "iPadMiniSixthGen-iPadMiniSixthGen", "iPadMiniSixthGenCellular-iPadMiniSixthGenCellular", "iPadNinthGen-iPadNinthGen", "iPadNinthGenCellular-iPadNinthGenCellular", "iPhoneSEThirdGen-iPhoneSEThirdGen", "iPadAirFifthGen-iPadAirFifthGen", "iPadAirFifthGenCellular-iPadAirFifthGenCellular"], "features":["iosUniversal"], "advisories":[], "isGameCenterEnabled":false, "kind":"software", "minimumOsVersion":"13.0", "trackCensoredName":"Potatso Lite", "languageCodesISO2A":["EN", "ZH"], "fileSizeBytes":"18907136", "sellerUrl":"https://potatso.com/en", "formattedPrice":"Free", "contentAdvisoryRating":"4+", "averageUserRatingForCurrentVersion":4.595620000000000260342858382500708103179931640625, "userRatingCountForCurrentVersion":2923, "averageUserRating":4.595620000000000260342858382500708103179931640625, "trackViewUrl":"https://apps.apple.com/us/app/potatso-lite/id1239860606?uo=4", "trackContentRating":"4+", "trackId":1239860606, "trackName":"Potatso Lite", "bundleId":"com.touchingapp.potatsolite", "primaryGenreName":"Utilities", "releaseDate":"2017-06-01T02:34:35Z", "genreIds":["6002", "6007"], "isVppDeviceBasedLicensingEnabled":true, "sellerName":"Potatso Lab LTD", "currentVersionReleaseDate":"2019-12-16T23:27:27Z", 11 | "releaseNotes":"With this update, we're bringing you some exciting new features and changes. \n\n=====================\n===== What's New =====\n=====================\n\n• The app will support iOS 13+ from now on.\n• We're introducing the brand new logo with clarity and simplicity.\n• We've redeisnged the whole UI to improve your using experience.\n• Some other internal performance improvements and bug fixes.", "primaryGenreId":6002, "currency":"USD", "version":"2.5.0", "wrapperType":"software", "artistId":1267906737, "artistName":"Potatso Lab LTD", "genres":["Utilities", "Productivity"], "price":0.00, 12 | "description":"Potatso Lite is a powerful network tool which empowers your phone to have fully customized network environment. It's friendly for both beginners and power users. \n\nPotatso now supports Shadowsocks, ShadowsocksR, HTTP and Socks5 proxies. You can either setup one by yourself or buy from any proxy providers.\n\nEmbedded smart rouing feature for Chinese users is super helpful which can cost less data in proxy servers and speed up domestic network traffic.\n\n=== Features ===\n- Custom proxy supports Shadowsocks, ShadowsocksR, HTTP and Socks5 \n- Run in the background sustainably without interrupting you\n- Both cellular and Wi-Fi are supported\n- Custom DNS support\n- Smart Routing for Chinese users \n\n=== Privacy ===\nWe respect your privacy so NO confidential data will be uploaded or shared with third parties\n\n=== Feedback ===\nPlease contact on hi@potatso.com", "userRatingCount":2923}] 13 | } -------------------------------------------------------------------------------- /reqs/schemas/schema_examples/store_buyproduct_req.log: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | appExtVrsId 5 | 848463733 6 | guid 7 | 22330C8C.C2E39C5C.06C40B91.D0990F95.9A890F9D.AB7F7EB4.56507025 8 | kbsync 9 | 10 | AAQAA5nBc3Q7ETi+TD1x8AM4wbw9sqWglXBaXwuIrlMR9OnAPY89zaoTvbe6PneuS1x1 11 | 31NxVIqrAsIZLmxy5be8dQtq8je/rtYTRlxduU9NwW4DBcplBx6vs9qhS3Y8B45Zz4T5 12 | dkmDG4UnS7xnAPwew7jEX/uY38zZhtKu4IN+sl/Whvyh2SkZg/5vGCtjav17CGbP8ZWo 13 | Ci3FhEqAByOL0g6zhPdTHyqF84Apg9fh395tGpzzAWB8mYsRQXJcUHH1cuJjO/qMTkZ4 14 | ZxIJqfMaDJpS20nFq+/Bfg9FvC/83AOPnDfXZTsol3PFKqQ6sLgz6dKIho4Qd2UPABnj 15 | kBx4TFPeYBlm2T6GKfi8tr+rDhsMrbNczpnaUS+3cesIOvDsE3YCX4isOmMtg5yrJ5pi 16 | 2GHuofHD2I7Dj6fOh79I7F5OZb71PUvbeABjxvS5b57LGNICSBc/GJ0CEja27kpYals+ 17 | bgYG0rVm+vqlAHwRpka5jzeK8DLrvTr22vtBLv62LpTVpVVglr5nbk99BcXG5gA= 18 | 19 | machineName 20 | DESKTOP-697LVJS 21 | mtApp 22 | com.apple.iTunes 23 | mtClientId 24 | 3z21abvCzFDuz5CYz9bdz19maFVKge 25 | mtEventTime 26 | 1652006678827 27 | mtPageId 28 | ccfce0ef-4ac8-4d5a-8e5f-79876ac474a4 29 | mtPageType 30 | Search 31 | mtPrevPage 32 | Purchases 33 | mtRequestId 34 | 3z21abvCzFDuz5CYz9bdz19maFVKgezL2X64FFVz1MGG 35 | mtTopic 36 | xp_its_main 37 | needDiv 38 | 0 39 | pg 40 | default 41 | price 42 | 0 43 | pricingParameters 44 | STDQ 45 | productType 46 | C 47 | salableAdamId 48 | 444934666 49 | 50 | 51 | 52 | 53 | 54 | 55 | ageCheck 56 | true 57 | appExtVrsId 58 | 848463733 59 | guid 60 | 22330C8C.C2E39C5C.06C40B91.D0990F95.9A890F9D.AB7F7EB4.56507025 61 | hasBeenAuthedForBuy 62 | true 63 | isInApp 64 | false 65 | kbsync 66 | 67 | AAQAA1c+HfGD4vNLZJYBMpBucSo1bxeaeTEZ3FGUKLq0skmzTxVik6IGoQMGaP6OWsJU 68 | lBoDb3hxaacD57bkiAgRXc/vr21/CipX55hKLoTE53yah3DwBR9tS1cG7oaHFLIh1Vmn 69 | RV7G9LJCQqwSAbr4ugEIVmLULkqaHDfTm8VNDXxYej1p8ghKggMcBT0se5cpDqpVn/bE 70 | qehnOl6QsupUNjjLzDm58bmERm/AjGJVBHQveG+4Y+Y4e1eUO6QQcntmypjmSLDqhI60 71 | 31rCV3zwTTZrHmmXEwsZiGgYlSVHR3ne+O9BE+LIPiQxDwIMvjfV6SrzoOUOLlOKvBsk 72 | kI29+6H0QNyMUXojWPQf7bfr+9NBTMgJoDNJd5hEGHqiSKnp9V1ALU8S8QwcYzi3ZDPu 73 | A36lMtgMOFEnYibnGnP5S8i2t43ZSBglExE9LGTGO0/IEWX9gEhKjvMDuIwMt9zdtpye 74 | CXO8siqHY4MtmLlfwZZc/480ZSHfRF8ZQHBa0J0/pgByNQiNe9KMihz+QN7cQI4= 75 | 76 | machineName 77 | DESKTOP-697LVJS 78 | mtApp 79 | com.apple.iTunes 80 | mtClientId 81 | 3z21abvCzFDuz5CYz9bdz19maFVKge 82 | mtEventTime 83 | 1652006678827 84 | mtPageId 85 | ccfce0ef-4ac8-4d5a-8e5f-79876ac474a4 86 | mtPageType 87 | Search 88 | mtPrevPage 89 | Purchases 90 | mtRequestId 91 | 3z21abvCzFDuz5CYz9bdz19maFVKgezL2X64FFVz1MGG 92 | mtTopic 93 | xp_its_main 94 | needDiv 95 | 0 96 | pg 97 | default 98 | price 99 | 0 100 | pricingParameters 101 | STDQ 102 | productType 103 | C 104 | salableAdamId 105 | 444934666 106 | 107 | -------------------------------------------------------------------------------- /reqs/schemas/schema_examples/store_buyproduct_resp.log: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pings 5 | https://xp.apple.com/report/2/xp_its_main?app=com.apple.iTunes&code=MZCommerce.ASN.ExpiredPasswordToken&buttons=%E8%8E%B7%E5%8F%96%3A%E5%8F%96%E6%B6%88&baseVersion=1&dsId=16916646015&eventVersion=1&storeFrontHeader=143465-19%2C32&eventTime=1652006682067&eventType=dialog&message=%E9%9C%80%E8%A6%81%E7%99%BB%E5%BD%95 6 | 7 | metrics 8 | 9 | dialogIdMZCommerce.ASN.ExpiredPasswordToken 10 | message需要登录 11 | messageCode2072 12 | options 13 | 14 | Get 15 | Cancel 16 | 17 | actionUrlp36-buy.itunes.apple.com/WebObjects/MZBuy.woa/wa/buyProduct 18 | asnState2 19 | mtAppcom.apple.iTunes 20 | mtClientId3z21abvCzFDuz5CYz9bdz19maFVKge 21 | mtEventTime2022-05-08 10:44:38 Etc/GMT 22 | mtPageIdccfce0ef-4ac8-4d5a-8e5f-79876ac474a4 23 | mtPageTypeSearch 24 | mtPrevPagePurchases 25 | mtRequestId3z21abvCzFDuz5CYz9bdz19maFVKgezL2X64FFVz1MGG 26 | mtTopicxp_its_main 27 | eventTypedialog 28 | 29 | failureType2072 30 | customerMessage需要登录 31 | m-allowed 32 | dialog 33 | kindauthorization 34 | m-allowed 35 | use-keychain 36 | isFree 37 | message需要登录 38 | explanation如果您有 Apple ID 和密码,请在此处输入。例如,如果您使用过 iTunes Store 或 iCloud,那么您已有 Apple ID。 39 | defaultButtonok 40 | okButtonString获取 41 | okButtonActionkindBuy 42 | buyParamsmtEventTime=1652006678827&salableAdamId=444934666&mtRequestId=3z21abvCzFDuz5CYz9bdz19maFVKgezL2X64FFVz1MGG&appExtVrsId=848463733&mtTopic=xp_its_main&guid=22330C8C.C2E39C5C.06C40B91.D0990F95.9A890F9D.AB7F7EB4.56507025&hasBeenAuthedForBuy=true&isInApp=false&price=0&mtClientId=3z21abvCzFDuz5CYz9bdz19maFVKge&productType=C&mtPageType=Search&mtPageId=ccfce0ef-4ac8-4d5a-8e5f-79876ac474a4&machineName=DESKTOP-697LVJS&ageCheck=true&pg=default&mtApp=com.apple.iTunes&needDiv=0&mtPrevPage=Purchases&pricingParameters=STDQ 43 | itemNameQQ 44 | 45 | cancelButtonString取消 46 | initialCheckboxValue 47 | cancel-purchase-batch 48 | 49 | 50 | 51 | 52 | 53 | 54 | pings 55 | 56 | jingleDocTypepurchaseSuccess 57 | jingleActionpurchaseProduct 58 | status0 59 | 60 | dsPersonId10964418715 61 | creditDisplay 62 | creditBalance1311811 63 | freeSongBalance1311811 64 | 65 | authorizeddownload-queue-item-count0 66 | songList 67 | 68 | 69 | metrics 70 | 71 | 72 | itemIds 73 | 74 | 580311103 75 | 76 | price0.00 77 | priceTypeSTDQ 78 | productTypes 79 | 80 | C 81 | 82 | currencyJPY 83 | exchangeRateToUSD0.0076722418 84 | commerceEvent_purchase_priceTypeSTDQ 85 | commerceEvent_storeFrontId143462 86 | commerceEvent_result_resultType0 87 | commerceEvent_flowType4 88 | commerceEvent_flowStep6 89 | 90 | 91 | duAnonymousPings 92 | 93 | https://xp.apple.com/report/2/xp_app_buy?clientId=0&sf=143462&adamId=580311103 94 | 95 | subscriptionStatus 96 | 97 | terms 98 | 99 | 100 | typeStore 101 | latestTerms28 102 | agreedToTerms31 103 | sourceaccount 104 | 105 | 106 | account 107 | 108 | isMinor 109 | suspectUnderage 110 | 111 | family 112 | 113 | hasFamily 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | pings 124 | 125 | jingleDocTypepurchaseSuccess 126 | jingleActionpurchaseProduct 127 | status0 128 | 129 | dsPersonId16916646015 130 | creditDisplay 131 | creditBalance1311811 132 | freeSongBalance1311811 133 | creditDisplayInternalÂ¥0.00+0+0+0+0+0 134 | 135 | authorized 136 | 137 | download-queue-item-count1 138 | songList 139 | 140 | 141 | songId444934666 142 | URLhttps://iosapps.itunes.apple.com/itunes-assets/Purple112/v4/8c/5d/34/8c5d343a-2132-8690-c6b1-866ec2f6b2f6/extDirwkazoqouvdywwgjk.lc.14519290919268642.5LAVFF7SAES2W.signed.dpkg.ipa?accessKey=1652201151_1282442801256894451_c%2FtMyevC%2FbCtPTcx3kpjPzNcDfSmgpz1CnzuNFgB%2F1n13VEU1IYDwlq1Xie8WXNHq4U4t341RRlyT3J1OI1Doy1%2FKOG8Pk4u38Kn30NHbCnmgCRC2r9lGlND9ZjU7AxGCQeVb5iHc74Vf5i7Exbg3hq5UfUddWq%2BBe7s3VEyvOX9Rikq0Hzj4OkYUkklrks95yuvrEhXjMFYO8YpQAr0tCjKoyU2rvjS%2BwnPYk5U0Hy6DnusEJ3JmJpJKC6EiLvb 143 | downloadKeyartworkURLhttps://is5-ssl.mzstatic.com/image/thumb/Purple112/v4/b9/ac/4e/b9ac4ef3-0152-fa92-872f-fe773d799117/AppIcon-1-0-0-1x_U007emarketing-0-0-0-7-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/2000x2000bb.jpgartwork-urls 144 | 145 | image-typedownload-queue-item 146 | default 147 | 148 | url 149 | https://is4-ssl.mzstatic.com/image/thumb/Purple112/v4/b9/ac/4e/b9ac4ef3-0152-fa92-872f-fe773d799117/AppIcon-1-0-0-1x_U007emarketing-0-0-0-7-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/57x57bb.jpg 150 | 151 | 152 | md5cb3a13a60ac4c9f507b0fefa351e0351chunks 153 | 154 | chunkSize10485760 155 | hashes 156 | 157 | a9c3b17b7fd5ce1380580c6f063c469b 158 | 8dfa7efed3cccca58987ec5fb5aad752 159 | c636e4bfcdc17f5ce23a8ef104a44fa3 160 | 90911e7b4d63bf8d4fc1c0bb2756211b 161 | 0c7ae0bfa13df1a3cb25d18eccf79083 162 | 4edbba71a6baabeba209cbe495144017 163 | 2b915fa08b532ef007787f3cf9ed68e6 164 | e8ae486763cd40c730a6f2008bb212df 165 | a91b20af08349a115d318a3c1a9ac69c 166 | 001a0a9e6f0f779365fd1a9e08993afa 167 | d9c965a523b445f662aac8eb49fa271e 168 | 22f61327c32a4569a4d81f29ae93f238 169 | 3caf910c1da7640a89a51c2ae33e8a3b 170 | 70fcd6e5517144b1298848bd964f4dcc 171 | 9fb4924aed5006d2c7037be6532d1652 172 | fb341fcaaf6027182f885628e15542f6 173 | a0999c33fa684fa21cf2d8c8a4d45e27 174 | 28025c9522f1dc05360e36a9e6da508b 175 | ea980ee5fae29238778cdd16a1f0a2fa 176 | 699c2c8bcd5b4b9353bba25a58bb45f8 177 | 10423be39910d01b9e763cc1514fef05 178 | 3da83694d61846e91a0a21981e88652a 179 | 937391e158c46e385bf1ebd2f17501aa 180 | 18a12b305b3a0086d76d4f9c72764a00 181 | c19f2b5c1eeb909db77b5d715e023511 182 | 80552d4ed44472cf0f307bc6e6e4180c 183 | 9af9bebfb73a15084186032efd64f95b 184 | bdc8305c863637d6031c079f3438b2b2 185 | 406fff31a2d714ffa1eb7c417232c478 186 | f29550bd0e718ca8588d13654a14e669 187 | ea916c4a8e8f772610bd4de2549ec891 188 | 9ff8f440fb8260ffe7e39e17215e8e9f 189 | b596fe96037d6e0e52401676b87fc6c0 190 | 1be6f6b3141c80d2b3ad952db9e92ebb 191 | 4a9061c50a209184f7d4a54f80ce61ef 192 | e6505a456ca2273700dcad1dead1f625 193 | 9ac28b8e7dd43be5fd309f16403afef8 194 | 5088792cdb4928532714a7023df1275d 195 | c0b0d04fd38919c8688c5e793b33d5e4 196 | a87331d5e314e6708f6cc67075e36c56 197 | 63ea3257f99d04b44730924cb4a0d553 198 | b3d953d072a3b0e8e264c16d96cebeaf 199 | 5eff58a53e7f7e87f57fcb038a4ad0a4 200 | e37467e9f2c7d336c3742b63ee70b4ab 201 | abfcdd0ba248ee9c5bc1b4bf8ab03c15 202 | 7c4d4ccfcaaef4708f3d1b0fce994294 203 | 927273c03da6f0fcdc674b40224c0a6d 204 | 648761edcf4c3d69e147b3671abee7a8 205 | 146e363283ed83bfd72a8c4d6b8fa7ac 206 | 69c8e188357d463d00bd7f8134786b1c 207 | babbb9b2f0c3d31a4d9e0e0d77852f5e 208 | ae46925c45e689783c38f065a9d1e7ea 209 | c7f4967cb830f008417c02009ae2196a 210 | 9b8f17524eb05a512c26bb9e2b89220b 211 | 11cdc44dd07fe5d9fa3eaba0a179704c 212 | d0f17891f1c141e39cfcacfb58c1bb3c 213 | 3273d004f5971ebee5625868d4978f69 214 | 0aa895b7d15ad0760156b3062fb6deeb 215 | 7b27b6e73b0731b307399aa4da837df9 216 | 217 | 218 | isStreamable 219 | uncompressedSize795979776 220 | sinfs id 0 sinf AAAEIHNpbmYAAAAMZnJtYWdhbWUAAAAUc2NobQAAAABpdHVuAAAAAAAAA3BzY2hpAAAADHVzZXLwT4h/AAAADGNyZHTenU/fAAAADGFzZHQAAAAAAAAADGtleSAAAAACAAAAGGl2aXaD6ySnLt5l3Pdp75fJ0mjxAAAAWHJpZ2h2ZUlEAAEOnHBsYXQAAAABYXZlcgEBAQB0cmFu3p1P33NpbmcAAAAAc29uZxqFKgp0b29sUDYwNG1lZGkAAACAbW9kZQAAIABoaTMyAAAAAwAAAQhuYW1l5q63IOWmueS7jQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcBwcml2eDOeoQvqIT8I9Y9+tU7xixDGAGJI8TvGWPhI2r7kjf9vKy/JunWkBOLN7Ft3eFaFidNSVs3VTlvYSulf3VT0QAc0+3/pFznoHsNC5pMpQ6y0HdBW+8nG+JbiLjeTU3Nj2akunI45wVAiE2IWr1mERJwFG5CavbGOvHM9JBxkHaczFWUUm60zUHuMiBGZiZVDbVI4Oilxdn0vRb3c2P0zDrhOyxez9B7ppEMzJpFkDa2ieFSE0peNxsx8xi+BLWqQ88IZiCpCrLma3P5Wj40hb0nXclQY2t9Zvnw54A2DC/NHEUEDq1T/cZvAi16AdDRB0/GWOTLf7RNf1R8glahArjoD+fddyk9MuVwEN63YqCH/bNh1L7+hpk0Vh2tDGuEy/KDA30kZgF9Z2FED9LAQpCmx1BHUc60NDq++mCImFNiO528OnwirxjZoMWCZWsyCBL+RDTLcNLZOVDwKJcCqP+xLhbbSD8yoQhug/5reFOIUieODiQHGnfVmUmVe9sDuoJmEME3MHMwUHIpGkVfXVE6SPxA2eoN6EReeKwVwbDeLsp/r/dA8jX7BWW9WSjjfAAAAAAAAAAAAAACIc2lnbggl0CkIwRg6tBbdAWQKT2ZlMkYC2bZvWrsyeLzUy+BSYJAs7v2jr1DkRH/xdJbaDjvmkCvS75EUlCXU0oJYT8BydxZzTpJNMALVfwuJNELOkm8sqlCN1/zP1KM3XOeEMu/k6MmtV4nG3zOtLRcKHg5YRz17WcccdTSz251J2Ugn purchaseDate2022-05-08T10:45:51Z 221 | download-id501382282407089488 222 | is-in-queue 223 | 224 | asset-info 225 | 226 | file-size611092999 227 | flavor10:purple 228 | 229 | metadata 230 | 231 | MacUIRequiredDeviceCapabilities 232 | 233 | arm64 234 | 235 | UIRequiredDeviceCapabilities 236 | 237 | arm64 238 | 239 | WKRunsIndependentlyOfCompanionApp 240 | WKWatchOnly 241 | appleWatchEnabled 242 | artistId292374531 243 | artistNameTencent Technology (Shenzhen) Company Limited 244 | bundleDisplayNameQQ 245 | bundleShortVersionString8.8.88 246 | bundleVersion8.8.88.662 247 | copyrightCopyright © 1998- 2022 Tencent. All Rights Reserved 248 | fileExtension.app 249 | gameCenterEnabled 250 | gameCenterEverEnabled 251 | genre社交 252 | genreId6005 253 | itemId444934666 254 | itemNameQQ 255 | kindsoftware 256 | nameTranscriptions 257 | 258 | zh-Hans-CN 259 | 260 | QQ 261 | 262 | 263 | playlistNameTencent Technology (Shenzhen) Company Limited 264 | product-typeios-app 265 | rating 266 | 267 | content偶尔/轻微的色情内容或裸露 , Advisory.NO.GAMBLING_CONTESTS , 偶尔/轻微的成人或性暗示题材 , Advisory.NO.UNRESTRICTED_WEB_ACCESS and Advisory.NO.TRUE_GAMBLING 268 | label12+ 269 | rank300 270 | systemitunes-games 271 | 272 | releaseDate2011-06-23T03:33:55Z 273 | requiresRosetta 274 | runsOnAppleSilicon 275 | runsOnIntel 276 | s143465 277 | software-platformios 278 | softwareIcon57x57URLhttps://is4-ssl.mzstatic.com/image/thumb/Purple112/v4/b9/ac/4e/b9ac4ef3-0152-fa92-872f-fe773d799117/AppIcon-1-0-0-1x_U007emarketing-0-0-0-7-0-0-sRGB-0-0-0-GLES2_U002c0-512MB-85-220-0-0.png/114x114bb.jpg 279 | softwareIconNeedsShine 280 | softwareSupportedDeviceIds 281 | 282 | 2 283 | 9 284 | 4 285 | 286 | softwareVersionBundleIdcom.tencent.mqq 287 | softwareVersionExternalIdentifier848463733 288 | softwareVersionExternalIdentifiers 289 | 290 | 3843900 291 | 3876776 292 | 3941034 293 | 3973775 294 | 4070873 295 | 4135846 296 | 4321059 297 | 4492645 298 | 4917185 299 | 5632593 300 | 6232232 301 | 6860432 302 | 7792605 303 | 9642362 304 | 11556077 305 | 11818464 306 | 12638046 307 | 13422327 308 | 14959445 309 | 15410008 310 | 15765932 311 | 15854719 312 | 16122679 313 | 31562763 314 | 275882650 315 | 385122645 316 | 580102645 317 | 595393136 318 | 608133076 319 | 629072654 320 | 687502658 321 | 747082669 322 | 811253584 323 | 811445779 324 | 811179715 325 | 811445780 326 | 811590055 327 | 811669050 328 | 812133257 329 | 812375519 330 | 812625692 331 | 812972631 332 | 813031156 333 | 813192464 334 | 813298393 335 | 813463229 336 | 813961231 337 | 813962159 338 | 814174262 339 | 814318376 340 | 814527796 341 | 814531991 342 | 814639613 343 | 814754396 344 | 814882132 345 | 815144899 346 | 815147083 347 | 815188393 348 | 815573881 349 | 815574602 350 | 815607136 351 | 815810968 352 | 815897122 353 | 815938087 354 | 815938591 355 | 816130933 356 | 816210673 357 | 816305041 358 | 816356364 359 | 816843896 360 | 816912335 361 | 817028549 362 | 817235792 363 | 817473714 364 | 817549698 365 | 817788181 366 | 817933532 367 | 818110324 368 | 818431910 369 | 818825180 370 | 818979113 371 | 819096686 372 | 819416223 373 | 819489902 374 | 819842838 375 | 819893353 376 | 820113905 377 | 820199943 378 | 820442929 379 | 820548304 380 | 820595060 381 | 821268583 382 | 821341311 383 | 821500924 384 | 821954014 385 | 822037007 386 | 822096329 387 | 822279520 388 | 822523036 389 | 822895308 390 | 822957820 391 | 823194852 392 | 823309872 393 | 823713346 394 | 824097583 395 | 824171129 396 | 824301257 397 | 824389600 398 | 825024981 399 | 825145808 400 | 825307653 401 | 825347730 402 | 825611268 403 | 825729315 404 | 825895542 405 | 825933124 406 | 826313718 407 | 826632543 408 | 826837026 409 | 827460275 410 | 828106847 411 | 828385681 412 | 828600919 413 | 828666943 414 | 828716670 415 | 828897691 416 | 829301009 417 | 829496800 418 | 829679760 419 | 829821912 420 | 830133231 421 | 830530856 422 | 830742895 423 | 831337375 424 | 831405629 425 | 831472755 426 | 831824011 427 | 832139548 428 | 832542612 429 | 832827329 430 | 833393416 431 | 833855517 432 | 834104017 433 | 834138755 434 | 834768091 435 | 834880993 436 | 834939578 437 | 835135459 438 | 835524672 439 | 835716180 440 | 835976856 441 | 836375483 442 | 836825545 443 | 836945925 444 | 837334930 445 | 837735298 446 | 837835768 447 | 837881604 448 | 838238560 449 | 839235987 450 | 839700113 451 | 840003771 452 | 840041841 453 | 840921423 454 | 841227891 455 | 841968948 456 | 842099251 457 | 842166600 458 | 842303496 459 | 842463280 460 | 842670770 461 | 842892434 462 | 843105491 463 | 843416604 464 | 843638409 465 | 843881653 466 | 844115468 467 | 844170367 468 | 844356613 469 | 844638728 470 | 844786323 471 | 844986204 472 | 845375859 473 | 846332674 474 | 846748317 475 | 847332305 476 | 847747163 477 | 848017318 478 | 848463733 479 | 480 | vendorId69276 481 | 482 | drmVersionNumber0 483 | versionRestrictions16843008 484 | storeCohort10|date=1652005800000&sf=143465&pgtp=Search&pgid=ccfce0ef-4ac8-4d5a-8e5f-79876ac474a4&prpg=Purchases 485 | hasOrEverHasHadIAP 486 | 487 | 488 | 489 | 490 | 491 | download-queue-info 492 | 493 | download-queue-item-count0 494 | dsid16916646015 495 | is-auto-download-machine 496 | 497 | 498 | metrics 499 | 500 | 501 | itemIds 502 | 503 | 444934666 504 | 505 | price0.00 506 | priceTypeSTDQ 507 | productTypes 508 | 509 | C 510 | 511 | mtAppcom.apple.iTunes 512 | mtClientId3z21abvCzFDuz5CYz9bdz19maFVKge 513 | mtEventTime2022-05-08 10:44:38 Etc/GMT 514 | mtPageIdccfce0ef-4ac8-4d5a-8e5f-79876ac474a4 515 | mtPageTypeSearch 516 | mtPrevPagePurchases 517 | mtRequestId3z21abvCzFDuz5CYz9bdz19maFVKgezL2X64FFVz1MGG 518 | mtTopicxp_its_main 519 | currencyCNY 520 | exchangeRateToUSD0.1490268546 521 | commerceEvent_purchase_priceTypeSTDQ 522 | commerceEvent_storeFrontId143465 523 | commerceEvent_result_resultType0 524 | commerceEvent_flowType4 525 | commerceEvent_flowStep6 526 | 527 | 528 | duAnonymousPings 529 | 530 | https://xp.apple.com/report/2/xp_app_buy?clientId=0&sf=143465&adamId=444934666 531 | 532 | subscriptionStatus 533 | 534 | music 535 | 536 | statusDisabled 537 | reason 538 | isAdmin 539 | isNotEligibleForFreeTrial 540 | 541 | terms 542 | 543 | 544 | typeStore 545 | latestTerms22 546 | agreedToTerms22 547 | sourceaccount 548 | 549 | 550 | account 551 | 552 | isMinor 553 | suspectUnderage 554 | 555 | family 556 | 557 | hasFamily 558 | 559 | 560 | 561 | 562 | -------------------------------------------------------------------------------- /reqs/schemas/store_authenticate_req.py: -------------------------------------------------------------------------------- 1 | from reprlib import repr as limitedRepr 2 | 3 | 4 | class StoreAuthenticateReq: 5 | 6 | _types_map = { 7 | "appleId": {"type": str, "subtype": None}, 8 | "attempt": {"type": str, "subtype": None}, 9 | "createSession": {"type": str, "subtype": None}, 10 | "guid": {"type": str, "subtype": None}, 11 | "password": {"type": str, "subtype": None}, 12 | "rmp": {"type": str, "subtype": None}, 13 | "why": {"type": str, "subtype": None}, 14 | } 15 | _formats_map = {} 16 | _validations_map = { 17 | "appleId": { 18 | "required": True, 19 | }, 20 | "attempt": { 21 | "required": True, 22 | }, 23 | "createSession": { 24 | "required": True, 25 | }, 26 | "guid": { 27 | "required": True, 28 | }, 29 | "password": { 30 | "required": True, 31 | }, 32 | "rmp": { 33 | "required": True, 34 | }, 35 | "why": { 36 | "required": True, 37 | }, 38 | } 39 | 40 | def __init__( 41 | self, 42 | appleId: str = None, 43 | attempt: str = None, 44 | createSession: str = None, 45 | guid: str = None, 46 | password: str = None, 47 | rmp: str = None, 48 | why: str = None, 49 | ): 50 | pass 51 | self.__appleId = appleId 52 | self.__attempt = attempt 53 | self.__createSession = createSession 54 | self.__guid = guid 55 | self.__password = password 56 | self.__rmp = rmp 57 | self.__why = why 58 | 59 | def _get_appleId(self): 60 | return self.__appleId 61 | 62 | def _set_appleId(self, value): 63 | if not isinstance(value, str): 64 | raise TypeError("appleId must be str") 65 | 66 | self.__appleId = value 67 | 68 | appleId = property(_get_appleId, _set_appleId) 69 | 70 | def _get_attempt(self): 71 | return self.__attempt 72 | 73 | def _set_attempt(self, value): 74 | if not isinstance(value, str): 75 | raise TypeError("attempt must be str") 76 | 77 | self.__attempt = value 78 | 79 | attempt = property(_get_attempt, _set_attempt) 80 | 81 | def _get_createSession(self): 82 | return self.__createSession 83 | 84 | def _set_createSession(self, value): 85 | if not isinstance(value, str): 86 | raise TypeError("createSession must be str") 87 | 88 | self.__createSession = value 89 | 90 | createSession = property(_get_createSession, _set_createSession) 91 | 92 | def _get_guid(self): 93 | return self.__guid 94 | 95 | def _set_guid(self, value): 96 | if not isinstance(value, str): 97 | raise TypeError("guid must be str") 98 | 99 | self.__guid = value 100 | 101 | guid = property(_get_guid, _set_guid) 102 | 103 | def _get_password(self): 104 | return self.__password 105 | 106 | def _set_password(self, value): 107 | if not isinstance(value, str): 108 | raise TypeError("password must be str") 109 | 110 | self.__password = value 111 | 112 | password = property(_get_password, _set_password) 113 | 114 | def _get_rmp(self): 115 | return self.__rmp 116 | 117 | def _set_rmp(self, value): 118 | if not isinstance(value, str): 119 | raise TypeError("rmp must be str") 120 | 121 | self.__rmp = value 122 | 123 | rmp = property(_get_rmp, _set_rmp) 124 | 125 | def _get_why(self): 126 | return self.__why 127 | 128 | def _set_why(self, value): 129 | if not isinstance(value, str): 130 | raise TypeError("why must be str") 131 | 132 | self.__why = value 133 | 134 | why = property(_get_why, _set_why) 135 | 136 | @staticmethod 137 | def from_dict(d): 138 | v = {} 139 | if "appleId" in d: 140 | v["appleId"] = ( 141 | str.from_dict(d["appleId"]) 142 | if hasattr(str, "from_dict") 143 | else d["appleId"] 144 | ) 145 | if "attempt" in d: 146 | v["attempt"] = ( 147 | str.from_dict(d["attempt"]) 148 | if hasattr(str, "from_dict") 149 | else d["attempt"] 150 | ) 151 | if "createSession" in d: 152 | v["createSession"] = ( 153 | str.from_dict(d["createSession"]) 154 | if hasattr(str, "from_dict") 155 | else d["createSession"] 156 | ) 157 | if "guid" in d: 158 | v["guid"] = ( 159 | str.from_dict(d["guid"]) if hasattr(str, "from_dict") else d["guid"] 160 | ) 161 | if "password" in d: 162 | v["password"] = ( 163 | str.from_dict(d["password"]) 164 | if hasattr(str, "from_dict") 165 | else d["password"] 166 | ) 167 | if "rmp" in d: 168 | v["rmp"] = ( 169 | str.from_dict(d["rmp"]) if hasattr(str, "from_dict") else d["rmp"] 170 | ) 171 | if "why" in d: 172 | v["why"] = ( 173 | str.from_dict(d["why"]) if hasattr(str, "from_dict") else d["why"] 174 | ) 175 | return StoreAuthenticateReq(**v) 176 | 177 | def as_dict(self): 178 | d = {} 179 | if self.__appleId is not None: 180 | d["appleId"] = ( 181 | self.__appleId.as_dict() 182 | if hasattr(self.__appleId, "as_dict") 183 | else self.__appleId 184 | ) 185 | if self.__attempt is not None: 186 | d["attempt"] = ( 187 | self.__attempt.as_dict() 188 | if hasattr(self.__attempt, "as_dict") 189 | else self.__attempt 190 | ) 191 | if self.__createSession is not None: 192 | d["createSession"] = ( 193 | self.__createSession.as_dict() 194 | if hasattr(self.__createSession, "as_dict") 195 | else self.__createSession 196 | ) 197 | if self.__guid is not None: 198 | d["guid"] = ( 199 | self.__guid.as_dict() 200 | if hasattr(self.__guid, "as_dict") 201 | else self.__guid 202 | ) 203 | if self.__password is not None: 204 | d["password"] = ( 205 | self.__password.as_dict() 206 | if hasattr(self.__password, "as_dict") 207 | else self.__password 208 | ) 209 | if self.__rmp is not None: 210 | d["rmp"] = ( 211 | self.__rmp.as_dict() if hasattr(self.__rmp, "as_dict") else self.__rmp 212 | ) 213 | if self.__why is not None: 214 | d["why"] = ( 215 | self.__why.as_dict() if hasattr(self.__why, "as_dict") else self.__why 216 | ) 217 | return d 218 | 219 | def __repr__(self): 220 | return "".format( 221 | limitedRepr( 222 | self.__appleId[:20] 223 | if isinstance(self.__appleId, bytes) 224 | else self.__appleId 225 | ), 226 | limitedRepr( 227 | self.__attempt[:20] 228 | if isinstance(self.__attempt, bytes) 229 | else self.__attempt 230 | ), 231 | limitedRepr( 232 | self.__createSession[:20] 233 | if isinstance(self.__createSession, bytes) 234 | else self.__createSession 235 | ), 236 | limitedRepr( 237 | self.__guid[:20] if isinstance(self.__guid, bytes) else self.__guid 238 | ), 239 | limitedRepr( 240 | self.__password[:20] 241 | if isinstance(self.__password, bytes) 242 | else self.__password 243 | ), 244 | limitedRepr( 245 | self.__rmp[:20] if isinstance(self.__rmp, bytes) else self.__rmp 246 | ), 247 | limitedRepr( 248 | self.__why[:20] if isinstance(self.__why, bytes) else self.__why 249 | ), 250 | ) 251 | -------------------------------------------------------------------------------- /reqs/schemas/store_buyproduct_req.py: -------------------------------------------------------------------------------- 1 | from reprlib import repr as limitedRepr 2 | 3 | 4 | class StoreBuyproductReq: 5 | 6 | _types_map = { 7 | "ageCheck": {"type": str, "subtype": None}, 8 | "appExtVrsId": {"type": str, "subtype": None}, 9 | "guid": {"type": str, "subtype": None}, 10 | "hasBeenAuthedForBuy": {"type": str, "subtype": None}, 11 | "isInApp": {"type": str, "subtype": None}, 12 | "kbsync": {"type": str, "subtype": None}, 13 | "sbsync": {"type": str, "subtype": None}, 14 | "afds": {"type": str, "subtype": None}, 15 | "machineName": {"type": str, "subtype": None}, 16 | "mtApp": {"type": str, "subtype": None}, 17 | "mtClientId": {"type": str, "subtype": None}, 18 | "mtEventTime": {"type": str, "subtype": None}, 19 | "mtPageId": {"type": str, "subtype": None}, 20 | "mtPageType": {"type": str, "subtype": None}, 21 | "mtPrevPage": {"type": str, "subtype": None}, 22 | "mtRequestId": {"type": str, "subtype": None}, 23 | "mtTopic": {"type": str, "subtype": None}, 24 | "needDiv": {"type": str, "subtype": None}, 25 | "pg": {"type": str, "subtype": None}, 26 | "price": {"type": str, "subtype": None}, 27 | "pricingParameters": {"type": str, "subtype": None}, 28 | "productType": {"type": str, "subtype": None}, 29 | "salableAdamId": {"type": str, "subtype": None}, 30 | "hasAskedToFulfillPreorder": {"type": str, "subtype": None}, 31 | "buyWithoutAuthorization": {"type": str, "subtype": None}, 32 | "hasDoneAgeCheck": {"type": str, "subtype": None}, 33 | "hasConfirmedPaymentSheet": {"type": str, "subtype": None}, 34 | "asn": {"type": str, "subtype": None}, 35 | } 36 | _formats_map = {} 37 | _validations_map = { 38 | "ageCheck": { 39 | "required": False, 40 | }, 41 | "appExtVrsId": { 42 | "required": True, 43 | }, 44 | "guid": { 45 | "required": True, 46 | }, 47 | "hasBeenAuthedForBuy": { 48 | "required": False, 49 | }, 50 | "isInApp": { 51 | "required": False, 52 | }, 53 | "kbsync": { 54 | "required": True, 55 | }, 56 | "sbsync": { 57 | "required": False, 58 | }, 59 | "afds": { 60 | "required": False, 61 | }, 62 | "machineName": { 63 | "required": False, 64 | }, 65 | "mtApp": { 66 | "required": False, 67 | }, 68 | "mtClientId": { 69 | "required": False, 70 | }, 71 | "mtEventTime": { 72 | "required": False, 73 | }, 74 | "mtPageId": { 75 | "required": False, 76 | }, 77 | "mtPageType": { 78 | "required": False, 79 | }, 80 | "mtPrevPage": { 81 | "required": False, 82 | }, 83 | "mtRequestId": { 84 | "required": False, 85 | }, 86 | "mtTopic": { 87 | "required": False, 88 | }, 89 | "needDiv": { 90 | "required": False, 91 | }, 92 | "pg": { 93 | "required": False, 94 | }, 95 | "price": { 96 | "required": True, 97 | }, 98 | "pricingParameters": { 99 | "required": True, 100 | }, 101 | "productType": { 102 | "required": True, 103 | }, 104 | "salableAdamId": { 105 | "required": True, 106 | }, 107 | "hasAskedToFulfillPreorder": { 108 | "required": False, 109 | }, 110 | "buyWithoutAuthorization": { 111 | "required": False, 112 | }, 113 | "hasDoneAgeCheck": { 114 | "required": False, 115 | }, 116 | "hasConfirmedPaymentSheet": { 117 | "required": False, 118 | }, 119 | "asn": { 120 | "required": False, 121 | }, 122 | } 123 | 124 | def __init__( 125 | self, 126 | ageCheck: str = None, 127 | appExtVrsId: str = None, 128 | guid: str = None, 129 | hasBeenAuthedForBuy: str = None, 130 | isInApp: str = None, 131 | kbsync: str = None, 132 | sbsync: str = None, 133 | afds: str = None, 134 | machineName: str = None, 135 | mtApp: str = None, 136 | mtClientId: str = None, 137 | mtEventTime: str = None, 138 | mtPageId: str = None, 139 | mtPageType: str = None, 140 | mtPrevPage: str = None, 141 | mtRequestId: str = None, 142 | mtTopic: str = None, 143 | needDiv: str = None, 144 | pg: str = None, 145 | price: str = None, 146 | pricingParameters: str = None, 147 | productType: str = None, 148 | salableAdamId: str = None, 149 | hasAskedToFulfillPreorder: str = None, 150 | buyWithoutAuthorization: str = None, 151 | hasDoneAgeCheck: str = None, 152 | hasConfirmedPaymentSheet: str = None, 153 | asn: str = None, 154 | ): 155 | pass 156 | self.__ageCheck = ageCheck 157 | self.__appExtVrsId = appExtVrsId 158 | self.__guid = guid 159 | self.__hasBeenAuthedForBuy = hasBeenAuthedForBuy 160 | self.__isInApp = isInApp 161 | self.__kbsync = kbsync 162 | self.__sbsync = sbsync 163 | self.__afds = afds 164 | self.__machineName = machineName 165 | self.__mtApp = mtApp 166 | self.__mtClientId = mtClientId 167 | self.__mtEventTime = mtEventTime 168 | self.__mtPageId = mtPageId 169 | self.__mtPageType = mtPageType 170 | self.__mtPrevPage = mtPrevPage 171 | self.__mtRequestId = mtRequestId 172 | self.__mtTopic = mtTopic 173 | self.__needDiv = needDiv 174 | self.__pg = pg 175 | self.__price = price 176 | self.__pricingParameters = pricingParameters 177 | self.__productType = productType 178 | self.__salableAdamId = salableAdamId 179 | self.__hasAskedToFulfillPreorder = hasAskedToFulfillPreorder 180 | self.__buyWithoutAuthorization = buyWithoutAuthorization 181 | self.__hasDoneAgeCheck = hasDoneAgeCheck 182 | self.__hasConfirmedPaymentSheet = hasConfirmedPaymentSheet 183 | self.__asn = asn 184 | 185 | def _get_ageCheck(self): 186 | return self.__ageCheck 187 | 188 | def _set_ageCheck(self, value): 189 | if value is not None and not isinstance(value, str): 190 | raise TypeError("ageCheck must be str") 191 | 192 | self.__ageCheck = value 193 | 194 | ageCheck = property(_get_ageCheck, _set_ageCheck) 195 | 196 | def _get_appExtVrsId(self): 197 | return self.__appExtVrsId 198 | 199 | def _set_appExtVrsId(self, value): 200 | if not isinstance(value, str): 201 | raise TypeError("appExtVrsId must be str") 202 | 203 | self.__appExtVrsId = value 204 | 205 | appExtVrsId = property(_get_appExtVrsId, _set_appExtVrsId) 206 | 207 | def _get_guid(self): 208 | return self.__guid 209 | 210 | def _set_guid(self, value): 211 | if not isinstance(value, str): 212 | raise TypeError("guid must be str") 213 | 214 | self.__guid = value 215 | 216 | guid = property(_get_guid, _set_guid) 217 | 218 | def _get_hasBeenAuthedForBuy(self): 219 | return self.__hasBeenAuthedForBuy 220 | 221 | def _set_hasBeenAuthedForBuy(self, value): 222 | if value is not None and not isinstance(value, str): 223 | raise TypeError("hasBeenAuthedForBuy must be str") 224 | 225 | self.__hasBeenAuthedForBuy = value 226 | 227 | hasBeenAuthedForBuy = property(_get_hasBeenAuthedForBuy, _set_hasBeenAuthedForBuy) 228 | 229 | def _get_isInApp(self): 230 | return self.__isInApp 231 | 232 | def _set_isInApp(self, value): 233 | if value is not None and not isinstance(value, str): 234 | raise TypeError("isInApp must be str") 235 | 236 | self.__isInApp = value 237 | 238 | isInApp = property(_get_isInApp, _set_isInApp) 239 | 240 | def _get_kbsync(self): 241 | return self.__kbsync 242 | 243 | def _set_kbsync(self, value): 244 | if not isinstance(value, str): 245 | raise TypeError("kbsync must be str") 246 | 247 | self.__kbsync = value 248 | 249 | kbsync = property(_get_kbsync, _set_kbsync) 250 | 251 | def _get_sbsync(self): 252 | return self.__sbsync 253 | 254 | def _set_sbsync(self, value): 255 | if not isinstance(value, str): 256 | raise TypeError("sbsync must be str") 257 | 258 | self.__sbsync = value 259 | 260 | sbsync = property(_get_sbsync, _set_sbsync) 261 | 262 | def _get_afds(self): 263 | return self.__afds 264 | 265 | def _set_afds(self, value): 266 | if not isinstance(value, str): 267 | raise TypeError("afds must be str") 268 | 269 | self.__afds = value 270 | 271 | afds = property(_get_afds, _set_afds) 272 | 273 | def _get_machineName(self): 274 | return self.__machineName 275 | 276 | def _set_machineName(self, value): 277 | if value is not None and not isinstance(value, str): 278 | raise TypeError("machineName must be str") 279 | 280 | self.__machineName = value 281 | 282 | machineName = property(_get_machineName, _set_machineName) 283 | 284 | def _get_mtApp(self): 285 | return self.__mtApp 286 | 287 | def _set_mtApp(self, value): 288 | if value is not None and not isinstance(value, str): 289 | raise TypeError("mtApp must be str") 290 | 291 | self.__mtApp = value 292 | 293 | mtApp = property(_get_mtApp, _set_mtApp) 294 | 295 | def _get_mtClientId(self): 296 | return self.__mtClientId 297 | 298 | def _set_mtClientId(self, value): 299 | if value is not None and not isinstance(value, str): 300 | raise TypeError("mtClientId must be str") 301 | 302 | self.__mtClientId = value 303 | 304 | mtClientId = property(_get_mtClientId, _set_mtClientId) 305 | 306 | def _get_mtEventTime(self): 307 | return self.__mtEventTime 308 | 309 | def _set_mtEventTime(self, value): 310 | if value is not None and not isinstance(value, str): 311 | raise TypeError("mtEventTime must be str") 312 | 313 | self.__mtEventTime = value 314 | 315 | mtEventTime = property(_get_mtEventTime, _set_mtEventTime) 316 | 317 | def _get_mtPageId(self): 318 | return self.__mtPageId 319 | 320 | def _set_mtPageId(self, value): 321 | if value is not None and not isinstance(value, str): 322 | raise TypeError("mtPageId must be str") 323 | 324 | self.__mtPageId = value 325 | 326 | mtPageId = property(_get_mtPageId, _set_mtPageId) 327 | 328 | def _get_mtPageType(self): 329 | return self.__mtPageType 330 | 331 | def _set_mtPageType(self, value): 332 | if value is not None and not isinstance(value, str): 333 | raise TypeError("mtPageType must be str") 334 | 335 | self.__mtPageType = value 336 | 337 | mtPageType = property(_get_mtPageType, _set_mtPageType) 338 | 339 | def _get_mtPrevPage(self): 340 | return self.__mtPrevPage 341 | 342 | def _set_mtPrevPage(self, value): 343 | if value is not None and not isinstance(value, str): 344 | raise TypeError("mtPrevPage must be str") 345 | 346 | self.__mtPrevPage = value 347 | 348 | mtPrevPage = property(_get_mtPrevPage, _set_mtPrevPage) 349 | 350 | def _get_mtRequestId(self): 351 | return self.__mtRequestId 352 | 353 | def _set_mtRequestId(self, value): 354 | if value is not None and not isinstance(value, str): 355 | raise TypeError("mtRequestId must be str") 356 | 357 | self.__mtRequestId = value 358 | 359 | mtRequestId = property(_get_mtRequestId, _set_mtRequestId) 360 | 361 | def _get_mtTopic(self): 362 | return self.__mtTopic 363 | 364 | def _set_mtTopic(self, value): 365 | if value is not None and not isinstance(value, str): 366 | raise TypeError("mtTopic must be str") 367 | 368 | self.__mtTopic = value 369 | 370 | mtTopic = property(_get_mtTopic, _set_mtTopic) 371 | 372 | def _get_needDiv(self): 373 | return self.__needDiv 374 | 375 | def _set_needDiv(self, value): 376 | if value is not None and not isinstance(value, str): 377 | raise TypeError("needDiv must be str") 378 | 379 | self.__needDiv = value 380 | 381 | needDiv = property(_get_needDiv, _set_needDiv) 382 | 383 | def _get_pg(self): 384 | return self.__pg 385 | 386 | def _set_pg(self, value): 387 | if value is not None and not isinstance(value, str): 388 | raise TypeError("pg must be str") 389 | 390 | self.__pg = value 391 | 392 | pg = property(_get_pg, _set_pg) 393 | 394 | def _get_price(self): 395 | return self.__price 396 | 397 | def _set_price(self, value): 398 | if not isinstance(value, str): 399 | raise TypeError("price must be str") 400 | 401 | self.__price = value 402 | 403 | price = property(_get_price, _set_price) 404 | 405 | def _get_pricingParameters(self): 406 | return self.__pricingParameters 407 | 408 | def _set_pricingParameters(self, value): 409 | if not isinstance(value, str): 410 | raise TypeError("pricingParameters must be str") 411 | 412 | self.__pricingParameters = value 413 | 414 | pricingParameters = property(_get_pricingParameters, _set_pricingParameters) 415 | 416 | def _get_productType(self): 417 | return self.__productType 418 | 419 | def _set_productType(self, value): 420 | if not isinstance(value, str): 421 | raise TypeError("productType must be str") 422 | 423 | self.__productType = value 424 | 425 | productType = property(_get_productType, _set_productType) 426 | 427 | def _get_salableAdamId(self): 428 | return self.__salableAdamId 429 | 430 | def _set_salableAdamId(self, value): 431 | if not isinstance(value, str): 432 | raise TypeError("salableAdamId must be str") 433 | 434 | self.__salableAdamId = value 435 | 436 | salableAdamId = property(_get_salableAdamId, _set_salableAdamId) 437 | 438 | def _get_hasAskedToFulfillPreorder(self): 439 | return self.__hasAskedToFulfillPreorder 440 | 441 | def _set_hasAskedToFulfillPreorder(self, value): 442 | if value is not None and not isinstance(value, str): 443 | raise TypeError("hasAskedToFulfillPreorder must be str") 444 | 445 | self.__hasAskedToFulfillPreorder = value 446 | 447 | hasAskedToFulfillPreorder = property( 448 | _get_hasAskedToFulfillPreorder, _set_hasAskedToFulfillPreorder 449 | ) 450 | 451 | def _get_buyWithoutAuthorization(self): 452 | return self.__buyWithoutAuthorization 453 | 454 | def _set_buyWithoutAuthorization(self, value): 455 | if value is not None and not isinstance(value, str): 456 | raise TypeError("buyWithoutAuthorization must be str") 457 | 458 | self.__buyWithoutAuthorization = value 459 | 460 | buyWithoutAuthorization = property( 461 | _get_buyWithoutAuthorization, _set_buyWithoutAuthorization 462 | ) 463 | 464 | def _get_hasDoneAgeCheck(self): 465 | return self.__hasDoneAgeCheck 466 | 467 | def _set_hasDoneAgeCheck(self, value): 468 | if value is not None and not isinstance(value, str): 469 | raise TypeError("hasDoneAgeCheck must be str") 470 | 471 | self.__hasDoneAgeCheck = value 472 | 473 | hasDoneAgeCheck = property(_get_hasDoneAgeCheck, _set_hasDoneAgeCheck) 474 | 475 | def _get_hasConfirmedPaymentSheet(self): 476 | return self.__hasConfirmedPaymentSheet 477 | 478 | def _set_hasConfirmedPaymentSheet(self, value): 479 | if value is not None and not isinstance(value, str): 480 | raise TypeError("hasConfirmedPaymentSheet must be str") 481 | 482 | self.__hasConfirmedPaymentSheet = value 483 | 484 | hasConfirmedPaymentSheet = property(_get_hasConfirmedPaymentSheet, _set_hasConfirmedPaymentSheet) 485 | 486 | def _get_asn(self): 487 | return self.__asn 488 | 489 | def _set_asn(self, value): 490 | if value is not None and not isinstance(value, str): 491 | raise TypeError("asn must be str") 492 | 493 | self.__asn = value 494 | 495 | asn = property(_get_asn, _set_asn) 496 | 497 | @staticmethod 498 | def from_dict(d): 499 | v = {} 500 | if "ageCheck" in d: 501 | v["ageCheck"] = ( 502 | str.from_dict(d["ageCheck"]) 503 | if hasattr(str, "from_dict") 504 | else d["ageCheck"] 505 | ) 506 | if "appExtVrsId" in d: 507 | v["appExtVrsId"] = ( 508 | str.from_dict(d["appExtVrsId"]) 509 | if hasattr(str, "from_dict") 510 | else d["appExtVrsId"] 511 | ) 512 | if "guid" in d: 513 | v["guid"] = ( 514 | str.from_dict(d["guid"]) if hasattr(str, "from_dict") else d["guid"] 515 | ) 516 | if "hasBeenAuthedForBuy" in d: 517 | v["hasBeenAuthedForBuy"] = ( 518 | str.from_dict(d["hasBeenAuthedForBuy"]) 519 | if hasattr(str, "from_dict") 520 | else d["hasBeenAuthedForBuy"] 521 | ) 522 | if "isInApp" in d: 523 | v["isInApp"] = ( 524 | str.from_dict(d["isInApp"]) 525 | if hasattr(str, "from_dict") 526 | else d["isInApp"] 527 | ) 528 | if "kbsync" in d: 529 | v["kbsync"] = ( 530 | str.from_dict(d["kbsync"]) if hasattr(str, "from_dict") else d["kbsync"] 531 | ) 532 | if "sbsync" in d: 533 | v["sbsync"] = ( 534 | str.from_dict(d["sbsync"]) if hasattr(str, "from_dict") else d["sbsync"] 535 | ) 536 | if "afds" in d: 537 | v["afds"] = ( 538 | str.from_dict(d["afds"]) if hasattr(str, "from_dict") else d["afds"] 539 | ) 540 | if "machineName" in d: 541 | v["machineName"] = ( 542 | str.from_dict(d["machineName"]) 543 | if hasattr(str, "from_dict") 544 | else d["machineName"] 545 | ) 546 | if "mtApp" in d: 547 | v["mtApp"] = ( 548 | str.from_dict(d["mtApp"]) if hasattr(str, "from_dict") else d["mtApp"] 549 | ) 550 | if "mtClientId" in d: 551 | v["mtClientId"] = ( 552 | str.from_dict(d["mtClientId"]) 553 | if hasattr(str, "from_dict") 554 | else d["mtClientId"] 555 | ) 556 | if "mtEventTime" in d: 557 | v["mtEventTime"] = ( 558 | str.from_dict(d["mtEventTime"]) 559 | if hasattr(str, "from_dict") 560 | else d["mtEventTime"] 561 | ) 562 | if "mtPageId" in d: 563 | v["mtPageId"] = ( 564 | str.from_dict(d["mtPageId"]) 565 | if hasattr(str, "from_dict") 566 | else d["mtPageId"] 567 | ) 568 | if "mtPageType" in d: 569 | v["mtPageType"] = ( 570 | str.from_dict(d["mtPageType"]) 571 | if hasattr(str, "from_dict") 572 | else d["mtPageType"] 573 | ) 574 | if "mtPrevPage" in d: 575 | v["mtPrevPage"] = ( 576 | str.from_dict(d["mtPrevPage"]) 577 | if hasattr(str, "from_dict") 578 | else d["mtPrevPage"] 579 | ) 580 | if "mtRequestId" in d: 581 | v["mtRequestId"] = ( 582 | str.from_dict(d["mtRequestId"]) 583 | if hasattr(str, "from_dict") 584 | else d["mtRequestId"] 585 | ) 586 | if "mtTopic" in d: 587 | v["mtTopic"] = ( 588 | str.from_dict(d["mtTopic"]) 589 | if hasattr(str, "from_dict") 590 | else d["mtTopic"] 591 | ) 592 | if "needDiv" in d: 593 | v["needDiv"] = ( 594 | str.from_dict(d["needDiv"]) 595 | if hasattr(str, "from_dict") 596 | else d["needDiv"] 597 | ) 598 | if "pg" in d: 599 | v["pg"] = str.from_dict(d["pg"]) if hasattr(str, "from_dict") else d["pg"] 600 | if "price" in d: 601 | v["price"] = ( 602 | str.from_dict(d["price"]) if hasattr(str, "from_dict") else d["price"] 603 | ) 604 | if "pricingParameters" in d: 605 | v["pricingParameters"] = ( 606 | str.from_dict(d["pricingParameters"]) 607 | if hasattr(str, "from_dict") 608 | else d["pricingParameters"] 609 | ) 610 | if "productType" in d: 611 | v["productType"] = ( 612 | str.from_dict(d["productType"]) 613 | if hasattr(str, "from_dict") 614 | else d["productType"] 615 | ) 616 | if "salableAdamId" in d: 617 | v["salableAdamId"] = ( 618 | str.from_dict(d["salableAdamId"]) 619 | if hasattr(str, "from_dict") 620 | else d["salableAdamId"] 621 | ) 622 | if "hasAskedToFulfillPreorder" in d: 623 | v["hasAskedToFulfillPreorder"] = ( 624 | str.from_dict(d["hasAskedToFulfillPreorder"]) 625 | if hasattr(str, "from_dict") 626 | else d["hasAskedToFulfillPreorder"] 627 | ) 628 | if "buyWithoutAuthorization" in d: 629 | v["buyWithoutAuthorization"] = ( 630 | str.from_dict(d["buyWithoutAuthorization"]) 631 | if hasattr(str, "from_dict") 632 | else d["buyWithoutAuthorization"] 633 | ) 634 | if "hasDoneAgeCheck" in d: 635 | v["hasDoneAgeCheck"] = ( 636 | str.from_dict(d["hasDoneAgeCheck"]) 637 | if hasattr(str, "from_dict") 638 | else d["hasDoneAgeCheck"] 639 | ) 640 | if "hasConfirmedPaymentSheet" in d: 641 | v["hasConfirmedPaymentSheet"] = ( 642 | str.from_dict(d["hasConfirmedPaymentSheet"]) 643 | if hasattr(str, "from_dict") 644 | else d["hasConfirmedPaymentSheet"] 645 | ) 646 | if "asn" in d: 647 | v["asn"] = ( 648 | str.from_dict(d["asn"]) 649 | if hasattr(str, "from_dict") 650 | else d["asn"] 651 | ) 652 | return StoreBuyproductReq(**v) 653 | 654 | def as_dict(self): 655 | d = {} 656 | if self.__ageCheck is not None: 657 | d["ageCheck"] = ( 658 | self.__ageCheck.as_dict() 659 | if hasattr(self.__ageCheck, "as_dict") 660 | else self.__ageCheck 661 | ) 662 | if self.__appExtVrsId is not None: 663 | d["appExtVrsId"] = ( 664 | self.__appExtVrsId.as_dict() 665 | if hasattr(self.__appExtVrsId, "as_dict") 666 | else self.__appExtVrsId 667 | ) 668 | if self.__guid is not None: 669 | d["guid"] = ( 670 | self.__guid.as_dict() 671 | if hasattr(self.__guid, "as_dict") 672 | else self.__guid 673 | ) 674 | if self.__hasBeenAuthedForBuy is not None: 675 | d["hasBeenAuthedForBuy"] = ( 676 | self.__hasBeenAuthedForBuy.as_dict() 677 | if hasattr(self.__hasBeenAuthedForBuy, "as_dict") 678 | else self.__hasBeenAuthedForBuy 679 | ) 680 | if self.__isInApp is not None: 681 | d["isInApp"] = ( 682 | self.__isInApp.as_dict() 683 | if hasattr(self.__isInApp, "as_dict") 684 | else self.__isInApp 685 | ) 686 | if self.__kbsync is not None: 687 | d["kbsync"] = ( 688 | self.__kbsync.as_dict() 689 | if hasattr(self.__kbsync, "as_dict") 690 | else self.__kbsync 691 | ) 692 | if self.__sbsync is not None: 693 | d["sbsync"] = ( 694 | self.__sbsync.as_dict() 695 | if hasattr(self.__sbsync, "as_dict") 696 | else self.__sbsync 697 | ) 698 | if self.__afds is not None: 699 | d["afds"] = ( 700 | self.__afds.as_dict() 701 | if hasattr(self.__afds, "as_dict") 702 | else self.__afds 703 | ) 704 | if self.__machineName is not None: 705 | d["machineName"] = ( 706 | self.__machineName.as_dict() 707 | if hasattr(self.__machineName, "as_dict") 708 | else self.__machineName 709 | ) 710 | if self.__mtApp is not None: 711 | d["mtApp"] = ( 712 | self.__mtApp.as_dict() 713 | if hasattr(self.__mtApp, "as_dict") 714 | else self.__mtApp 715 | ) 716 | if self.__mtClientId is not None: 717 | d["mtClientId"] = ( 718 | self.__mtClientId.as_dict() 719 | if hasattr(self.__mtClientId, "as_dict") 720 | else self.__mtClientId 721 | ) 722 | if self.__mtEventTime is not None: 723 | d["mtEventTime"] = ( 724 | self.__mtEventTime.as_dict() 725 | if hasattr(self.__mtEventTime, "as_dict") 726 | else self.__mtEventTime 727 | ) 728 | if self.__mtPageId is not None: 729 | d["mtPageId"] = ( 730 | self.__mtPageId.as_dict() 731 | if hasattr(self.__mtPageId, "as_dict") 732 | else self.__mtPageId 733 | ) 734 | if self.__mtPageType is not None: 735 | d["mtPageType"] = ( 736 | self.__mtPageType.as_dict() 737 | if hasattr(self.__mtPageType, "as_dict") 738 | else self.__mtPageType 739 | ) 740 | if self.__mtPrevPage is not None: 741 | d["mtPrevPage"] = ( 742 | self.__mtPrevPage.as_dict() 743 | if hasattr(self.__mtPrevPage, "as_dict") 744 | else self.__mtPrevPage 745 | ) 746 | if self.__mtRequestId is not None: 747 | d["mtRequestId"] = ( 748 | self.__mtRequestId.as_dict() 749 | if hasattr(self.__mtRequestId, "as_dict") 750 | else self.__mtRequestId 751 | ) 752 | if self.__mtTopic is not None: 753 | d["mtTopic"] = ( 754 | self.__mtTopic.as_dict() 755 | if hasattr(self.__mtTopic, "as_dict") 756 | else self.__mtTopic 757 | ) 758 | if self.__needDiv is not None: 759 | d["needDiv"] = ( 760 | self.__needDiv.as_dict() 761 | if hasattr(self.__needDiv, "as_dict") 762 | else self.__needDiv 763 | ) 764 | if self.__pg is not None: 765 | d["pg"] = ( 766 | self.__pg.as_dict() if hasattr(self.__pg, "as_dict") else self.__pg 767 | ) 768 | if self.__price is not None: 769 | d["price"] = ( 770 | self.__price.as_dict() 771 | if hasattr(self.__price, "as_dict") 772 | else self.__price 773 | ) 774 | if self.__pricingParameters is not None: 775 | d["pricingParameters"] = ( 776 | self.__pricingParameters.as_dict() 777 | if hasattr(self.__pricingParameters, "as_dict") 778 | else self.__pricingParameters 779 | ) 780 | if self.__productType is not None: 781 | d["productType"] = ( 782 | self.__productType.as_dict() 783 | if hasattr(self.__productType, "as_dict") 784 | else self.__productType 785 | ) 786 | if self.__salableAdamId is not None: 787 | d["salableAdamId"] = ( 788 | self.__salableAdamId.as_dict() 789 | if hasattr(self.__salableAdamId, "as_dict") 790 | else self.__salableAdamId 791 | ) 792 | if self.__hasAskedToFulfillPreorder is not None: 793 | d["hasAskedToFulfillPreorder"] = ( 794 | self.__hasAskedToFulfillPreorder.as_dict() 795 | if hasattr(self.__hasAskedToFulfillPreorder, "as_dict") 796 | else self.__hasAskedToFulfillPreorder 797 | ) 798 | if self.__buyWithoutAuthorization is not None: 799 | d["buyWithoutAuthorization"] = ( 800 | self.__buyWithoutAuthorization.as_dict() 801 | if hasattr(self.__buyWithoutAuthorization, "as_dict") 802 | else self.__buyWithoutAuthorization 803 | ) 804 | if self.__hasDoneAgeCheck is not None: 805 | d["hasDoneAgeCheck"] = ( 806 | self.__hasDoneAgeCheck.as_dict() 807 | if hasattr(self.__hasDoneAgeCheck, "as_dict") 808 | else self.__hasDoneAgeCheck 809 | ) 810 | if self.__hasConfirmedPaymentSheet is not None: 811 | d["hasConfirmedPaymentSheet"] = ( 812 | self.__hasConfirmedPaymentSheet.as_dict() 813 | if hasattr(self.__hasConfirmedPaymentSheet, "as_dict") 814 | else self.__hasConfirmedPaymentSheet 815 | ) 816 | if self.__asn is not None: 817 | d["asn"] = ( 818 | self.__asn.as_dict() 819 | if hasattr(self.__asn, "as_dict") 820 | else self.__asn 821 | ) 822 | return d 823 | 824 | def __repr__(self): 825 | return "".format( 826 | limitedRepr( 827 | self.__ageCheck[:20] 828 | if isinstance(self.__ageCheck, bytes) 829 | else self.__ageCheck 830 | ), 831 | limitedRepr( 832 | self.__appExtVrsId[:20] 833 | if isinstance(self.__appExtVrsId, bytes) 834 | else self.__appExtVrsId 835 | ), 836 | limitedRepr( 837 | self.__guid[:20] if isinstance(self.__guid, bytes) else self.__guid 838 | ), 839 | limitedRepr( 840 | self.__hasBeenAuthedForBuy[:20] 841 | if isinstance(self.__hasBeenAuthedForBuy, bytes) 842 | else self.__hasBeenAuthedForBuy 843 | ), 844 | limitedRepr( 845 | self.__isInApp[:20] 846 | if isinstance(self.__isInApp, bytes) 847 | else self.__isInApp 848 | ), 849 | limitedRepr( 850 | self.__kbsync[:20] 851 | if isinstance(self.__kbsync, bytes) 852 | else self.__kbsync 853 | ), 854 | limitedRepr( 855 | self.__sbsync[:20] 856 | if isinstance(self.__sbsync, bytes) 857 | else self.__sbsync 858 | ), 859 | limitedRepr( 860 | self.__afds[:20] 861 | if isinstance(self.__afds, bytes) 862 | else self.__afds 863 | ), 864 | limitedRepr( 865 | self.__machineName[:20] 866 | if isinstance(self.__machineName, bytes) 867 | else self.__machineName 868 | ), 869 | limitedRepr( 870 | self.__mtApp[:20] if isinstance(self.__mtApp, bytes) else self.__mtApp 871 | ), 872 | limitedRepr( 873 | self.__mtClientId[:20] 874 | if isinstance(self.__mtClientId, bytes) 875 | else self.__mtClientId 876 | ), 877 | limitedRepr( 878 | self.__mtEventTime[:20] 879 | if isinstance(self.__mtEventTime, bytes) 880 | else self.__mtEventTime 881 | ), 882 | limitedRepr( 883 | self.__mtPageId[:20] 884 | if isinstance(self.__mtPageId, bytes) 885 | else self.__mtPageId 886 | ), 887 | limitedRepr( 888 | self.__mtPageType[:20] 889 | if isinstance(self.__mtPageType, bytes) 890 | else self.__mtPageType 891 | ), 892 | limitedRepr( 893 | self.__mtPrevPage[:20] 894 | if isinstance(self.__mtPrevPage, bytes) 895 | else self.__mtPrevPage 896 | ), 897 | limitedRepr( 898 | self.__mtRequestId[:20] 899 | if isinstance(self.__mtRequestId, bytes) 900 | else self.__mtRequestId 901 | ), 902 | limitedRepr( 903 | self.__mtTopic[:20] 904 | if isinstance(self.__mtTopic, bytes) 905 | else self.__mtTopic 906 | ), 907 | limitedRepr( 908 | self.__needDiv[:20] 909 | if isinstance(self.__needDiv, bytes) 910 | else self.__needDiv 911 | ), 912 | limitedRepr(self.__pg[:20] if isinstance(self.__pg, bytes) else self.__pg), 913 | limitedRepr( 914 | self.__price[:20] if isinstance(self.__price, bytes) else self.__price 915 | ), 916 | limitedRepr( 917 | self.__pricingParameters[:20] 918 | if isinstance(self.__pricingParameters, bytes) 919 | else self.__pricingParameters 920 | ), 921 | limitedRepr( 922 | self.__productType[:20] 923 | if isinstance(self.__productType, bytes) 924 | else self.__productType 925 | ), 926 | limitedRepr( 927 | self.__salableAdamId[:20] 928 | if isinstance(self.__salableAdamId, bytes) 929 | else self.__salableAdamId 930 | ), 931 | limitedRepr( 932 | self.__hasAskedToFulfillPreorder[:20] 933 | if isinstance(self.__hasAskedToFulfillPreorder, bytes) 934 | else self.__hasAskedToFulfillPreorder 935 | ), 936 | limitedRepr( 937 | self.__buyWithoutAuthorization[:20] 938 | if isinstance(self.__buyWithoutAuthorization, bytes) 939 | else self.__buyWithoutAuthorization 940 | ), 941 | limitedRepr( 942 | self.__hasDoneAgeCheck[:20] 943 | if isinstance(self.__hasDoneAgeCheck, bytes) 944 | else self.__hasDoneAgeCheck 945 | ), 946 | limitedRepr( 947 | self.__hasConfirmedPaymentSheet[:20] 948 | if isinstance(self.__hasConfirmedPaymentSheet, bytes) 949 | else self.__hasConfirmedPaymentSheet 950 | ), 951 | limitedRepr( 952 | self.__asn[:20] 953 | if isinstance(self.__asn, bytes) 954 | else self.__asn 955 | ), 956 | ) 957 | -------------------------------------------------------------------------------- /reqs/schemas/store_download_req.py: -------------------------------------------------------------------------------- 1 | from reprlib import repr as limitedRepr 2 | 3 | 4 | class StoreDownloadReq: 5 | 6 | _types_map = { 7 | "creditDisplay": {"type": str, "subtype": None}, 8 | "guid": {"type": str, "subtype": None}, 9 | "salableAdamId": {"type": str, "subtype": None}, 10 | "externalVersionId": {"type": str, "subtype": None}, 11 | } 12 | _formats_map = {} 13 | _validations_map = { 14 | "creditDisplay": { 15 | "required": True, 16 | }, 17 | "guid": { 18 | "required": True, 19 | }, 20 | "salableAdamId": { 21 | "required": True, 22 | }, 23 | "externalVersionId": { 24 | "required": False, 25 | }, 26 | } 27 | 28 | def __init__( 29 | self, 30 | creditDisplay: str = None, 31 | guid: str = None, 32 | salableAdamId: str = None, 33 | externalVersionId: str = None, 34 | ): 35 | pass 36 | self.__creditDisplay = creditDisplay 37 | self.__guid = guid 38 | self.__salableAdamId = salableAdamId 39 | self.__externalVersionId = externalVersionId 40 | 41 | def _get_creditDisplay(self): 42 | return self.__creditDisplay 43 | 44 | def _set_creditDisplay(self, value): 45 | if not isinstance(value, str): 46 | raise TypeError("creditDisplay must be str") 47 | 48 | self.__creditDisplay = value 49 | 50 | creditDisplay = property(_get_creditDisplay, _set_creditDisplay) 51 | 52 | def _get_guid(self): 53 | return self.__guid 54 | 55 | def _set_guid(self, value): 56 | if not isinstance(value, str): 57 | raise TypeError("guid must be str") 58 | 59 | self.__guid = value 60 | 61 | guid = property(_get_guid, _set_guid) 62 | 63 | def _get_salableAdamId(self): 64 | return self.__salableAdamId 65 | 66 | def _set_salableAdamId(self, value): 67 | if not isinstance(value, str): 68 | raise TypeError("salableAdamId must be str") 69 | 70 | self.__salableAdamId = value 71 | 72 | salableAdamId = property(_get_salableAdamId, _set_salableAdamId) 73 | 74 | def _get_externalVersionId(self): 75 | return self.__externalVersionId 76 | 77 | def _set_externalVersionId(self, value): 78 | if value is not None and not isinstance(value, str): 79 | raise TypeError("externalVersionId must be str") 80 | 81 | self.__externalVersionId = value 82 | 83 | externalVersionId = property(_get_externalVersionId, _set_externalVersionId) 84 | 85 | @staticmethod 86 | def from_dict(d): 87 | v = {} 88 | if "creditDisplay" in d: 89 | v["creditDisplay"] = ( 90 | str.from_dict(d["creditDisplay"]) 91 | if hasattr(str, "from_dict") 92 | else d["creditDisplay"] 93 | ) 94 | if "guid" in d: 95 | v["guid"] = ( 96 | str.from_dict(d["guid"]) if hasattr(str, "from_dict") else d["guid"] 97 | ) 98 | if "salableAdamId" in d: 99 | v["salableAdamId"] = ( 100 | str.from_dict(d["salableAdamId"]) 101 | if hasattr(str, "from_dict") 102 | else d["salableAdamId"] 103 | ) 104 | if "externalVersionId" in d: 105 | v["externalVersionId"] = ( 106 | str.from_dict(d["externalVersionId"]) 107 | if hasattr(str, "from_dict") 108 | else d["externalVersionId"] 109 | ) 110 | return StoreDownloadReq(**v) 111 | 112 | def as_dict(self): 113 | d = {} 114 | if self.__creditDisplay is not None: 115 | d["creditDisplay"] = ( 116 | self.__creditDisplay.as_dict() 117 | if hasattr(self.__creditDisplay, "as_dict") 118 | else self.__creditDisplay 119 | ) 120 | if self.__guid is not None: 121 | d["guid"] = ( 122 | self.__guid.as_dict() 123 | if hasattr(self.__guid, "as_dict") 124 | else self.__guid 125 | ) 126 | if self.__salableAdamId is not None: 127 | d["salableAdamId"] = ( 128 | self.__salableAdamId.as_dict() 129 | if hasattr(self.__salableAdamId, "as_dict") 130 | else self.__salableAdamId 131 | ) 132 | if self.__externalVersionId is not None: 133 | d["externalVersionId"] = ( 134 | self.__externalVersionId.as_dict() 135 | if hasattr(self.__externalVersionId, "as_dict") 136 | else self.__externalVersionId 137 | ) 138 | return d 139 | 140 | def __repr__(self): 141 | return "".format( 142 | limitedRepr( 143 | self.__creditDisplay[:20] 144 | if isinstance(self.__creditDisplay, bytes) 145 | else self.__creditDisplay 146 | ), 147 | limitedRepr( 148 | self.__guid[:20] if isinstance(self.__guid, bytes) else self.__guid 149 | ), 150 | limitedRepr( 151 | self.__salableAdamId[:20] 152 | if isinstance(self.__salableAdamId, bytes) 153 | else self.__salableAdamId 154 | ), 155 | limitedRepr( 156 | self.__externalVersionId[:20] 157 | if isinstance(self.__externalVersionId, bytes) 158 | else self.__externalVersionId 159 | ), 160 | ) 161 | -------------------------------------------------------------------------------- /reqs/store.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import json 3 | import pickle 4 | import plistlib 5 | import requests 6 | from reqs.schemas.store_authenticate_req import StoreAuthenticateReq 7 | from reqs.schemas.store_authenticate_resp import StoreAuthenticateResp 8 | from reqs.schemas.store_buyproduct_req import StoreBuyproductReq 9 | from reqs.schemas.store_buyproduct_resp import StoreBuyproductResp 10 | from reqs.schemas.store_download_req import StoreDownloadReq 11 | from reqs.schemas.store_download_resp import StoreDownloadResp 12 | 13 | class StoreException(Exception): 14 | def __init__(self, req, resp, errMsg, errType=None): 15 | self.req = req 16 | self.resp = resp # type: StoreDownloadResp 17 | self.errMsg = errMsg 18 | self.errType = errType 19 | super().__init__( 20 | "Store %s error: %s" % (self.req, self.errMsg) if not self.errType else 21 | "Store %s error: %s, errorType: %s" % (self.req, self.errMsg, self.errType) 22 | ) 23 | 24 | #CONFIGURATOR_UA = "Configurator/2.0 (Macintosh; OS X 10.12.6; 16G29) AppleWebKit/2603.3.8" 25 | CONFIGURATOR_UA = 'Configurator/2.0 (Macintosh; OS X 10.12.6; 16G29) AppleWebKit/2603.3.8 iOS/14.2 hwp/t8020' 26 | 27 | class StoreClientAuth(object): 28 | def __init__(self, appleId=None, password=None): 29 | self.appleId = appleId 30 | self.password = password 31 | self.guid = None # the guid will not be used in itunes server mode 32 | self.accountName = None 33 | self.authHeaders = None 34 | self.authCookies = None 35 | 36 | def __str__(self): 37 | return f"<{self.accountName} [{self.guid}]>" 38 | 39 | def _generateGuid(self, appleId): 40 | ''' 41 | Derive a GUID for an appleId. For each appleId, the GUID will always remain the same 42 | :param appleId: 43 | :return: 44 | ''' 45 | DEFAULT_GUID = '000C2941396B' # this GUID is blocked 46 | # number of chars to use from DEFAULT_GUID as prefix (0..12) 47 | GUID_DEFAULT_PREFIX = 2 48 | # something unique 49 | GUID_SEED = 'CAFEBABE' 50 | # something between 0 and 30 51 | GUID_POS = 10 52 | 53 | # generate a unique guid out of the appleId 54 | h = hashlib.sha1((GUID_SEED + appleId + GUID_SEED).encode("utf-8")).hexdigest() 55 | defaultPart = DEFAULT_GUID[:GUID_DEFAULT_PREFIX] 56 | hashPart = h[GUID_POS: GUID_POS + (len(DEFAULT_GUID) - GUID_DEFAULT_PREFIX)] 57 | guid = (defaultPart + hashPart).upper() 58 | return guid 59 | 60 | def login(self, sess): 61 | if not self.guid: 62 | self.guid = self._generateGuid(self.appleId) 63 | 64 | req = StoreAuthenticateReq(appleId=self.appleId, password=self.password, attempt='4', createSession="true", 65 | guid=self.guid, rmp='0', why='signIn') 66 | url = "https://p46-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate?guid=%s" % self.guid 67 | while True: 68 | r = sess.post(url, 69 | headers={ 70 | "Accept": "*/*", 71 | "Content-Type": "application/x-www-form-urlencoded", 72 | "User-Agent": CONFIGURATOR_UA, 73 | }, data=plistlib.dumps(req.as_dict()), allow_redirects=False) 74 | if r.status_code == 302: 75 | url = r.headers['Location'] 76 | continue 77 | break 78 | d = plistlib.loads(r.content) 79 | resp = StoreAuthenticateResp.from_dict(d) 80 | if not resp.m_allowed: 81 | raise StoreException("authenticate", d, resp.customerMessage, resp.failureType) 82 | 83 | self.authHeaders = {} 84 | self.authHeaders['X-Dsid'] = self.authHeaders['iCloud-Dsid'] = str(resp.download_queue_info.dsid) 85 | self.authHeaders['X-Apple-Store-Front'] = r.headers.get('x-set-apple-store-front') 86 | self.authHeaders['X-Token'] = resp.passwordToken 87 | self.authCookies = pickle.dumps(sess.cookies).hex() 88 | 89 | self.accountName = resp.accountInfo.address.firstName + " " + resp.accountInfo.address.lastName 90 | def save(self): 91 | return json.dumps(self.__dict__) 92 | 93 | @classmethod 94 | def load(cls, j): 95 | obj = json.loads(j) 96 | ret = cls() 97 | ret.__dict__.update(obj) 98 | return ret 99 | 100 | class StoreClient(object): 101 | def __init__(self, sess: requests.Session): 102 | self.sess = sess 103 | self.iTunes_provider = None 104 | self.authInfo = None 105 | 106 | def authenticate_load_session(self, sessionContent): 107 | self.authInfo = StoreClientAuth.load(sessionContent) 108 | if self.authInfo.authHeaders is None or self.authInfo.authCookies is None: 109 | raise Exception("invalid auth session") 110 | self.sess.headers = dict(self.authInfo.authHeaders) 111 | self.sess.cookies = pickle.loads(bytes.fromhex(self.authInfo.authCookies)) 112 | 113 | def authenticate_save_session(self): 114 | return self.authInfo.save() 115 | 116 | def authenticate(self, appleId, password): 117 | if not self.authInfo: 118 | self.authInfo = StoreClientAuth(appleId, password) 119 | self.authInfo.login(self.sess) 120 | self.sess.headers = dict(self.authInfo.authHeaders) 121 | self.sess.cookies = pickle.loads(bytes.fromhex(self.authInfo.authCookies)) 122 | 123 | # ==> 🛠 [Verbose] Performing request: curl -k -X POST \ 124 | # -H "iCloud-DSID: 12263680861" \ 125 | # -H "Content-Type: application/x-www-form-urlencoded" \ 126 | # -H "User-Agent: Configurator/2.0 (Macintosh; OS X 10.12.6; 16G29) AppleWebKit/2603.3.8" \ 127 | # -H "X-Dsid: 12263680861" \ 128 | # -d ' 129 | # 130 | # 131 | # 132 | # creditDisplay 133 | # 134 | # guid 135 | # 000C2941396B 136 | # salableAdamId 137 | # 1239860606 138 | # 139 | # 140 | # ' \ 141 | # https://p25-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/volumeStoreDownloadProduct?guid=000C2941396Bk 142 | def volumeStoreDownloadProduct(self, appId, appVerId=""): 143 | req = StoreDownloadReq(creditDisplay="", guid=self.authInfo.guid, salableAdamId=appId, externalVersionId=appVerId) 144 | hdrs = { 145 | "Content-Type": "application/x-www-form-urlencoded", 146 | "User-Agent": CONFIGURATOR_UA, 147 | } 148 | url = "https://p25-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/volumeStoreDownloadProduct?guid=%s" % self.authInfo.guid 149 | payload = req.as_dict() 150 | r = self.sess.post(url, 151 | headers=hdrs, 152 | data=plistlib.dumps(payload)) 153 | d = plistlib.loads(r.content) 154 | resp = StoreDownloadResp.from_dict(d) 155 | if resp.cancel_purchase_batch: 156 | raise StoreException("volumeStoreDownloadProduct", d, resp.customerMessage, '%s-%s' % (resp.failureType, resp.metrics)) 157 | return resp 158 | 159 | def buyProduct(self, appId, appVer='', productType='C', pricingParameters='STDQ'): 160 | # STDQ - buy, STDRDL - redownload, SWUPD - update 161 | url = "https://p25-buy.itunes.apple.com/WebObjects/MZBuy.woa/wa/buyProduct" 162 | 163 | itunes_internal = self.iTunes_provider(url) 164 | hdrs = itunes_internal.pop('headers') 165 | guid = itunes_internal.pop('guid') 166 | kbsync = itunes_internal.pop('kbsync') 167 | 168 | if not appVer: 169 | from reqs.itunes import iTunesClient 170 | iTunes = iTunesClient(self.sess) 171 | appVer = iTunes.getAppVerId(appId, hdrs['X-Apple-Store-Front']) 172 | 173 | req = StoreBuyproductReq( 174 | guid=guid, 175 | salableAdamId=str(appId), 176 | appExtVrsId=str(appVer) if appVer else None, 177 | 178 | price='0', 179 | productType=productType, 180 | pricingParameters=pricingParameters, 181 | 182 | ageCheck='true', 183 | hasBeenAuthedForBuy='true', 184 | isInApp='false', 185 | hasConfirmedPaymentSheet='true', 186 | asn='1', 187 | ) 188 | payload = req.as_dict() 189 | payload['kbsync'] = kbsync # kbsync is bytes, but json schema does not support it, so we have to assign it 190 | if 'sbsync' in itunes_internal: 191 | payload['sbsync'] = itunes_internal.pop('sbsync') # sbsync is the same as kbsync 192 | if 'afds' in itunes_internal: 193 | payload['afds'] = itunes_internal.pop('afds') 194 | 195 | hdrs = dict(hdrs) 196 | hdrs["Content-Type"] = "application/x-apple-plist" 197 | 198 | r = self.sess.post(url, 199 | headers=hdrs, 200 | data=plistlib.dumps(payload) 201 | ) 202 | 203 | d = plistlib.loads(r.content) 204 | resp = StoreBuyproductResp.from_dict(d) 205 | if resp.cancel_purchase_batch: 206 | raise StoreException("buyProduct", d, resp.customerMessage, '%s-%s' % (resp.failureType, resp.metrics)) 207 | return resp 208 | 209 | def buyProduct_purchase(self, appId, productType='C'): 210 | url = "https://buy.itunes.apple.com/WebObjects/MZBuy.woa/wa/buyProduct" 211 | req = StoreBuyproductReq( 212 | guid=self.authInfo.guid, 213 | salableAdamId=str(appId), 214 | appExtVrsId='0', 215 | 216 | price='0', 217 | productType=productType, 218 | pricingParameters='STDQ', 219 | 220 | hasAskedToFulfillPreorder='true', 221 | buyWithoutAuthorization='true', 222 | hasDoneAgeCheck='true', 223 | hasConfirmedPaymentSheet='true', 224 | ) 225 | payload = req.as_dict() 226 | 227 | r = self.sess.post(url, 228 | headers={ 229 | "Content-Type": "application/x-apple-plist", 230 | "User-Agent": "Configurator/2.15 (Macintosh; OS X 11.0.0; 16G29) AppleWebKit/2603.3.8", 231 | }, 232 | data=plistlib.dumps(payload)) 233 | 234 | if r.status_code == 500: 235 | raise StoreException("buyProduct_purchase", None, 'purchased_before') 236 | 237 | d = plistlib.loads(r.content) 238 | resp = StoreBuyproductResp.from_dict(d) 239 | if resp.status != 0 or resp.jingleDocType != 'purchaseSuccess': 240 | raise StoreException("buyProduct_purchase", d, resp.customerMessage, 241 | '%s-%s' % (resp.status, resp.jingleDocType)) 242 | return resp 243 | 244 | def purchase(self, appId): 245 | if self.iTunes_provider: 246 | return None # iTunes mode will automatically purchase the app if not purchased 247 | else: 248 | return self.buyProduct_purchase(appId) 249 | 250 | def download(self, appId, appVer='', isRedownload=True): 251 | if self.iTunes_provider: 252 | return self.buyProduct(appId, appVer, pricingParameters='STDRDL' if isRedownload else 'STDQ') 253 | else: 254 | return self.volumeStoreDownloadProduct(appId, appVer) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | rich>=10.2.2 2 | requests>=2.25.0 3 | --------------------------------------------------------------------------------