def __add_task(_user: UserInfo): _account = IosAccountInfo.objects.get(account=_user.account) _project = IosProjectInfo.objects.get(project=_user.project) _profile = IosProfileInfo.objects.get(sid="%s:%s" % (_user.account, _user.project)) _cert = IosCertInfo.objects.get( sid="%s:%s" % (_user.account, str_json_a(_profile.certs)[0])) Assert(_profile, "[%s][%s]证书无效" % (_project.project, _account.account)) Assert(_project.md5sum, "项目[%s]原始ipa还没上传" % _project.project) Assert(_cert.cert_p12, "项目[%s]p12还没上传" % _project.project) Log("[%s]发布任务[%s]" % (_user.project, _user.account)) _task, _ = TaskInfo.objects.get_or_create(uuid=_user.uuid) _task.state = "none" _task.worker = "" _task.save() resign_ipa.delay( **{ "uuid": _user.uuid, "cert": "iPhone Developer: %s" % _cert.name, "cert_url": entry("/apple/download_cert?uuid=%s" % _user.uuid), "cert_md5": md5bytes(base64decode(_cert.cert_p12)), "mp_url": entry("/apple/download_mp?uuid=%s" % _user.uuid), "mp_md5": md5bytes(base64decode(_profile.profile)), "project": _project.project, "ipa_url": entry("/projects/%s/orig.ipa" % _user.project), "ipa_md5": _project.md5sum, "ipa_new": "%s_%s.ipa" % (_account.team_id, _account.devices_num), "upload_url": entry("/apple/upload_ipa?uuid=%s" % _user.uuid), "process_url": entry("/apple/task_state?uuid=%s" % _user.uuid), })
def mongo_pack_set(key: str, value: dict, model: str, size=1 * 1000 * 1000): """ 大于16M的插入 以多个对象存在 """ db = mongo(model) v = json_str(value) if len(v) < size: return mongo_set(key, value, model) # 需要切一下 Assert("__pack__" not in value, "数据内不能有 __pack__ 字段") Assert("__no__" not in value, "数据内不能有 __no__ 字段") Assert("__value__" not in value, "数据内不能有 __value__ 字段") Assert("__length__" not in value, "数据内不能有 __length__ 字段") pack_length = int(ceil(len(v) / size)) new_value = list( map( lambda x: { "__pack__": True, "__no__": v, "__length__": pack_length, "__value__": v[x * size:(x + 1) * size], }, range(0, pack_length))) if mongo(model).find_one({ "_id": "%s_%s" % (model, key) }, ["__pack__", "__length__"]).get("__length__", 0) > pack_length: # 删除旧的部分 mongo_pack_pop(key, model) mongo_set(key, new_value[0], model) for each in new_value[1:]: mongo_set(key, each, model) return True
def security_code_sms(phone: str, sms: str): Assert("apple" in sms.lower(), "短信非验证码短信[%s]" % sms) code = re.compile(r"\d{4,6}").findall(sms) Assert(len(code), "短信非验证码短信[%s]" % sms) code = code[0] _account = IosAccountInfo.objects.filter( phone=phone).first() # type: IosAccountInfo publish_security_code(_account.account if _account else "*", code, now()) return { "succ": True, }
def __add_task(_user: UserInfo): _account = IosAccountInfo.objects.filter( account=_user.account).first() # type:IosAccountInfo _project = IosProjectInfo.objects.filter( project=_user.project).first() # type:IosProjectInfo _profile = IosProfileInfo.objects.filter( sid="%s:%s" % (_user.account, _user.project)).first() # type:IosProfileInfo Assert(_profile, "[%s][%s]证书无效" % (_project.project, _account.account)) Log("[%s]发布任务[%s]" % (_user.project, _user.account)) db_session.publish( "task:package", json_str({ "cert": "iPhone Developer: zhangming luo", "cert_p12": "", "mp_url": entry("/apple/download_mp?uuid=%s" % _user.uuid), "mp_md5": md5bytes(base64decode(_profile.profile)), "project": _project.project, "ipa_url": _asset_url("%s/orig.ipa" % _user.project), "ipa_md5": _project.md5sum, "ipa_new": "%s_%s.ipa" % (_account.team_id, _account.devices_num), "upload_url": entry("/apple/upload_ipa?project=%s&account=%s" % (_user.project, _user.account)), "ts": now(), }))
def task_state(uuid: str, worker: str = "", state: str = "", auto_start=True): _task, _ = TaskInfo.objects.get_or_create(uuid=uuid) if state: if _task.worker: Assert(_task.worker == worker, "越权更改任务[%s]状态[%s]=>[%s]" % (uuid, _task.worker, worker)) else: _task.worker = worker _task.save() if _task.state != state: Log("任务[%s]状态变更[%s]=>[%s]" % (uuid, _task.state, state)) _task.state = state _task.save() return { "succ": True, } else: if auto_start and _task.state in {"fail", "expire", "none", ""}: # noinspection PyTypeChecker rebuild({ "uuid": uuid, }) # 获取当前的状态 return { "code": 1 if _task.state in {"fail", "expire"} else 0, "finished": _task.state == "succ", "progress": "%d%%" % ((_states.index(_task.state) + 1) * 100 / len(_states)) if _task.state in _states else "0%", }
def newbee(project: str): """ 根据项目生成具体的一个可以注册新设备的uuid """ _info = IosProjectInfo.objects.filter( project=project).first() # type: IosProjectInfo Assert(_info is not None, "找不到对应的项目[%s]" % project) return { "uuid": _newbee(_info), }
def _get_cert(info: IosAccountInfo) -> IosCertInfo: cert = IosCertInfo.objects.filter( app=info.account, expire__gt=datetime.datetime.utcfromtimestamp(now() // 1000), type_str="development", ).first() # type: IosCertInfo if not cert: # todo: 生成证书 pass return Assert(cert, "缺少现成的开发[iOS App Development]证书[%s]" % info.account)
def upload_cert_p12(account: str, file: bytes, password: str = "q1w2e3r4"): p12 = crypto.load_pkcs12(file, password) name = re.match(r"iPhone Developer: (.+) \([A-Z0-9]+\)", p12.get_friendlyname().decode("utf8")).groups() Assert(len(name), "非法的p12文件") name = name[0] # type: str _cert = IosCertInfo.objects.get(sid="%s:%s" % (account, name)) _cert.cert_p12 = base64(file) _cert.save() return { "succ": True, }
def _get_app(_config: IosAccountHelper, project: str) -> IosAppInfo: app = IosAppInfo.objects.filter( sid="%s:%s" % (_config.account, project), ).first() # type: IosAppInfo if not app: _project = IosProjectInfo.objects.filter( project=project).first() # type: IosProjectInfo _identifier = "%s.%s" % (_project.bundle_prefix, GetRandomName()) _config.post( "验证一个app", "https://developer.apple.com/services-account/QH65B2/account/ios/identifiers/validateAppId.action?teamId=", data={ "teamId": _config.team_id, "appIdName": "ci%s" % project, "appIdentifierString": _identifier, "type": "explicit", "explicitIdentifier": _identifier, }, ) _config.post( "注册一个app", "https://developer.apple.com/services-account/v1/bundleIds", data=json_str({ "data": { "type": "bundleIds", "attributes": { "name": "ci%s" % project, "identifier": _identifier, "platform": "IOS", "seedId": _config.team_id, "teamId": _config.team_id, }, "relationships": { "bundleIdCapabilities": { "data": tran(get_capability, str_json(_project.capability)), }, }, }, }), ex_headers={ "content-type": "application/vnd.api+json", }, csrf=True, json_api=False, status=201, ) __list_all_app(_config, project) app = IosAppInfo.objects.filter( sid="%s:%s" % (_config.account, project), ).first() # type: IosAppInfo return Assert(app, "账号[%s]缺少app[%s]" % (_config.account, project))
def upload_ipa(project: str, account: str, file: bytes): base = os.path.join("static/income", project) os.makedirs(base, exist_ok=True) _info = IosAccountInfo.objects.filter( account=account).first() # type:IosAccountInfo Assert(_info, "账号[%s]不存在" % account) filename = "%s_%s.ipa" % (_info.team_id, _info.devices_num) with open(os.path.join(base, filename), mode="wb") as fout: fout.write(file) Log("[%s]收到新打包的ipa[%s]" % (account, filename)) return { "succ": True, }
def download_profile(uuid: str): """ 基于用户id下载 """ _user = UserInfo.objects.filter(uuid=uuid).first() # type: UserInfo Assert(_user is not None, "没有找到uuid[%s]" % uuid) _config = IosAccountHelper( IosAccountInfo.objects.filter(account=_user.account).first()) _info = IosProfileInfo.objects.filter( sid="%s" % _config.account).first() # type: IosProfileInfo return { "encodedProfile": _info.profile, }
def login_by_fastlane(_req: HttpRequest, cmd: str = "", account: str = ""): """ 通过命令获取会话 fastlane spaceauth -u [email protected] ex. export FASTLANE_SESSION='---\n- !ruby/object:HTTP::Cookie\n name: myacinfo\n value: DAWTKNV2e32a0823580640561dc9dfd382265048c32c2aa5b04485930b2ada1d1c7eba28dee6c6065c749f708f2a429f8f9f2d0f2f7d2ad629652ca5bc3383c0772d51c6ca34a2f7b09141f7b19c358c2b25d0738079ab1e48a06610228a574342c84ef13349ede1a012c25155e265a17989a3b09631dd953954505153fb7ef71aecfe303530cb684c89e8402cb82d8d4d93c3fc3c1209e334f65f71c7ae0cfdf0349ec757abcb104a591f5bea25ac0f1207004c19645b80ed82fb5cd0d3a740224b2f3aef9e91b049bb63a94ae3c76d027411f077069865209d733617a7a84f54cf7e9488e9b4f0a918d29f184f5ec76d95b5f55def61682f70b7f10fc12dc43d6e380213dd1f702a4f3ccab3ad438cd0f6a87c295e028a12ec410aa3fa689210d040377995914d4d3718b90f85ad5452d5db47ef9ae11c6b3216cf8ab61025adc203b0bf072ce832240c384d83f0f4aaf477a3c7313de4c20c5e32c530ff1ad76aebcd8538ac485a9a46941dfa94ee2f3fb40e38666533326562333665333834343461323366383636666563303166613533303330656361323836MVRYV2\n domain: apple.com\n for_domain: true\n path: "/"\n secure: true\n httponly: true\n expires: \n max_age: \n created_at: 2019-03-15 11:55:51.031441000 +08:00\n accessed_at: 2019-03-15 11:55:51.041509000 +08:00\n- !ruby/object:HTTP::Cookie\n name: dqsid\n value: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJzZnp2ZGFldGJPeWpXaTc1LVpTRmNBIn0.IEYqXF-pxIYdIwP2rdNRNhoxdCzJgGxt4olTZa2fXo8\n domain: olympus.itunes.apple.com\n for_domain: false\n path: "/"\n secure: true\n httponly: true\n expires: \n max_age: 1800\n created_at: &1 2019-03-15 11:55:52.798977000 +08:00\n accessed_at: *1\n curl localhost:8000/apple/login_by_fastlane --data $FASTLANE_SESSION """ if not cmd: cmd = _req.body.decode("utf-8") Assert(len(cmd), "命令行不对") cmd = cmd.replace("\\n", "") cookie = dict(re.compile(r"name:\s+(\S+)\s+value:\s+(\S+)").findall(cmd)) if not account: rsp = requests.post( "https://developer.apple.com/services-account/QH65B2/account/getUserProfile", headers={"cookie": to_form_url(cookie, split=';')}) if rsp.status_code != 200: return { "succ": False, "reason": "无效的fastlane export", } data = rsp.json() account = data["userProfile"]["email"] if account: _info = IosAccountInfo.objects.filter( account=account).first() # type:IosAccountInfo if not _info: _info = IosAccountInfo() _info.account = account _info.save() _info.cookie = json_str(cookie) _info.headers = json_str({}) _info.save() Log("通过fastlane登录[%s]" % account) return { "succ": True, "msg": "登录[%s]成功" % account, } else: return { "succ": False, "msg": "请求不具备提取登录信息", }
def _package(orig_file, provisioning_profile, certificate, output_path): base = tempfile.mkdtemp() ipa_file = os.path.join(base, "orig.ipa") mp_file = os.path.join(base, "mp.ipa") os.symlink(os.path.abspath(orig_file), ipa_file) os.symlink(os.path.abspath(provisioning_profile), mp_file) Log("准备ipa[%s]" % ipa_file) # with Block("打包", fail=False): # app = app_argument(ipa_file).unpack_to_dir(base) # Log("准备provision") # app.provision(mp_file) # Log("构造entitlements") # app.create_entitlements() # Log("签名证书") # app.sign(certificate) # Log("重新封包") # app.package(output_path) # Log("ipa[%s]" % output_path) try: if not os.path.isfile(output_path) or True: # 方案二打包 sh_file = os.path.join(base, "provisions.sh") if os.path.exists("provisions.sh"): os.symlink(os.path.abspath("provisions.sh"), sh_file) else: os.symlink(os.path.abspath("tools/provisions.sh"), sh_file) _shell_run("%s -p %s -c '%s' %s " % (sh_file, mp_file, certificate, ipa_file), succ_only=True, verbose=True, debug=True, pwd=base) Assert(os.path.isfile(os.path.join(base, "stage", "out.ipa")), "打包脚本失败了") os.rename(os.path.join(base, "stage", "out.ipa"), os.path.abspath(output_path)) if not os.path.isfile(output_path): raise Fail("打包失败") finally: shutil.rmtree(base, ignore_errors=True)
def upload_ipa(worker: str, uuid: str, file: bytes): _user = UserInfo.objects.get(uuid=uuid) project = _user.project account = _user.account base = os.path.join("static/income", project) os.makedirs(base, exist_ok=True) _info = IosAccountInfo.objects.filter( account=account).first() # type:IosAccountInfo Assert(_info, "账号[%s]不存在" % account) filename = "%s_%s.ipa" % (_info.team_id, _info.devices_num) with open(os.path.join(base, filename), mode="wb") as fout: fout.write(file) Log("[%s]收到新打包的ipa[%s]" % (account, filename)) # todo: 遍历所有的设备关联的包? _task, _ = TaskInfo.objects.get_or_create(uuid=uuid) if _task.worker in {worker, "none"}: _task.state = "succ" _task.worker = worker _task.size = len(file) _task.save() return { "succ": True, }
def login_by_curl(_req: HttpRequest, cmd: str = "", account: str = ""): """ 通过拦截网页的curl 请求 https://developer.apple.com/account/getUserProfile ex. curl 'https://developer.apple.com/services-account/QH65B2/account/getUserProfile' -H 'origin: https://developer.apple.com' -H 'accept-encoding: gzip, deflate, br' -H 'accept-language: zh-CN,zh;q=0.9' -H 'csrf: cf0796aee015fe0f03e7ccc656ba4b898b696cc1072027988d89b1f6e607fd67' -H 'cookie: geo=SG; ccl=SR+vWVA/vYTrzR1LkZE2tw==; s_fid=56EE3E795513A2B4-16F81B5466B36881; s_cc=true; s_vi=[CS]v1|2E425B0B852E2C90-40002C5160000006[CE]; dslang=CN-ZH; site=CHN; s_pathLength=developer%3D2%2C; acn01=v+zxzKnMyleYWzjWuNuW1Y9+kAJBxfozY2UAH0paNQB+FA==; myacinfo=DAWTKNV2a5c238e8d27e8ed221c8978cfb02ea94b22777f25ffec5abb1a855da8debe4f59d60b506eae457dec4670d5ca9663ed72c3d1976a9f87c53653fae7c63699abe64991180d7c107c50ce88be233047fc96de200c3f23947bfbf2e064c7b9a7652002d285127345fe15adf53bab3d347704cbc0a8b856338680722e5d0387a5eb763d258cf19b79318be28c4abd01e27029d2ef26a1bd0dff61d141380a1b496b92825575735d0be3dd02a934db2d788c9d6532b6a36bc69d244cc9b4873cef8f4a3a90695f172f6f521330f67f20791fd7d62dfc9d6de43899ec26a8485191d62e2c5139f81fca2388d57374ff31f9f689ad373508bcd74975ddd3d3b7875fe3235323962636433633833653433363562313034383164333833643736393763303538353038396439MVRYV2; DSESSIONID=1c3smahkpfbkp7k3fju30279uoba8p8480gs5ajjgsbbvn8lubqt; s_sq=%5B%5BB%5D%5D' -H 'user-locale: en_US' -H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.81 Safari/537.36 QQBrowser/4.5.122.400' -H 'content-type: application/json' -H 'accept: application/json' -H 'cache-control: max-age=0, no-cache, no-store, must-revalidate, proxy-revalidate' -H 'authority: developer.apple.com' -H 'referer: https://developer.apple.com/account/' -H 'csrf_ts: 1552204197631' --data-binary '{}' --compressed """ if not cmd: cmd = _req.body.decode("utf-8") Assert(len(cmd) and cmd.startswith("curl"), "命令行不对") parsed_context = curl_parse_context(cmd) params = { "data": parsed_context.data, "headers": dict( filter(lambda x: not x[0].startswith(":"), parsed_context.headers.items())), "cookies": parsed_context.cookies, } if parsed_context.method == 'get': rsp = requests.get( parsed_context.url, **params, ) else: rsp = requests.post( parsed_context.url, **params, ) if rsp.status_code != 200: return { "succ": False, "reason": "无效的curl", } if parsed_context.url != "https://developer.apple.com/services-account/QH65B2/account/getUserProfile": data = rsp.json() account = data["userProfile"]["email"] else: rsp = requests.post( "https://developer.apple.com/services-account/QH65B2/account/getUserProfile", headers={ "cookie": parsed_context.cookies, }) data = rsp.json() account = data["userProfile"]["email"] if account: _info = IosAccountInfo.objects.filter( account=account).first() # type:IosAccountInfo if not _info: _info = IosAccountInfo() _info.account = account _info.save() _info.cookie = json_str(parsed_context.cookies) _info.headers = json_str(parsed_context.headers) _info.save() Log("通过curl登录[%s]" % account) return { "succ": True, "msg": "登录[%s]成功" % account, } else: return { "succ": False, "msg": "请求不具备提取登录信息", }
def __init__(self, key: str, timeout=None): Assert(key, "key必须有效") self.__key = "lock:" + key self.__lock = False self.__timeout = timeout
def post(self, title: str, url: str, data: Union[Dict, str] = None, is_json=True, log=True, cache: Union[bool, int] = False, csrf=False, json_api=True, method="POST", is_binary=False, ex_headers=None, status=200): if cache is True: expire = 3600 * 1000 else: expire = cache if not self.is_login: self.__login() start = now() rsp_str = "#NODATA#" try: if "teamId=" in url: if "teamId=%s" % self.team_id not in url: url = url.replace("teamId=", "teamId=%s" % self.team_id) if cache: rsp_str = _cache(url, data) or rsp_str headers = { 'cookie': to_form_url({"myacinfo": self.cookie["myacinfo"]}, split=';'), } if csrf: headers.update({ 'csrf': self.csrf, 'csrf_ts': self.csrf_ts, }) if ex_headers: headers.update(ex_headers) if rsp_str == "#NODATA#": if method.upper() == "GET": rsp = requests.get(url, params=data, headers=headers, timeout=3, verify=False) else: rsp = requests.post(url, data=data, headers=headers, timeout=3, verify=False) rsp_str = rsp.text if rsp.headers.get("csrf"): self.csrf = rsp.headers["csrf"] self.csrf_ts = int(rsp.headers["csrf_ts"]) Assert(rsp.status_code == status, "请求[%s]异常[%s]" % (title, rsp.status_code)) if json_api: _data = str_json_i(rsp_str) or {} if _data.get("resultCode") == 1100: self.__logout() raise Fail("登录[%s]过期了[%s][%s]" % (self.account, _data.get("resultString"), _data.get("userString"))) Assert( _data.get("resultCode") == 0, "请求业务[%s]失败[%s][%s]" % (title, _data.get("resultString"), _data.get("userString"))) if log: Log("apple请求[%s][%s]发送[%r]成功[%r]" % (title, now() - start, data, rsp_str)) if is_binary: rsp_str = base64(rsp.content) if cache: _set_cache(url, data, rsp_str, expire=expire) if is_json: return str_json(rsp_str) elif is_binary: return base64decode(rsp_str) else: return rsp_str except Exception as e: if log: Log("apple请求[%s][%s]发送[%r]失败[%r]" % (title, now() - start, data, rsp_str)) raise e
def __login(self): if self.csrf_ts > now(): return with Block("账号登录"): ret = requests.post( "https://developer.apple.com/services-account/QH65B2/account/getTeams", json={ "includeInMigrationTeams": 1, }, headers={ 'cookie': to_form_url(self.cookie, split=';'), }, timeout=3, verify=False).json() if not self.team_id else {} if ret.get("resultCode") == 0: self.teams = list(map(lambda x: x["teamId"], ret["teams"])) self.info.team_id = self.team_id = self.teams[0] self.info.teams = json_str(self.teams) self.info.save() else: # 重新登录 self.session = requests.session() self.session.headers["User-Agent"] = "Spaceship 2.117.1" rsp = self.session.get( "https://olympus.itunes.apple.com/v1/app/config?hostname=itunesconnect.apple.com" ).json() self.session.headers["X-Apple-Widget-Key"] = rsp[ "authServiceKey"] # self.session.headers["X-Apple-Widget-Key"] = "16452abf721961a1728885bef033f28e" self.session.headers["Accept"] = "application/json" rsp = self.session.post( "https://idmsa.apple.com/appleauth/auth/signin", json={ "accountName": self.account, "password": self.password, "rememberMe": True, }, timeout=3) self.session.headers["x-apple-id-session-id"] = rsp.headers[ "x-apple-id-session-id"] self.session.headers["scnt"] = rsp.headers["scnt"] if rsp.status_code == 409: # 二次验证 # noinspection PyUnusedLocal rsp = self.session.post( "https://idmsa.apple.com/appleauth/auth") # Log("===> https://idmsa.apple.com/appleauth/auth [%s] %s" % (rsp.status_code, rsp.json())) # 切手机验证码 rsp = self.session.put( "https://idmsa.apple.com/appleauth/auth/verify/phone", json={ "phoneNumber": { "id": 1 }, "mode": "sms", }) Assert(rsp.status_code == 200, "[%s]短信发送失败" % self.account) # Log("===> https://idmsa.apple.com/appleauth/auth/verify/phone [%s] %s" % (rsp.status_code, rsp.json())) _wait_code(self.info, self.session, now()) self.cookie.update(self.session.cookies) self.__expire = now() + 3600 * 1000 if not self.team_id: ret = requests.post( "https://developer.apple.com/services-account/QH65B2/account/getTeams", json={ "includeInMigrationTeams": 1, }, headers={ 'cookie': to_form_url(self.cookie, split=';'), }, timeout=3, verify=False).json() if ret["resultCode"] == 0: self.teams = list(map(lambda x: x["teamId"], ret["teams"])) self.info.team_id = self.team_id = self.teams[0] self.info.teams = json_str(self.teams) self.info.save() else: Log("[%s]获取team失败登出了" % self.account) self.__logout() Log("apple账号[%s:%s]登录完成了" % (self.account, self.team_id))
def __add_device(account: IosAccountInfo, udid: str, project: str) -> bool: title = "设备%s" % udid _config = IosAccountHelper(account) try: _device = IosDeviceInfo.objects.filter( udid=udid).first() # type:Optional[IosDeviceInfo] if not _device: # 先注册设备 ret = _config.post( "验证设备udid", "https://developer.apple.com/services-account/QH65B2/account/ios/device/validateDevices.action?teamId=", { "deviceNames": title, "deviceNumbers": udid, "register": "single", "teamId": _config.team_id, }, cache=True) Assert( len(ret["failedDevices"]) == 0, "验证udid请求失败[%s][%s]" % (udid, ret["validationMessages"])) __list_all_devices(_config) ret = _config.post( "添加设备", "https://developer.apple.com/services-account/QH65B2/account/ios/device/addDevices.action?teamId=%s" % _config.team_id, { "deviceClasses": "iphone", "deviceNames": title, "deviceNumbers": udid, "register": "single", "teamId": _config.team_id, }, csrf=True) Assert(ret["resultCode"] == 0, "添加udid请求失败[%s]" % udid) Assert(not ret["validationMessages"], "添加udid请求失败[%s]" % udid) Assert(ret["devices"], "添加udid请求失败[%s]" % udid) device = ret["devices"][0] # type: Dict _reg_device(device["deviceId"], device["deviceNumber"], device["model"], device["serialNumber"]) with Block("更新"): ret, _info = __list_all_profile(_config, project) if not _info: _info = IosProfileInfo() _info.sid = "%s:%s" % (_config.account, project) _info.app = _config.account _info.devices = "[]" _info.devices_num = 0 _info.project = project devices = str_json(_info.devices) # type: List[str] if udid in devices: pass else: devices.append(udid) with Block("默认全开当期的设备"): # noinspection PyTypeChecker devices = list( set(devices + str_json(_config.info.devices))) _cert = _get_cert(_config.info) _app = _get_app(_config, project) found = False for each in ret["provisioningProfiles"]: # type: Dict if each["name"] != "专用 %s" % project: continue # todo: 过期更新 ret = _config.post( "更新ProvisioningProfile", "https://developer.apple.com/services-account/QH65B2/account/ios/profile/regenProvisioningProfile.action?teamId=", data={ "provisioningProfileId": each["provisioningProfileId"], "distributionType": "limited", "subPlatform": "", "returnFullObjects": False, "provisioningProfileName": each["name"], "appIdId": _app.app_id_id, "certificateIds": _cert.cert_req_id, "deviceIds": ",".join(_get_device_id(devices).values()), }, csrf=True) Assert(ret["resultCode"] == 0) _info.devices = json_str(devices) _info.profile_id = each["provisioningProfileId"] # noinspection PyTypeChecker _info.profile = ret["provisioningProfile"][ "encodedProfile"] _info.expire = _to_dt( ret["provisioningProfile"]["dateExpire"]) _info.save() found = True Log("更新证书[%s]添加设备[%s][%s]成功" % (project, udid, len(devices))) break if not found: ret = _config.post( "创建ProvisioningProfile", "https://developer.apple.com/services-account/QH65B2/account/ios/profile/createProvisioningProfile.action?teamId=", data={ "subPlatform": "", "certificateIds": _cert.cert_req_id, "deviceIds": ",".join(_get_device_id(devices).values()), "template": "", "returnFullObjects": False, "distributionTypeLabel": "distributionTypeLabel", "distributionType": "limited", "appIdId": _app.app_id_id, "appIdName": _app.name, "appIdPrefix": _app.prefix, "appIdIdentifier": _app.identifier, "provisioningProfileName": "专用 %s" % project, }, csrf=True) Assert(ret["resultCode"] == 0) # noinspection PyTypeChecker _info.profile_id = ret["provisioningProfile"][ "provisioningProfileId"] # noinspection PyTypeChecker _info.profile = ret["provisioningProfile"][ "encodedProfile"] _info.expire = _to_dt( ret["provisioningProfile"]["dateExpire"]) _info.save() Log("添加证书[%s]添加设备[%s][%s]成功" % (project, udid, len(devices))) except Exception as e: Trace("添加设备出错了[%s]" % e, e) return False return True