Initial
This commit is contained in:
101
README.md
Executable file
101
README.md
Executable 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
16
config.ini
Executable 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
BIN
src_ios/Clutch-1.3
Normal file
Binary file not shown.
BIN
src_ios/Clutch-2.0.4
Normal file
BIN
src_ios/Clutch-2.0.4
Normal file
Binary file not shown.
45
src_mac/cfg.py
Executable file
45
src_mac/cfg.py
Executable 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
36
src_mac/crack.py
Executable 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
21
src_mac/download.py
Executable 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
20
src_mac/extract_versions.py
Executable 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
4
src_mac/ipatool-py/.gitignore
vendored
Executable file
@@ -0,0 +1,4 @@
|
||||
.idea
|
||||
__pycache__
|
||||
venv/
|
||||
downloaded/
|
||||
112
src_mac/ipatool-py/README.md
Executable file
112
src_mac/ipatool-py/README.md
Executable 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
567
src_mac/ipatool-py/main.py
Executable 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()
|
||||
0
src_mac/ipatool-py/reqs/__init__.py
Executable file
0
src_mac/ipatool-py/reqs/__init__.py
Executable file
47
src_mac/ipatool-py/reqs/itunes.py
Executable file
47
src_mac/ipatool-py/reqs/itunes.py
Executable 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('&', '&')
|
||||
appParamDict = dict((c.split('=') for c in json.loads('"%s"' % appParam).split('&')))
|
||||
appVer = appParamDict['appExtVrsId']
|
||||
return appVer
|
||||
18
src_mac/ipatool-py/reqs/schemas/README.md
Executable file
18
src_mac/ipatool-py/reqs/schemas/README.md
Executable 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
|
||||
```
|
||||
0
src_mac/ipatool-py/reqs/schemas/__init__.py
Executable file
0
src_mac/ipatool-py/reqs/schemas/__init__.py
Executable file
1593
src_mac/ipatool-py/reqs/schemas/itunes_lookup_resp.py
Executable file
1593
src_mac/ipatool-py/reqs/schemas/itunes_lookup_resp.py
Executable file
File diff suppressed because it is too large
Load Diff
0
src_mac/ipatool-py/reqs/schemas/schema_defs/__init__.py
Executable file
0
src_mac/ipatool-py/reqs/schemas/schema_defs/__init__.py
Executable file
12
src_mac/ipatool-py/reqs/schemas/schema_defs/__main__.py
Executable file
12
src_mac/ipatool-py/reqs/schemas/schema_defs/__main__.py
Executable 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)])
|
||||
222
src_mac/ipatool-py/reqs/schemas/schema_defs/itunes_lookup_resp.json
Executable file
222
src_mac/ipatool-py/reqs/schemas/schema_defs/itunes_lookup_resp.json
Executable 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"
|
||||
]
|
||||
}
|
||||
37
src_mac/ipatool-py/reqs/schemas/schema_defs/store_authenticate_req.json
Executable file
37
src_mac/ipatool-py/reqs/schemas/schema_defs/store_authenticate_req.json
Executable 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"
|
||||
]
|
||||
}
|
||||
324
src_mac/ipatool-py/reqs/schemas/schema_defs/store_authenticate_resp.json
Executable file
324
src_mac/ipatool-py/reqs/schemas/schema_defs/store_authenticate_resp.json
Executable 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"
|
||||
]
|
||||
}
|
||||
120
src_mac/ipatool-py/reqs/schemas/schema_defs/store_buyproduct_req.json
Executable file
120
src_mac/ipatool-py/reqs/schemas/schema_defs/store_buyproduct_req.json
Executable 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"
|
||||
]
|
||||
}
|
||||
732
src_mac/ipatool-py/reqs/schemas/schema_defs/store_buyproduct_resp.json
Executable file
732
src_mac/ipatool-py/reqs/schemas/schema_defs/store_buyproduct_resp.json
Executable 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"
|
||||
]
|
||||
}
|
||||
24
src_mac/ipatool-py/reqs/schemas/schema_defs/store_download_req.json
Executable file
24
src_mac/ipatool-py/reqs/schemas/schema_defs/store_download_req.json
Executable 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"
|
||||
]
|
||||
}
|
||||
497
src_mac/ipatool-py/reqs/schemas/schema_defs/store_download_resp.json
Executable file
497
src_mac/ipatool-py/reqs/schemas/schema_defs/store_download_resp.json
Executable 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"
|
||||
]
|
||||
}
|
||||
13
src_mac/ipatool-py/reqs/schemas/schema_examples/itunes_lookup_resp.log
Executable file
13
src_mac/ipatool-py/reqs/schemas/schema_examples/itunes_lookup_resp.log
Executable 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}]
|
||||
}
|
||||
107
src_mac/ipatool-py/reqs/schemas/schema_examples/store_buyproduct_req.log
Executable file
107
src_mac/ipatool-py/reqs/schemas/schema_examples/store_buyproduct_req.log
Executable 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>
|
||||
561
src_mac/ipatool-py/reqs/schemas/schema_examples/store_buyproduct_resp.log
Executable file
561
src_mac/ipatool-py/reqs/schemas/schema_examples/store_buyproduct_resp.log
Executable 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&code=MZCommerce.ASN.ExpiredPasswordToken&buttons=%E8%8E%B7%E5%8F%96%3A%E5%8F%96%E6%B6%88&baseVersion=1&dsId=16916646015&eventVersion=1&storeFrontHeader=143465-19%2C32&eventTime=1652006682067&eventType=dialog&message=%E9%9C%80%E8%A6%81%E7%99%BB%E5%BD%95</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&salableAdamId=444934666&mtRequestId=3z21abvCzFDuz5CYz9bdz19maFVKgezL2X64FFVz1MGG&appExtVrsId=848463733&mtTopic=xp_its_main&guid=22330C8C.C2E39C5C.06C40B91.D0990F95.9A890F9D.AB7F7EB4.56507025&hasBeenAuthedForBuy=true&isInApp=false&price=0&mtClientId=3z21abvCzFDuz5CYz9bdz19maFVKge&productType=C&mtPageType=Search&mtPageId=ccfce0ef-4ac8-4d5a-8e5f-79876ac474a4&machineName=DESKTOP-697LVJS&ageCheck=true&pg=default&mtApp=com.apple.iTunes&needDiv=0&mtPrevPage=Purchases&pricingParameters=STDQ</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&sf=143462&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 © 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&sf=143465&pgtp=Search&pgid=ccfce0ef-4ac8-4d5a-8e5f-79876ac474a4&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&sf=143465&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>
|
||||
250
src_mac/ipatool-py/reqs/schemas/store_authenticate_req.py
Executable file
250
src_mac/ipatool-py/reqs/schemas/store_authenticate_req.py
Executable 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
|
||||
),
|
||||
)
|
||||
2638
src_mac/ipatool-py/reqs/schemas/store_authenticate_resp.py
Executable file
2638
src_mac/ipatool-py/reqs/schemas/store_authenticate_resp.py
Executable file
File diff suppressed because it is too large
Load Diff
956
src_mac/ipatool-py/reqs/schemas/store_buyproduct_req.py
Executable file
956
src_mac/ipatool-py/reqs/schemas/store_buyproduct_req.py
Executable 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
|
||||
),
|
||||
)
|
||||
5758
src_mac/ipatool-py/reqs/schemas/store_buyproduct_resp.py
Executable file
5758
src_mac/ipatool-py/reqs/schemas/store_buyproduct_resp.py
Executable file
File diff suppressed because it is too large
Load Diff
160
src_mac/ipatool-py/reqs/schemas/store_download_req.py
Executable file
160
src_mac/ipatool-py/reqs/schemas/store_download_req.py
Executable 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
|
||||
),
|
||||
)
|
||||
3910
src_mac/ipatool-py/reqs/schemas/store_download_resp.py
Executable file
3910
src_mac/ipatool-py/reqs/schemas/store_download_resp.py
Executable file
File diff suppressed because it is too large
Load Diff
254
src_mac/ipatool-py/reqs/store.py
Executable file
254
src_mac/ipatool-py/reqs/store.py
Executable 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)
|
||||
2
src_mac/ipatool-py/requirements.txt
Executable file
2
src_mac/ipatool-py/requirements.txt
Executable file
@@ -0,0 +1,2 @@
|
||||
rich>=10.2.2
|
||||
requests>=2.25.0
|
||||
269
src_mac/lib.py
Executable file
269
src_mac/lib.py
Executable 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
32
src_mac/move_em.py
Executable 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
69
src_mac/repack_ipa.py
Executable 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
35
src_mac/server.py
Executable 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()
|
||||
92
src_win/actions-iTunes-header/action.yml
Executable file
92
src_win/actions-iTunes-header/action.yml
Executable 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
|
||||
22
src_win/actions-iTunes-header/patch_itunes.py
Executable file
22
src_win/actions-iTunes-header/patch_itunes.py
Executable 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)
|
||||
113
src_win/actions-iTunes-header/workflow_helper/iTunesDownload/get_header.py
Executable file
113
src_win/actions-iTunes-header/workflow_helper/iTunesDownload/get_header.py
Executable 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()
|
||||
100
src_win/actions-iTunes-header/workflow_helper/iTunesDownload/get_header_rpc.js
Executable file
100
src_win/actions-iTunes-header/workflow_helper/iTunesDownload/get_header_rpc.js
Executable 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);
|
||||
},
|
||||
};
|
||||
@@ -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
|
||||
22
src_win/actions-iTunes-header/workflow_helper/iTunesInstall/patch_itunes.py
Executable file
22
src_win/actions-iTunes-header/workflow_helper/iTunesInstall/patch_itunes.py
Executable 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)
|
||||
156
src_win/actions-iTunes-header/workflow_helper/itunes_auto_login.py
Executable file
156
src_win/actions-iTunes-header/workflow_helper/itunes_auto_login.py
Executable 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!")
|
||||
2
src_win/libimobiledevice/README.md
Executable file
2
src_win/libimobiledevice/README.md
Executable file
@@ -0,0 +1,2 @@
|
||||
Libimobiledevice compiled for Windows
|
||||
Updated 18/05/2020. Compiled by iFred09
|
||||
BIN
src_win/libimobiledevice/idevice_id.exe
Executable file
BIN
src_win/libimobiledevice/idevice_id.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/ideviceactivation.exe
Executable file
BIN
src_win/libimobiledevice/ideviceactivation.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicebackup.exe
Executable file
BIN
src_win/libimobiledevice/idevicebackup.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicebackup2.exe
Executable file
BIN
src_win/libimobiledevice/idevicebackup2.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicecrashreport.exe
Executable file
BIN
src_win/libimobiledevice/idevicecrashreport.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicedate.exe
Executable file
BIN
src_win/libimobiledevice/idevicedate.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicedebug.exe
Executable file
BIN
src_win/libimobiledevice/idevicedebug.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicedebugserverproxy.exe
Executable file
BIN
src_win/libimobiledevice/idevicedebugserverproxy.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicediagnostics.exe
Executable file
BIN
src_win/libimobiledevice/idevicediagnostics.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/ideviceenterrecovery.exe
Executable file
BIN
src_win/libimobiledevice/ideviceenterrecovery.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/ideviceimagemounter.exe
Executable file
BIN
src_win/libimobiledevice/ideviceimagemounter.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/ideviceinfo.exe
Executable file
BIN
src_win/libimobiledevice/ideviceinfo.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/ideviceinstaller.exe
Executable file
BIN
src_win/libimobiledevice/ideviceinstaller.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicename.exe
Executable file
BIN
src_win/libimobiledevice/idevicename.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicenotificationproxy.exe
Executable file
BIN
src_win/libimobiledevice/idevicenotificationproxy.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicepair.exe
Executable file
BIN
src_win/libimobiledevice/idevicepair.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/ideviceprovision.exe
Executable file
BIN
src_win/libimobiledevice/ideviceprovision.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicerestore.exe
Executable file
BIN
src_win/libimobiledevice/idevicerestore.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicescreenshot.exe
Executable file
BIN
src_win/libimobiledevice/idevicescreenshot.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicesetlocation.exe
Executable file
BIN
src_win/libimobiledevice/idevicesetlocation.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/idevicesyslog.exe
Executable file
BIN
src_win/libimobiledevice/idevicesyslog.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/inetcat.exe
Executable file
BIN
src_win/libimobiledevice/inetcat.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/iproxy.exe
Executable file
BIN
src_win/libimobiledevice/iproxy.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/irecovery.exe
Executable file
BIN
src_win/libimobiledevice/irecovery.exe
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libbrotlicommon.dll
Executable file
BIN
src_win/libimobiledevice/libbrotlicommon.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libbrotlidec.dll
Executable file
BIN
src_win/libimobiledevice/libbrotlidec.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libbz2-1.dll
Executable file
BIN
src_win/libimobiledevice/libbz2-1.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libcrypto-1_1.dll
Executable file
BIN
src_win/libimobiledevice/libcrypto-1_1.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libcurl-4.dll
Executable file
BIN
src_win/libimobiledevice/libcurl-4.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libeay32.dll
Executable file
BIN
src_win/libimobiledevice/libeay32.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libffi-6.dll
Executable file
BIN
src_win/libimobiledevice/libffi-6.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libgcc_s_dw2-1.dll
Executable file
BIN
src_win/libimobiledevice/libgcc_s_dw2-1.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libgmp-10.dll
Executable file
BIN
src_win/libimobiledevice/libgmp-10.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libgnutls-30.dll
Executable file
BIN
src_win/libimobiledevice/libgnutls-30.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libhogweed-4-2.dll
Executable file
BIN
src_win/libimobiledevice/libhogweed-4-2.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libhogweed-4.dll
Executable file
BIN
src_win/libimobiledevice/libhogweed-4.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libiconv-2.dll
Executable file
BIN
src_win/libimobiledevice/libiconv-2.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libideviceactivation-2.dll
Executable file
BIN
src_win/libimobiledevice/libideviceactivation-2.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libideviceactivation.dll
Executable file
BIN
src_win/libimobiledevice/libideviceactivation.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libidn-11.dll
Executable file
BIN
src_win/libimobiledevice/libidn-11.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libidn2-0.dll
Executable file
BIN
src_win/libimobiledevice/libidn2-0.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libimobiledevice.dll
Executable file
BIN
src_win/libimobiledevice/libimobiledevice.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libintl-8.dll
Executable file
BIN
src_win/libimobiledevice/libintl-8.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libirecovery.dll
Executable file
BIN
src_win/libimobiledevice/libirecovery.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/liblzma-5.dll
Executable file
BIN
src_win/libimobiledevice/liblzma-5.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libnettle-6-2.dll
Executable file
BIN
src_win/libimobiledevice/libnettle-6-2.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libnettle-6.dll
Executable file
BIN
src_win/libimobiledevice/libnettle-6.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libnghttp2-14.dll
Executable file
BIN
src_win/libimobiledevice/libnghttp2-14.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libp11-kit-0.dll
Executable file
BIN
src_win/libimobiledevice/libp11-kit-0.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libplist++-3.dll
Executable file
BIN
src_win/libimobiledevice/libplist++-3.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libplist++.dll
Executable file
BIN
src_win/libimobiledevice/libplist++.dll
Executable file
Binary file not shown.
BIN
src_win/libimobiledevice/libplist.dll
Executable file
BIN
src_win/libimobiledevice/libplist.dll
Executable file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user