This commit is contained in:
relikd
2024-04-02 21:54:37 +02:00
commit 8f04d673d9
119 changed files with 20136 additions and 0 deletions

101
README.md Executable file
View File

@@ -0,0 +1,101 @@
# IPA Archiver
Scripts to download and unlock `.ipa` files for historical preservation. Every version of all the apps you ever purchased.
Personal usage only. This is not intended for piracy. Apple anounced they will shutdown older software and this is my attempt at preserving what I have bought and be able to run it even if Apple decides to delete everything.
## Requirements
You'll need:
- Windows 7 PC (I have not tested a VM but if it detects your iDevice via USB then it should be ok)
- macOS device (to run the main scripts, maybe it can be done with Windows alone, haven't tested)
- iDevice (jailbroken)
### Windows
- Install iTunes 12.6.5.3 [64bit](https://secure-appldnld.apple.com/itunes12/091-87819-20180912-69177170-B085-11E8-B6AB-C1D03409AD2A6/iTunes64Setup.exe)
(for completeness: [32bit](https://secure-appldnld.apple.com/itunes12/091-87820-20180912-69177170-B085-11E8-B6AB-C1D03409AD2A5/iTunesSetup.exe) though not needed)
- Install Python 3.8
- Clone [NyaMisty/actions-iTunes-header](https://github.com/NyaMisty/actions-iTunes-header) or use the one provided with this repo.
- Compile [libimobiledevice](https://github.com/libimobiledevice/libimobiledevice) or download [iFred09/libimobiledevice-windows](https://github.com/iFred09/libimobiledevice-windows) or use the one provided with this repo.
- In your firewall, open TCP ports `8117` (step 5, below) and `9000` (step 4, below).
- Enable network sharing on your home directory (this is how macOS will access the files)
Instructions:
1. Copy the `src_win` folder to windows.
2. If you have not done so already, patch iTunes with `src_win/actions-iTunes-header/workflow_helper/iTunesInstall/patch_itunes.py`
3. Start `iTunes`
4. Start `src_win/actions-iTunes-header/workflow_helper/iTunesDownload/get_header.py`
5. Start `src_win/win_server.py`
6. Connect your iDevice with Windows via USB
### iOS
- Apply a jailbreak corresponding to your iOS version
- Install SSH & change password
- Generate and copy an SSH key file (without password) to the device
### macOS
- Clone [NyaMisty/ipatool-py](https://github.com/NyaMisty/ipatool-py) or use the one provided with this repo.
- Connect to your network share (in Finder `Cmd+K` on `smb://your-pc`)
- Adjust your `config.ini` accordingly (see below)
- Edit your `~/.ssh/config` and add the iPad destination:
```
Host ipad
HostName 192.168.0.0
User root
PreferredAuthentications publickey
IdentityFile ~/.ssh/ipad.private-key
```
You should be able to connect to the iPad just by typing `ssh ipad` (without password prompt).
## Config.ini
- `itunes_server` & `win_server` should point to your Windows machine (with corresponding IP port)
- `ssh_cmd_crack`: the command used to launch the cracking (e.g. `Clutch`). Notice that we first remove all previous cracks.
- `ssh_cmd_sync`: Used to download the cracked apps. Notice that both commands just use "ipad" to connect to the device.
- `max_os`: most likely the iOS version running on your iDevice.
- `convert_binary_plist`: Turn this only on, if the Plist is somehow broken. We dont want to modify the app if we can avoid it.
- `sync_in`: same folder as `ssh_cmd_sync` will download into
- `sync_out`: network folder on your windows machine, same place where you copied your `src_win/queued` folder.
- `complete`: folder used to query new app ids. This is constantly updated before and after each app. E.g., `_versions.json` in each bundle-id dir.
- `download_fix`: download folder for IPA files. They remain there until they have been cracked.
- `download_tmp`: temporary folder for `ipatool-py`. Once an app is fully downloaded, the app moved to `download_fix`
## Usage
1. You need to generate a history version list. Each app you download from iTunes has its history attached. You can, for example, download the latest version of an app via iTunes (Win) and run `./src_mac/extract_versions.py -m network-dir/to/*.ipa`. This will read all versions and extract them to the `done` directory. Note: the `-m` flag will move some of the IPA files if they are within the `max_os` range. Omit the flag if you want to keep the source files as is.
2. Your `complete` folder should now have a bunch of folders (one per app), each with a `_versions.json` file. Run `src_mac/download.py` to download all historic versions up until the last compatible iOS version as defined per config.
3. Run `src_mac/crack.py` to start the cracking process. You can run this in parallel to step 2.
## Known issues
Both, the cracking and the download script will fail once in a while.
`crack.py` fails:
1. Most likely due to an (un)install timeout. I assume `libimobiledevice` to be the culprit. Just run the script again. (PS: In theory it should be fine to call the script in a while-true loop, though I wouldn't want to do it unsupervised.)
2. If it fails with "no apps to crack", then the install probably failed. This can happen if you are not authorized to install the ipa. For example, if the `.sinf` is missing. Check you file or re-download it.
`download.py` can fail for many reasons:
1. The most common one: one of the files inside the zip has unicode characters. Python will raise a `File name in directory X and header Y differ` exception. Where both are nearly identical except for some unprintable chars. As long as the filename is not ending on `/Info.plist`, you can unzip it manually, for example with [Keka](https://github.com/aonez/Keka). Duplicate the `.ipa` file and double click to extract it. This should create a folder which ends on either " 2" or " copy" (depending on how you copy the file). Then run repack manually: `./src_mac/repack_ipa.py download/*\ 2` (or " copy")
2. If the file indeed ends on `/Info.plist`, then the unicode char is in the app name. Unzip as before, but before running repack, you have to move the `SC_Info/*.sinf` from the weird looking `Payload/*.app` to the non-broken app bundle. Right-click and "Show Package Contents". After you copied the file (there should be two now, `.sinf` and `.supp`), you can delte the weird looking app and continue with repack.
3. In very rare cases, the previous error also breaks the whole download process. You may have to wait until the IPA is cracked or temporarily exclude it in the `download.py` script. Most likely you will need to manually update the `_versions.json`.

16
config.ini Executable file
View File

@@ -0,0 +1,16 @@
[main]
itunes_server = http://192.168.0.0:9000
win_server = http://192.168.0.0:8117
ssh_cmd_crack = ssh ipad 'rm -rf /var/root/Documents/Cracked/; Clutch -a'
ssh_cmd_sync = scp 'ipad:/var/root/Documents/Cracked/*' ./dropbox_in
max_os = 5.1.1
[zip]
convert_binary_plist = false
[paths]
sync_in = ./dropbox_in/
sync_out = /Volumes/Users/usr/Desktop/src_win/queued
complete = ./done/
download_fix = ./download/
download_tmp = ./download_tmp/

BIN
src_ios/Clutch-1.3 Normal file

Binary file not shown.

BIN
src_ios/Clutch-2.0.4 Normal file

Binary file not shown.

45
src_mac/cfg.py Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env python3
from configparser import ConfigParser
from pathlib import Path
import logging
import os
os.chdir(Path(__file__).parent.parent)
logging.basicConfig(format='%(asctime)s [%(levelname)s] %(message)s',
level=logging.INFO)
Log = logging.getLogger('main')
class Cfg():
def __init__(self) -> None:
cfg = ConfigParser()
cfg.read('config.ini')
# [main]
self.itunes_server = cfg.get('main', 'itunes_server')
self.win_server = cfg.get('main', 'win_server')
self.ssh_cmd_crack = cfg.get('main', 'ssh_cmd_crack')
self.ssh_cmd_sync = cfg.get('main', 'ssh_cmd_sync')
self.max_os = cfg.get('main', 'max_os')
# [zip]
self.convert_plist = cfg.getboolean('zip', 'convert_binary_plist')
# [paths]
self.sync_in = Path(cfg.get('paths', 'sync_in'))
self.sync_out = Path(cfg.get('paths', 'sync_out'))
self.completed = Path(cfg.get('paths', 'complete'))
self.download_fix = Path(cfg.get('paths', 'download_fix'))
self.download_tmp = Path(cfg.get('paths', 'download_tmp'))
# config validation
for path in [self.sync_in, self.sync_out, self.completed]:
if not path.exists():
raise FileNotFoundError(f'Directory "{path}" does not exist.')
# create dirs
self.download_fix.mkdir(parents=True, exist_ok=True)
self.download_tmp.mkdir(parents=True, exist_ok=True)
def __str__(self):
return str(self.__dict__)
CONFIG = Cfg()
# print(CONFIG)

36
src_mac/crack.py Executable file
View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python3
from subprocess import Popen
from time import sleep
import os
from cfg import CONFIG, Log
from move_em import moveEmAll
from server import WinServer
print('Start cracking ...')
while True:
file = next(CONFIG.sync_out.glob('*.ipa'), None)
if not file:
print('Nothing to do. Retry in 10s ...')
sleep(10)
continue
Log.info('[install] %s ...', file.name)
WinServer.install(file)
Log.info('[crack] %s ...', file.name)
shell = Popen(CONFIG.ssh_cmd_crack, shell=True)
if shell.wait() != 0:
raise RuntimeError('Error during cracking command')
Log.info('[pull] %s ...', file.name)
shell = Popen(CONFIG.ssh_cmd_sync, shell=True)
if shell.wait() != 0:
raise RuntimeError('Error during sync-download')
Log.info('[delete] %s', file.name)
os.remove(file) # file on sync_out dir
moveEmAll()

21
src_mac/download.py Executable file
View File

@@ -0,0 +1,21 @@
#!/usr/bin/env python3
from cfg import CONFIG, Log
from lib import downloadAllUntil, enumAppIds
from repack_ipa import repackIpa
# findLatestVersion(000, CONFIG.max_os)
# downloadSpecificVersion(000, 000)
# exit(0)
appIds = list(enumAppIds())
# appIds = [000]
for i, appId in enumerate(appIds):
Log.info('Checking [%d/%d] %s', i + 1, len(appIds), appId)
for i in range(150):
path = downloadAllUntil(i, appId, CONFIG.max_os, rmIncompatible=True)
if path:
repackIpa(path)
print('done.')

20
src_mac/extract_versions.py Executable file
View File

@@ -0,0 +1,20 @@
#!/usr/bin/env python3
from argparse import ArgumentParser
import shutil
from cfg import CONFIG
from lib import downloadPath, updateVersionMap, versionToInt
if __name__ == '__main__':
cli = ArgumentParser()
cli.add_argument('ipa', nargs='+')
cli.add_argument('-m', '--move', action='store_true')
args = cli.parse_args()
max_os = versionToInt(CONFIG.max_os)
for fname in args.ipa:
info = updateVersionMap(fname)
if args.move and versionToInt(info.osVer) <= max_os:
download_path = downloadPath(info.appId, info.verId)
shutil.move(fname, download_path)

4
src_mac/ipatool-py/.gitignore vendored Executable file
View File

@@ -0,0 +1,4 @@
.idea
__pycache__
venv/
downloaded/

112
src_mac/ipatool-py/README.md Executable file
View File

@@ -0,0 +1,112 @@
# IPATool-py
Python version of IPATool!
**Now Supports**:
- **Purchasing App via `--purchase`**
- **Downloading Old IPA via iTunes Server**
## Installation
```
pip3 install -r requirements.txt
```
## Usage
There're three commands: lookup, historyver, download. Each command's usage can be found by `-h` option.
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)
### Download Newest Version
Download an app using bundleID (-o can be omited)
```
python3 main.py lookup -b com.touchingapp.potatsolite -c JP download -e APPLE_EMAIL -p APPLE_PWD -o DIR
```
Or appID (`XXXXX` in the `apps.apple.com/app/xxxx/idXXXXXX`)
```
python3 main.py download -i XXXXX
```
You can also purchase apps when downloading using `--purchase`:
```
python3 main.py lookup -b com.touchingapp.potatsolite -c JP download --purchase -e APPLE_EMAIL -p APPLE_PWD -o DIR
```
### Download OLD Version
Old versions must be downloaded together with `iTunes Server`. You can get `iTunes Server` in several ways:
- Using [action-ipadown](https://github.com/NyaMisty/action-ipadown) directly, which integrated this tool
- NOTE: this method does not support 2FA
- Manually way with Windows: (supports 2FA)
- download this repo: https://github.com/NyaMisty/actions-iTunes-header
- install iTunes 12.6.5.3, from https://secure-appldnld.apple.com/itunes12/091-87819-20180912-69177170-B085-11E8-B6AB-C1D03409AD2A6/iTunes64Setup.exe
- patch the iTunes using `actions-iTunes-header/workflow_helper/iTunesInstall/patch_itunes.py`
- install frida: `pip3 install frida`
- open iTunes, sign out & re-login your account
- run: `actions-iTunes-header/workflow_helper/iTunesDownload/get_header.py`
- Manually way with jailbroken iOS device: (supports 2FA)
- download [KbsyncTool](https://github.com/Lessica/KbsyncTool/releases)
- install `com.darwindev.kbsync_XXX.deb` on your jailbroken device
- exec `kbsynctool -s 9000` on your jailbroken device
- you will find log `Using -s http://192.168.100.227:9000/...`, use it as server address in next step
After setting up the server, you can run this tool to download a specific version
```
python3 main.py lookup -b com.touchingapp.potatsolite -c JP download -s http://127.0.0.1:9000 --appVerId 833889087
```
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)
### Get History Ver (usually used together with JSON)
Get all appVerId of app from Apple
```
python3 main.py lookup -b com.touchingapp.potatsolite -c JP historyver -e APPLE_EMAIL -p APPLE_PWD
```
### Lookup (usually used together with JSON)
Query app basic information:
- By bundleID: `python3 main.py lookup -b com.touchingapp.potatsolite -c JP`
- By appID: `python3 main.py lookup -i 1239860606 -c JP`
Query appVerId:
```
python3 main.py lookup -b com.touchingapp.potatsolite -c JP --get-verid
```
### Headless Usage
For each command you can use `--json` switch to get result of command in JSON
```
python3 main.py --json lookup -b com.touchingapp.potatsolite -c JP --get-verid
python3 main.py --json lookup -b com.touchingapp.potatsolite -c JP historyver -e APPLE_EMAIL -p APPLE_PWD
```
### For Large Scale Scraping
You can download all versions of an app like this:
```
python3 main.py --json download --itunes-server http://XXX.XXX.XXX.XXX:9000 --appId 414478124 --purchase --downloadAllVersion
```
- In this mode, errors will only be logged instead of interrupting the whole process
- For each downloaded app version, it will output a line of json in stdout like this:
```
{"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}
```
Logs will only be printed to stderr, so you can parse this line for automation.
## Development
- 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`
- See more information on how to generate schema in reqs/schemas directory
## Credit
- Thanks @majd's ipatool, which is written in swift

567
src_mac/ipatool-py/main.py Executable file
View File

@@ -0,0 +1,567 @@
#!/usr/bin/python3
import os
import pickle
import sys
import time
import zipfile
from requests.adapters import HTTPAdapter
from urllib3 import Retry
from reqs.itunes import *
from reqs.store import *
import reprlib
reprlib.aRepr.maxstring = 200
import argparse
import logging
from rich.logging import RichHandler
from rich.console import Console
import rich
rich.get_console().file = sys.stderr
if rich.get_console().width < 100:
rich.get_console().width = 100
logging_handler = RichHandler(rich_tracebacks=True)
logging.basicConfig(
level="INFO",
format="%(message)s",
datefmt="[%X]",
handlers=[logging_handler]
)
logging.getLogger('urllib3').setLevel(logging.WARNING)
retryLogger = logging.getLogger('urllib3.util.retry')
retryLogger.setLevel(logging.DEBUG)
retryLogger.handlers = [logging_handler]
retryLogger.propagate = False
logger = logging.getLogger('main')
import requests
def get_zipinfo_datetime(timestamp=None):
# Some applications need reproducible .whl files, but they can't do this without forcing
# the timestamp of the individual ZipInfo objects. See issue #143.
timestamp = int(timestamp or time.time())
return time.gmtime(timestamp)[0:6]
def downloadFile(url, outfile, retries=10):
for retry in range(retries):
try:
_downloadFile(url, outfile)
break
except Exception as e:
logger.info("Error during downloading %s (retry %d/%d), error %s", url, retry, retries, e)
os.remove(outfile)
logger.info("Download success in retry %d", retry)
download_sess = requests.Session()
download_sess.headers = {"User-Agent": CONFIGURATOR_UA}
DOWNLOAD_READ_TIMEOUT = 25.0
def _downloadFile(url, outfile):
with download_sess.get(url, stream=True, timeout=DOWNLOAD_READ_TIMEOUT) as r:
if not r.headers.get('Content-Length'):
raise Exception("server is not returning Content-Length!")
totalLen = int(r.headers.get('Content-Length', '0'))
downLen = 0
r.raise_for_status()
try:
with open(outfile, 'wb') as f:
lastLen = 0
for chunk in r.iter_content(chunk_size=1 * 1024 * 1024):
# If you have chunk encoded response uncomment if
# and set chunk_size parameter to None.
# if chunk:
f.write(chunk)
downLen += len(chunk)
if totalLen and downLen - lastLen > totalLen * 0.05:
logger.info("Download progress: %3.2f%% (%5.1fM /%5.1fM)" % (
downLen / totalLen * 100, downLen / 1024 / 1024, totalLen / 1024 / 1024))
lastLen = downLen
finally:
if downLen != totalLen: # ensure no partial downloaded files exists
os.unlink(outfile)
if downLen != totalLen:
raise Exception("failed to completely download the IPA file")
return outfile
class IPATool(object):
def __init__(self):
self.sess = requests.Session()
retry_strategy = Retry(
connect=4,
read=4,
# total=8,
status=20,
allowed_methods=None,
status_forcelist=[429, 502, 503],
backoff_factor=1.0,
respect_retry_after_header=False,
)
self.sess.mount("https://", HTTPAdapter(max_retries=retry_strategy))
self.sess.mount("http://", HTTPAdapter(max_retries=retry_strategy))
IPATOOL_PROXY = os.getenv("IPATOOL_PROXY")
if IPATOOL_PROXY is not None:
self.sess.proxies.update(
{'http': IPATOOL_PROXY, 'https': IPATOOL_PROXY})
# self.sess.headers = {}
self.sess.headers = {"Connection": "close"}
self.sess.keep_alive = False
self.appId = None
# self.appInfo = None
self.appVerId = None
self.appVerIds = None
self.jsonOut = None
def tool_main(self):
commparser = argparse.ArgumentParser(description='IPATool-Python Commands.', add_help=False)
subp = commparser.add_subparsers(dest='command', required=True)
lookup_p = subp.add_parser('lookup')
id_group = lookup_p.add_mutually_exclusive_group(required=True)
id_group.add_argument('--bundle-id', '-b', dest='bundle_id')
id_group.add_argument('--appId', '-i', dest='appId')
lookup_p.add_argument('--country', '-c', dest='country', required=True)
lookup_p.add_argument('--get-verid', dest='get_verid', action='store_true')
lookup_p.set_defaults(func=self.handleLookup)
def add_auth_options(p):
auth_p = p.add_argument_group('Auth Options', 'Must specify either Apple ID & Password, or iTunes Server URL')
appleid = auth_p.add_argument('--appleid', '-e')
passwd = auth_p.add_argument('--password', '-p')
sessdir = auth_p.add_argument('--session-dir', dest='session_dir', default=None)
itunessrv = auth_p.add_argument('--itunes-server', '-s', dest='itunes_server')
## Multiple hack here just to achieve (appleid & password) || itunes_server
# p._optionals.conflict_handler = 'ignore'
# p._optionals._handle_conflict_ignore = lambda *args: None
auth_p = p.add_mutually_exclusive_group(required=True)
auth_p._group_actions.append(appleid)
auth_p._group_actions.append(itunessrv)
# auth_p._group_actions.append(sessdir)
auth_p = p.add_mutually_exclusive_group(required=True)
auth_p._group_actions.append(passwd)
auth_p._group_actions.append(itunessrv)
purchase_p = subp.add_parser('purchase')
add_auth_options(purchase_p)
purchase_p.add_argument('--appId', '-i', dest='appId')
purchase_p.set_defaults(func=self.handlePurchase)
down_p = subp.add_parser('download')
add_auth_options(down_p)
down_p.add_argument('--appId', '-i', dest='appId')
down_p.add_argument('--appVerId', dest='appVerId')
down_p.add_argument('--purchase', action='store_true')
down_p.add_argument('--downloadAllVersion', action='store_true')
down_p.add_argument('--output-dir', '-o', dest='output_dir', default='.')
down_p.set_defaults(func=self.handleDownload)
his_p = subp.add_parser('historyver')
his_p.add_argument('--appId', '-i', dest='appId')
his_p.add_argument('--purchase', action='store_true')
his_p.add_argument('--output-dir', '-o', dest='output_dir', default='.')
add_auth_options(his_p)
his_p.set_defaults(func=self.handleHistoryVersion)
parser = argparse.ArgumentParser(description='IPATool-Python.', parents=[commparser])
parser.add_argument('--log-level', '-l', dest='log_level', default='info',
help='output level (default: info)')
parser.add_argument('--json', dest='out_json', action='store_true',
help='output json in stdout (log will always be put into stderr)')
# parse global flags & first comm's arguments
args, rest = parser.parse_known_args()
logging.getLogger().setLevel(args.log_level.upper())
outJson = args.out_json
while True:
args.func(args)
if not rest:
break
args, rest = commparser.parse_known_args(rest)
if outJson and self.jsonOut:
print(json.dumps(self.jsonOut, ensure_ascii=False))
def _outputJson(self, obj):
self.jsonOut = obj
def handleLookup(self, args):
if args.bundle_id:
s = 'BundleID %s' % args.bundle_id
else:
s = 'AppID %s' % args.appId
logger.info('Looking up app in country %s with BundleID %s' % (args.country, s))
iTunes = iTunesClient(self.sess)
appInfos = iTunes.lookup(bundleId=args.bundle_id, appId=args.appId, country=args.country)
if appInfos.resultCount != 1:
logger.fatal("Failed to find app in country %s with %s" % (args.country, s))
return
appInfo = appInfos.results[0]
logger.info("Found app:\n\tName: %s\n\tVersion: %s\n\tbundleId: %s\n\tappId: %s" % (appInfo.trackName, appInfo.version, appInfo.bundleId, appInfo.trackId))
self.appId = appInfo.trackId
# self.appInfo = appInfo
ret = {
"name": appInfo.trackName,
"version": appInfo.version,
"appId": appInfo.trackId,
"bundleId": appInfo.bundleId,
}
if args.get_verid:
logger.info("Retrieving verId using iTunes webpage...")
verId = iTunes.getAppVerId(self.appId, args.country)
logger.info("Got current verId using iTunes webpage: %s" % verId)
ret["appVerId"] = verId
self._outputJson(ret)
storeClientCache = {}
def _get_StoreClient(self, args):
cachekey = args.itunes_server or args.appleid
store, lastseen = self.storeClientCache.get(cachekey, (None, None,))
if store:
if time.time() - lastseen < 30.0:
return store
del self.storeClientCache[cachekey]
newSess = pickle.loads(pickle.dumps(self.sess))
Store = StoreClient(newSess)
if args.itunes_server:
logger.info("Using iTunes interface %s to download app!" % args.itunes_server)
servUrl = args.itunes_server
def handle_iTunes_provider(url):
startTime = time.time()
r = requests.get(servUrl, params={
'url': url
})
logger.debug("got itunes header in %.2f seconds", time.time() - startTime)
ret = r.json()
kbsync = bytes.fromhex(ret.pop('kbsync'))
guid = ret.pop('guid')
retHdrs = ret.pop('headers')
handled = {
'headers': retHdrs,
'kbsync': kbsync,
'guid': guid,
}
if 'sbsync' in ret:
handled['sbsync'] = bytes.fromhex(ret.pop('sbsync'))
if 'afds' in ret:
handled['afds'] = ret.pop('afds')
return handled
Store.iTunes_provider = handle_iTunes_provider
else:
appleid = args.appleid
applepass = args.password
needLogin = True
session_cache = os.path.join(args.session_dir, args.appleid) if args.session_dir else None
if session_cache and os.path.exists(session_cache):
needLogin = False
try:
# inside try in case the file format changed
with open(session_cache, "r") as f:
content = f.read()
Store.authenticate_load_session(content)
except Exception as e:
logger.warning(f"Error loading session {session_cache}")
os.unlink(session_cache)
needLogin = True
else:
logger.info('Loaded session for %s' % (str(Store.authInfo)))
if needLogin:
logger.info("Logging into iTunes as %s ..." % appleid)
Store.authenticate(appleid, applepass)
logger.info('Logged in as %s' % (str(Store.authInfo)))
if session_cache:
with open(session_cache, "w") as f:
f.write(Store.authenticate_save_session())
def authedPost(*args, **kwargs):
if 'MZFinance.woa/wa/authenticate' in args[0]:
return Store.sess.original_post(*args, **kwargs)
for i in range(3):
r = Store.sess.original_post(*args, **kwargs)
isAuthFail = False
try:
d = plistlib.loads(r.content)
if str(d['failureType']) in ("2034", "1008"):
isAuthFail = True
except:
return r
if not isAuthFail:
return r
Store.authenticate(appleid, applepass)
if session_cache:
with open(session_cache, "w") as f:
f.write(Store.authenticate_save_session())
continue
Store.sess.original_post = Store.sess.post
Store.sess.post = authedPost
self.storeClientCache[cachekey] = (Store, time.time(),)
return Store
def _handleStoreException(self, _e):
e = _e # type: StoreException
logger.fatal("Store %s failed! Message: %s%s" % (e.req, e.errMsg, " (errorType %s)" % e.errType if e.errType else ''))
logger.fatal(" Raw Response: %s" % (e.resp))
def handlePurchase(self, args):
Store = self._get_StoreClient(args)
logger.info('Try to purchase appId %s' % (self.appId))
try:
Store.purchase(self.appId)
except StoreException as e:
if e.errMsg == 'purchased_before':
logger.warning('You have already purchased appId %s before' % (self.appId))
else:
raise
def handleHistoryVersion(self, args, caches=True):
if args.appId:
self.appId = args.appId
if not self.appId:
logger.fatal("appId not supplied!")
return
versionsJsonPath = args.output_dir + f"/historyver_{self.appId}.json"
if caches:
if os.path.exists(versionsJsonPath):
cacheContent = None
try:
with open(versionsJsonPath) as f:
cacheContent = json.load(f)
except:
pass
if cacheContent is not None:
logger.info('Loaded history version cache for appId %s' % self.appId)
self.appVerIds = cacheContent['appVerIds']
return
logger.info('Retrieving history version for appId %s' % self.appId)
try:
Store = self._get_StoreClient(args)
logger.info('Retrieving download info for appId %s' % (self.appId))
if args.purchase:
logger.info('Purchasing appId %s' % (self.appId))
# We have already successfully purchased, so don't purchase again :)
self.handlePurchase(args)
args.purchase = False
downResp = Store.download(self.appId, '', isRedownload=not args.purchase)
logger.debug('Got download info: %s', downResp)
if args.purchase:
# We have already successfully purchased, so don't purchase again :)
args.purchase = False
if not downResp.songList:
logger.fatal("failed to get app download info!")
raise StoreException('download', downResp, 'no songList')
downInfo = downResp.songList[0]
logger.info('Got available version ids %s', downInfo.metadata.softwareVersionExternalIdentifiers)
self._outputJson({
"appVerIds": downInfo.metadata.softwareVersionExternalIdentifiers
})
self.appVerIds = downInfo.metadata.softwareVersionExternalIdentifiers
if caches:
with open(versionsJsonPath, 'w') as f:
json.dump({
'appVerIds': self.appVerIds,
}, f)
except StoreException as e:
self._handleStoreException(e)
if not e.errMsg.startswith('http error status') and not e.errMsg.startswith(
'We are temporarily unable to process your request') and not e.errMsg.startswith(
"License not found"):
# this error is persistent (e.g. app deleted)
self.appVerIds = []
if caches:
with open(versionsJsonPath, 'w') as f:
json.dump({
'appVerIds': self.appVerIds,
'error': str(e),
'errorResp': str(e.resp),
}, f)
def handleDownload(self, args):
os.makedirs(args.output_dir, exist_ok=True)
if args.downloadAllVersion:
if os.path.exists(args.output_dir + "/all_done"):
logger.info('Already fully finished, skipping!')
return
self.handleHistoryVersion(args, caches=True)
if not self.appVerIds:
logger.fatal('failed to retrive history versions for appId %s', args.appId)
return
everything_succ = True
for appVerId in self.appVerIds:
self.jsonOut = None
stateFile = args.output_dir + '/' + str(appVerId) + '.finish'
if os.path.exists(stateFile):
logger.info('Skipping already downloaded')
continue
try:
self.appVerId = appVerId
self.downloadOne(args)
if args.out_json and self.jsonOut:
print(json.dumps(self.jsonOut, ensure_ascii=False))
if self.jsonOut is not None: # successfully finished
with open(stateFile, 'w') as f:
f.write('1')
except Exception as e:
logger.fatal("error during downloading appVerId %s", appVerId, exc_info=1)
everything_succ = False
finally:
self.jsonOut = None
if everything_succ:
with open(args.output_dir + "/all_done", 'w') as f:
f.write("1")
else:
self.downloadOne(args)
def downloadOne(self, args):
if args.appId:
self.appId = args.appId
if args.appVerId:
self.appVerId = args.appVerId
if not self.appId:
logger.fatal("appId not supplied!")
return
logger.info("Downloading appId %s appVerId %s", self.appId, self.appVerId)
try:
appleid = args.appleid
Store = self._get_StoreClient(args)
if args.purchase:
self.handlePurchase(args)
logger.info('Retrieving download info for appId %s%s' % (self.appId, " with versionId %s" % self.appVerId if self.appVerId else ""))
downResp = Store.download(self.appId, self.appVerId, isRedownload=not args.purchase)
logger.debug('Got download info: %s', downResp.as_dict())
if not downResp.songList:
logger.fatal("failed to get app download info!")
raise StoreException('download', downResp, 'no songList')
downInfo = downResp.songList[0]
appName = downInfo.metadata.bundleDisplayName
appId = downInfo.songId
appBundleId = downInfo.metadata.softwareVersionBundleId
appVerId = downInfo.metadata.softwareVersionExternalIdentifier
# when downloading history versions, bundleShortVersionString will always give a wrong version number (the newest one)
# should use bundleVersion in these cases
appVer = downInfo.metadata.bundleShortVersionString if not self.appVerId else downInfo.metadata.bundleVersion
logger.info(f'Downloading app {appName} ({appBundleId}) with appId {appId} (version {appVer}, versionId {appVerId})')
# if self.appInfo:
filename = '%s-%s-%s-%s.ipa' % (appBundleId,
appVer,
appId,
appVerId)
# else:
# filename = '%s-%s.ipa' % (self.appId, appVerId)
filepath = os.path.join(args.output_dir, filename)
logger.info("Downloading ipa to %s" % filepath)
downloadFile(downInfo.URL, filepath)
metadata = downInfo.metadata.as_dict()
if appleid:
metadata["apple-id"] = appleid
metadata["userName"] = appleid
logger.info("Writing out iTunesMetadata.plist...")
if zipfile.is_zipfile(filepath):
with zipfile.ZipFile(filepath, 'a') as ipaFile:
logger.debug("Writing iTunesMetadata.plist")
ipaFile.writestr(zipfile.ZipInfo("iTunesMetadata.plist", get_zipinfo_datetime()), plistlib.dumps(metadata))
logger.debug("Writing IPAToolInfo.plist")
ipaFile.writestr(zipfile.ZipInfo("IPAToolInfo.plist", get_zipinfo_datetime()), plistlib.dumps(downResp.as_dict()))
def findAppContentPath(c):
if not c.startswith('Payload/'):
return False
pathparts = c.strip('/').split('/')
if len(pathparts) != 2:
return False
if not pathparts[1].endswith(".app"):
return False
return True
appContentDirChoices = [c for c in ipaFile.namelist() if findAppContentPath(c)]
if len(appContentDirChoices) != 1:
raise Exception("failed to find appContentDir, choices %s", appContentDirChoices)
appContentDir = appContentDirChoices[0].rstrip('/')
processedSinf = False
if (appContentDir + '/SC_Info/Manifest.plist') in ipaFile.namelist():
#Try to get the Manifest.plist file, since it doesn't always exist.
scManifestData = ipaFile.read(appContentDir + '/SC_Info/Manifest.plist')
logger.debug("Got SC_Info/Manifest.plist: %s", scManifestData)
scManifest = plistlib.loads(scManifestData)
sinfs = {c.id: c.sinf for c in downInfo.sinfs}
if 'SinfPaths' in scManifest:
for i, sinfPath in enumerate(scManifest['SinfPaths']):
logger.debug("Writing sinf to %s", sinfPath)
ipaFile.writestr(appContentDir + '/' + sinfPath, sinfs[i])
processedSinf = True
if not processedSinf:
logger.info('Manifest.plist does not exist! Assuming it is an old app without one...')
infoListData = ipaFile.read(appContentDir + '/Info.plist') #Is this not loaded anywhere yet?
infoList = plistlib.loads(infoListData)
sinfPath = appContentDir + '/SC_Info/'+infoList['CFBundleExecutable']+".sinf"
logger.debug("Writing sinf to %s", sinfPath)
#Assuming there is only one .sinf file, hence the 0
ipaFile.writestr(sinfPath, downInfo.sinfs[0].sinf)
processedSinf = True
logger.info("Downloaded ipa to %s" % filename)
else:
plist = filepath[:-4]+".info.plist"
with open(plist, "wb") as f:
f.write(plistlib.dumps(downResp.as_dict()))
plist = filepath[:-4]+".plist"
with open(plist, "wb") as f:
f.write(plistlib.dumps(metadata))
logger.info("Downloaded ipa to %s and plist to %s" % (filename, plist))
self._outputJson({
"appName": appName,
"appBundleId": appBundleId,
"appVer": appVer,
"appId": appId,
"appVerId": appVerId,
"downloadedIPA": filepath,
"downloadedVerId": appVerId,
"downloadURL": downInfo.URL,
})
except StoreException as e:
self._handleStoreException(e)
def main():
tool = IPATool()
tool.tool_main()
if __name__ == '__main__':
main()

View File

View File

@@ -0,0 +1,47 @@
__all__ = ['iTunesClient']
import json
import re
from reqs.schemas.itunes_lookup_resp import ItunesLookupResp
import requests
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"}
class iTunesClient(object):
def __init__(self, sess: requests.Session):
self.sess = sess
# curl -k -X GET \
# -H "Content-Type: application/x-www-form-urlencoded" \
# https://itunes.apple.com/lookup?bundleId=com.touchingapp.potatsolite&limit=1&media=software
def lookup(self, bundleId=None, appId=None, term=None, country="US", limit=1, media="software") -> ItunesLookupResp:
r = self.sess.get("https://itunes.apple.com/lookup?",
params={
"bundleId": bundleId,
"id": appId,
"term": term,
"country": country,
"limit": limit,
"media": media,
},
headers={
"Content-Type": "application/x-www-form-urlencoded",
})
return ItunesLookupResp.from_dict(r.json())
def getAppVerId(self, appId, country):
if not ',' in country:
storeFront = STORE_TABLE[country.upper()]
else:
storeFront = country
appInfo = requests.get("https://apps.apple.com/app/id%s" % appId, headers={"X-Apple-Store-Front": storeFront}).text
try:
appParam = re.findall(r'"buyParams":"(.*?)"', appInfo)[0]
except:
appParam = re.findall(r'buy-params="(.*?)"', appInfo)[0]
appParam = appParam.replace('&amp;', '&')
appParamDict = dict((c.split('=') for c in json.loads('"%s"' % appParam).split('&')))
appVer = appParamDict['appExtVrsId']
return appVer

View File

@@ -0,0 +1,18 @@
# Schema Definitions for ipatool-py
In this directory, JSON Schema Definition files are stored in `schema_defs`, with corresponding plist examples in `schema_examples`
## How to generate schema from plist
1. Convert plist to corresponding JSON, using either `plistlib` or online tools
2. Use https://www.liquid-technologies.com/online-json-to-schema-converter to convert JSON to schema
- You have to switch the `Array Rules` option to `List Validation`
3. Merge different request / response body's schema manually
- Usually the "required" field can help you merging the json schema
## Regenerate schema bindings
Run this in current folder:
```
python3 -m schema_defs
```

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
import os
import os.path as path
import subprocess
curpath = path.dirname(__file__)
outpath = path.dirname(curpath)
for p in os.listdir(curpath):
if not p.endswith('.json'):
continue
#subprocess.call(["jsonschema2popo2", "--translate-properties", "--use-types", "-o", path.join(outpath, p.split('.')[0] + '.py'), path.join(curpath, p)])
print("Converting %s" % p)
subprocess.call(["jsonschema2popo2", "--use-types", "-o", path.join(outpath, p.split('.')[0] + '.py'), path.join(curpath, p)])

View File

@@ -0,0 +1,222 @@
{
"title": "iTunes Lookup Resp",
"type": "object",
"properties": {
"resultCount": {
"type": "integer"
},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"appletvScreenshotUrls": {
"type": "array",
"items": {
"type": ["number","string","boolean","object","array", "null"]
}
},
"screenshotUrls": {
"type": "array",
"items": {
"type": "string"
}
},
"ipadScreenshotUrls": {
"type": "array",
"items": {
"type": "string"
}
},
"artworkUrl60": {
"type": "string"
},
"artworkUrl512": {
"type": "string"
},
"artworkUrl100": {
"type": "string"
},
"artistViewUrl": {
"type": "string"
},
"supportedDevices": {
"type": "array",
"items": {
"type": "string"
}
},
"advisories": {
"type": "array",
"items": {
"type": ["number","string","boolean","object","array", "null"]
}
},
"isGameCenterEnabled": {
"type": "boolean"
},
"kind": {
"type": "string"
},
"features": {
"type": "array",
"items": {
"type": "string"
}
},
"minimumOsVersion": {
"type": "string"
},
"trackCensoredName": {
"type": "string"
},
"languageCodesISO2A": {
"type": "array",
"items": {
"type": "string"
}
},
"fileSizeBytes": {
"type": "string"
},
"formattedPrice": {
"type": "string"
},
"contentAdvisoryRating": {
"type": "string"
},
"averageUserRatingForCurrentVersion": {
"type": "number"
},
"userRatingCountForCurrentVersion": {
"type": "integer"
},
"averageUserRating": {
"type": "number"
},
"trackViewUrl": {
"type": "string"
},
"trackContentRating": {
"type": "string"
},
"releaseDate": {
"type": "string"
},
"genreIds": {
"type": "array",
"items": {
"type": "string"
}
},
"primaryGenreName": {
"type": "string"
},
"trackId": {
"type": "integer"
},
"trackName": {
"type": "string"
},
"sellerName": {
"type": "string"
},
"isVppDeviceBasedLicensingEnabled": {
"type": "boolean"
},
"currentVersionReleaseDate": {
"type": "string"
},
"releaseNotes": {
"type": "string"
},
"primaryGenreId": {
"type": "integer"
},
"currency": {
"type": "string"
},
"version": {
"type": "string"
},
"wrapperType": {
"type": "string"
},
"artistId": {
"type": "integer"
},
"artistName": {
"type": "string"
},
"genres": {
"type": "array",
"items": {
"type": "string"
}
},
"price": {
"type": "number"
},
"description": {
"type": "string"
},
"bundleId": {
"type": "string"
},
"userRatingCount": {
"type": "integer"
}
},
"required": [
"appletvScreenshotUrls",
"screenshotUrls",
"ipadScreenshotUrls",
"artworkUrl60",
"artworkUrl512",
"artworkUrl100",
"artistViewUrl",
"supportedDevices",
"advisories",
"isGameCenterEnabled",
"kind",
"features",
"minimumOsVersion",
"trackCensoredName",
"languageCodesISO2A",
"fileSizeBytes",
"formattedPrice",
"contentAdvisoryRating",
"averageUserRatingForCurrentVersion",
"userRatingCountForCurrentVersion",
"averageUserRating",
"trackViewUrl",
"trackContentRating",
"releaseDate",
"genreIds",
"primaryGenreName",
"trackId",
"trackName",
"sellerName",
"isVppDeviceBasedLicensingEnabled",
"currentVersionReleaseDate",
"releaseNotes",
"primaryGenreId",
"currency",
"version",
"wrapperType",
"artistId",
"artistName",
"genres",
"price",
"description",
"bundleId",
"userRatingCount"
]
}
}
},
"required": [
"resultCount",
"results"
]
}

View File

@@ -0,0 +1,37 @@
{
"title": "Store Authenticate Req",
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"appleId": {
"type": "string"
},
"attempt": {
"type": "string"
},
"createSession": {
"type": "string"
},
"guid": {
"type": "string"
},
"password": {
"type": "string"
},
"rmp": {
"type": "string"
},
"why": {
"type": "string"
}
},
"required": [
"appleId",
"attempt",
"createSession",
"guid",
"password",
"rmp",
"why"
]
}

View File

@@ -0,0 +1,324 @@
{
"title": "Store Authenticate Resp",
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"pings": {
"type": "array",
"items": {
"type": ["number","string","boolean","object","array", "null"]
}
},
"cancel-purchase-batch": {
"type": "boolean"
},
"customerMessage": {
"type": "string"
},
"failureType": {
"type": "string"
},
"accountInfo": {
"type": "object",
"properties": {
"appleId": {
"type": "string"
},
"address": {
"type": "object",
"properties": {
"firstName": {
"type": "string"
},
"lastName": {
"type": "string"
}
},
"required": [
"firstName",
"lastName"
]
}
},
"required": [
"appleId",
"address"
]
},
"passwordToken": {
"type": "string"
},
"clearToken": {
"type": "string"
},
"m-allowed": {
"type": "boolean"
},
"is-cloud-enabled": {
"type": "string"
},
"dsPersonId": {
"type": "string"
},
"creditDisplay": {
"type": "string"
},
"creditBalance": {
"type": "string"
},
"freeSongBalance": {
"type": "string"
},
"isManagedStudent": {
"type": "boolean"
},
"action": {
"type": "object",
"properties": {
"kind": {
"type": "string"
}
},
"required": [
"kind"
]
},
"subscriptionStatus": {
"type": "object",
"properties": {
"terms": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"latestTerms": {
"type": "integer"
},
"agreedToTerms": {
"type": "integer"
},
"source": {
"type": "string"
}
},
"required": [
"type",
"latestTerms",
"agreedToTerms",
"source"
]
}
},
"account": {
"type": "object",
"properties": {
"isMinor": {
"type": "boolean"
},
"suspectUnderage": {
"type": "boolean"
}
},
"required": [
"isMinor",
"suspectUnderage"
]
},
"family": {
"type": "object",
"properties": {
"hasFamily": {
"type": "boolean"
}
},
"required": [
"hasFamily"
]
}
},
"required": [
"terms",
"account",
"family"
]
},
"accountFlags": {
"type": "object",
"properties": {
"personalization": {
"type": "boolean"
},
"underThirteen": {
"type": "boolean"
},
"identityLastVerified": {
"type": "integer"
},
"verifiedExpirationDate": {
"type": "integer"
},
"retailDemo": {
"type": "boolean"
},
"autoPlay": {
"type": "boolean"
},
"isDisabledAccount": {
"type": "boolean"
},
"isRestrictedAccount": {
"type": "boolean"
},
"isManagedAccount": {
"type": "boolean"
},
"isInRestrictedRegion": {
"type": "boolean"
},
"accountFlagsVersion": {
"type": "integer"
},
"hasAgreedToTerms": {
"type": "boolean"
},
"hasAgreedToAppClipTerms": {
"type": "boolean"
},
"hasWatchHardwareOffer": {
"type": "boolean"
},
"isInFamily": {
"type": "boolean"
},
"hasSubscriptionFamilySharingEnabled": {
"type": "boolean"
}
},
"required": [
"personalization",
"underThirteen",
"identityLastVerified",
"verifiedExpirationDate",
"retailDemo",
"autoPlay",
"isDisabledAccount",
"isRestrictedAccount",
"isManagedAccount",
"isInRestrictedRegion",
"accountFlagsVersion",
"hasAgreedToTerms",
"hasAgreedToAppClipTerms",
"hasWatchHardwareOffer",
"isInFamily",
"hasSubscriptionFamilySharingEnabled"
]
},
"status": {
"type": "integer"
},
"download-queue-info": {
"type": "object",
"properties": {
"download-queue-item-count": {
"type": "integer"
},
"dsid": {
"type": "integer"
},
"is-auto-download-machine": {
"type": "boolean"
}
},
"required": [
"download-queue-item-count",
"dsid",
"is-auto-download-machine"
]
},
"privacyAcknowledgement": {
"type": "object",
"properties": {
"com.apple.onboarding.appstore": {
"type": "integer"
},
"com.apple.onboarding.applemusic": {
"type": "integer"
},
"com.apple.onboarding.videos": {
"type": "integer"
},
"com.apple.onboarding.itunesstore": {
"type": "integer"
},
"com.apple.onboarding.itunesu": {
"type": "integer"
},
"com.apple.onboarding.applearcade": {
"type": "integer"
}
},
"required": [
"com.apple.onboarding.appstore",
"com.apple.onboarding.applemusic",
"com.apple.onboarding.videos",
"com.apple.onboarding.itunesstore",
"com.apple.onboarding.itunesu",
"com.apple.onboarding.applearcade"
]
},
"dialog": {
"type": "object",
"properties": {
"m-allowed": {
"type": "boolean"
},
"message": {
"type": "string"
},
"explanation": {
"type": "string"
},
"defaultButton": {
"type": "string"
},
"okButtonString": {
"type": "string"
},
"initialCheckboxValue": {
"type": "boolean"
}
},
"required": [
"m-allowed",
"message",
"explanation",
"defaultButton",
"okButtonString",
"initialCheckboxValue"
]
}
},
"required": [
"pings",
"accountInfo",
"passwordToken",
"clearToken",
"m-allowed",
"is-cloud-enabled",
"dsPersonId",
"creditDisplay",
"creditBalance",
"freeSongBalance",
"isManagedStudent",
"action",
"subscriptionStatus",
"accountFlags",
"status",
"download-queue-info",
"privacyAcknowledgement",
"dialog"
]
}

View File

@@ -0,0 +1,120 @@
{
"title": "Store BuyProduct Req",
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"ageCheck": {
"type": "string"
},
"appExtVrsId": {
"type": "string"
},
"guid": {
"type": "string"
},
"hasBeenAuthedForBuy": {
"type": "string"
},
"isInApp": {
"type": "string"
},
"kbsync": {
"type": "string"
},
"sbsync": {
"type": "string"
},
"afds": {
"type": "string"
},
"machineName": {
"type": "string"
},
"mtApp": {
"type": "string"
},
"mtClientId": {
"type": "string"
},
"mtEventTime": {
"type": "string"
},
"mtPageId": {
"type": "string"
},
"mtPageType": {
"type": "string"
},
"mtPrevPage": {
"type": "string"
},
"mtRequestId": {
"type": "string"
},
"mtTopic": {
"type": "string"
},
"needDiv": {
"type": "string"
},
"pg": {
"type": "string"
},
"price": {
"type": "string"
},
"pricingParameters": {
"type": "string"
},
"productType": {
"type": "string"
},
"salableAdamId": {
"type": "string"
},
"hasAskedToFulfillPreorder": {
"type": "string"
},
"buyWithoutAuthorization": {
"type": "string"
},
"hasDoneAgeCheck": {
"type": "string"
},
"hasConfirmedPaymentSheet": {
"type": "string"
},
"asn": {
"type": "string"
}
},
"required": [
"guid",
"kbsync",
"price",
"pricingParameters",
"productType",
"salableAdamId",
"appExtVrsId"
],
"optional": [
"ageCheck",
"hasBeenAuthedForBuy",
"hasConfirmedPaymentSheet",
"asn",
"isInApp",
"machineName",
"mtApp",
"mtClientId",
"mtEventTime",
"mtPageId",
"mtPageType",
"mtPrevPage",
"mtRequestId",
"mtTopic",
"needDiv",
"pg"
]
}

View File

@@ -0,0 +1,732 @@
{
"title": "Store BuyProduct Resp",
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"pings": {
"type": "array",
"items": {
"type": ["number","string","boolean","object","array", "null"]
}
},
"jingleDocType": {
"type": "string"
},
"jingleAction": {
"type": "string"
},
"status": {
"type": "integer"
},
"dsPersonId": {
"type": "string"
},
"creditDisplay": {
"type": "string"
},
"creditBalance": {
"type": "string"
},
"freeSongBalance": {
"type": "string"
},
"creditDisplayInternal": {
"type": "string"
},
"authorized": {
"type": "boolean"
},
"download-queue-item-count": {
"type": "integer"
},
"songList": {
"type": "array",
"items": {
"type": "object",
"properties": {
"songId": {
"type": "integer"
},
"URL": {
"type": "string"
},
"downloadKey": {
"type": "string"
},
"artworkURL": {
"type": "string"
},
"artwork-urls": {
"type": "object",
"properties": {
"image-type": {
"type": "string"
},
"default": {
"type": "object",
"properties": {
"url": {
"type": "string"
}
},
"required": [
"url"
]
}
},
"required": [
"image-type",
"default"
]
},
"md5": {
"type": "string"
},
"chunks": {
"type": "object",
"properties": {
"chunkSize": {
"type": "integer"
},
"hashes": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"chunkSize",
"hashes"
]
},
"isStreamable": {
"type": "boolean"
},
"uncompressedSize": {
"type": "integer"
},
"sinfs": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"sinf": {
"type": "string"
}
},
"required": [
"id",
"sinf"
]
}
},
"purchaseDate": {
"type": "string"
},
"download-id": {
"type": "string"
},
"is-in-queue": {
"type": "boolean"
},
"asset-info": {
"type": "object",
"properties": {
"file-size": {
"type": "integer"
},
"flavor": {
"type": "string"
}
},
"required": [
"file-size",
"flavor"
]
},
"metadata": {
"type": "object",
"properties": {
"MacUIRequiredDeviceCapabilities": {
"type": "object",
"properties": {
"arm64": {
"type": "boolean"
}
},
"required": [
"arm64"
]
},
"UIRequiredDeviceCapabilities": {
"type": "object",
"properties": {
"arm64": {
"type": "boolean"
}
},
"required": [
"arm64"
]
},
"WKRunsIndependentlyOfCompanionApp": {
"type": "boolean"
},
"WKWatchOnly": {
"type": "boolean"
},
"appleWatchEnabled": {
"type": "boolean"
},
"artistId": {
"type": "integer"
},
"artistName": {
"type": "string"
},
"bundleDisplayName": {
"type": "string"
},
"bundleShortVersionString": {
"type": "string"
},
"bundleVersion": {
"type": "string"
},
"copyright": {
"type": "string"
},
"fileExtension": {
"type": "string"
},
"gameCenterEnabled": {
"type": "boolean"
},
"gameCenterEverEnabled": {
"type": "boolean"
},
"genre": {
"type": "string"
},
"genreId": {
"type": "integer"
},
"itemId": {
"type": "integer"
},
"itemName": {
"type": "string"
},
"kind": {
"type": "string"
},
"nameTranscriptions": {
"type": "object",
"properties": {
"zh-Hans-CN": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"zh-Hans-CN"
]
},
"playlistName": {
"type": "string"
},
"product-type": {
"type": "string"
},
"rating": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"label": {
"type": "string"
},
"rank": {
"type": "integer"
},
"system": {
"type": "string"
}
},
"required": [
"content",
"label",
"rank",
"system"
]
},
"releaseDate": {
"type": "string"
},
"requiresRosetta": {
"type": "boolean"
},
"runsOnAppleSilicon": {
"type": "boolean"
},
"runsOnIntel": {
"type": "boolean"
},
"s": {
"type": "integer"
},
"software-platform": {
"type": "string"
},
"softwareIcon57x57URL": {
"type": "string"
},
"softwareIconNeedsShine": {
"type": "boolean"
},
"softwareSupportedDeviceIds": {
"type": "array",
"items": {
"type": "integer"
}
},
"softwareVersionBundleId": {
"type": "string"
},
"softwareVersionExternalIdentifier": {
"type": "integer"
},
"softwareVersionExternalIdentifiers": {
"type": "array",
"items": {
"type": "integer"
}
},
"vendorId": {
"type": "integer"
},
"drmVersionNumber": {
"type": "integer"
},
"versionRestrictions": {
"type": "integer"
},
"storeCohort": {
"type": "string"
},
"hasOrEverHasHadIAP": {
"type": "boolean"
}
},
"required": [
"MacUIRequiredDeviceCapabilities",
"UIRequiredDeviceCapabilities",
"WKRunsIndependentlyOfCompanionApp",
"WKWatchOnly",
"appleWatchEnabled",
"artistId",
"artistName",
"bundleDisplayName",
"bundleShortVersionString",
"bundleVersion",
"copyright",
"fileExtension",
"gameCenterEnabled",
"gameCenterEverEnabled",
"genre",
"genreId",
"itemId",
"itemName",
"kind",
"nameTranscriptions",
"playlistName",
"product-type",
"rating",
"releaseDate",
"requiresRosetta",
"runsOnAppleSilicon",
"runsOnIntel",
"s",
"software-platform",
"softwareIcon57x57URL",
"softwareIconNeedsShine",
"softwareSupportedDeviceIds",
"softwareVersionBundleId",
"softwareVersionExternalIdentifier",
"softwareVersionExternalIdentifiers",
"vendorId",
"drmVersionNumber",
"versionRestrictions",
"storeCohort",
"hasOrEverHasHadIAP"
]
}
},
"required": [
"songId",
"URL",
"downloadKey",
"artworkURL",
"artwork-urls",
"md5",
"chunks",
"isStreamable",
"uncompressedSize",
"sinfs",
"purchaseDate",
"download-id",
"is-in-queue",
"asset-info",
"metadata"
]
}
},
"download-queue-info": {
"type": "object",
"properties": {
"download-queue-item-count": {
"type": "integer"
},
"dsid": {
"type": "integer"
},
"is-auto-download-machine": {
"type": "boolean"
}
},
"required": [
"download-queue-item-count",
"dsid",
"is-auto-download-machine"
]
},
"metrics": {
"type": "object",
"properties": {
"itemIds": {
"type": "array",
"items": {
"type": "integer"
}
},
"price": {
"type": "integer"
},
"priceType": {
"type": "string"
},
"productTypes": {
"type": "array",
"items": {
"type": "string"
}
},
"mtApp": {
"type": "string"
},
"mtClientId": {
"type": "string"
},
"mtEventTime": {
"type": "string"
},
"mtPageId": {
"type": "string"
},
"mtPageType": {
"type": "string"
},
"mtPrevPage": {
"type": "string"
},
"mtRequestId": {
"type": "string"
},
"mtTopic": {
"type": "string"
},
"currency": {
"type": "string"
},
"exchangeRateToUSD": {
"type": "number"
},
"commerceEvent_purchase_priceType": {
"type": "string"
},
"commerceEvent_storeFrontId": {
"type": "string"
},
"commerceEvent_result_resultType": {
"type": "integer"
},
"commerceEvent_flowType": {
"type": "integer"
},
"commerceEvent_flowStep": {
"type": "integer"
},
"dialogId": {
"type": "string"
},
"message": {
"type": "string"
},
"messageCode": {
"type": "string"
},
"options": {
"type": "array",
"items": {
"type": "string"
}
},
"actionUrl": {
"type": "string"
},
"asnState": {
"type": "integer"
},
"eventType": {
"type": "string"
}
},
"required": [
"mtApp",
"mtClientId",
"mtEventTime",
"mtPageId",
"mtPageType",
"mtPrevPage",
"mtRequestId",
"mtTopic"
],
"optional": [
"itemIds",
"price",
"priceType",
"productTypes",
"currency",
"exchangeRateToUSD",
"commerceEvent_purchase_priceType",
"commerceEvent_storeFrontId",
"commerceEvent_result_resultType",
"commerceEvent_flowType",
"commerceEvent_flowStep",
"dialogId",
"message",
"messageCode",
"options",
"actionUrl",
"asnState",
"eventType"
]
},
"duAnonymousPings": {
"type": "array",
"items": {
"type": "string"
}
},
"subscriptionStatus": {
"type": "object",
"properties": {
"music": {
"type": "object",
"properties": {
"status": {
"type": "string"
},
"reason": {
"type": "string"
},
"isAdmin": {
"type": "boolean"
},
"isNotEligibleForFreeTrial": {
"type": "boolean"
}
},
"required": [
"status",
"reason",
"isAdmin",
"isNotEligibleForFreeTrial"
]
},
"terms": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"latestTerms": {
"type": "integer"
},
"agreedToTerms": {
"type": "integer"
},
"source": {
"type": "string"
}
},
"required": [
"type",
"latestTerms",
"agreedToTerms",
"source"
]
}
},
"account": {
"type": "object",
"properties": {
"isMinor": {
"type": "boolean"
},
"suspectUnderage": {
"type": "boolean"
}
},
"required": [
"isMinor",
"suspectUnderage"
]
},
"family": {
"type": "object",
"properties": {
"hasFamily": {
"type": "boolean"
}
},
"required": [
"hasFamily"
]
}
},
"required": [
"music",
"terms",
"account",
"family"
]
},
"cancel-purchase-batch": {
"type": "boolean"
},
"failureType": {
"type": "string"
},
"customerMessage": {
"type": "string"
},
"m-allowed": {
"type": "boolean"
},
"dialog": {
"type": "object",
"properties": {
"kind": {
"type": "string"
},
"m-allowed": {
"type": "boolean"
},
"use-keychain": {
"type": "boolean"
},
"isFree": {
"type": "boolean"
},
"message": {
"type": "string"
},
"explanation": {
"type": "string"
},
"defaultButton": {
"type": "string"
},
"okButtonString": {
"type": "string"
},
"okButtonAction": {
"type": "object",
"properties": {
"kind": {
"type": "string"
},
"buyParams": {
"type": "string"
},
"itemName": {
"type": "string"
}
},
"required": [
"kind",
"buyParams",
"itemName"
]
},
"cancelButtonString": {
"type": "string"
},
"initialCheckboxValue": {
"type": "boolean"
}
},
"required": [
"kind",
"m-allowed",
"use-keychain",
"isFree",
"message",
"explanation",
"defaultButton",
"okButtonString",
"okButtonAction",
"cancelButtonString",
"initialCheckboxValue"
]
}
},
"required": [
"pings",
"metrics"
],
"optional": [
"jingleDocType",
"jingleAction",
"status",
"dsPersonId",
"creditDisplay",
"creditBalance",
"freeSongBalance",
"creditDisplayInternal",
"authorized",
"download-queue-item-count",
"songList",
"download-queue-info",
"duAnonymousPings",
"subscriptionStatus",
"failureType",
"customerMessage",
"m-allowed",
"dialog",
"cancel-purchase-batch"
]
}

View File

@@ -0,0 +1,24 @@
{
"title": "Store Download Req",
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"creditDisplay": {
"type": "string"
},
"guid": {
"type": "string"
},
"salableAdamId": {
"type": "string"
},
"appExtVrsId": {
"type": "string"
}
},
"required": [
"creditDisplay",
"guid",
"salableAdamId"
]
}

View File

@@ -0,0 +1,497 @@
{
"title": "Store Download Resp",
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"pings": {
"type": "array",
"items": {
"type": ["number","string","boolean","object","array", "null"]
}
},
"cancel-purchase-batch": {
"type": "boolean"
},
"customerMessage": {
"type": "string"
},
"failureType": {
"type": "string"
},
"jingleDocType": {
"type": "string"
},
"jingleAction": {
"type": "string"
},
"status": {
"type": "integer"
},
"dsPersonId": {
"type": "string"
},
"creditDisplay": {
"type": "string"
},
"creditBalance": {
"type": "string"
},
"freeSongBalance": {
"type": "string"
},
"authorized": {
"type": "boolean"
},
"download-queue-item-count": {
"type": "integer"
},
"songList": {
"type": "array",
"items": {
"type": "object",
"properties": {
"songId": {
"type": "integer"
},
"URL": {
"type": "string"
},
"downloadKey": {
"type": "string"
},
"artworkURL": {
"type": "string"
},
"artwork-urls": {
"type": "object",
"properties": {
"image-type": {
"type": "string"
},
"default": {
"type": "object",
"properties": {
"url": {
"type": "string"
}
},
"required": [
"url"
]
}
},
"required": [
"image-type",
"default"
]
},
"md5": {
"type": "string"
},
"chunks": {
"type": "object",
"properties": {
"chunkSize": {
"type": "integer"
},
"hashes": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"chunkSize",
"hashes"
]
},
"isStreamable": {
"type": "boolean"
},
"uncompressedSize": {
"type": "string"
},
"sinfs": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"sinf": {
"type": "string"
}
},
"required": [
"id",
"sinf"
]
}
},
"purchaseDate": {
"type": "string"
},
"download-id": {
"type": "string"
},
"is-in-queue": {
"type": "boolean"
},
"asset-info": {
"type": "object",
"properties": {
"file-size": {
"type": "integer"
},
"flavor": {
"type": "string"
}
},
"required": [
"file-size",
"flavor"
]
},
"metadata": {
"type": "object",
"properties": {
"MacUIRequiredDeviceCapabilities": {
"type": "object",
"properties": {
"arm64": {
"type": "boolean"
},
"gamekit": {
"type": "boolean"
},
"metal": {
"type": "boolean"
}
},
"required": [
"arm64",
"gamekit",
"metal"
]
},
"UIRequiredDeviceCapabilities": {
"type": "object",
"properties": {
"arm64": {
"type": "boolean"
},
"gamekit": {
"type": "boolean"
},
"metal": {
"type": "boolean"
}
},
"required": [
"arm64",
"gamekit",
"metal"
]
},
"artistId": {
"type": "integer"
},
"artistName": {
"type": "string"
},
"bundleDisplayName": {
"type": "string"
},
"bundleShortVersionString": {
"type": "string"
},
"bundleVersion": {
"type": "string"
},
"copyright": {
"type": "string"
},
"fileExtension": {
"type": "string"
},
"gameCenterEnabled": {
"type": "boolean"
},
"gameCenterEverEnabled": {
"type": "boolean"
},
"genre": {
"type": "string"
},
"genreId": {
"type": "integer"
},
"itemId": {
"type": "integer"
},
"itemName": {
"type": "string"
},
"kind": {
"type": "string"
},
"playlistName": {
"type": "string"
},
"product-type": {
"type": "string"
},
"rating": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"label": {
"type": "string"
},
"rank": {
"type": "integer"
},
"system": {
"type": "string"
}
},
"required": [
"content",
"label",
"rank",
"system"
]
},
"releaseDate": {
"type": "string"
},
"requiresRosetta": {
"type": "boolean"
},
"runsOnAppleSilicon": {
"type": "boolean"
},
"runsOnIntel": {
"type": "boolean"
},
"s": {
"type": "integer"
},
"software-platform": {
"type": "string"
},
"softwareIcon57x57URL": {
"type": "string"
},
"softwareIconNeedsShine": {
"type": "boolean"
},
"softwareSupportedDeviceIds": {
"type": "array",
"items": {
"type": "integer"
}
},
"softwareVersionBundleId": {
"type": "string"
},
"softwareVersionExternalIdentifier": {
"type": "integer"
},
"softwareVersionExternalIdentifiers": {
"type": "array",
"items": {
"type": "integer"
}
},
"subgenres": {
"type": "array",
"items": {
"type": "object",
"properties": {
"genre": {
"type": "string"
},
"genreId": {
"type": "integer"
}
},
"required": [
"genre",
"genreId"
]
}
},
"vendorId": {
"type": "integer"
},
"drmVersionNumber": {
"type": "integer"
},
"versionRestrictions": {
"type": "integer"
}
},
"required": [
"MacUIRequiredDeviceCapabilities",
"UIRequiredDeviceCapabilities",
"artistId",
"artistName",
"bundleDisplayName",
"bundleShortVersionString",
"bundleVersion",
"copyright",
"fileExtension",
"gameCenterEnabled",
"gameCenterEverEnabled",
"genre",
"genreId",
"itemId",
"itemName",
"kind",
"playlistName",
"product-type",
"rating",
"releaseDate",
"requiresRosetta",
"runsOnAppleSilicon",
"runsOnIntel",
"s",
"software-platform",
"softwareIcon57x57URL",
"softwareIconNeedsShine",
"softwareSupportedDeviceIds",
"softwareVersionBundleId",
"softwareVersionExternalIdentifier",
"softwareVersionExternalIdentifiers",
"subgenres",
"vendorId",
"drmVersionNumber",
"versionRestrictions"
]
}
},
"required": [
"songId",
"URL",
"downloadKey",
"artworkURL",
"artwork-urls",
"md5",
"chunks",
"isStreamable",
"uncompressedSize",
"sinfs",
"purchaseDate",
"download-id",
"is-in-queue",
"asset-info",
"metadata"
]
}
},
"metrics": {
"type": "object",
"properties": {
"itemIds": {
"type": "array",
"items": {
"type": "integer"
}
},
"currency": {
"type": "string"
},
"exchangeRateToUSD": {
"type": "number"
}
},
"required": [
"itemIds",
"currency",
"exchangeRateToUSD"
]
},
"subscriptionStatus": {
"type": "object",
"properties": {
"terms": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"latestTerms": {
"type": "integer"
},
"agreedToTerms": {
"type": "integer"
},
"source": {
"type": "string"
}
},
"required": [
"type",
"latestTerms",
"agreedToTerms",
"source"
]
}
},
"account": {
"type": "object",
"properties": {
"isMinor": {
"type": "boolean"
},
"suspectUnderage": {
"type": "boolean"
}
},
"required": [
"isMinor",
"suspectUnderage"
]
},
"family": {
"type": "object",
"properties": {
"hasFamily": {
"type": "boolean"
}
},
"required": [
"hasFamily"
]
}
},
"required": [
"terms",
"account",
"family"
]
}
},
"required": [
"pings",
"jingleDocType",
"jingleAction",
"status",
"dsPersonId",
"creditDisplay",
"creditBalance",
"freeSongBalance",
"authorized",
"download-queue-item-count",
"songList",
"metrics",
"subscriptionStatus"
]
}

View File

@@ -0,0 +1,13 @@
{
"resultCount":1,
"results": [
{
"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"],
"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":[],
"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",
"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",
"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",
"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",
"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,
"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}]
}

View File

@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>appExtVrsId</key>
<string>848463733</string>
<key>guid</key>
<string>22330C8C.C2E39C5C.06C40B91.D0990F95.9A890F9D.AB7F7EB4.56507025</string>
<key>kbsync</key>
<data>
AAQAA5nBc3Q7ETi+TD1x8AM4wbw9sqWglXBaXwuIrlMR9OnAPY89zaoTvbe6PneuS1x1
31NxVIqrAsIZLmxy5be8dQtq8je/rtYTRlxduU9NwW4DBcplBx6vs9qhS3Y8B45Zz4T5
dkmDG4UnS7xnAPwew7jEX/uY38zZhtKu4IN+sl/Whvyh2SkZg/5vGCtjav17CGbP8ZWo
Ci3FhEqAByOL0g6zhPdTHyqF84Apg9fh395tGpzzAWB8mYsRQXJcUHH1cuJjO/qMTkZ4
ZxIJqfMaDJpS20nFq+/Bfg9FvC/83AOPnDfXZTsol3PFKqQ6sLgz6dKIho4Qd2UPABnj
kBx4TFPeYBlm2T6GKfi8tr+rDhsMrbNczpnaUS+3cesIOvDsE3YCX4isOmMtg5yrJ5pi
2GHuofHD2I7Dj6fOh79I7F5OZb71PUvbeABjxvS5b57LGNICSBc/GJ0CEja27kpYals+
bgYG0rVm+vqlAHwRpka5jzeK8DLrvTr22vtBLv62LpTVpVVglr5nbk99BcXG5gA=
</data>
<key>machineName</key>
<string>DESKTOP-697LVJS</string>
<key>mtApp</key>
<string>com.apple.iTunes</string>
<key>mtClientId</key>
<string>3z21abvCzFDuz5CYz9bdz19maFVKge</string>
<key>mtEventTime</key>
<string>1652006678827</string>
<key>mtPageId</key>
<string>ccfce0ef-4ac8-4d5a-8e5f-79876ac474a4</string>
<key>mtPageType</key>
<string>Search</string>
<key>mtPrevPage</key>
<string>Purchases</string>
<key>mtRequestId</key>
<string>3z21abvCzFDuz5CYz9bdz19maFVKgezL2X64FFVz1MGG</string>
<key>mtTopic</key>
<string>xp_its_main</string>
<key>needDiv</key>
<string>0</string>
<key>pg</key>
<string>default</string>
<key>price</key>
<string>0</string>
<key>pricingParameters</key>
<string>STDQ</string>
<key>productType</key>
<string>C</string>
<key>salableAdamId</key>
<string>444934666</string>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>ageCheck</key>
<string>true</string>
<key>appExtVrsId</key>
<string>848463733</string>
<key>guid</key>
<string>22330C8C.C2E39C5C.06C40B91.D0990F95.9A890F9D.AB7F7EB4.56507025</string>
<key>hasBeenAuthedForBuy</key>
<string>true</string>
<key>isInApp</key>
<string>false</string>
<key>kbsync</key>
<data>
AAQAA1c+HfGD4vNLZJYBMpBucSo1bxeaeTEZ3FGUKLq0skmzTxVik6IGoQMGaP6OWsJU
lBoDb3hxaacD57bkiAgRXc/vr21/CipX55hKLoTE53yah3DwBR9tS1cG7oaHFLIh1Vmn
RV7G9LJCQqwSAbr4ugEIVmLULkqaHDfTm8VNDXxYej1p8ghKggMcBT0se5cpDqpVn/bE
qehnOl6QsupUNjjLzDm58bmERm/AjGJVBHQveG+4Y+Y4e1eUO6QQcntmypjmSLDqhI60
31rCV3zwTTZrHmmXEwsZiGgYlSVHR3ne+O9BE+LIPiQxDwIMvjfV6SrzoOUOLlOKvBsk
kI29+6H0QNyMUXojWPQf7bfr+9NBTMgJoDNJd5hEGHqiSKnp9V1ALU8S8QwcYzi3ZDPu
A36lMtgMOFEnYibnGnP5S8i2t43ZSBglExE9LGTGO0/IEWX9gEhKjvMDuIwMt9zdtpye
CXO8siqHY4MtmLlfwZZc/480ZSHfRF8ZQHBa0J0/pgByNQiNe9KMihz+QN7cQI4=
</data>
<key>machineName</key>
<string>DESKTOP-697LVJS</string>
<key>mtApp</key>
<string>com.apple.iTunes</string>
<key>mtClientId</key>
<string>3z21abvCzFDuz5CYz9bdz19maFVKge</string>
<key>mtEventTime</key>
<string>1652006678827</string>
<key>mtPageId</key>
<string>ccfce0ef-4ac8-4d5a-8e5f-79876ac474a4</string>
<key>mtPageType</key>
<string>Search</string>
<key>mtPrevPage</key>
<string>Purchases</string>
<key>mtRequestId</key>
<string>3z21abvCzFDuz5CYz9bdz19maFVKgezL2X64FFVz1MGG</string>
<key>mtTopic</key>
<string>xp_its_main</string>
<key>needDiv</key>
<string>0</string>
<key>pg</key>
<string>default</string>
<key>price</key>
<string>0</string>
<key>pricingParameters</key>
<string>STDQ</string>
<key>productType</key>
<string>C</string>
<key>salableAdamId</key>
<string>444934666</string>
</dict>
</plist>

View File

@@ -0,0 +1,561 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<plist version="1.0">
<dict>
<key>pings</key>
<array><string>https://xp.apple.com/report/2/xp_its_main?app=com.apple.iTunes&amp;code=MZCommerce.ASN.ExpiredPasswordToken&amp;buttons=%E8%8E%B7%E5%8F%96%3A%E5%8F%96%E6%B6%88&amp;baseVersion=1&amp;dsId=16916646015&amp;eventVersion=1&amp;storeFrontHeader=143465-19%2C32&amp;eventTime=1652006682067&amp;eventType=dialog&amp;message=%E9%9C%80%E8%A6%81%E7%99%BB%E5%BD%95</string>
</array>
<key>metrics</key>
<dict>
<key>dialogId</key><string>MZCommerce.ASN.ExpiredPasswordToken</string>
<key>message</key><string>需要登录</string>
<key>messageCode</key><string>2072</string>
<key>options</key>
<array>
<string>Get</string>
<string>Cancel</string>
</array>
<key>actionUrl</key><string>p36-buy.itunes.apple.com/WebObjects/MZBuy.woa/wa/buyProduct</string>
<key>asnState</key><integer>2</integer>
<key>mtApp</key><string>com.apple.iTunes</string>
<key>mtClientId</key><string>3z21abvCzFDuz5CYz9bdz19maFVKge</string>
<key>mtEventTime</key><string>2022-05-08 10:44:38 Etc/GMT</string>
<key>mtPageId</key><string>ccfce0ef-4ac8-4d5a-8e5f-79876ac474a4</string>
<key>mtPageType</key><string>Search</string>
<key>mtPrevPage</key><string>Purchases</string>
<key>mtRequestId</key><string>3z21abvCzFDuz5CYz9bdz19maFVKgezL2X64FFVz1MGG</string>
<key>mtTopic</key><string>xp_its_main</string>
<key>eventType</key><string>dialog</string>
</dict>
<key>failureType</key><string>2072</string>
<key>customerMessage</key><string>需要登录</string>
<key>m-allowed</key><true/>
<key>dialog</key>
<dict><key>kind</key><string>authorization</string>
<key>m-allowed</key><true/>
<key>use-keychain</key><true/>
<key>isFree</key><true/>
<key>message</key><string>需要登录</string>
<key>explanation</key><string>如果您有 Apple ID 和密码,请在此处输入。例如,如果您使用过 iTunes Store 或 iCloud,那么您已有 Apple ID。</string>
<key>defaultButton</key><string>ok</string>
<key>okButtonString</key><string>获取</string>
<key>okButtonAction</key><dict><key>kind</key><string>Buy</string>
<key>buyParams</key><string>mtEventTime=1652006678827&amp;salableAdamId=444934666&amp;mtRequestId=3z21abvCzFDuz5CYz9bdz19maFVKgezL2X64FFVz1MGG&amp;appExtVrsId=848463733&amp;mtTopic=xp_its_main&amp;guid=22330C8C.C2E39C5C.06C40B91.D0990F95.9A890F9D.AB7F7EB4.56507025&amp;hasBeenAuthedForBuy=true&amp;isInApp=false&amp;price=0&amp;mtClientId=3z21abvCzFDuz5CYz9bdz19maFVKge&amp;productType=C&amp;mtPageType=Search&amp;mtPageId=ccfce0ef-4ac8-4d5a-8e5f-79876ac474a4&amp;machineName=DESKTOP-697LVJS&amp;ageCheck=true&amp;pg=default&amp;mtApp=com.apple.iTunes&amp;needDiv=0&amp;mtPrevPage=Purchases&amp;pricingParameters=STDQ</string>
<key>itemName</key><string>QQ</string>
</dict>
<key>cancelButtonString</key><string>取消</string>
<key>initialCheckboxValue</key><true/></dict>
<key>cancel-purchase-batch</key><true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<plist version="1.0">
<dict>
<key>pings</key>
<array></array>
<key>jingleDocType</key><string>purchaseSuccess</string>
<key>jingleAction</key><string>purchaseProduct</string>
<key>status</key><integer>0</integer>
<key>dsPersonId</key><string>10964418715</string>
<key>creditDisplay</key><string></string>
<key>creditBalance</key><string>1311811</string>
<key>freeSongBalance</key><string>1311811</string>
<key>authorized</key><false/><key>download-queue-item-count</key><integer>0</integer>
<key>songList</key>
<array>
</array>
<key>metrics</key>
<dict>
<key>itemIds</key>
<array>
<integer>580311103</integer>
</array>
<key>price</key><real>0.00</real>
<key>priceType</key><string>STDQ</string>
<key>productTypes</key>
<array>
<string>C</string>
</array>
<key>currency</key><string>JPY</string>
<key>exchangeRateToUSD</key><real>0.0076722418</real>
<key>commerceEvent_purchase_priceType</key><string>STDQ</string>
<key>commerceEvent_storeFrontId</key><string>143462</string>
<key>commerceEvent_result_resultType</key><integer>0</integer>
<key>commerceEvent_flowType</key><integer>4</integer>
<key>commerceEvent_flowStep</key><integer>6</integer>
</dict>
<key>duAnonymousPings</key>
<array>
<string>https://xp.apple.com/report/2/xp_app_buy?clientId=0&amp;sf=143462&amp;adamId=580311103</string>
</array>
<key>subscriptionStatus</key>
<dict>
<key>terms</key>
<array>
<dict>
<key>type</key><string>Store</string>
<key>latestTerms</key><integer>28</integer>
<key>agreedToTerms</key><integer>31</integer>
<key>source</key><string>account</string>
</dict>
</array>
<key>account</key>
<dict>
<key>isMinor</key><false/>
<key>suspectUnderage</key><false/>
</dict>
<key>family</key>
<dict>
<key>hasFamily</key><false/>
</dict>
</dict>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<plist version="1.0">
<dict>
<key>pings</key>
<array></array>
<key>jingleDocType</key><string>purchaseSuccess</string>
<key>jingleAction</key><string>purchaseProduct</string>
<key>status</key><integer>0</integer>
<key>dsPersonId</key><string>16916646015</string>
<key>creditDisplay</key><string></string>
<key>creditBalance</key><string>1311811</string>
<key>freeSongBalance</key><string>1311811</string>
<key>creditDisplayInternal</key><string>Â¥0.00+0+0+0+0+0</string>
<key>authorized</key><true/>
<key>download-queue-item-count</key><integer>1</integer>
<key>songList</key>
<array>
<dict>
<key>songId</key><integer>444934666</integer>
<key>URL</key><string>https://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</string>
<key>downloadKey</key><string></string><key>artworkURL</key><string>https://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.jpg</string><key>artwork-urls</key>
<dict>
<key>image-type</key><string>download-queue-item</string>
<key>default</key>
<dict>
<key>url</key>
<string>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</string>
</dict>
</dict>
<key>md5</key><string>cb3a13a60ac4c9f507b0fefa351e0351</string><key>chunks</key>
<dict>
<key>chunkSize</key><integer>10485760</integer>
<key>hashes</key>
<array>
<string>a9c3b17b7fd5ce1380580c6f063c469b</string>
<string>8dfa7efed3cccca58987ec5fb5aad752</string>
<string>c636e4bfcdc17f5ce23a8ef104a44fa3</string>
<string>90911e7b4d63bf8d4fc1c0bb2756211b</string>
<string>0c7ae0bfa13df1a3cb25d18eccf79083</string>
<string>4edbba71a6baabeba209cbe495144017</string>
<string>2b915fa08b532ef007787f3cf9ed68e6</string>
<string>e8ae486763cd40c730a6f2008bb212df</string>
<string>a91b20af08349a115d318a3c1a9ac69c</string>
<string>001a0a9e6f0f779365fd1a9e08993afa</string>
<string>d9c965a523b445f662aac8eb49fa271e</string>
<string>22f61327c32a4569a4d81f29ae93f238</string>
<string>3caf910c1da7640a89a51c2ae33e8a3b</string>
<string>70fcd6e5517144b1298848bd964f4dcc</string>
<string>9fb4924aed5006d2c7037be6532d1652</string>
<string>fb341fcaaf6027182f885628e15542f6</string>
<string>a0999c33fa684fa21cf2d8c8a4d45e27</string>
<string>28025c9522f1dc05360e36a9e6da508b</string>
<string>ea980ee5fae29238778cdd16a1f0a2fa</string>
<string>699c2c8bcd5b4b9353bba25a58bb45f8</string>
<string>10423be39910d01b9e763cc1514fef05</string>
<string>3da83694d61846e91a0a21981e88652a</string>
<string>937391e158c46e385bf1ebd2f17501aa</string>
<string>18a12b305b3a0086d76d4f9c72764a00</string>
<string>c19f2b5c1eeb909db77b5d715e023511</string>
<string>80552d4ed44472cf0f307bc6e6e4180c</string>
<string>9af9bebfb73a15084186032efd64f95b</string>
<string>bdc8305c863637d6031c079f3438b2b2</string>
<string>406fff31a2d714ffa1eb7c417232c478</string>
<string>f29550bd0e718ca8588d13654a14e669</string>
<string>ea916c4a8e8f772610bd4de2549ec891</string>
<string>9ff8f440fb8260ffe7e39e17215e8e9f</string>
<string>b596fe96037d6e0e52401676b87fc6c0</string>
<string>1be6f6b3141c80d2b3ad952db9e92ebb</string>
<string>4a9061c50a209184f7d4a54f80ce61ef</string>
<string>e6505a456ca2273700dcad1dead1f625</string>
<string>9ac28b8e7dd43be5fd309f16403afef8</string>
<string>5088792cdb4928532714a7023df1275d</string>
<string>c0b0d04fd38919c8688c5e793b33d5e4</string>
<string>a87331d5e314e6708f6cc67075e36c56</string>
<string>63ea3257f99d04b44730924cb4a0d553</string>
<string>b3d953d072a3b0e8e264c16d96cebeaf</string>
<string>5eff58a53e7f7e87f57fcb038a4ad0a4</string>
<string>e37467e9f2c7d336c3742b63ee70b4ab</string>
<string>abfcdd0ba248ee9c5bc1b4bf8ab03c15</string>
<string>7c4d4ccfcaaef4708f3d1b0fce994294</string>
<string>927273c03da6f0fcdc674b40224c0a6d</string>
<string>648761edcf4c3d69e147b3671abee7a8</string>
<string>146e363283ed83bfd72a8c4d6b8fa7ac</string>
<string>69c8e188357d463d00bd7f8134786b1c</string>
<string>babbb9b2f0c3d31a4d9e0e0d77852f5e</string>
<string>ae46925c45e689783c38f065a9d1e7ea</string>
<string>c7f4967cb830f008417c02009ae2196a</string>
<string>9b8f17524eb05a512c26bb9e2b89220b</string>
<string>11cdc44dd07fe5d9fa3eaba0a179704c</string>
<string>d0f17891f1c141e39cfcacfb58c1bb3c</string>
<string>3273d004f5971ebee5625868d4978f69</string>
<string>0aa895b7d15ad0760156b3062fb6deeb</string>
<string>7b27b6e73b0731b307399aa4da837df9</string>
</array>
</dict>
<key>isStreamable</key><true/>
<key>uncompressedSize</key><integer>795979776</integer>
<key>sinfs</key><array> <dict> <key>id</key> <integer>0</integer> <key>sinf</key> <data>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</data> </dict></array><key>purchaseDate</key><string>2022-05-08T10:45:51Z</string>
<key>download-id</key><string>501382282407089488</string>
<key>is-in-queue</key><true/>
<key>asset-info</key>
<dict>
<key>file-size</key><integer>611092999</integer>
<key>flavor</key><string>10:purple</string>
</dict>
<key>metadata</key><dict>
<key>MacUIRequiredDeviceCapabilities</key>
<dict>
<key>arm64</key><true/>
</dict>
<key>UIRequiredDeviceCapabilities</key>
<dict>
<key>arm64</key><true/>
</dict>
<key>WKRunsIndependentlyOfCompanionApp</key><false/>
<key>WKWatchOnly</key><false/>
<key>appleWatchEnabled</key><true/>
<key>artistId</key><integer>292374531</integer>
<key>artistName</key><string>Tencent Technology (Shenzhen) Company Limited</string>
<key>bundleDisplayName</key><string>QQ</string>
<key>bundleShortVersionString</key><string>8.8.88</string>
<key>bundleVersion</key><string>8.8.88.662</string>
<key>copyright</key><string>Copyright &#169; 1998- 2022 Tencent. All Rights Reserved</string>
<key>fileExtension</key><string>.app</string>
<key>gameCenterEnabled</key><false/>
<key>gameCenterEverEnabled</key><false/>
<key>genre</key><string>社交</string>
<key>genreId</key><integer>6005</integer>
<key>itemId</key><integer>444934666</integer>
<key>itemName</key><string>QQ</string>
<key>kind</key><string>software</string>
<key>nameTranscriptions</key>
<dict>
<key>zh-Hans-CN</key>
<array>
<string>QQ</string>
</array>
</dict>
<key>playlistName</key><string>Tencent Technology (Shenzhen) Company Limited</string>
<key>product-type</key><string>ios-app</string>
<key>rating</key>
<dict>
<key>content</key><string>偶尔/轻微的色情内容或裸露 , Advisory.NO.GAMBLING_CONTESTS , 偶尔/轻微的成人或性暗示题材 , Advisory.NO.UNRESTRICTED_WEB_ACCESS and Advisory.NO.TRUE_GAMBLING</string>
<key>label</key><string>12+</string>
<key>rank</key><integer>300</integer>
<key>system</key><string>itunes-games</string>
</dict>
<key>releaseDate</key><string>2011-06-23T03:33:55Z</string>
<key>requiresRosetta</key><false/>
<key>runsOnAppleSilicon</key><true/>
<key>runsOnIntel</key><false/>
<key>s</key><integer>143465</integer>
<key>software-platform</key><string>ios</string>
<key>softwareIcon57x57URL</key><string>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/114x114bb.jpg</string>
<key>softwareIconNeedsShine</key><false/>
<key>softwareSupportedDeviceIds</key>
<array>
<integer>2</integer>
<integer>9</integer>
<integer>4</integer>
</array>
<key>softwareVersionBundleId</key><string>com.tencent.mqq</string>
<key>softwareVersionExternalIdentifier</key><integer>848463733</integer>
<key>softwareVersionExternalIdentifiers</key>
<array>
<integer>3843900</integer>
<integer>3876776</integer>
<integer>3941034</integer>
<integer>3973775</integer>
<integer>4070873</integer>
<integer>4135846</integer>
<integer>4321059</integer>
<integer>4492645</integer>
<integer>4917185</integer>
<integer>5632593</integer>
<integer>6232232</integer>
<integer>6860432</integer>
<integer>7792605</integer>
<integer>9642362</integer>
<integer>11556077</integer>
<integer>11818464</integer>
<integer>12638046</integer>
<integer>13422327</integer>
<integer>14959445</integer>
<integer>15410008</integer>
<integer>15765932</integer>
<integer>15854719</integer>
<integer>16122679</integer>
<integer>31562763</integer>
<integer>275882650</integer>
<integer>385122645</integer>
<integer>580102645</integer>
<integer>595393136</integer>
<integer>608133076</integer>
<integer>629072654</integer>
<integer>687502658</integer>
<integer>747082669</integer>
<integer>811253584</integer>
<integer>811445779</integer>
<integer>811179715</integer>
<integer>811445780</integer>
<integer>811590055</integer>
<integer>811669050</integer>
<integer>812133257</integer>
<integer>812375519</integer>
<integer>812625692</integer>
<integer>812972631</integer>
<integer>813031156</integer>
<integer>813192464</integer>
<integer>813298393</integer>
<integer>813463229</integer>
<integer>813961231</integer>
<integer>813962159</integer>
<integer>814174262</integer>
<integer>814318376</integer>
<integer>814527796</integer>
<integer>814531991</integer>
<integer>814639613</integer>
<integer>814754396</integer>
<integer>814882132</integer>
<integer>815144899</integer>
<integer>815147083</integer>
<integer>815188393</integer>
<integer>815573881</integer>
<integer>815574602</integer>
<integer>815607136</integer>
<integer>815810968</integer>
<integer>815897122</integer>
<integer>815938087</integer>
<integer>815938591</integer>
<integer>816130933</integer>
<integer>816210673</integer>
<integer>816305041</integer>
<integer>816356364</integer>
<integer>816843896</integer>
<integer>816912335</integer>
<integer>817028549</integer>
<integer>817235792</integer>
<integer>817473714</integer>
<integer>817549698</integer>
<integer>817788181</integer>
<integer>817933532</integer>
<integer>818110324</integer>
<integer>818431910</integer>
<integer>818825180</integer>
<integer>818979113</integer>
<integer>819096686</integer>
<integer>819416223</integer>
<integer>819489902</integer>
<integer>819842838</integer>
<integer>819893353</integer>
<integer>820113905</integer>
<integer>820199943</integer>
<integer>820442929</integer>
<integer>820548304</integer>
<integer>820595060</integer>
<integer>821268583</integer>
<integer>821341311</integer>
<integer>821500924</integer>
<integer>821954014</integer>
<integer>822037007</integer>
<integer>822096329</integer>
<integer>822279520</integer>
<integer>822523036</integer>
<integer>822895308</integer>
<integer>822957820</integer>
<integer>823194852</integer>
<integer>823309872</integer>
<integer>823713346</integer>
<integer>824097583</integer>
<integer>824171129</integer>
<integer>824301257</integer>
<integer>824389600</integer>
<integer>825024981</integer>
<integer>825145808</integer>
<integer>825307653</integer>
<integer>825347730</integer>
<integer>825611268</integer>
<integer>825729315</integer>
<integer>825895542</integer>
<integer>825933124</integer>
<integer>826313718</integer>
<integer>826632543</integer>
<integer>826837026</integer>
<integer>827460275</integer>
<integer>828106847</integer>
<integer>828385681</integer>
<integer>828600919</integer>
<integer>828666943</integer>
<integer>828716670</integer>
<integer>828897691</integer>
<integer>829301009</integer>
<integer>829496800</integer>
<integer>829679760</integer>
<integer>829821912</integer>
<integer>830133231</integer>
<integer>830530856</integer>
<integer>830742895</integer>
<integer>831337375</integer>
<integer>831405629</integer>
<integer>831472755</integer>
<integer>831824011</integer>
<integer>832139548</integer>
<integer>832542612</integer>
<integer>832827329</integer>
<integer>833393416</integer>
<integer>833855517</integer>
<integer>834104017</integer>
<integer>834138755</integer>
<integer>834768091</integer>
<integer>834880993</integer>
<integer>834939578</integer>
<integer>835135459</integer>
<integer>835524672</integer>
<integer>835716180</integer>
<integer>835976856</integer>
<integer>836375483</integer>
<integer>836825545</integer>
<integer>836945925</integer>
<integer>837334930</integer>
<integer>837735298</integer>
<integer>837835768</integer>
<integer>837881604</integer>
<integer>838238560</integer>
<integer>839235987</integer>
<integer>839700113</integer>
<integer>840003771</integer>
<integer>840041841</integer>
<integer>840921423</integer>
<integer>841227891</integer>
<integer>841968948</integer>
<integer>842099251</integer>
<integer>842166600</integer>
<integer>842303496</integer>
<integer>842463280</integer>
<integer>842670770</integer>
<integer>842892434</integer>
<integer>843105491</integer>
<integer>843416604</integer>
<integer>843638409</integer>
<integer>843881653</integer>
<integer>844115468</integer>
<integer>844170367</integer>
<integer>844356613</integer>
<integer>844638728</integer>
<integer>844786323</integer>
<integer>844986204</integer>
<integer>845375859</integer>
<integer>846332674</integer>
<integer>846748317</integer>
<integer>847332305</integer>
<integer>847747163</integer>
<integer>848017318</integer>
<integer>848463733</integer>
</array>
<key>vendorId</key><integer>69276</integer>
<key>drmVersionNumber</key><integer>0</integer>
<key>versionRestrictions</key><integer>16843008</integer>
<key>storeCohort</key><string>10|date=1652005800000&amp;sf=143465&amp;pgtp=Search&amp;pgid=ccfce0ef-4ac8-4d5a-8e5f-79876ac474a4&amp;prpg=Purchases</string>
<key>hasOrEverHasHadIAP</key><true/>
</dict>
</dict>
</array>
<key>download-queue-info</key>
<dict>
<key>download-queue-item-count</key><integer>0</integer>
<key>dsid</key><integer>16916646015</integer>
<key>is-auto-download-machine</key><false/>
</dict>
<key>metrics</key>
<dict>
<key>itemIds</key>
<array>
<integer>444934666</integer>
</array>
<key>price</key><real>0.00</real>
<key>priceType</key><string>STDQ</string>
<key>productTypes</key>
<array>
<string>C</string>
</array>
<key>mtApp</key><string>com.apple.iTunes</string>
<key>mtClientId</key><string>3z21abvCzFDuz5CYz9bdz19maFVKge</string>
<key>mtEventTime</key><string>2022-05-08 10:44:38 Etc/GMT</string>
<key>mtPageId</key><string>ccfce0ef-4ac8-4d5a-8e5f-79876ac474a4</string>
<key>mtPageType</key><string>Search</string>
<key>mtPrevPage</key><string>Purchases</string>
<key>mtRequestId</key><string>3z21abvCzFDuz5CYz9bdz19maFVKgezL2X64FFVz1MGG</string>
<key>mtTopic</key><string>xp_its_main</string>
<key>currency</key><string>CNY</string>
<key>exchangeRateToUSD</key><real>0.1490268546</real>
<key>commerceEvent_purchase_priceType</key><string>STDQ</string>
<key>commerceEvent_storeFrontId</key><string>143465</string>
<key>commerceEvent_result_resultType</key><integer>0</integer>
<key>commerceEvent_flowType</key><integer>4</integer>
<key>commerceEvent_flowStep</key><integer>6</integer>
</dict>
<key>duAnonymousPings</key>
<array>
<string>https://xp.apple.com/report/2/xp_app_buy?clientId=0&amp;sf=143465&amp;adamId=444934666</string>
</array>
<key>subscriptionStatus</key>
<dict>
<key>music</key>
<dict>
<key>status</key><string>Disabled</string>
<key>reason</key><string></string>
<key>isAdmin</key><false/>
<key>isNotEligibleForFreeTrial</key><false/>
</dict>
<key>terms</key>
<array>
<dict>
<key>type</key><string>Store</string>
<key>latestTerms</key><integer>22</integer>
<key>agreedToTerms</key><integer>22</integer>
<key>source</key><string>account</string>
</dict>
</array>
<key>account</key>
<dict>
<key>isMinor</key><false/>
<key>suspectUnderage</key><false/>
</dict>
<key>family</key>
<dict>
<key>hasFamily</key><false/>
</dict>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,250 @@
from reprlib import repr as limitedRepr
class StoreAuthenticateReq:
_types_map = {
"appleId": {"type": str, "subtype": None},
"attempt": {"type": str, "subtype": None},
"createSession": {"type": str, "subtype": None},
"guid": {"type": str, "subtype": None},
"password": {"type": str, "subtype": None},
"rmp": {"type": str, "subtype": None},
"why": {"type": str, "subtype": None},
}
_formats_map = {}
_validations_map = {
"appleId": {
"required": True,
},
"attempt": {
"required": True,
},
"createSession": {
"required": True,
},
"guid": {
"required": True,
},
"password": {
"required": True,
},
"rmp": {
"required": True,
},
"why": {
"required": True,
},
}
def __init__(
self,
appleId: str = None,
attempt: str = None,
createSession: str = None,
guid: str = None,
password: str = None,
rmp: str = None,
why: str = None,
):
pass
self.__appleId = appleId
self.__attempt = attempt
self.__createSession = createSession
self.__guid = guid
self.__password = password
self.__rmp = rmp
self.__why = why
def _get_appleId(self):
return self.__appleId
def _set_appleId(self, value):
if not isinstance(value, str):
raise TypeError("appleId must be str")
self.__appleId = value
appleId = property(_get_appleId, _set_appleId)
def _get_attempt(self):
return self.__attempt
def _set_attempt(self, value):
if not isinstance(value, str):
raise TypeError("attempt must be str")
self.__attempt = value
attempt = property(_get_attempt, _set_attempt)
def _get_createSession(self):
return self.__createSession
def _set_createSession(self, value):
if not isinstance(value, str):
raise TypeError("createSession must be str")
self.__createSession = value
createSession = property(_get_createSession, _set_createSession)
def _get_guid(self):
return self.__guid
def _set_guid(self, value):
if not isinstance(value, str):
raise TypeError("guid must be str")
self.__guid = value
guid = property(_get_guid, _set_guid)
def _get_password(self):
return self.__password
def _set_password(self, value):
if not isinstance(value, str):
raise TypeError("password must be str")
self.__password = value
password = property(_get_password, _set_password)
def _get_rmp(self):
return self.__rmp
def _set_rmp(self, value):
if not isinstance(value, str):
raise TypeError("rmp must be str")
self.__rmp = value
rmp = property(_get_rmp, _set_rmp)
def _get_why(self):
return self.__why
def _set_why(self, value):
if not isinstance(value, str):
raise TypeError("why must be str")
self.__why = value
why = property(_get_why, _set_why)
@staticmethod
def from_dict(d):
v = {}
if "appleId" in d:
v["appleId"] = (
str.from_dict(d["appleId"])
if hasattr(str, "from_dict")
else d["appleId"]
)
if "attempt" in d:
v["attempt"] = (
str.from_dict(d["attempt"])
if hasattr(str, "from_dict")
else d["attempt"]
)
if "createSession" in d:
v["createSession"] = (
str.from_dict(d["createSession"])
if hasattr(str, "from_dict")
else d["createSession"]
)
if "guid" in d:
v["guid"] = (
str.from_dict(d["guid"]) if hasattr(str, "from_dict") else d["guid"]
)
if "password" in d:
v["password"] = (
str.from_dict(d["password"])
if hasattr(str, "from_dict")
else d["password"]
)
if "rmp" in d:
v["rmp"] = (
str.from_dict(d["rmp"]) if hasattr(str, "from_dict") else d["rmp"]
)
if "why" in d:
v["why"] = (
str.from_dict(d["why"]) if hasattr(str, "from_dict") else d["why"]
)
return StoreAuthenticateReq(**v)
def as_dict(self):
d = {}
if self.__appleId is not None:
d["appleId"] = (
self.__appleId.as_dict()
if hasattr(self.__appleId, "as_dict")
else self.__appleId
)
if self.__attempt is not None:
d["attempt"] = (
self.__attempt.as_dict()
if hasattr(self.__attempt, "as_dict")
else self.__attempt
)
if self.__createSession is not None:
d["createSession"] = (
self.__createSession.as_dict()
if hasattr(self.__createSession, "as_dict")
else self.__createSession
)
if self.__guid is not None:
d["guid"] = (
self.__guid.as_dict()
if hasattr(self.__guid, "as_dict")
else self.__guid
)
if self.__password is not None:
d["password"] = (
self.__password.as_dict()
if hasattr(self.__password, "as_dict")
else self.__password
)
if self.__rmp is not None:
d["rmp"] = (
self.__rmp.as_dict() if hasattr(self.__rmp, "as_dict") else self.__rmp
)
if self.__why is not None:
d["why"] = (
self.__why.as_dict() if hasattr(self.__why, "as_dict") else self.__why
)
return d
def __repr__(self):
return "<Class StoreAuthenticateReq. appleId: {}, attempt: {}, createSession: {}, guid: {}, password: {}, rmp: {}, why: {}>".format(
limitedRepr(
self.__appleId[:20]
if isinstance(self.__appleId, bytes)
else self.__appleId
),
limitedRepr(
self.__attempt[:20]
if isinstance(self.__attempt, bytes)
else self.__attempt
),
limitedRepr(
self.__createSession[:20]
if isinstance(self.__createSession, bytes)
else self.__createSession
),
limitedRepr(
self.__guid[:20] if isinstance(self.__guid, bytes) else self.__guid
),
limitedRepr(
self.__password[:20]
if isinstance(self.__password, bytes)
else self.__password
),
limitedRepr(
self.__rmp[:20] if isinstance(self.__rmp, bytes) else self.__rmp
),
limitedRepr(
self.__why[:20] if isinstance(self.__why, bytes) else self.__why
),
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,956 @@
from reprlib import repr as limitedRepr
class StoreBuyproductReq:
_types_map = {
"ageCheck": {"type": str, "subtype": None},
"appExtVrsId": {"type": str, "subtype": None},
"guid": {"type": str, "subtype": None},
"hasBeenAuthedForBuy": {"type": str, "subtype": None},
"isInApp": {"type": str, "subtype": None},
"kbsync": {"type": str, "subtype": None},
"sbsync": {"type": str, "subtype": None},
"afds": {"type": str, "subtype": None},
"machineName": {"type": str, "subtype": None},
"mtApp": {"type": str, "subtype": None},
"mtClientId": {"type": str, "subtype": None},
"mtEventTime": {"type": str, "subtype": None},
"mtPageId": {"type": str, "subtype": None},
"mtPageType": {"type": str, "subtype": None},
"mtPrevPage": {"type": str, "subtype": None},
"mtRequestId": {"type": str, "subtype": None},
"mtTopic": {"type": str, "subtype": None},
"needDiv": {"type": str, "subtype": None},
"pg": {"type": str, "subtype": None},
"price": {"type": str, "subtype": None},
"pricingParameters": {"type": str, "subtype": None},
"productType": {"type": str, "subtype": None},
"salableAdamId": {"type": str, "subtype": None},
"hasAskedToFulfillPreorder": {"type": str, "subtype": None},
"buyWithoutAuthorization": {"type": str, "subtype": None},
"hasDoneAgeCheck": {"type": str, "subtype": None},
"hasConfirmedPaymentSheet": {"type": str, "subtype": None},
"asn": {"type": str, "subtype": None},
}
_formats_map = {}
_validations_map = {
"ageCheck": {
"required": False,
},
"appExtVrsId": {
"required": True,
},
"guid": {
"required": True,
},
"hasBeenAuthedForBuy": {
"required": False,
},
"isInApp": {
"required": False,
},
"kbsync": {
"required": True,
},
"sbsync": {
"required": False,
},
"afds": {
"required": False,
},
"machineName": {
"required": False,
},
"mtApp": {
"required": False,
},
"mtClientId": {
"required": False,
},
"mtEventTime": {
"required": False,
},
"mtPageId": {
"required": False,
},
"mtPageType": {
"required": False,
},
"mtPrevPage": {
"required": False,
},
"mtRequestId": {
"required": False,
},
"mtTopic": {
"required": False,
},
"needDiv": {
"required": False,
},
"pg": {
"required": False,
},
"price": {
"required": True,
},
"pricingParameters": {
"required": True,
},
"productType": {
"required": True,
},
"salableAdamId": {
"required": True,
},
"hasAskedToFulfillPreorder": {
"required": False,
},
"buyWithoutAuthorization": {
"required": False,
},
"hasDoneAgeCheck": {
"required": False,
},
"hasConfirmedPaymentSheet": {
"required": False,
},
"asn": {
"required": False,
},
}
def __init__(
self,
ageCheck: str = None,
appExtVrsId: str = None,
guid: str = None,
hasBeenAuthedForBuy: str = None,
isInApp: str = None,
kbsync: str = None,
sbsync: str = None,
afds: str = None,
machineName: str = None,
mtApp: str = None,
mtClientId: str = None,
mtEventTime: str = None,
mtPageId: str = None,
mtPageType: str = None,
mtPrevPage: str = None,
mtRequestId: str = None,
mtTopic: str = None,
needDiv: str = None,
pg: str = None,
price: str = None,
pricingParameters: str = None,
productType: str = None,
salableAdamId: str = None,
hasAskedToFulfillPreorder: str = None,
buyWithoutAuthorization: str = None,
hasDoneAgeCheck: str = None,
hasConfirmedPaymentSheet: str = None,
asn: str = None,
):
pass
self.__ageCheck = ageCheck
self.__appExtVrsId = appExtVrsId
self.__guid = guid
self.__hasBeenAuthedForBuy = hasBeenAuthedForBuy
self.__isInApp = isInApp
self.__kbsync = kbsync
self.__sbsync = sbsync
self.__afds = afds
self.__machineName = machineName
self.__mtApp = mtApp
self.__mtClientId = mtClientId
self.__mtEventTime = mtEventTime
self.__mtPageId = mtPageId
self.__mtPageType = mtPageType
self.__mtPrevPage = mtPrevPage
self.__mtRequestId = mtRequestId
self.__mtTopic = mtTopic
self.__needDiv = needDiv
self.__pg = pg
self.__price = price
self.__pricingParameters = pricingParameters
self.__productType = productType
self.__salableAdamId = salableAdamId
self.__hasAskedToFulfillPreorder = hasAskedToFulfillPreorder
self.__buyWithoutAuthorization = buyWithoutAuthorization
self.__hasDoneAgeCheck = hasDoneAgeCheck
self.__hasConfirmedPaymentSheet = hasConfirmedPaymentSheet
self.__asn = asn
def _get_ageCheck(self):
return self.__ageCheck
def _set_ageCheck(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("ageCheck must be str")
self.__ageCheck = value
ageCheck = property(_get_ageCheck, _set_ageCheck)
def _get_appExtVrsId(self):
return self.__appExtVrsId
def _set_appExtVrsId(self, value):
if not isinstance(value, str):
raise TypeError("appExtVrsId must be str")
self.__appExtVrsId = value
appExtVrsId = property(_get_appExtVrsId, _set_appExtVrsId)
def _get_guid(self):
return self.__guid
def _set_guid(self, value):
if not isinstance(value, str):
raise TypeError("guid must be str")
self.__guid = value
guid = property(_get_guid, _set_guid)
def _get_hasBeenAuthedForBuy(self):
return self.__hasBeenAuthedForBuy
def _set_hasBeenAuthedForBuy(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("hasBeenAuthedForBuy must be str")
self.__hasBeenAuthedForBuy = value
hasBeenAuthedForBuy = property(_get_hasBeenAuthedForBuy, _set_hasBeenAuthedForBuy)
def _get_isInApp(self):
return self.__isInApp
def _set_isInApp(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("isInApp must be str")
self.__isInApp = value
isInApp = property(_get_isInApp, _set_isInApp)
def _get_kbsync(self):
return self.__kbsync
def _set_kbsync(self, value):
if not isinstance(value, str):
raise TypeError("kbsync must be str")
self.__kbsync = value
kbsync = property(_get_kbsync, _set_kbsync)
def _get_sbsync(self):
return self.__sbsync
def _set_sbsync(self, value):
if not isinstance(value, str):
raise TypeError("sbsync must be str")
self.__sbsync = value
sbsync = property(_get_sbsync, _set_sbsync)
def _get_afds(self):
return self.__afds
def _set_afds(self, value):
if not isinstance(value, str):
raise TypeError("afds must be str")
self.__afds = value
afds = property(_get_afds, _set_afds)
def _get_machineName(self):
return self.__machineName
def _set_machineName(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("machineName must be str")
self.__machineName = value
machineName = property(_get_machineName, _set_machineName)
def _get_mtApp(self):
return self.__mtApp
def _set_mtApp(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("mtApp must be str")
self.__mtApp = value
mtApp = property(_get_mtApp, _set_mtApp)
def _get_mtClientId(self):
return self.__mtClientId
def _set_mtClientId(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("mtClientId must be str")
self.__mtClientId = value
mtClientId = property(_get_mtClientId, _set_mtClientId)
def _get_mtEventTime(self):
return self.__mtEventTime
def _set_mtEventTime(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("mtEventTime must be str")
self.__mtEventTime = value
mtEventTime = property(_get_mtEventTime, _set_mtEventTime)
def _get_mtPageId(self):
return self.__mtPageId
def _set_mtPageId(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("mtPageId must be str")
self.__mtPageId = value
mtPageId = property(_get_mtPageId, _set_mtPageId)
def _get_mtPageType(self):
return self.__mtPageType
def _set_mtPageType(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("mtPageType must be str")
self.__mtPageType = value
mtPageType = property(_get_mtPageType, _set_mtPageType)
def _get_mtPrevPage(self):
return self.__mtPrevPage
def _set_mtPrevPage(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("mtPrevPage must be str")
self.__mtPrevPage = value
mtPrevPage = property(_get_mtPrevPage, _set_mtPrevPage)
def _get_mtRequestId(self):
return self.__mtRequestId
def _set_mtRequestId(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("mtRequestId must be str")
self.__mtRequestId = value
mtRequestId = property(_get_mtRequestId, _set_mtRequestId)
def _get_mtTopic(self):
return self.__mtTopic
def _set_mtTopic(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("mtTopic must be str")
self.__mtTopic = value
mtTopic = property(_get_mtTopic, _set_mtTopic)
def _get_needDiv(self):
return self.__needDiv
def _set_needDiv(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("needDiv must be str")
self.__needDiv = value
needDiv = property(_get_needDiv, _set_needDiv)
def _get_pg(self):
return self.__pg
def _set_pg(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("pg must be str")
self.__pg = value
pg = property(_get_pg, _set_pg)
def _get_price(self):
return self.__price
def _set_price(self, value):
if not isinstance(value, str):
raise TypeError("price must be str")
self.__price = value
price = property(_get_price, _set_price)
def _get_pricingParameters(self):
return self.__pricingParameters
def _set_pricingParameters(self, value):
if not isinstance(value, str):
raise TypeError("pricingParameters must be str")
self.__pricingParameters = value
pricingParameters = property(_get_pricingParameters, _set_pricingParameters)
def _get_productType(self):
return self.__productType
def _set_productType(self, value):
if not isinstance(value, str):
raise TypeError("productType must be str")
self.__productType = value
productType = property(_get_productType, _set_productType)
def _get_salableAdamId(self):
return self.__salableAdamId
def _set_salableAdamId(self, value):
if not isinstance(value, str):
raise TypeError("salableAdamId must be str")
self.__salableAdamId = value
salableAdamId = property(_get_salableAdamId, _set_salableAdamId)
def _get_hasAskedToFulfillPreorder(self):
return self.__hasAskedToFulfillPreorder
def _set_hasAskedToFulfillPreorder(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("hasAskedToFulfillPreorder must be str")
self.__hasAskedToFulfillPreorder = value
hasAskedToFulfillPreorder = property(
_get_hasAskedToFulfillPreorder, _set_hasAskedToFulfillPreorder
)
def _get_buyWithoutAuthorization(self):
return self.__buyWithoutAuthorization
def _set_buyWithoutAuthorization(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("buyWithoutAuthorization must be str")
self.__buyWithoutAuthorization = value
buyWithoutAuthorization = property(
_get_buyWithoutAuthorization, _set_buyWithoutAuthorization
)
def _get_hasDoneAgeCheck(self):
return self.__hasDoneAgeCheck
def _set_hasDoneAgeCheck(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("hasDoneAgeCheck must be str")
self.__hasDoneAgeCheck = value
hasDoneAgeCheck = property(_get_hasDoneAgeCheck, _set_hasDoneAgeCheck)
def _get_hasConfirmedPaymentSheet(self):
return self.__hasConfirmedPaymentSheet
def _set_hasConfirmedPaymentSheet(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("hasConfirmedPaymentSheet must be str")
self.__hasConfirmedPaymentSheet = value
hasConfirmedPaymentSheet = property(_get_hasConfirmedPaymentSheet, _set_hasConfirmedPaymentSheet)
def _get_asn(self):
return self.__asn
def _set_asn(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("asn must be str")
self.__asn = value
asn = property(_get_asn, _set_asn)
@staticmethod
def from_dict(d):
v = {}
if "ageCheck" in d:
v["ageCheck"] = (
str.from_dict(d["ageCheck"])
if hasattr(str, "from_dict")
else d["ageCheck"]
)
if "appExtVrsId" in d:
v["appExtVrsId"] = (
str.from_dict(d["appExtVrsId"])
if hasattr(str, "from_dict")
else d["appExtVrsId"]
)
if "guid" in d:
v["guid"] = (
str.from_dict(d["guid"]) if hasattr(str, "from_dict") else d["guid"]
)
if "hasBeenAuthedForBuy" in d:
v["hasBeenAuthedForBuy"] = (
str.from_dict(d["hasBeenAuthedForBuy"])
if hasattr(str, "from_dict")
else d["hasBeenAuthedForBuy"]
)
if "isInApp" in d:
v["isInApp"] = (
str.from_dict(d["isInApp"])
if hasattr(str, "from_dict")
else d["isInApp"]
)
if "kbsync" in d:
v["kbsync"] = (
str.from_dict(d["kbsync"]) if hasattr(str, "from_dict") else d["kbsync"]
)
if "sbsync" in d:
v["sbsync"] = (
str.from_dict(d["sbsync"]) if hasattr(str, "from_dict") else d["sbsync"]
)
if "afds" in d:
v["afds"] = (
str.from_dict(d["afds"]) if hasattr(str, "from_dict") else d["afds"]
)
if "machineName" in d:
v["machineName"] = (
str.from_dict(d["machineName"])
if hasattr(str, "from_dict")
else d["machineName"]
)
if "mtApp" in d:
v["mtApp"] = (
str.from_dict(d["mtApp"]) if hasattr(str, "from_dict") else d["mtApp"]
)
if "mtClientId" in d:
v["mtClientId"] = (
str.from_dict(d["mtClientId"])
if hasattr(str, "from_dict")
else d["mtClientId"]
)
if "mtEventTime" in d:
v["mtEventTime"] = (
str.from_dict(d["mtEventTime"])
if hasattr(str, "from_dict")
else d["mtEventTime"]
)
if "mtPageId" in d:
v["mtPageId"] = (
str.from_dict(d["mtPageId"])
if hasattr(str, "from_dict")
else d["mtPageId"]
)
if "mtPageType" in d:
v["mtPageType"] = (
str.from_dict(d["mtPageType"])
if hasattr(str, "from_dict")
else d["mtPageType"]
)
if "mtPrevPage" in d:
v["mtPrevPage"] = (
str.from_dict(d["mtPrevPage"])
if hasattr(str, "from_dict")
else d["mtPrevPage"]
)
if "mtRequestId" in d:
v["mtRequestId"] = (
str.from_dict(d["mtRequestId"])
if hasattr(str, "from_dict")
else d["mtRequestId"]
)
if "mtTopic" in d:
v["mtTopic"] = (
str.from_dict(d["mtTopic"])
if hasattr(str, "from_dict")
else d["mtTopic"]
)
if "needDiv" in d:
v["needDiv"] = (
str.from_dict(d["needDiv"])
if hasattr(str, "from_dict")
else d["needDiv"]
)
if "pg" in d:
v["pg"] = str.from_dict(d["pg"]) if hasattr(str, "from_dict") else d["pg"]
if "price" in d:
v["price"] = (
str.from_dict(d["price"]) if hasattr(str, "from_dict") else d["price"]
)
if "pricingParameters" in d:
v["pricingParameters"] = (
str.from_dict(d["pricingParameters"])
if hasattr(str, "from_dict")
else d["pricingParameters"]
)
if "productType" in d:
v["productType"] = (
str.from_dict(d["productType"])
if hasattr(str, "from_dict")
else d["productType"]
)
if "salableAdamId" in d:
v["salableAdamId"] = (
str.from_dict(d["salableAdamId"])
if hasattr(str, "from_dict")
else d["salableAdamId"]
)
if "hasAskedToFulfillPreorder" in d:
v["hasAskedToFulfillPreorder"] = (
str.from_dict(d["hasAskedToFulfillPreorder"])
if hasattr(str, "from_dict")
else d["hasAskedToFulfillPreorder"]
)
if "buyWithoutAuthorization" in d:
v["buyWithoutAuthorization"] = (
str.from_dict(d["buyWithoutAuthorization"])
if hasattr(str, "from_dict")
else d["buyWithoutAuthorization"]
)
if "hasDoneAgeCheck" in d:
v["hasDoneAgeCheck"] = (
str.from_dict(d["hasDoneAgeCheck"])
if hasattr(str, "from_dict")
else d["hasDoneAgeCheck"]
)
if "hasConfirmedPaymentSheet" in d:
v["hasConfirmedPaymentSheet"] = (
str.from_dict(d["hasConfirmedPaymentSheet"])
if hasattr(str, "from_dict")
else d["hasConfirmedPaymentSheet"]
)
if "asn" in d:
v["asn"] = (
str.from_dict(d["asn"])
if hasattr(str, "from_dict")
else d["asn"]
)
return StoreBuyproductReq(**v)
def as_dict(self):
d = {}
if self.__ageCheck is not None:
d["ageCheck"] = (
self.__ageCheck.as_dict()
if hasattr(self.__ageCheck, "as_dict")
else self.__ageCheck
)
if self.__appExtVrsId is not None:
d["appExtVrsId"] = (
self.__appExtVrsId.as_dict()
if hasattr(self.__appExtVrsId, "as_dict")
else self.__appExtVrsId
)
if self.__guid is not None:
d["guid"] = (
self.__guid.as_dict()
if hasattr(self.__guid, "as_dict")
else self.__guid
)
if self.__hasBeenAuthedForBuy is not None:
d["hasBeenAuthedForBuy"] = (
self.__hasBeenAuthedForBuy.as_dict()
if hasattr(self.__hasBeenAuthedForBuy, "as_dict")
else self.__hasBeenAuthedForBuy
)
if self.__isInApp is not None:
d["isInApp"] = (
self.__isInApp.as_dict()
if hasattr(self.__isInApp, "as_dict")
else self.__isInApp
)
if self.__kbsync is not None:
d["kbsync"] = (
self.__kbsync.as_dict()
if hasattr(self.__kbsync, "as_dict")
else self.__kbsync
)
if self.__sbsync is not None:
d["sbsync"] = (
self.__sbsync.as_dict()
if hasattr(self.__sbsync, "as_dict")
else self.__sbsync
)
if self.__afds is not None:
d["afds"] = (
self.__afds.as_dict()
if hasattr(self.__afds, "as_dict")
else self.__afds
)
if self.__machineName is not None:
d["machineName"] = (
self.__machineName.as_dict()
if hasattr(self.__machineName, "as_dict")
else self.__machineName
)
if self.__mtApp is not None:
d["mtApp"] = (
self.__mtApp.as_dict()
if hasattr(self.__mtApp, "as_dict")
else self.__mtApp
)
if self.__mtClientId is not None:
d["mtClientId"] = (
self.__mtClientId.as_dict()
if hasattr(self.__mtClientId, "as_dict")
else self.__mtClientId
)
if self.__mtEventTime is not None:
d["mtEventTime"] = (
self.__mtEventTime.as_dict()
if hasattr(self.__mtEventTime, "as_dict")
else self.__mtEventTime
)
if self.__mtPageId is not None:
d["mtPageId"] = (
self.__mtPageId.as_dict()
if hasattr(self.__mtPageId, "as_dict")
else self.__mtPageId
)
if self.__mtPageType is not None:
d["mtPageType"] = (
self.__mtPageType.as_dict()
if hasattr(self.__mtPageType, "as_dict")
else self.__mtPageType
)
if self.__mtPrevPage is not None:
d["mtPrevPage"] = (
self.__mtPrevPage.as_dict()
if hasattr(self.__mtPrevPage, "as_dict")
else self.__mtPrevPage
)
if self.__mtRequestId is not None:
d["mtRequestId"] = (
self.__mtRequestId.as_dict()
if hasattr(self.__mtRequestId, "as_dict")
else self.__mtRequestId
)
if self.__mtTopic is not None:
d["mtTopic"] = (
self.__mtTopic.as_dict()
if hasattr(self.__mtTopic, "as_dict")
else self.__mtTopic
)
if self.__needDiv is not None:
d["needDiv"] = (
self.__needDiv.as_dict()
if hasattr(self.__needDiv, "as_dict")
else self.__needDiv
)
if self.__pg is not None:
d["pg"] = (
self.__pg.as_dict() if hasattr(self.__pg, "as_dict") else self.__pg
)
if self.__price is not None:
d["price"] = (
self.__price.as_dict()
if hasattr(self.__price, "as_dict")
else self.__price
)
if self.__pricingParameters is not None:
d["pricingParameters"] = (
self.__pricingParameters.as_dict()
if hasattr(self.__pricingParameters, "as_dict")
else self.__pricingParameters
)
if self.__productType is not None:
d["productType"] = (
self.__productType.as_dict()
if hasattr(self.__productType, "as_dict")
else self.__productType
)
if self.__salableAdamId is not None:
d["salableAdamId"] = (
self.__salableAdamId.as_dict()
if hasattr(self.__salableAdamId, "as_dict")
else self.__salableAdamId
)
if self.__hasAskedToFulfillPreorder is not None:
d["hasAskedToFulfillPreorder"] = (
self.__hasAskedToFulfillPreorder.as_dict()
if hasattr(self.__hasAskedToFulfillPreorder, "as_dict")
else self.__hasAskedToFulfillPreorder
)
if self.__buyWithoutAuthorization is not None:
d["buyWithoutAuthorization"] = (
self.__buyWithoutAuthorization.as_dict()
if hasattr(self.__buyWithoutAuthorization, "as_dict")
else self.__buyWithoutAuthorization
)
if self.__hasDoneAgeCheck is not None:
d["hasDoneAgeCheck"] = (
self.__hasDoneAgeCheck.as_dict()
if hasattr(self.__hasDoneAgeCheck, "as_dict")
else self.__hasDoneAgeCheck
)
if self.__hasConfirmedPaymentSheet is not None:
d["hasConfirmedPaymentSheet"] = (
self.__hasConfirmedPaymentSheet.as_dict()
if hasattr(self.__hasConfirmedPaymentSheet, "as_dict")
else self.__hasConfirmedPaymentSheet
)
if self.__asn is not None:
d["asn"] = (
self.__asn.as_dict()
if hasattr(self.__asn, "as_dict")
else self.__asn
)
return d
def __repr__(self):
return "<Class StoreBuyproductReq. ageCheck: {}, appExtVrsId: {}, guid: {}, hasBeenAuthedForBuy: {}, isInApp: {}, kbsync: {}, sbsync: {}, afds: {}, machineName: {}, mtApp: {}, mtClientId: {}, mtEventTime: {}, mtPageId: {}, mtPageType: {}, mtPrevPage: {}, mtRequestId: {}, mtTopic: {}, needDiv: {}, pg: {}, price: {}, pricingParameters: {}, productType: {}, salableAdamId: {}, hasAskedToFulfillPreorder: {}, buyWithoutAuthorization: {}, hasDoneAgeCheck: {}>".format(
limitedRepr(
self.__ageCheck[:20]
if isinstance(self.__ageCheck, bytes)
else self.__ageCheck
),
limitedRepr(
self.__appExtVrsId[:20]
if isinstance(self.__appExtVrsId, bytes)
else self.__appExtVrsId
),
limitedRepr(
self.__guid[:20] if isinstance(self.__guid, bytes) else self.__guid
),
limitedRepr(
self.__hasBeenAuthedForBuy[:20]
if isinstance(self.__hasBeenAuthedForBuy, bytes)
else self.__hasBeenAuthedForBuy
),
limitedRepr(
self.__isInApp[:20]
if isinstance(self.__isInApp, bytes)
else self.__isInApp
),
limitedRepr(
self.__kbsync[:20]
if isinstance(self.__kbsync, bytes)
else self.__kbsync
),
limitedRepr(
self.__sbsync[:20]
if isinstance(self.__sbsync, bytes)
else self.__sbsync
),
limitedRepr(
self.__afds[:20]
if isinstance(self.__afds, bytes)
else self.__afds
),
limitedRepr(
self.__machineName[:20]
if isinstance(self.__machineName, bytes)
else self.__machineName
),
limitedRepr(
self.__mtApp[:20] if isinstance(self.__mtApp, bytes) else self.__mtApp
),
limitedRepr(
self.__mtClientId[:20]
if isinstance(self.__mtClientId, bytes)
else self.__mtClientId
),
limitedRepr(
self.__mtEventTime[:20]
if isinstance(self.__mtEventTime, bytes)
else self.__mtEventTime
),
limitedRepr(
self.__mtPageId[:20]
if isinstance(self.__mtPageId, bytes)
else self.__mtPageId
),
limitedRepr(
self.__mtPageType[:20]
if isinstance(self.__mtPageType, bytes)
else self.__mtPageType
),
limitedRepr(
self.__mtPrevPage[:20]
if isinstance(self.__mtPrevPage, bytes)
else self.__mtPrevPage
),
limitedRepr(
self.__mtRequestId[:20]
if isinstance(self.__mtRequestId, bytes)
else self.__mtRequestId
),
limitedRepr(
self.__mtTopic[:20]
if isinstance(self.__mtTopic, bytes)
else self.__mtTopic
),
limitedRepr(
self.__needDiv[:20]
if isinstance(self.__needDiv, bytes)
else self.__needDiv
),
limitedRepr(self.__pg[:20] if isinstance(self.__pg, bytes) else self.__pg),
limitedRepr(
self.__price[:20] if isinstance(self.__price, bytes) else self.__price
),
limitedRepr(
self.__pricingParameters[:20]
if isinstance(self.__pricingParameters, bytes)
else self.__pricingParameters
),
limitedRepr(
self.__productType[:20]
if isinstance(self.__productType, bytes)
else self.__productType
),
limitedRepr(
self.__salableAdamId[:20]
if isinstance(self.__salableAdamId, bytes)
else self.__salableAdamId
),
limitedRepr(
self.__hasAskedToFulfillPreorder[:20]
if isinstance(self.__hasAskedToFulfillPreorder, bytes)
else self.__hasAskedToFulfillPreorder
),
limitedRepr(
self.__buyWithoutAuthorization[:20]
if isinstance(self.__buyWithoutAuthorization, bytes)
else self.__buyWithoutAuthorization
),
limitedRepr(
self.__hasDoneAgeCheck[:20]
if isinstance(self.__hasDoneAgeCheck, bytes)
else self.__hasDoneAgeCheck
),
limitedRepr(
self.__hasConfirmedPaymentSheet[:20]
if isinstance(self.__hasConfirmedPaymentSheet, bytes)
else self.__hasConfirmedPaymentSheet
),
limitedRepr(
self.__asn[:20]
if isinstance(self.__asn, bytes)
else self.__asn
),
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,160 @@
from reprlib import repr as limitedRepr
class StoreDownloadReq:
_types_map = {
"creditDisplay": {"type": str, "subtype": None},
"guid": {"type": str, "subtype": None},
"salableAdamId": {"type": str, "subtype": None},
"appExtVrsId": {"type": str, "subtype": None},
}
_formats_map = {}
_validations_map = {
"creditDisplay": {
"required": True,
},
"guid": {
"required": True,
},
"salableAdamId": {
"required": True,
},
"appExtVrsId": {
"required": False,
},
}
def __init__(
self,
creditDisplay: str = None,
guid: str = None,
salableAdamId: str = None,
appExtVrsId: str = None,
):
pass
self.__creditDisplay = creditDisplay
self.__guid = guid
self.__salableAdamId = salableAdamId
self.__appExtVrsId = appExtVrsId
def _get_creditDisplay(self):
return self.__creditDisplay
def _set_creditDisplay(self, value):
if not isinstance(value, str):
raise TypeError("creditDisplay must be str")
self.__creditDisplay = value
creditDisplay = property(_get_creditDisplay, _set_creditDisplay)
def _get_guid(self):
return self.__guid
def _set_guid(self, value):
if not isinstance(value, str):
raise TypeError("guid must be str")
self.__guid = value
guid = property(_get_guid, _set_guid)
def _get_salableAdamId(self):
return self.__salableAdamId
def _set_salableAdamId(self, value):
if not isinstance(value, str):
raise TypeError("salableAdamId must be str")
self.__salableAdamId = value
salableAdamId = property(_get_salableAdamId, _set_salableAdamId)
def _get_appExtVrsId(self):
return self.__appExtVrsId
def _set_appExtVrsId(self, value):
if value is not None and not isinstance(value, str):
raise TypeError("appExtVrsId must be str")
self.__appExtVrsId = value
appExtVrsId = property(_get_appExtVrsId, _set_appExtVrsId)
@staticmethod
def from_dict(d):
v = {}
if "creditDisplay" in d:
v["creditDisplay"] = (
str.from_dict(d["creditDisplay"])
if hasattr(str, "from_dict")
else d["creditDisplay"]
)
if "guid" in d:
v["guid"] = (
str.from_dict(d["guid"]) if hasattr(str, "from_dict") else d["guid"]
)
if "salableAdamId" in d:
v["salableAdamId"] = (
str.from_dict(d["salableAdamId"])
if hasattr(str, "from_dict")
else d["salableAdamId"]
)
if "appExtVrsId" in d:
v["appExtVrsId"] = (
str.from_dict(d["appExtVrsId"])
if hasattr(str, "from_dict")
else d["appExtVrsId"]
)
return StoreDownloadReq(**v)
def as_dict(self):
d = {}
if self.__creditDisplay is not None:
d["creditDisplay"] = (
self.__creditDisplay.as_dict()
if hasattr(self.__creditDisplay, "as_dict")
else self.__creditDisplay
)
if self.__guid is not None:
d["guid"] = (
self.__guid.as_dict()
if hasattr(self.__guid, "as_dict")
else self.__guid
)
if self.__salableAdamId is not None:
d["salableAdamId"] = (
self.__salableAdamId.as_dict()
if hasattr(self.__salableAdamId, "as_dict")
else self.__salableAdamId
)
if self.__appExtVrsId is not None:
d["appExtVrsId"] = (
self.__appExtVrsId.as_dict()
if hasattr(self.__appExtVrsId, "as_dict")
else self.__appExtVrsId
)
return d
def __repr__(self):
return "<Class StoreDownloadReq. creditDisplay: {}, guid: {}, salableAdamId: {}, appExtVrsId: {}>".format(
limitedRepr(
self.__creditDisplay[:20]
if isinstance(self.__creditDisplay, bytes)
else self.__creditDisplay
),
limitedRepr(
self.__guid[:20] if isinstance(self.__guid, bytes) else self.__guid
),
limitedRepr(
self.__salableAdamId[:20]
if isinstance(self.__salableAdamId, bytes)
else self.__salableAdamId
),
limitedRepr(
self.__appExtVrsId[:20]
if isinstance(self.__appExtVrsId, bytes)
else self.__appExtVrsId
),
)

File diff suppressed because it is too large Load Diff

254
src_mac/ipatool-py/reqs/store.py Executable file
View File

@@ -0,0 +1,254 @@
import hashlib
import json
import pickle
import plistlib
import requests
from reqs.schemas.store_authenticate_req import StoreAuthenticateReq
from reqs.schemas.store_authenticate_resp import StoreAuthenticateResp
from reqs.schemas.store_buyproduct_req import StoreBuyproductReq
from reqs.schemas.store_buyproduct_resp import StoreBuyproductResp
from reqs.schemas.store_download_req import StoreDownloadReq
from reqs.schemas.store_download_resp import StoreDownloadResp
class StoreException(Exception):
def __init__(self, req, resp, errMsg, errType=None):
self.req = req
self.resp = resp # type: StoreDownloadResp
self.errMsg = errMsg
self.errType = errType
super().__init__(
"Store %s error: %s" % (self.req, self.errMsg) if not self.errType else
"Store %s error: %s, errorType: %s" % (self.req, self.errMsg, self.errType)
)
#CONFIGURATOR_UA = "Configurator/2.0 (Macintosh; OS X 10.12.6; 16G29) AppleWebKit/2603.3.8"
CONFIGURATOR_UA = 'Configurator/2.0 (Macintosh; OS X 10.12.6; 16G29) AppleWebKit/2603.3.8 iOS/14.2 hwp/t8020'
class StoreClientAuth(object):
def __init__(self, appleId=None, password=None):
self.appleId = appleId
self.password = password
self.guid = None # the guid will not be used in itunes server mode
self.accountName = None
self.authHeaders = None
self.authCookies = None
def __str__(self):
return f"<{self.accountName} [{self.guid}]>"
def _generateGuid(self, appleId):
'''
Derive a GUID for an appleId. For each appleId, the GUID will always remain the same
:param appleId:
:return:
'''
DEFAULT_GUID = '000C2941396B' # this GUID is blocked
# number of chars to use from DEFAULT_GUID as prefix (0..12)
GUID_DEFAULT_PREFIX = 2
# something unique
GUID_SEED = 'CAFEBABE'
# something between 0 and 30
GUID_POS = 10
# generate a unique guid out of the appleId
h = hashlib.sha1((GUID_SEED + appleId + GUID_SEED).encode("utf-8")).hexdigest()
defaultPart = DEFAULT_GUID[:GUID_DEFAULT_PREFIX]
hashPart = h[GUID_POS: GUID_POS + (len(DEFAULT_GUID) - GUID_DEFAULT_PREFIX)]
guid = (defaultPart + hashPart).upper()
return guid
def login(self, sess):
if not self.guid:
self.guid = self._generateGuid(self.appleId)
req = StoreAuthenticateReq(appleId=self.appleId, password=self.password, attempt='4', createSession="true",
guid=self.guid, rmp='0', why='signIn')
url = "https://p46-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/authenticate?guid=%s" % self.guid
while True:
r = sess.post(url,
headers={
"Accept": "*/*",
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": CONFIGURATOR_UA,
}, data=plistlib.dumps(req.as_dict()), allow_redirects=False)
if r.status_code == 302:
url = r.headers['Location']
continue
break
d = plistlib.loads(r.content)
resp = StoreAuthenticateResp.from_dict(d)
if not resp.m_allowed:
raise StoreException("authenticate", d, resp.customerMessage, resp.failureType)
self.authHeaders = {}
self.authHeaders['X-Dsid'] = self.authHeaders['iCloud-Dsid'] = str(resp.download_queue_info.dsid)
self.authHeaders['X-Apple-Store-Front'] = r.headers.get('x-set-apple-store-front')
self.authHeaders['X-Token'] = resp.passwordToken
self.authCookies = pickle.dumps(sess.cookies).hex()
self.accountName = resp.accountInfo.address.firstName + " " + resp.accountInfo.address.lastName
def save(self):
return json.dumps(self.__dict__)
@classmethod
def load(cls, j):
obj = json.loads(j)
ret = cls()
ret.__dict__.update(obj)
return ret
class StoreClient(object):
def __init__(self, sess: requests.Session):
self.sess = sess
self.iTunes_provider = None
self.authInfo = None
def authenticate_load_session(self, sessionContent):
self.authInfo = StoreClientAuth.load(sessionContent)
if self.authInfo.authHeaders is None or self.authInfo.authCookies is None:
raise Exception("invalid auth session")
self.sess.headers = dict(self.authInfo.authHeaders)
self.sess.cookies = pickle.loads(bytes.fromhex(self.authInfo.authCookies))
def authenticate_save_session(self):
return self.authInfo.save()
def authenticate(self, appleId, password):
if not self.authInfo:
self.authInfo = StoreClientAuth(appleId, password)
self.authInfo.login(self.sess)
self.sess.headers = dict(self.authInfo.authHeaders)
self.sess.cookies = pickle.loads(bytes.fromhex(self.authInfo.authCookies))
# ==> 🛠 [Verbose] Performing request: curl -k -X POST \
# -H "iCloud-DSID: 12263680861" \
# -H "Content-Type: application/x-www-form-urlencoded" \
# -H "User-Agent: Configurator/2.0 (Macintosh; OS X 10.12.6; 16G29) AppleWebKit/2603.3.8" \
# -H "X-Dsid: 12263680861" \
# -d '<?xml version="1.0" encoding="UTF-8"?>
# <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
# <plist version="1.0">
# <dict>
# <key>creditDisplay</key>
# <string></string>
# <key>guid</key>
# <string>000C2941396B</string>
# <key>salableAdamId</key>
# <string>1239860606</string>
# </dict>
# </plist>
# ' \
# https://p25-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/volumeStoreDownloadProduct?guid=000C2941396Bk
def volumeStoreDownloadProduct(self, appId, appVerId=""):
req = StoreDownloadReq(creditDisplay="", guid=self.authInfo.guid, salableAdamId=appId, appExtVrsId=appVerId)
hdrs = {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": CONFIGURATOR_UA,
}
url = "https://p25-buy.itunes.apple.com/WebObjects/MZFinance.woa/wa/volumeStoreDownloadProduct?guid=%s" % self.authInfo.guid
payload = req.as_dict()
r = self.sess.post(url,
headers=hdrs,
data=plistlib.dumps(payload))
d = plistlib.loads(r.content)
resp = StoreDownloadResp.from_dict(d)
if resp.cancel_purchase_batch:
raise StoreException("volumeStoreDownloadProduct", d, resp.customerMessage, '%s-%s' % (resp.failureType, resp.metrics))
return resp
def buyProduct(self, appId, appVer='', productType='C', pricingParameters='STDQ'):
# STDQ - buy, STDRDL - redownload, SWUPD - update
url = "https://p25-buy.itunes.apple.com/WebObjects/MZBuy.woa/wa/buyProduct"
itunes_internal = self.iTunes_provider(url)
hdrs = itunes_internal.pop('headers')
guid = itunes_internal.pop('guid')
kbsync = itunes_internal.pop('kbsync')
if not appVer:
from reqs.itunes import iTunesClient
iTunes = iTunesClient(self.sess)
appVer = iTunes.getAppVerId(appId, hdrs['X-Apple-Store-Front'])
req = StoreBuyproductReq(
guid=guid,
salableAdamId=str(appId),
appExtVrsId=str(appVer) if appVer else None,
price='0',
productType=productType,
pricingParameters=pricingParameters,
ageCheck='true',
hasBeenAuthedForBuy='true',
isInApp='false',
hasConfirmedPaymentSheet='true',
asn='1',
)
payload = req.as_dict()
payload['kbsync'] = kbsync # kbsync is bytes, but json schema does not support it, so we have to assign it
if 'sbsync' in itunes_internal:
payload['sbsync'] = itunes_internal.pop('sbsync') # sbsync is the same as kbsync
if 'afds' in itunes_internal:
payload['afds'] = itunes_internal.pop('afds')
hdrs = dict(hdrs)
hdrs["Content-Type"] = "application/x-apple-plist"
r = self.sess.post(url,
headers=hdrs,
data=plistlib.dumps(payload)
)
d = plistlib.loads(r.content)
resp = StoreBuyproductResp.from_dict(d)
if resp.cancel_purchase_batch:
raise StoreException("buyProduct", d, resp.customerMessage, '%s-%s' % (resp.failureType, resp.metrics))
return resp
def buyProduct_purchase(self, appId, productType='C'):
url = "https://buy.itunes.apple.com/WebObjects/MZBuy.woa/wa/buyProduct"
req = StoreBuyproductReq(
guid=self.authInfo.guid,
salableAdamId=str(appId),
appExtVrsId='0',
price='0',
productType=productType,
pricingParameters='STDQ',
hasAskedToFulfillPreorder='true',
buyWithoutAuthorization='true',
hasDoneAgeCheck='true',
hasConfirmedPaymentSheet='true',
)
payload = req.as_dict()
r = self.sess.post(url,
headers={
"Content-Type": "application/x-apple-plist",
"User-Agent": "Configurator/2.15 (Macintosh; OS X 11.0.0; 16G29) AppleWebKit/2603.3.8",
},
data=plistlib.dumps(payload))
if r.status_code == 500:
raise StoreException("buyProduct_purchase", None, 'purchased_before')
d = plistlib.loads(r.content)
resp = StoreBuyproductResp.from_dict(d)
if resp.status != 0 or resp.jingleDocType != 'purchaseSuccess':
raise StoreException("buyProduct_purchase", d, resp.customerMessage,
'%s-%s' % (resp.status, resp.jingleDocType))
return resp
def purchase(self, appId):
if self.iTunes_provider:
return None # iTunes mode will automatically purchase the app if not purchased
else:
return self.buyProduct_purchase(appId)
def download(self, appId, appVer='', isRedownload=True):
if self.iTunes_provider:
return self.buyProduct(appId, appVer, pricingParameters='STDRDL' if isRedownload else 'STDQ')
else:
return self.volumeStoreDownloadProduct(appId, appVer)

View File

@@ -0,0 +1,2 @@
rich>=10.2.2
requests>=2.25.0

269
src_mac/lib.py Executable file
View File

@@ -0,0 +1,269 @@
#!/usr/bin/env python3
from pathlib import Path
from subprocess import run
from typing import Dict, NamedTuple
from zipfile import ZipFile
import json
import os
import plistlib
import shutil
from cfg import CONFIG, Log
# ------------------------------
# Types
# ------------------------------
VersionMap = Dict[int, str]
class FlatVersion(NamedTuple):
verId: int
verOs: int
class InfoPlist(NamedTuple):
appId: int
verId: int
allVersions: 'list[int]'
bundleId: str
osVer: str
class LocalIpaFile(NamedTuple):
cracked: bool
path: Path
# ------------------------------
# IPA tool
# ------------------------------
def ipaTool(*args: 'str|Path') -> None:
# '--json'
run(['python3', Path(__file__).parent/'ipatool-py'/'main.py'] + list(args))
def ipaToolHistory(appId: int):
Log.info('history for appid=%d', appId)
ipaTool('historyver', '-s', CONFIG.itunes_server,
'-o', CONFIG.download_tmp, '-i', str(appId))
def ipaToolDownload(appId: int, verId: int):
Log.info('download appid=%d verid=%d', appId, verId)
ipaTool('download', '-s', CONFIG.itunes_server, '-o', CONFIG.download_tmp,
'-i', str(appId), '--appVerId', str(verId))
# ------------------------------
# Path handling
# ------------------------------
def pathForApp(appId: int) -> 'Path|None':
return next(CONFIG.completed.glob(f'* - {appId}/'), None)
def pathForIpa(appId: int, appVerId: int) -> 'Path|None':
app_path = pathForApp(appId)
if not app_path:
return None
return next(app_path.glob(f'* - {appVerId}.ipa'), None)
# ------------------------------
# IPA content reading
# ------------------------------
def ipaReadInfoPlist(fname: Path) -> InfoPlist:
with ZipFile(fname) as zip:
itunesPlist = plistlib.loads(zip.read('iTunesMetadata.plist'))
for entry in zip.filelist:
p = entry.filename.split('/')
if len(p) == 3 and p[0] == 'Payload' and p[2] == 'Info.plist':
infoPlist = plistlib.loads(zip.read(entry))
break
return InfoPlist(
itunesPlist['itemId'],
itunesPlist['softwareVersionExternalIdentifier'],
itunesPlist['softwareVersionExternalIdentifiers'],
# itunesPlist['softwareVersionBundleId']
infoPlist['CFBundleIdentifier'],
infoPlist.get('MinimumOSVersion', '1.0'),
)
# ------------------------------
# Version Map
# ------------------------------
def readVersionMap(appId: int) -> 'VersionMap|None':
app_dir = pathForApp(appId)
if app_dir:
ver_map_json = app_dir / '_versions.json'
if ver_map_json.exists():
with open(ver_map_json, 'rb') as fp:
data: dict[str, str] = json.load(fp)
return {int(k): v for k, v in data.items()}
return None
def readVersionMapFromTemp(appId: int) -> 'VersionMap|None':
hist_json = CONFIG.download_tmp / f'historyver_{appId}.json'
if hist_json.exists():
with open(hist_json, 'rb') as fp:
allVerIds: list[int] = json.load(fp)['appVerIds']
return {x: '' for x in allVerIds}
return None
def writeVersionMap(appId: int, data: VersionMap):
app_dir = pathForApp(appId)
assert app_dir, f'app dir must exist for {appId} before calling this.'
with open(app_dir / '_versions.json', 'w') as fp:
json.dump(data, fp, indent=2, sort_keys=True)
hist_json = CONFIG.download_tmp / f'historyver_{appId}.json'
if hist_json.exists():
os.remove(hist_json)
def updateVersionMap(fname: Path) -> InfoPlist:
''' Returns iOS version string '''
info = ipaReadInfoPlist(fname)
if not pathForApp(info.appId):
app_dir = CONFIG.completed / f'{info.bundleId} - {info.appId}'
app_dir.mkdir(parents=True, exist_ok=True)
data = readVersionMap(info.appId)
if not data:
data = readVersionMapFromTemp(info.appId)
if not data:
data: 'VersionMap|None' = {x: '' for x in info.allVersions}
assert data, f'by now, history json for {info.appId} should exist!'
if data.get(info.verId) != info.osVer:
for x in info.allVersions:
if x not in data:
data[x] = ''
data[info.verId] = info.osVer
Log.info('update version for %s (%s)', info.appId, info.bundleId)
writeVersionMap(info.appId, data)
return info
def flattenVersionMap(data: VersionMap) -> 'list[FlatVersion]':
return sorted(FlatVersion(k, versionToInt(v)) for k, v in data.items())
def loadFlatVersionMap(appId: int) -> 'list[FlatVersion]':
data = readVersionMap(appId)
if not data:
data = readVersionMapFromTemp(appId)
if not data: # needs download
ipaToolHistory(appId)
data = readVersionMapFromTemp(appId)
if not data:
raise RuntimeError(f'could not download version history for {appId}')
return flattenVersionMap(data)
# ------------------------------
# Helper
# ------------------------------
def versionToInt(ver: str) -> int:
if not ver:
return 0
major, minor, patch, *_ = ver.split('.') + [0, 0, 0]
return int(major) * 1_00_00 + int(minor) * 1_00 + int(patch)
def enumAppIds():
return sorted([int(x.parent.name.split(' ')[-1])
for x in CONFIG.completed.glob('*/_versions.json')])
def downloadPath(appId: int, verId: int):
return CONFIG.download_fix / f'{appId}.{verId}.ipa'
# ------------------------------
# Actual logic
# ------------------------------
def downloadSpecificVersion(appId: int, verId: int) -> LocalIpaFile:
ipa_path = pathForIpa(appId, verId)
if ipa_path:
return LocalIpaFile(True, ipa_path) # already cracked
download_path = downloadPath(appId, verId)
if download_path.exists():
return LocalIpaFile(False, download_path) # needs cracking
ipaToolDownload(appId, verId)
tmp_file = next(CONFIG.download_tmp.glob(f'*-{appId}-{verId}.ipa'), None)
if not tmp_file:
raise RuntimeError(f'Could not download ipa {appId} {verId}')
shutil.move(tmp_file.as_posix(), download_path)
updateVersionMap(download_path)
return LocalIpaFile(False, download_path)
def findLatestVersion(
appId: int, maxOS: str, *, rmIncompatible: bool
) -> 'int|None':
ver_map = loadFlatVersionMap(appId)
_maxOS = versionToInt(maxOS)
def proc_index(i: int) -> bool:
verId, osVer = ver_map[i]
if not osVer:
ipa_file = downloadSpecificVersion(appId, verId)
info = ipaReadInfoPlist(ipa_file.path)
osVer = versionToInt(info.osVer)
if rmIncompatible and osVer > _maxOS and not ipa_file.cracked:
os.remove(ipa_file.path)
Log.debug('app: %d ver: %d iOS: %s ...', appId, verId, osVer)
return osVer <= _maxOS
if not proc_index(0):
Log.warning(f'No compatible version for {appId}')
return None
imin, imax = 1, len(ver_map) - 1
best_i = 0
while imin <= imax:
i = imin + (imax - imin) // 2
if proc_index(i):
best_i = i
imin = i + 1
else:
best_i = i - 1
imax = i - 1
return ver_map[best_i].verId
def downloadAllUntil(
idx: int, appId: int, maxOS: str, *, rmIncompatible: bool
) -> 'Path|None':
ver_map = loadFlatVersionMap(appId)
_maxOS = versionToInt(maxOS)
if idx >= len(ver_map):
return None
if any(x.verOs > _maxOS for x in ver_map[:idx + 1]):
return None
verId = ver_map[idx].verId
ipa_file = downloadSpecificVersion(appId, verId)
if ipa_file.cracked:
return None
info = ipaReadInfoPlist(ipa_file.path)
osVer = versionToInt(info.osVer)
if osVer <= _maxOS:
return ipa_file.path
elif rmIncompatible:
os.remove(ipa_file.path)

32
src_mac/move_em.py Executable file
View File

@@ -0,0 +1,32 @@
#!/usr/bin/env python3
from pathlib import Path
import os
import shutil
from cfg import CONFIG, Log
from lib import ipaReadInfoPlist
from server import WinServer
def moveEmAll():
for fname in CONFIG.sync_in.glob('*.ipa'):
info = ipaReadInfoPlist(Path(fname))
FROM = Path(fname)
new_name = FROM.name[:-4] + f' - {info.verId}.ipa'
DEST = next(CONFIG.completed.glob(f'{info.bundleId} */')) / new_name
Log.info('[mv] -> %s', DEST.name)
shutil.move(FROM.as_posix(), DEST)
# cleanup download files
orig_filename = f'{info.appId}.{info.verId}.ipa'
download_file = CONFIG.download_fix / orig_filename
if download_file.exists():
Log.info('[delete] %s', download_file)
os.remove(download_file)
Log.info('[uninstall] %s', info.bundleId)
WinServer.uninstall(info.bundleId)
if __name__ == '__main__':
moveEmAll()

69
src_mac/repack_ipa.py Executable file
View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
from argparse import ArgumentParser
from pathlib import Path
from subprocess import run
from zipfile import ZipFile
import os
import re
import shutil
from cfg import CONFIG, Log
_rx_coh = re.compile(r'\s*<key>storeCohort<\/key>\s*<string>[^<]*<\/string>')
def cleanupZipDir(path: Path):
if (path / 'IPAToolInfo.plist').exists():
os.remove(path / 'IPAToolInfo.plist')
shutil.rmtree(path / 'META-INF', ignore_errors=True)
if CONFIG.convert_plist:
ii = next((path / 'Payload').glob('*.app')) / 'Info.plist'
run(f'/usr/libexec/PlistBuddy -x -c print {ii} > {ii}.tmp', shell=True)
os.remove(ii)
os.rename(f'{ii}.tmp', ii)
with open(path / 'iTunesMetadata.plist', 'r') as fp:
data = fp.read()
start, end = _rx_coh.search(data).span() # type: ignore assume exist
with open(path / 'iTunesMetadata.plist', 'w') as fp:
fp.write(data[:start])
fp.write(data[end:])
def repackIpa(ipa_path: Path):
is_dir = ipa_path.is_dir() and (ipa_path / 'IPAToolInfo.plist').exists()
tmp_unzip_dir = ipa_path if is_dir else Path('tmp_unzip')
target_path = CONFIG.sync_out / ipa_path.name
if is_dir: # in case of manual extraction (needed for utf8 filenames)
just_app_ver_id = target_path.name.split(' ', 1)[0] # " 2" or " copy"
target_path = target_path.with_name(just_app_ver_id + '.ipa')
if target_path.exists():
return
if ipa_path.is_file():
Log.info('[unzip] %s', ipa_path)
shutil.rmtree(tmp_unzip_dir, ignore_errors=True)
tmp_unzip_dir.mkdir(exist_ok=True)
with ZipFile(ipa_path) as zip:
zip.extractall(tmp_unzip_dir)
else:
Log.info('[repack-dir] %s', ipa_path)
cleanupZipDir(tmp_unzip_dir)
Log.info('[zip] %s', target_path)
shutil.make_archive(str(target_path), 'zip', str(tmp_unzip_dir))
shutil.move(str(target_path) + '.zip', str(target_path))
shutil.rmtree(tmp_unzip_dir, ignore_errors=True)
if __name__ == '__main__':
cli = ArgumentParser()
cli.add_argument('ipa', type=Path, nargs='+')
args = cli.parse_args()
for fname in args.ipa:
repackIpa(fname)

35
src_mac/server.py Executable file
View File

@@ -0,0 +1,35 @@
#!/usr/bin/env python3
from pathlib import Path
from urllib.request import urlopen
from cfg import CONFIG, Log
class WinApiServer():
def __init__(self) -> None:
self.server_url = CONFIG.win_server
if self._post('up', '') != 'YES':
raise RuntimeError(
f'WinServer {self.server_url} does not seem to be running')
def _post(self, action: str, data: str) -> str:
''' With 10min timeout '''
url = f'{self.server_url}/{action}'
Log.debug('POST %s --data %s', url, data)
with urlopen(url, data=data.encode('utf8'), timeout=1200) as fp:
return fp.read().decode('utf8')
def install(self, fname: Path) -> str:
if fname.suffix != '.ipa':
raise ValueError(f'Not an *.ipa file: "{fname}"')
if not fname.exists() or fname.is_dir():
raise ValueError(f'File not found: "{fname}"')
if fname.absolute().parent != CONFIG.sync_out.absolute():
raise ValueError(f'Install file not in SYNC OUT dir: "{fname}"')
return self._post('install', fname.name)
def uninstall(self, bundleId: str) -> str:
return self._post('uninstall', bundleId)
WinServer = WinApiServer()

View File

@@ -0,0 +1,92 @@
name: 'Get iTunes Headers & Kbsync'
inputs:
apple_id:
required: true
apple_id_pwd:
required: true
ngrok_token:
description: "Token for RDP debugging"
required: false
runs:
using: "composite"
steps:
- name: Check if initialized
id: check
run: |
echo "Checking if we need to initialize iTunes again"
if [[ "$(cat /c/.iTunesInitialized)" == "${{ inputs.ngrok-token }}" ]]; then
echo " Need to re-init iTunes"
echo "NEED_INIT=1" >> $GITHUB_ENV
else
echo " Needn't to do anything"
echo "NEED_INIT=0" >> $GITHUB_ENV
fi
working-directory: ${{ github.action_path }}
shell: bash
- name: Setup iTunes
if: ${{ env.NEED_INIT == 1 }}
run: |
echo Setup iTunes...
start /wait taskkill /f /im iTunes* python*
workflow_helper\iTunesInstall\install_itunes.bat
working-directory: ${{ github.action_path }}
shell: cmd
- name: Setup Python Dependencies
if: ${{ env.NEED_INIT == 1 }}
run: |
echo Setup Python Dependencies...
pip3 install pywinauto frida Flask
working-directory: ${{ github.action_path }}
shell: cmd
#- uses: NyaMisty/reverse-rdp-windows-github-actions-ng@master
# if: ${{ always() && github.event_name == 'workflow_dispatch' && github.event.inputs.itunes_debug_enabled }}
# with:
# ngrok-token: ${{ inputs.ngrok_token }}
# password: Aa123456
# foreground: false
- name: Login iTunes
if: ${{ env.NEED_INIT == 1 }}
env:
APPLEID: ${{ inputs.apple_id }}
APPLEID_PWD: ${{ inputs.apple_id_pwd }}
run: |
echo Login iTunes...
python3 workflow_helper/itunes_auto_login.py %APPLEID% %APPLEID_PWD%
working-directory: ${{ github.action_path }}
shell: cmd
- name: Finish Initialization
if: ${{ env.NEED_INIT == 1 }}
env:
APPLEID: ${{ inputs.apple_id }}
run: |
echo "Finish Initialization..."
echo "$APPLEID" > /c/.iTunesInitialized
working-directory: ${{ github.action_path }}
shell: bash
- name: Start Frida Header Server
run: |
echo Start Frida Header Server!
curl -Lo psexec64.exe https://github.com/ComputerGuyYash/psexec/raw/main/PsExec64.exe
psexec64.exe -accepteula -nobanner -i -d cmd /c "python3.exe workflow_helper/iTunesDownload/get_header.py > C:\get_header.log 2>&1"
exit 0
working-directory: ${{ github.action_path }}
shell: cmd
- name: Test Frida Header Server
run: |
sleep 5
ret=0
echo "---------------- Before query headers ----------------"
cat /c/get_header.log
curl --fail-with-body -vv 127.0.0.1:9000 || ret=$?
echo "---------------- After query headers ----------------"
cat /c/get_header.log
exit $ret
working-directory: ${{ github.action_path }}
shell: bash

View File

@@ -0,0 +1,22 @@
import os
# for debug
P = r"C:\Program Files\iTunes\iTunes1.exe"
if not os.path.exists(P):
# for production
P = r"C:\Program Files\iTunes\iTunes.exe"
with open(P, 'rb') as f:
data = f.read()
data = bytearray(data)
# Patch passwordSettings (actually useless)
data[0x77daf4:0x77daf4+5] = b'\xBF\x03\x00\x00\x00'
data[0x77db7e:0x77db7e+2] = b'\xEB\x0A'
# Patch signIn reason to serverDialog
data[0x7a9ed4:0x7a9ed4+6] = b'\xB9\x23\xFF\xFF\xFF\x90'
with open(P, 'wb') as f:
f.write(data)

View File

@@ -0,0 +1,113 @@
# frida_rpc_player.py
'''
POST /WebObjects/MZBuy.woa/wa/buyProduct HTTP/1.1
Host: p46-buy.itunes.apple.com
User-Agent: iTunes/12.6.5 (Windows; Microsoft Windows 10.0 x64 Enterprise Edition (Build 19042); x64) AppleWebKit/7605.1033.1002.2
Content-Length: 1226
Content-Type: application/x-apple-plist
X-Dsid: 12263680861
X-Apple-Tz: 28800
X-Apple-MD: AAAABAAAABA3hIN/KYf4Ri4tdZAiwaB5
Cookie: amp=JG7EpvImu+/h0yS5mTqQMLarQce6xuYNfyzC6cun/bRt+7VLQUqfUrDB4cdRyV6SLDGBRCG3Oz/PcY28nx94GdTGkdOYX83a54KHZTSq3jI=; amia-12263680861=dPQK5oK/9b6q7HeGIids4a6gE5oiE8yLcVjQO7EknSTERhI0OawL4FQcmJ60560H+PbZhCCt6Na8jLSDiU09aw==; mt-asn-12263680861=2; mt-tkn-12263680861=AmDauzfLChTup9vGq17bGVJvSurhbsyoCLyrpJG8t8uECuyAI3xr3o/vS9pVpKeg4dSlCxJtLUS8+H47Qtpou0VGgGBAn/dXAPozhdJKTyPdUqyT4My89dujXg48XNgIVUjFj2w4IhN1gg5wn3BfSg1bFChodxuHadsRibnYzkUPmR+TVTnivu9e4/QLJmh58Va8rmg=; mt-tkn-10255130069=Ala2nKdNzZWOlapGgvRzcNExINc8j4sbrcMn3ruJ9M3Cb17MGiGqvyTe9yUzh/fSNLEL+4s0j57Eht/AWXrG2e3+GptHZlTi1vZ4a+3PdD9dtfSP8no7s9/bMX6JTtnE+fwV1z8QudsvAs+kjEz0MqFgoxFZZntBKI1L81+CBTnmsBolRdk+wqqPGZ3AD5bdybUTwD4=; countryVerified=1; mzf_in=467080; itspod=46; vrep=CN3W49ctEgQIChAAEgQICBAAEgQIBxAAEgQIBBAAEgQIDRAAEgQIBhAAEgQIDBAAEgQIAxAAEgQICRAAEgQIEBAAEgQIDhAAEgQIARAAEgQIDxAAEgQIERAAEgQIBRAAEgQICxAAEgQIAhAA; wosid-lite=jRoFTHWbZZZkSOfvfDELf0; mz_at_ssl-12263680861=AwUAAAECAAHXHQAAAABgqAeMT5/7LbF5106/zxFn9xH4n7jgwEw=; mz_at0-12263680861=AwQAAAECAAHXHQAAAABgp/7Z1enk1z4615lhp9QKlDQfmo7Hq60=; pldfltcid=524b36a58d0d4249a127affd05921dab046; X-Dsid=12263680861; xp_ab=1#WqjkRLH+-2+TCEF_ea00#yNFpB6B+-2+c9imSgD01; xp_abc=TCEF_ea00; mz_at0-10255130069=AwQAAAECAAHXHQAAAABgnNBzeacT12/WPi/iSOROyaMVQrfmkqE=; mz_at_ssl-10255130069=AwUAAAECAAHXHQAAAABgnNBzNHT9PbcrsAARLEWi0+8ElnKZ54c=; xp_ci=3z3h6Y0vzFQQz4XtzB5DzZvQkYhyo
X-Apple-AMD-M: rjK8HkwTcIoiOk6UoA9LXta0h/19+ss41RU7YLxGunAtLnl1Fus5i4VS56O71ifgDWwrEl/jIbPrDLfS
X-Apple-AMD: AAAABAAAABCqItI0AgfIcPIc0+zGpNtr
X-Apple-Store-Front: 143462-9,32
Accept-Language: zh-cn
Date: Fri, 21 May 2021 21:33:42 GMT
X-Apple-MD-M: rjK8HkwTcIoiOk6UoA9LXta0h/2Qyn969daQuVAdJbkmCnWee7s+joaQ3fvc4v1JTJB0sUJcDwc0Q12z
X-Apple-I-MD-RINFO: 50660608
X-Apple-I-MD-M: rjK8HkwTcIoiOk6UoA9LXta0h/0W22FXNYql4KAM12/n20O3iVf54IK85PbrLb51CvEfn5/b6eoX5d1d
X-Apple-I-MD: AAAABQAAABA+EMexoFPqOMeyQ5zIkQYPAAAAAg==
Accept-Encoding: gzip, deflate
Connection: close
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>guid</key>
<string>46994789.C2E39C5C.B1D62DC2.025D2569.3D6E49B5.AB7F7EB4.F6D17565</string>
<key>kbsync</key>
<data>
AAQAA5+HiDofKn/iW9aKVsKo1XU0cRuzPrQGMfC57fQaA7YaZBWlSS+s4SS2Ay6pchYG
x/h+cJccHMoA/NDtHcWsRl9nmDwzAnuW9WH7nHQAEt95wkj2eMQVBHyj3OSkSoEGFnS8
r4irSIve3V4np1lvbEUgHdJmOEV6gAd8SFzItmgxFImn55qoUX/xt4pdXagPmhBXaYFI
+B/uYLxOYLO4fM1fGpQQ8/1EIpzrbahFjpG8L8kMjhVIWpn2Ut30p2deyjcL47JbSjx7
/RId4XZlSEqXf9SOETcsxCkVhDWTghGYRWKlEe7j32CVrxOkg2lvlQLHB1XBhTpQiLSm
PR0hDIG5dQif0jaIeQJ9zGqq3kgkl9RlFHGbcvfMdrJsYGOgYMzXh1jfF/v21xPfauYh
PIE9LQu/TTiAGDvS+BYrM2xVaPHZPNUpLaVlk/2zIqiWo/Zj2se4FXGOl4LAVKqrbOYN
2YONkSY4CQOZTmNmoO6SBU2NJHBYtVVeIhOLGLoS9/xzPwMdFevvZJFD7iQvL0RDKCBt
z5r7dnFz2WlZsdx0aMsbV3HRdnJtmBh7zA3AaO/4rJtWAQqAID2gAF8YMoNdJJp13Bdr
f34iRpVYbsDxrBNJXtanTgDdNO3xXpyZhQTgOrBDcsv7mD68E1E6bE5ac97/z9R8cLf0
8/X8/ie7tUBcqVThW81DiM4RGh3LSg==
</data>
<key>price</key>
<string>0</string>
<key>pricingParameters</key>
<string>STDRDL</string>
<key>productType</key>
<string>C</string>
<key>salableAdamId</key>
<string>1234806557</string><key>appExtVrsId</key>
<string>839637337</string>
</dict>
</plist>
'''
import sys
import codecs
import frida
import plistlib
import os
import base64
##### Frida Part
procName = os.environ.get('ITUNES_PROCESS_NAME', 'iTunes.exe')
session = frida.attach(procName)
#session = frida.attach('iTunesFucked.exe')
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
with codecs.open(os.path.dirname(os.path.realpath(__file__)) + '/get_header_rpc.js', 'r', 'utf-8') as f:
source = f.read()
script = session.create_script(source)
script.load()
def on_message(message, data):
if message['type'] == 'send':
eprint("Frida: %s" % message['payload'])
elif message['type'] == 'error':
eprint("Frida error: %s" % message['stack'])
script.on('message', on_message)
rpc = script.exports
##### Flask Part
from flask import Flask, request
from flask import jsonify
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def getHeader():
hdrUrl = request.args.get('url', "https://p46-buy.itunes.apple.com/WebObjects/MZBuy.woa/wa/buyProduct")
retHdrs = rpc.get_header(hdrUrl)
eprint("Got Headers: %s" % retHdrs)
kbsync = retHdrs.pop('kbsync')
guid = retHdrs.pop('X-Guid')
return jsonify({
"headers": retHdrs,
"kbsync": kbsync,
"guid": guid
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9000)
session.detach()

View File

@@ -0,0 +1,100 @@
var getHeader = null;
function init() {
var CFURLCreateWithBytes = new NativeFunction(Module.findExportByName('CoreFoundation','CFURLCreateWithBytes'), 'pointer', ['pointer', 'pointer', 'int64', 'int64', 'int64'])
var CFDictionaryGetCount = new NativeFunction(Module.findExportByName('CoreFoundation','CFDictionaryGetCount'), 'int64', ['pointer'])
var CFCopyDescription = new NativeFunction(Module.findExportByName('CoreFoundation','CFCopyDescription'), 'pointer', ['pointer'])
var CFStringGetCStringPtr = new NativeFunction(Module.findExportByName('CoreFoundation','CFStringGetCStringPtr'), 'pointer', ['pointer', 'uint64'])
var CFStringGetCString = new NativeFunction(Module.findExportByName('CoreFoundation','CFStringGetCString'), 'int8', ['pointer', 'pointer', 'int64', 'int64'])
var CFStringGetLength = new NativeFunction(Module.findExportByName('CoreFoundation','CFStringGetLength'), 'int64', ['pointer'])
var CFDictionaryGetCount = new NativeFunction(Module.findExportByName('CoreFoundation','CFDictionaryGetCount'), 'int64', ['pointer'])
var CFDictionaryGetKeysAndValues = new NativeFunction(Module.findExportByName('CoreFoundation','CFDictionaryGetKeysAndValues'), 'void', ['pointer', 'pointer', 'pointer'])
var CFDataGetBytePtr = new NativeFunction(Module.findExportByName('CoreFoundation','CFDataGetBytePtr'), 'pointer', ['pointer'])
var CFDataGetLength = new NativeFunction(Module.findExportByName('CoreFoundation','CFDataGetLength'), 'uint64', ['pointer'])
var readCFStr = (cfs) => {
var cfslen = CFStringGetLength(cfs)
var cfsBuf = Memory.alloc(cfslen + 1)
CFStringGetCString(cfs, cfsBuf, cfslen + 1, 134217984)
return Memory.readUtf8String(cfsBuf, cfslen)
}
var readCFDict = (cfdict) => {
var dictCount = CFDictionaryGetCount(cfdict)
var dictKeys = Memory.alloc(dictCount * 8)
var dictValues = Memory.alloc(dictCount * 8)
CFDictionaryGetKeysAndValues(cfdict, dictKeys, dictValues)
var ret = {}
for (var i = 0; i < dictCount; i++) {
var k = readCFStr(dictKeys.add(8 * i).readPointer())
var v = readCFStr(dictValues.add(8 * i).readPointer())
ret[k] = v
}
return ret
}
var readCFData = (cfd) => {
var cfdlen = CFDataGetLength(cfd)
var cfdbuf = CFDataGetBytePtr(cfd)
return cfdbuf.readByteArray(cfdlen)
}
var itunesBase = Process.enumerateModulesSync()[0].base
var cfAllocator = itunesBase.add(0x22A8448).readPointer()
var kbsyncContext = itunesBase.add(0x22A6BBC).readU32()
var prepareAppleHdrWrap = new NativeFunction(itunesBase.add(0x87FFC0), 'pointer',['pointer','pointer','pointer']);
var get_cookie_val = new NativeFunction(itunesBase.add(0xBDE500), 'pointer',['pointer']);
var get_kbsync = new NativeFunction(itunesBase.add(0x74D210), 'pointer',['uint32', 'uint32', 'pointer']);
getHeader = function (url) {
var GlobalContext = itunesBase.add(0x22A9B18).readPointer()
var otpGlobalContext = GlobalContext
if (!GlobalContext.isNull()) {
otpGlobalContext = GlobalContext.add(62716).readPointer()
}
var otpContext = Memory.alloc(128)
otpContext.writePointer(otpGlobalContext)
otpContext.add(8).writeU64(0)
otpContext.add(16).writeU64(0)
otpContext.add(24).writeU16(1)
otpContext.add(26).writeU8(0)
otpContext.add(27).writeU8(1) // include X-Guid
otpContext.add(28).writeU8(1) // include ADIV1 Hdrs
otpContext.add(32).writeU32(0)
otpContext.add(36).writeU16(0)
otpContext.add(40).writeU32(0)
otpContext.add(48).writeU64(0)
otpContext.add(64).writeU64(0)
otpContext.add(72).writeU64(0)
otpContext.add(84).writeU64(0)
otpContext.add(112).writeU8(1)
var urlBuf = Memory.allocUtf8String(url)
var urlData = CFURLCreateWithBytes(cfAllocator, urlBuf, url.length, 0x8000100, 0)
var kbsync = get_kbsync(kbsyncContext, 1, otpGlobalContext)
var cookieCFStr = get_cookie_val(urlData)
var hdrDict = prepareAppleHdrWrap(NULL, urlData, otpContext)
//hdrdesc = CFCopyDescription(hdrDict)
//console.log(readCFStr(hdrdesc))
var cookieStr = readCFStr(cookieCFStr)
var hdrOutput = readCFDict(hdrDict)
hdrOutput['Cookie'] = cookieStr
var kbSyncData = readCFData(kbsync)
hdrOutput['kbsync'] = [...new Uint8Array(kbSyncData)].map(x => x.toString(16).padStart(2, '0')).join('');
return hdrOutput
};
}
rpc.exports = {
getHeader: (url) => {
if (!getHeader) {
init()
}
return getHeader(url);
},
};

View File

@@ -0,0 +1,13 @@
cd %~dp0
curl -LO https://secure-appldnld.apple.com/itunes12/091-87819-20180912-69177170-B085-11E8-B6AB-C1D03409AD2A6/iTunes64Setup.exe
iTunes64Setup.exe /extract
@REM start /wait msiexec.exe /i AppleApplicationSupport.msi /qn
start /wait msiexec.exe /i AppleApplicationSupport64.msi /qn
@REM start /wait msiexec.exe /i AppleMobileDeviceSupport64.msi /qn
@REM start /wait msiexec.exe /i AppleSoftwareUpdate.msi /qn
@REM start /wait msiexec.exe /i Bonjour64.msi /qn
start /wait msiexec.exe /i iTunes64.msi /qn
python3 patch_itunes.py

View File

@@ -0,0 +1,22 @@
import os
# for debug
P = r"C:\Program Files\iTunes\iTunes1.exe"
if not os.path.exists(P):
# for production
P = r"C:\Program Files\iTunes\iTunes.exe"
with open(P, 'rb') as f:
data = f.read()
data = bytearray(data)
# Patch passwordSettings (actually useless)
data[0x77daf4:0x77daf4+5] = b'\xBF\x03\x00\x00\x00'
data[0x77db7e:0x77db7e+2] = b'\xEB\x0A'
# Patch signIn reason to serverDialog
data[0x7a9ed4:0x7a9ed4+6] = b'\xB9\x23\xFF\xFF\xFF\x90'
with open(P, 'wb') as f:
f.write(data)

View File

@@ -0,0 +1,156 @@
import subprocess
import time
from pywinauto.application import Application
from win32con import *
import sys
ACCOUNT = sys.argv[1]
PASSWORD = sys.argv[2]
print("Launching iTunes...")
def initITunes():
subprocess.call('taskkill /f /im iTunes*', shell=True)
app = Application().start(r"C:\Program Files\iTunes\iTunes.exe")
app.wait_cpu_usage_lower()
time.sleep(8)
def debugTopWin():
topwin = app.top_window().wait('exists')
texts = []
texts += topwin.texts()
for c in topwin.iter_children():
texts += c.texts()
print("-- Cur top win: %s, texts: %s" % (topwin, texts))
def cleanAllDialog():
while True:
topwin = app.top_window().wait('exists')
if 'Dialog' in topwin.class_name():
print(" Closing dialog %s" % topwin.window_text())
app.top_window().Button0.click()
elif 'Tour' in topwin.window_text():
print(" Closing Window %s" % topwin.window_text())
topwin.close()
else:
break
app.wait_cpu_usage_lower()
time.sleep(5)
# Click all first-time dialogs (like License Agreements, missing audios)
cleanAllDialog()
# Calm down a bit before main window operations
app.wait_cpu_usage_lower()
debugTopWin()
# Click main window's first-time question ("No thanks" button)
try:
buttonText = app.iTunes.Button11.wait('ready').window_text()
print('Button11 text is: %s' % buttonText)
if 'Search' not in buttonText:
print("Clicked 'No Thanks' Button!")
app.iTunes.Button11.click_input()
app.wait_cpu_usage_lower()
time.sleep(4)
else:
raise Exception('stub')
except:
print("Not founding 'No Thanks' Button, passing on...")
# Start logging in by clicking toolbar menu "Account"
print("Clicking Account menu...")
app.iTunes.Application.Static3.click()
app.wait_cpu_usage_lower()
time.sleep(3)
debugTopWin()
# Detect whether we have "&S" in popup, which refers to "Sign in"
popup = app.PopupMenu
if '&S' not in popup.menu().item(1).text():
popup.close()
raise Exception("Already logged in!")
print("Signin menu presented, clicking to login!")
# not log in
popup.menu().item(1).click_input()
app.wait_cpu_usage_lower()
time.sleep(8)
debugTopWin()
for i in range(15):
dialog = app.top_window()
dialogWrap = dialog.wait('ready')
assert dialogWrap.friendly_class_name() == 'Dialog'
time.sleep(1.0)
try:
if dialogWrap.window_text() == 'iTunes' \
and dialog.Edit1.wait('ready').window_text() == 'Apple ID' \
and dialog.Edit2.wait('ready').window_text() == 'Password' \
and dialog.Button1.wait('exists').window_text() == '&Sign In':
break
except Exception as e:
continue
else:
raise Exception("Failed to find login window in 15 iterations!")
app.wait_cpu_usage_lower()
print("Setting login dialog edit texts")
appleid_Edit = dialog.Edit1
appleid_Edit.wait('ready')
appleid_Edit.click_input()
appleid_Edit.type_keys(ACCOUNT)
appleid_Edit.set_edit_text(ACCOUNT)
time.sleep(3)
pass_Edit = dialog.Edit2
pass_Edit.wait('ready')
pass_Edit.click_input()
pass_Edit.type_keys(PASSWORD)
pass_Edit.set_edit_text(PASSWORD)
time.sleep(3)
print("Clicking login button!")
loginButton = dialog.Button1
loginButton.wait('ready')
# click multiple times as pywinauto seems to have some bug
loginButton.click()
time.sleep(0.5)
try:
loginButton.click()
time.sleep(0.5)
loginButton.click_input()
except:
pass
print("Waiting login result...")
time.sleep(10)
debugTopWin()
if app.top_window().handle == dialogWrap.handle:
raise Exception("Failed to trigger Login button!")
elif app.top_window().window_text() == 'Verification Failed':
raise Exception("Verification Failed: %s" % app.top_window().Static2.window_text())
# Finish & Cleanup
print("Waiting all dialogs to finish")
cleanAllDialog()
for init_i in range(3):
try:
initITunes()
break
except Exception as e:
print("Init iTunes %d: Failed with %s" % (init_i, e))
import traceback; traceback.print_exc()
time.sleep(8)
print("Init iTunes Successfully!")

View File

@@ -0,0 +1,2 @@
Libimobiledevice compiled for Windows
Updated 18/05/2020. Compiled by iFred09

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More