通过Python上传到fir

方便测试上传app到fir平台,自己琢磨写了一个Python的上传方式。更好的体验请使用官方 fir-cli

使用方式  

python  firApi.py "${TOKEN}"  "${APP_PATH}"

firApi.py 如下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# project: 12月 
# author: NinEveN
# date: 2020/12/19

import requests
import zipfile, re, os, random, sys
from androguard.core.bytecodes import apk
import plistlib


class AppInfo(object):
    def __init__(self, app_path):
        self.app_path = app_path
        self.result = {}

    def make_app_png(self):
        zf = zipfile.ZipFile(self.app_path)
        name_list = zf.namelist()
        if self.app_path.endswith("apk"):
            try:
                apkobj = apk.APK(self.app_path)
            except Exception as err:
                raise err
            else:
                if apkobj.is_valid_APK():
                    print(apkobj.get_app_icon())
                    size = 0
                    iconfile = ""

                    fsize = len(zf.read(apkobj.get_app_icon()))
                    if size == 0:
                        iconfile = apkobj.get_app_icon()
                    if size < fsize:
                        size = fsize
                        iconfile = apkobj.get_app_icon()

                    print(size)

        elif self.app_path.endswith("ipa"):
            pattern = re.compile(r'Payload/[^/]*.app/AppIcon[^/]*[^(ipad)].png')
            size = 0
            iconfile = ""
            for path in name_list:
                m = pattern.match(path)
                if m is not None:
                    filepath = m.group()
                    fsize = len(zf.read(filepath))
                    if size == 0:
                        iconfile = filepath
                    if size < fsize:
                        size = fsize
                        iconfile = filepath
        else:
            raise Exception("File type error")


        if size == 0:
            raise Exception("File size error")

        filename = ''.join(random.sample(
            ['z', 'y', 'x', 'w', 'v', 'u', 't', 's', 'r', 'q', 'p', 'o', 'n', 'm', 'l', 'k', 'j', 'i', 'h', 'g', 'f',
             'e', 'd', 'c', 'b', 'a'], 16))
        if not os.path.isdir(os.path.join("tmp", "icon")):
            os.makedirs(os.path.join("tmp", "icon"))
        with open(os.path.join("tmp", "icon", filename), 'wb') as f:
            f.write(zf.read(iconfile))

        return os.path.join("tmp", "icon", filename)

    def get_app_data(self):
        if self.app_path.endswith("apk"):
            return self.__get_android_data()
        elif self.app_path.endswith("ipa"):
            return self.__get_ios_data()
        else:
            raise Exception("File type error")

    def __get_ios_info_path(self, ipaobj):
        infopath_re = re.compile(r'Payload/[^/]*.app/Info.plist')
        for i in ipaobj.namelist():
            m = infopath_re.match(i)
            if m is not None:
                return m.group()

    def __get_android_data(self):
        try:
            apkobj = apk.APK(self.app_path)
        except Exception as err:
            raise err
        else:
            if apkobj.is_valid_APK():
                versioncode = apkobj.get_androidversion_code()
                bundle_id = apkobj.get_package()
                labelname = apkobj.get_app_name()
                version = apkobj.get_androidversion_name()
                # sdk_version = apkobj.get_target_sdk_version()
                print(apkobj.get_app_icon())
                self.result = {
                    "labelname": labelname,
                    "bundle_id": bundle_id,
                    "versioncode": versioncode,
                    "version": version,
                    "type": "android",
                }
        return self.result

    def __get_ios_data(self):
        if zipfile.is_zipfile(self.app_path):
            ipaobj = zipfile.ZipFile(self.app_path)
            info_path = self.__get_ios_info_path(ipaobj)
            if info_path:
                plist_data = ipaobj.read(info_path)
                plist_root = plistlib.loads(plist_data)
                labelname = plist_root['CFBundleDisplayName']
                versioncode = plist_root['CFBundleVersion']
                bundle_id = plist_root['CFBundleIdentifier']
                version = plist_root['CFBundleShortVersionString']
                self.result = {
                    "labelname": labelname,
                    "bundle_id": bundle_id,
                    "versioncode": versioncode,
                    "version": version,
                    "type": "ios",
                }

            release_type_info = self.__get_ios_release_type(ipaobj)
            if info_path:
                plist_data = ipaobj.read(release_type_info)
                if b"ProvisionedDevices" in plist_data:
                    self.result["release_type"] = "Adhoc"
                else:
                    self.result["release_type"] = "Inhouse"
        return self.result

    def __get_ios_release_type(self, ipaobj):
        infopath_re = re.compile(r'Payload/[^/]*.app/embedded.mobileprovision')
        for i in ipaobj.namelist():
            m = infopath_re.match(i)
            if m is not None:
                return m.group()


class FirApi(object):
    def __init__(self, api_token):
        '''
        :param api_token: 长度为 32, 用户在 fir 的 api_token
        '''
        self.api_token = api_token
        self.app_url = "http://api.bq04.com/apps"

    def __request(self, action, *args, **kwargs):

        fun_re = getattr(requests, action, None)
        res = fun_re(*args, **kwargs)
        return res

    def get_version_by_id(self, id):
        '''

        :param id: 应用ID,可在"应用管理"->"基本信息"查看
        :return:
        '''
        url = "%s/latest/%s?api_token=%s" % (self.app_url, id, self.api_token)
        res = self.__request("get", url)
        if res.status_code == 200:
            return res.json()
        else:
            raise Exception(res.content)

    def get_version_by_bundle_id(self, bundle_id, type):
        '''

        :param bundle_id: Bundle ID(iOS) / Package name(Android)
        :param type: 应用类型 ( ios / android )
        :return:
        '''
        url = "%s/latest/%s?api_token=%s&type=%s" % (self.app_url,
                                                     bundle_id, self.api_token,
                                                     type)  # 使用 bundle_id 请求必填 ( ios / android )
        res = self.__request("get", url)
        if res.status_code == 200:
            return res.json()
        else:
            raise Exception(res.content)

    def __get_upload_token(self, bundle_id, type):
        '''

        :param bundle_id: App 的 bundleId(发布新应用时必填)
        :param type: ios 或者 android(发布新应用时必填)
        :return:
        '''
        data = {
            "type": type,
            "bundle_id": bundle_id,
            "api_token": self.api_token,
        }
        res = self.__request("post", self.app_url, data)
        if res.status_code == 201:
            return res.json()
        else:
            raise Exception(res.content)

    def upload_app(self, app_path):
        appobj = AppInfo(app_path)
        appinfo = appobj.get_app_data()
        icon_path = appobj.make_app_png()
        bundle_id = appinfo.get("bundle_id")
        upcretsdata = self.__get_upload_token(bundle_id, appinfo.get("type"))

        icon_key = upcretsdata["cert"]["icon"]["key"]
        icon_token = upcretsdata["cert"]["icon"]["token"]
        icon_upload_url = upcretsdata["cert"]["icon"]["upload_url"]
        short_url = upcretsdata["short"]
        download_domain = upcretsdata["download_domain"]

        icon_data = {
            "key": icon_key,
            "token": icon_token,
        }
        files = {'file': (os.path.basename(icon_path), open(icon_path, 'rb'), {'Expires': '0'})}
        res = self.__request("post", icon_upload_url, data=icon_data, files=files)
        if res.status_code == 200:
            # print(res.json())
            print("图标上传成功")
        else:
            raise Exception(res.content)

        binary_key = upcretsdata["cert"]["binary"]["key"]
        binary_token = upcretsdata["cert"]["binary"]["token"]
        binary_upload_url = upcretsdata["cert"]["binary"]["upload_url"]

        binary_data = {
            "key": binary_key,
            "token": binary_token,
            "x:name": appinfo["labelname"],
            "x:version": appinfo["version"],
            "x:build": appinfo["versioncode"],
            "x:changelog": ""
        }

        if appinfo["type"] == "ios": binary_data["x:release_type"] = appinfo.get("release_type",
                                                                                 "Adhoc")  # Adhoc: 内测版   Inhouse:企业版

        files = {'file': (os.path.basename(app_path), open(app_path, 'rb'), {'Expires': '0'})}
        res = self.__request("post", binary_upload_url, data=binary_data, files=files)
        if res.status_code == 200:
            # print(res.json())
            print("app上传成功")
            print("上传成功,下载链接: http://%s/%s?release_id=%s" % (download_domain, short_url,res.json()['release_id']))

            # QrCode.make_logo_qr(os.path.join("https://fir.im/", short_url), icon_path, "fir_%s.png"%(appinfo["labelname"]))
        else:
            raise Exception(res.content)

        #print("上传成功,下载链接: http://%s/%s" % (download_domain, short_url))

    def get_app_list(self):
        url = "%s?api_token=%s" % (self.app_url, self.api_token)
        res = self.__request("get", url)
        if res.status_code == 200:
            return res.json()
        else:
            raise Exception(res.content)

    def get_app_infos_by_id(self, id):
        '''

        :param id: 应用id,可在"应用管理"->"基本信息"查看
        :return:
        '''
        url = "%s/%s?api_token=%s" % (self.app_url, id, self.api_token)
        res = self.__request("get", url)
        if res.status_code == 200:
            return res.json()
        else:
            raise Exception(res.content)

    def modify_app_info(self, id, name=None, desc=None, short=None, genre_id=None,
                        is_opened=None, is_show_plaza=None, passwd=None,
                        store_link_visible=None):
        '''

        :param id: 应用id,可在"应用管理"->"基本信息"查看
        :param name: app 名称
        :param desc: app 详细描述
        :param short: 	app 短链接
        :param genre_id: 类别 id
        :param is_opened: 是否公开
        :param is_show_plaza: 是否展示在广场页
        :param passwd: 密码访问(不能和is_opened is_show_plaza联合使用)
        :param store_link_visible: 应用市场链接是否显示
        :return:
        '''
        url = "%s/%s" % (self.app_url, id)
        data = {
            "api_token": self.api_token,
        }
        if name: data["name"] = name
        if desc: data["desc"] = desc
        if short: data["short"] = short
        if genre_id: data["genre_id"] = genre_id
        if is_opened: data["is_opened"] = is_opened
        if is_show_plaza and not passwd: data["is_show_plaza"] = is_show_plaza
        if passwd and not is_show_plaza: data["passwd"] = passwd
        if is_show_plaza and passwd:
            raise Exception("密码访问不能和is_opened is_show_plaza联合使用")
        if store_link_visible: data["name"] = store_link_visible

        res = self.__request("put", url, data)
        if res.status_code == 200:
            return res.json()
        else:
            raise Exception(res.content)


if __name__ == '__main__':
    if len(sys.argv) != 3:
        raise ValueError('参数有误')

    fir = FirApi(sys.argv[1])

    app_path = sys.argv[2]
    if os.path.isfile(app_path):
        fir.upload_app(app_path)
    else:
        raise FileNotFoundError(app_path)



    # 获取版本信息
    # print(fir.get_version_by_id("5dfb54d3b2eb4612589c3040"))

    # 获取app详细信息
    # print(fir.get_app_infos_by_id("5dfb54d3b2eb4612589c3040"))
    # 获取列表
    # print(fir.get_app_list())

  • 通过Python上传到fir已关闭评论
  • 209 views
    A+
发布日期:2021年02月23日  所属分类:Python