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

import requests
import zipfile, re, os, random, io
from androguard.core.bytecodes import apk
import plistlib
import qrcode
from PIL import Image


def bytes2human(n):
    symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
    prefix = {}
    for i, s in enumerate(symbols):
        prefix[s] = 1 << (i + 1) * 10
    for s in reversed(symbols):
        if n >= prefix[s]:
            value = float(n) / prefix[s]
            return '%.1f%s' % (value, s)
    return '%sB' % n


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

    def make_app_png(self, png_path, filename):
        zf = zipfile.ZipFile(self.app_path)
        name_list = zf.namelist()
        if self.app_path.endswith("apk"):
            # pattern = re.compile(r'res/drawable[^/]*/app_icon.png')
            iconfile = apk.APK(self.app_path).get_app_icon()
            size = 999

        elif self.app_path.endswith("ipa"):
            size = 0
            pattern = re.compile(r'Payload/[^/]*.app/AppIcon[^/]*[^(ipad)].png')
            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")

        if not os.path.isdir(png_path):
            os.makedirs(png_path)

        with open(os.path.join(png_path, "%s.png" % filename), 'wb') as f:
            f.write(zf.read(iconfile))

        return "%s.png" % 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_min_sdk_version()
                self.result = {
                    "labelname": labelname,
                    "bundle_id": bundle_id,
                    "versioncode": versioncode,
                    "version": version,
                    "miniOSversion": sdk_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']
                miniOSversion = plist_root["MinimumOSVersion"]
                self.result = {
                    "labelname": labelname,
                    "bundle_id": bundle_id,
                    "versioncode": versioncode,
                    "version": version,
                    "miniOSversion": miniOSversion,
                    "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.fir.im/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"]

        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())
        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())
            QrCode.make_logo_qr(os.path.join("https://fir.im/", short_url), icon_path,
                                "fir_%s.png" % (appinfo["labelname"]))

        else:
            raise Exception(res.content)

    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)


class QrCode(object):

    @staticmethod
    def make_logo_qr(str, logo_path, save_path=None):
        qr = qrcode.QRCode(
            version=4,
            error_correction=qrcode.constants.ERROR_CORRECT_Q,
            box_size=8,
            border=2
        )
        # 添加转换内容
        qr.add_data(str)
        qr.make(fit=True)
        # 生成二维码
        img = qr.make_image()
        #
        img = img.convert("RGBA")

        # 添加logo
        if logo_path and os.path.exists(logo_path):
            icon = Image.open(logo_path)
            # 获取二维码图片的大小
            img_w, img_h = img.size

            factor = 4
            size_w = int(img_w / factor)
            size_h = int(img_h / factor)

            # logo图片的大小不能超过二维码图片的1/4
            icon_w, icon_h = icon.size
            if icon_w > size_w:
                icon_w = size_w
            if icon_h > size_h:
                icon_h = size_h
            icon = icon.resize((icon_w, icon_h), Image.ANTIALIAS)
            # 详见：http://pillow.readthedocs.org/handbook/tutorial.html

            # 计算logo在二维码图中的位置
            w = int((img_w - icon_w) / 2)
            h = int((img_h - icon_h) / 2)
            icon = icon.convert("RGBA")
            img.paste(icon, (w, h), icon)
            # 详见：http://pillow.readthedocs.org/reference/Image.html#PIL.Image.Image.paste

        # 保存处理后图片
        if save_path:
            if not os.path.isdir(os.path.dirname(save_path)):
                os.makedirs(os.path.dirname(save_path))
            img.save(save_path)
        else:
            imgByteArr = io.BytesIO()
            img.save(imgByteArr, format="PNG")
            return imgByteArr.getvalue()


if __name__ == '__main__':
    apk_path = "/root/hehemajiang.apk"
    API_TOKEN = "3d2a41159a7d8c396ddf0e88799de14a"

    fir = FirApi(API_TOKEN)

    # 上传
    fir.upload_app(apk_path)

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

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