Files
IPAToolClient/main.py
2025-11-06 22:53:16 +03:00

213 lines
6.1 KiB
Python

import asyncio
import getpass
import traceback
from pathlib import Path
from shutil import move
from time import sleep
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()
sleep(10)
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())