def __init__(self, info: IosAccountInfo): self.info = info self.account = info.account self.password = info.password self.teams = str_json_i(info.teams, default=[]) self.team_id = info.team_id self.headers = str_json(info.headers) self.cookie = str_json(info.cookie) # type: Dict[str,str] self.csrf = info.csrf self.csrf_ts = info.csrf_ts self.session = requests.session()
def _reg_cert(_config: IosAccountInfo, cert_req_id, name, cert_id, sn, type_str, expire): sid = "%s:%s" % (_config.account, name) orig = str_json( db_model.get("IosCertInfo:%s:%s" % (_config.account, name)) or '{}') obj = { "name": name, "app": _config.account, "cert_req_id": cert_req_id, "cert_id": cert_id, "sn": sn, "type_str": type_str, "expire": expire, "expire_str": date_time_str(expire), } if orig == obj: return cert_req_id db_model.set("IosCertInfo:%s:%s" % (_config.account, name), json_str(obj), ex=(expire - now()) // 1000) _info = IosCertInfo() _info.sid = sid _info.app = _config.account _info.cert_req_id = cert_req_id _info.cert_id = cert_id _info.sn = sn _info.type_str = type_str _info.name = name _info.create = now() _info.expire = datetime.datetime.utcfromtimestamp(expire // 1000) _info.save() Log("注册新的证书[%s][%s]" % (name, cert_req_id)) return cert_req_id
def __list_all_devices(_config: IosAccountHelper): ret = _config.post( "获取所有的列表", "https://developer.apple.com/services-account/QH65B2/account/ios/device/listDevices.action?teamId=", data={ "includeRemovedDevices": True, "includeAvailability": True, "pageNumber": 1, "pageSize": 100, "sort": "status%3dasc", "teamId": _config.team_id, }, log=False) for device_id in set(map(lambda x: x["deviceId"], ret["devices"])) - set( str_json(_config.info.devices).values()): # type: Dict device = list( filter(lambda x: x["deviceId"] == device_id, ret["devices"]))[0] _reg_device( _config.account, device["deviceId"], device["deviceNumber"], device.get("model", device.get("deviceClass", "#UNKNOWN#")), device.get("serialNumber", "#UNKNOWN#")) # 更新一下info devices = dict( map(lambda x: (x["deviceNumber"], x["deviceId"]), ret["devices"])) if json_str(devices) != _config.info.devices: Log("更新设备列表[%s]数量[%s]=>[%s]" % (_config.account, _config.info.devices_num, len(devices))) _config.info.devices = json_str(devices) _config.info.devices_num = len(devices) _config.info.save()
def mongo_pack_get(key: str, model: str, pop=False) -> Optional[Dict]: ret = _mongo_pack_get(key, model, pop=pop) if len(ret) == 0: return None if len(ret) == 1: return ret[0] return str_json("".join(map(lambda x: x["__value__"], ret)))
def _get_device_id(udid_list: List[str]) -> Dict[str, str]: return dict( zip( udid_list, map( lambda x: str_json(x)["device_id"] if x else x, db_model.mget( list(map(lambda x: "IosDeviceInfo:%s" % x, udid_list))))))
def info(_req: HttpRequest, project: str, uuid: str = ""): _project = IosProjectInfo.objects.filter( project=project).first() # type: IosProjectInfo udid = "" if not _project: return {} ret = str_json(_project.comments) ready = False if uuid: _user = UserInfo.objects.filter(uuid=uuid).first() # type: UserInfo if _user: ready = True udid = _user.udid else: Log("上传的uuid无效[%s]" % uuid) else: uuid = _req.get_signed_cookie("uuid", "", salt="zhihu") _user = UserInfo.objects.filter(uuid=uuid).first() # type: UserInfo if _user: ready = True udid = _user.udid else: Log("cookie中的uuid无效[%s]" % uuid) if ready: ret.update({ "ready": True, }) _task = TaskInfo.objects.filter(uuid=uuid).first() # type:TaskInfo if _task: if _task.state == "fail" or _task.expire.timestamp() * 1000 < now( ): __add_task(_user) else: uuid = _newbee(_project) ret.update({ "ready": False, }) ret.update({ "uuid": uuid, }) rsp = JsonResponse({ "ret": 0, "result": ret, "download_id": random_str(32), }) rsp.set_signed_cookie("uuid", uuid, salt="zhihu", expires=3600 * 24) if udid: rsp.set_signed_cookie("udid", _user.udid, salt="zhihu", expires=300 * 3600 * 24) return rsp
def info(project: str): _project = IosProjectInfo.objects.filter( project=project).first() # type: IosProjectInfo if _project: ret = str_json(_project.comments) ret.update({ "uuid": _newbee(_project), }) return ret return {}
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 add_device(_content: bytes, uuid: str, udid: str = ""): _key = "uuid:%s" % uuid _detail = str_json(db_session.get(_key) or "{}") _account = _detail.get("account") project = _detail["project"] if not _detail: raise Fail("无效的uuid[%s]" % uuid) if not udid: # todo: 验证来源 with Block("提取udid", fail=False): udid = re.compile(br"<key>UDID</key>\n\s+<string>(\w+)</string>" ).findall(_content)[0].decode("utf8") if not udid: # 提取udid失败后删除uuid db_session.delete(_key) return { "succ": False, } for _user in UserInfo.objects.filter(udid=udid): _account = _user.account if _user.project == _detail["project"]: if uuid != _user.uuid: Log("转移设备的[%s]的uuid[%s]=>[%s]" % (udid, uuid, _user.uuid)) uuid = _user.uuid break if not _account: Log("为设备[%s]分配账号" % udid) _account = __fetch_account(udid, project, __add_device) else: _account = IosAccountInfo.objects.filter(account=_account).first() _user = UserInfo(uuid=uuid) _user.udid = udid _user.project = _detail["project"] _user.account = _account.account _user.save() if test_env(): # 测试环境可能uuid要重复测 pass else: db_session.delete(_key) __add_task(_user) return HttpResponsePermanentRedirect( "https://iosstore.sklxsj.com/detail.php?project=%s" % project)
def _reg_device(account: str, device_id: str, udid: str, model: str, sn: str) -> str: # 需要缓存 _info = IosDeviceInfo() _info.sid = "%s:%s" % (account, udid) _info.account = account _info.udid = udid _info.device_id = device_id _info.model = model _info.sn = sn _info.create = now() _info.save() Log("注册新的设备[%s][%s][%s]" % (udid, device_id, sn)) _account = IosAccountInfo.objects.get(account=account) device_map = str_json(_account.devices) device_map[udid] = device_id if json_str(device_map) != _account.devices: _account.devices = json_str(device_map) _account.devices_num = len(device_map) _account.save() return udid
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 __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
def manifest(uuid: str, need_process=True, download_id: str = ""): _user = UserInfo.objects.get(uuid=uuid) _account = IosAccountInfo.objects.get(account=_user.account) _project = IosProjectInfo.objects.get(project=_user.project) _app = IosAppInfo.objects.get(sid="%s:%s" % (_user.account, _user.project)) _comments = str_json(_project.comments) content = """\ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>items</key> <array> <dict> <key>assets</key> <array> <dict> <key>kind</key> <string>software-package</string> <key>url</key> <string>%(url)s</string> </dict> <dict> <key>kind</key> <string>display-image</string> <key>url</key> <string>%(icon)s</string> </dict> <dict> <key>kind</key> <string>full-size-image</string> <key>url</key> <string>%(icon)s</string> </dict> </array> <key>metadata</key> <dict> <key>bundle-identifier</key> <string>%(app)s</string> <key>bundle-version</key> <string>%(version)s</string> <key>kind</key> <string>software</string> <key>title</key> <string>%(title)s</string> </dict> </dict> </array> </dict> </plist> """ % { "url": static_entry("/income/%s/%s_%s.ipa") % (_project.project, _account.team_id, _account.devices_num) if not need_process else entry( "/apple/download_ipa/uuid/%s/download_id/%s" % (uuid, download_id or random_str())), "uuid": uuid, "title": _comments["name"], "icon": _comments["icon"], "app": _app.identifier, "version": "1.0.0", } response = HttpResponse(content) response['Content-Type'] = 'application/octet-stream; charset=utf-8' response["Content-Disposition"] = 'attachment; filename="app.plist"' return response
def validate_json(value: str): try: str_json(value) except ValueError: raise ValidationError(_('不合法的json字符串'), code='invalid')