216 lines
6.1 KiB
Python
216 lines
6.1 KiB
Python
import asyncio
|
|
import getpass
|
|
import traceback
|
|
from pathlib import Path
|
|
from shutil import move
|
|
|
|
from aiohttp import ClientTimeout
|
|
from httpwrapper import AsyncClientConfig, BaseAsyncClient
|
|
from jsoner import json_read_async, json_write_async
|
|
|
|
from models import UdidInfo
|
|
|
|
CACHE_DIR = Path("cache")
|
|
CACHE_DIR.mkdir(exist_ok=True)
|
|
|
|
APPS_DIR = Path("apps")
|
|
APPS_DIR.mkdir(exist_ok=True)
|
|
|
|
|
|
def move_ipa(from_path: str, to_path: str):
|
|
while True:
|
|
try:
|
|
move(from_path, to_path)
|
|
return
|
|
except Exception:
|
|
traceback.print_exc()
|
|
|
|
|
|
class IPAToolClient(BaseAsyncClient):
|
|
def __init__(self, host: str, port: int):
|
|
super().__init__(
|
|
f"http://{host}:{port}",
|
|
config=AsyncClientConfig(timeout=ClientTimeout(300)),
|
|
)
|
|
|
|
async def auth(self, user: str, pswd: str) -> bool:
|
|
r = await self._post(
|
|
"/auth/login",
|
|
params={
|
|
"user": user,
|
|
"pswd": pswd,
|
|
"keychain": pswd,
|
|
},
|
|
)
|
|
if r.status != 200:
|
|
return False
|
|
json_data = await r.json()
|
|
if isinstance(json_data, bool):
|
|
return json_data
|
|
return False
|
|
|
|
async def auth_check(self, user: str) -> bool:
|
|
r = await self._post(
|
|
"/auth/check",
|
|
params={
|
|
"user": user,
|
|
},
|
|
)
|
|
if r.status != 200:
|
|
return False
|
|
json_data = await r.json()
|
|
if isinstance(json_data, bool):
|
|
return json_data
|
|
return False
|
|
|
|
async def app_info(self, query: str, limit: int, keychain: str) -> dict:
|
|
r = await self._post(
|
|
"/app/search",
|
|
params={
|
|
"query": query,
|
|
"limit": limit,
|
|
"keychain": keychain,
|
|
},
|
|
)
|
|
if r.status != 200:
|
|
return {}
|
|
json_data = await r.json()
|
|
if isinstance(json_data, dict):
|
|
return json_data
|
|
return {}
|
|
|
|
async def app_upload(self, output_path: str, save_path: str):
|
|
r = await self._get(
|
|
"/app/upload",
|
|
params={
|
|
"pswd": "81928192",
|
|
"output_path": output_path,
|
|
},
|
|
)
|
|
if r.status != 200:
|
|
return False
|
|
try:
|
|
content = await r.read()
|
|
with open(save_path, "wb") as f:
|
|
f.write(content)
|
|
return True
|
|
except Exception:
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
async def app_download(
|
|
self,
|
|
bundle_id: str,
|
|
output_path: str,
|
|
keychain: str,
|
|
) -> bool:
|
|
r = await self._post(
|
|
"/app/download",
|
|
params={
|
|
"keychain": keychain,
|
|
"bundle_id": bundle_id,
|
|
"output_path": output_path,
|
|
},
|
|
)
|
|
if r.status != 200:
|
|
return False
|
|
json_data = await r.json()
|
|
if isinstance(json_data, dict):
|
|
return json_data.get("success", False)
|
|
return False
|
|
|
|
async def app_delete(self, output_path: str) -> bool:
|
|
r = await self._delete(
|
|
"/app/delete",
|
|
params={
|
|
"pswd": "81928192",
|
|
"output_path": output_path,
|
|
},
|
|
)
|
|
if r.status != 200:
|
|
return False
|
|
json_data = await r.json()
|
|
if isinstance(json_data, bool):
|
|
return json_data
|
|
return False
|
|
|
|
|
|
async def __main(client: IPAToolClient, udid: str, info: UdidInfo):
|
|
output_path = f"{info.app_name}.ipa"
|
|
await client.app_delete(output_path)
|
|
|
|
for _ in range(30):
|
|
if await client.app_download(info.bundle_id, output_path, info.pswd):
|
|
break
|
|
print(udid, "download failed, retrying...")
|
|
await asyncio.sleep(3)
|
|
print(udid, "downloaded")
|
|
|
|
save_path = APPS_DIR / f"{info.app_name}-{udid}.ipa"
|
|
for _ in range(10):
|
|
if await client.app_upload(output_path, str(save_path)):
|
|
return True
|
|
return False
|
|
|
|
|
|
async def _main(client: IPAToolClient, udid: str, info: UdidInfo) -> bool:
|
|
if not await client.auth(info.user, info.pswd):
|
|
print(udid, "no auth response")
|
|
return False
|
|
if not (response := await client.app_info(info.app_name, 1, info.pswd)):
|
|
print(udid, "no app info response")
|
|
return False
|
|
if not (apps := response.get("apps", [])):
|
|
print(udid, "no apps in response")
|
|
return False
|
|
for app in apps:
|
|
if app.get("bundleID", "") != info.bundle_id:
|
|
continue
|
|
if not (version := app.get("version", "")):
|
|
print(udid, "no version in response")
|
|
continue
|
|
old_version = ""
|
|
cache_file = CACHE_DIR / f"{udid}.{info.app_name}.json"
|
|
if json_data := await json_read_async(cache_file):
|
|
old_version = json_data.get("version", "")
|
|
if old_version == version:
|
|
print(udid, "already have version", version)
|
|
return False
|
|
print(udid, "new version", version)
|
|
# await json_write_async(cache_file, {"version": version})
|
|
if await __main(client, udid, info):
|
|
await json_write_async(cache_file, {"version": version})
|
|
|
|
print(udid, "uploaded")
|
|
|
|
ipa_file = f"{info.app_name}-{udid}.ipa"
|
|
save_path = APPS_DIR / ipa_file
|
|
to_path = f"/Users/{getpass.getuser()}/{ipa_file}"
|
|
move_ipa(str(save_path), to_path)
|
|
print(udid, "moved")
|
|
return True
|
|
print(udid, "not uploaded")
|
|
return False
|
|
return False
|
|
|
|
|
|
async def main(json_data: dict):
|
|
tasks = []
|
|
for udid, value in json_data.items():
|
|
info = UdidInfo(**value)
|
|
client = IPAToolClient(info.host, info.port)
|
|
task = _main(client, udid, info)
|
|
tasks.append(task)
|
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
|
|
|
|
async def start():
|
|
while True:
|
|
json_data = await json_read_async("info.json")
|
|
await main(json_data)
|
|
await asyncio.sleep(120)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(start())
|