
"""微信接口类"""

from kyger.utility import *
from kyger.parser_ini import Config


class Weixin(object):
    def __init__(self, db=None, kg=None, xml_dict={}):
        # 数据库
        from kyger.db import MySQL
        self.db = db if db else MySQL()
        self.kg = kg
        self.xml = xml_dict  # 微信服务器发过来的xml解析之后的字典数据
        self.access_token = self.get_access_token()  # access_token

    @staticmethod
    def form_data(file_path):
        """
        form-data数据拼接
        :param file_path: [str] 文件路径
        :return:
        """
        if exists(file_path, type='file'):
            broundy = str_random(32, 3, 1)
            file_data = get_contents(file_path, 'rb', charset="")
            param = (broundy, file_path.split('/')[-1])
            start = '--%s\r\nContent-Disposition: form-data; name="media"; filename="%s"\r\n\r\n' % param
            end = '\r\n--%s--\r\n' % broundy
            data_code = start.encode() + file_data + end.encode()
            header = {"Content-Type": "multipart/form-data; boundary=%s" % broundy}
            return data_code, header
        else:
            return False

    def check_signature(self):
        """
        验证请求是否为微信服务器发来的
        """
        import hashlib
        signature = self.kg['get'].get('signature', '')
        timestamp = self.kg['get'].get('timestamp', '')
        nonce = self.kg['get'].get('nonce', '')
        token = Config('/config/settings.ini').get('weixin')['token']
        param_list = [token, timestamp, nonce]
        param_list.sort()
        str_temp = ''.join(param_list)
        sha1 = hashlib.sha1(str_temp.encode('utf-8'))
        hashcode = sha1.hexdigest()
        if hashcode == signature:
            if self.kg['get'].get('echostr', ''):
                return self.kg['get'].get('echostr', '')
            else:
                return True
        else:
            return False

    def get_access_token(self):
        """
        :return: 返回access_token
        """
        weixin_ini = Config('/config/settings.ini').get('weixin')  # 读取配置文件
        # 判断是否有缓存文件
        if exists(url_absolute('temp/access_token.json'), type='file'):
            access_token = json2dict(file='temp/access_token.json')

            # 小于1个半小时，直接读缓存文件
            dif_time = int(self.kg['run_start_time'][1]) - access_token['date']
            if dif_time < 5400 and (access_token['appid'], access_token['appsecret']) == (weixin_ini['appid'], weixin_ini['appsecret']):
                return access_token['access_token']

        # 文件不存在或者过期
        url = 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s'
        access_token_url = url % (weixin_ini['appid'], weixin_ini['appsecret'])

        # 获取access_token
        weixin_data = json2dict(get_contents(access_token_url))
        if weixin_data.get('access_token', ''):
            temp = {
                "access_token": weixin_data['access_token'],
                "date": int(self.kg['run_start_time'][1]),
                "appid": weixin_ini['appid'],
                "appsecret": weixin_ini['appsecret']
            }
            dict2json_file(temp, file='temp/access_token.json')
            return weixin_data['access_token']
        # 没有取到
        else:
            return ''

    def message_save(self, type=0, content='', audit=0, openid=''):
        """
        消息记录，向表[weixin_message]中写入记录(用户消息及事件记录表、消息推送及回复数据记录)
        :param type: [int] 消息类型，见数据表字段说明
        :param content: [str] 接收或推送（回复）的消息内容：消息类型相对应的消息内容
        :param audit: [int] 接收的消息审核状态：0未审核、1已审核
        :param openid: [str] 用户OPENID，默认为当前交到的OPENID
        :return:
        """
        weixin_ini = Config('/config/settings.ini').get('weixin')  # 读取配置文件
        if not openid: openid = self.xml.get('FromUserName', '')
        if not openid: return False
        data = self.db.list(table='activity', where='acstart = 1', shift=1)
        actid = data['id'] if data else 0
        add = {
            'openid': openid,
            'type': type,
            'content': content,
            'recom': 0,
            'audit': audit,
            'appid': weixin_ini['appid'],
            'actid': actid,
            'addtime': int(self.kg['run_start_time'][1])
        }
        r_id = self.db.add('message', add)
        if type == 2:
            self.download_media(r_id, '.jpg')
        elif type == 3:
            self.download_media(r_id, '.amr')
        elif type == 4:
            self.download_media(r_id, '.mov')

    def download_media(self, id, suffix='.jpg'):
        """
        下载消息中的媒体文件
        :param id: [int]
        :param suffix: [str] 后缀名
        :return:
        """
        current = int(self.kg['run_start_time'][1])
        path = url_absolute('/upload/message/%s' % date(current, '%Y%m'))

        # 路径不存在创建目录
        if not exists(path, type='dir'):
            import os
            os.makedirs(path)
        url = 'http://file.api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s'
        res = get_contents(url % (self.access_token, self.xml['MediaId']), charset='')
        filename = path + "%s_%s" % (str(id), str_random(3, 3, 1)) + suffix
        put_contents(filename, res, 'wb')
        edit = {'content': '/' + url_relatively(filename)}
        self.db.edit('message', edit, id)

    def get_openid(self, code=''):
        """
        微信页面获取openid
        :param code: [str] 微信返回的code
        :return:
        """
        wx_config = Config('/config/settings.ini').get('weixin')
        # 完成回调得到code获取openid
        if code:
            url = 'https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code'
            url = url % (wx_config['appid'], wx_config['appsecret'], code)
            res = json2dict(get_contents(url, method='GET'))
            return self.errcode2msg(res)
        # 进行重定向进行回调
        else:
            param = (wx_config['appid'], self.kg['server']['WEB_URL'])
            url = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=getopenid#wechat_redirect'
            return url % param

    def update_menu(self, menu):
        """
        创建或更新菜单
        :param menu: [dict] 菜单数据
        """
        url = 'https://api.weixin.qq.com/cgi-bin/menu/create?access_token=' + self.access_token
        msg = json2dict(get_contents(url, data=menu, method='POST'))
        return self.errcode2msg(msg)

    def get_menu(self):
        """
        获取菜单数据
        :return: 返回自定义菜单配置数据也即上一次创建自定义菜单的pers_data数据
        """
        url = "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=" + self.access_token
        d = json2dict(get_contents(url=url))
        return self.errcode2msg(d)

    def get_fans_list(self, next_openid=''):
        """
        拉取列表的后一个用户的OPENID
        :param next_openid: [str] 用户的id
        :return: 返回响应码 正确时{
                            "total":2,
                            "count":2,
                            "data":{
                            "openid":["OPENID1","OPENID2"]},
                            "next_openid":"NEXT_OPENID"
                        }
        """
        next_openid = '&next_openid=%s' % next_openid if next_openid else ''
        url = 'https://api.weixin.qq.com/cgi-bin/user/get?access_token=%s%s' % (self.access_token, next_openid)
        d = json2dict(get_contents(url))
        return self.errcode2msg(d)

    def get_fans_info(self, openid, group=1):
        """
        获取用户信息
        :param openid: [str] 用户的id
        :param group: [int] 用户组id
        :return: 返回响应码 正确时{
                        "subscribe": 1,
                        "openid": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M",
                        "nickname": "Band",
                        "sex": 1,
                        "language": "zh_CN",
                        "city": "广州",
                        "province": "广东",
                        "country": "中国",
                        "headimgurl":"http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
                        "subscribe_time": 1382694957,
                        "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
                        "remark": "",
                        "groupid": 0,
                        "tagid_list":[128,2],
                        "subscribe_scene": "ADD_SCENE_QR_CODE",
                        "qr_scene": 98765,
                        "qr_scene_str": ""
                    }
        """

        # 获取用户信息
        url = 'https://api.weixin.qq.com/cgi-bin/user/info?access_token=%s&openid=%s&lang=zh_CN' % (self.access_token, openid)
        data = json2dict(get_contents(url))

        # 获取用户分组ID
        if group == 1:
            url = 'https://api.weixin.qq.com/cgi-bin/groups/getid?access_token=%s' % self.access_token
            g = json2dict(get_contents(url, data=json2dict({"openid": openid}, trans=False), method='POST'))
            data['groupid'] = numeric(g['groupid'])
        return data

    def new_group(self, name):
        """
        创建新分组
        :param name: [str] 分组名
        :return:
        """
        url = "https://api.weixin.qq.com/cgi-bin/groups/create?access_token=%s" % self.access_token
        wx_data = json2dict(get_contents(url, data=json2dict({"group": {"name": name}}, trans=False), method='POST'))
        if wx_data.get('errcode', ''):
            return False
        self.db.add('wx_group', {'id': wx_data['group']['id'], 'name': wx_data['group']['name'], 'count': 0})
        return wx_data

    def update_group(self, group_id, name):
        """
        修改分组名
        :param group_id: [int] 要修改的分组id
        :param name:  [str] 修改的分组名
        :return: {"errcode": 0, "errmsg": "ok"}或{"errcode":40013,"errmsg":"invalid appid"}
        """
        url = "https://api.weixin.qq.com/cgi-bin/groups/update?access_token=%s" % self.access_token
        wx_data = json2dict(get_contents(url, data=json2dict({"group": {"id": group_id, "name": name}}, trans=False), method='POST'))
        if wx_data.get('errmsg', '') == 'ok':
            self.db.edit('wx_group', {"name": name}, 'id = %s' % group_id)
        return wx_data

    def move_fans_group(self, openid, group_id):
        """
        移动用户的分组
        :param openid: [str] 用户的openid
        :param group_id: [int] 分组的id
        :return:{"errcode": 0, "errmsg": "ok"}或{"errcode":40013,"errmsg":"invalid appid"}
        """
        url = "https://api.weixin.qq.com/cgi-bin/groups/members/update?access_token=%s" % self.access_token
        wx_data = json2dict(get_contents(url, data=json2dict({"openid": openid, "to_groupid": group_id}, trans=False), method='POST'))
        if wx_data.get('errmsg', '') == 'ok':
            self.db.edit('wx_fans', {"groupid": group_id}, 'openid = "%s"' % openid)
        return wx_data

    def get_groups(self):
        """
        获取所有用户(粉丝/关注者)分组信息
        """
        url = 'https://api.weixin.qq.com/cgi-bin/groups/get?access_token=%s' % self.access_token
        data = json2dict(get_contents(url))
        return data['groups']

    def syn_groups(self):
        """
        同步用户(粉丝)分组信息
        """
        # 获取并更新用户组
        groups = self.get_groups()
        if isinstance(groups[0]['id'], int):
            # 清空现有会员组
            self.db.run_sql("TRUNCATE `%swx_group`" % self.db.cfg["prefix"], act='dele', log=True)
            values = []
            for row in groups:
                values.append('(%s, "%s", %s)' % (row['id'], row['name'], row['count']))
            self.db.run_sql("INSERT INTO `%swx_group` (`id`, `name`, `count`) VALUES %s" % (self.db.cfg["prefix"], ','.join(values)), act='add')
            return True
        else:
            return False

    def wx_cs_get(self):
        """
        获取客服基本信息
        :return:
        """
        url = "https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=%s" % self.access_token
        wx_data = json2dict(get_contents(url, method='GET'))
        return self.errcode2msg(wx_data)

    def wx_cs_online(self, data):
        """
        整合客服数据和在线接待情况
        :param data: [dict] 调用wx_cs_get获得的数据，一般直接读取缓存文件temp/wx_cx.json
        :return:
        """
        # 在线状态
        url = "https://api.weixin.qq.com/cgi-bin/customservice/getonlinekflist?access_token=%s" % self.access_token
        online = json2dict(get_contents(url, method='GET'))
        res = {}
        if online and 'kf_online_list' in online:
            for row in online['kf_online_list']:
                res[str(row['kf_id'])] = row

            for row in data:
                if row['kf_id'] in res:
                    row['online'] = 1
                    row['accepted_case'] = res[row['kf_id']]['accepted_case']
                else:
                    row['online'] = 0
                    row['accepted_case'] = 0
        return data

    def wx_cs_add(self, kf_account, nickname):
        """
        添加客服
        :param kf_account: [str] 完整客服帐号，格式为：帐号前缀@公众号微信号，帐号前缀最多10个字符，必须是英文、数字字符或者下划线，后缀为公众号微信号，长度不超过30个字符
        :param nickname: [str] 客服昵称，最长16个字
        :return:
        """
        import json
        url = "https://api.weixin.qq.com/customservice/kfaccount/add?access_token=%s" % self.access_token
        account = {
            "kf_account": kf_account,
            "nickname": nickname
        }
        wx_data = json2dict(get_contents(url, data=json.dumps(account, ensure_ascii=False), method='POST'))
        return self.errcode2msg(wx_data)

    def wx_cs_del(self, kf_account):
        """
        删除客服帐号
        :param kf_account: [str] 完整客服帐号，格式为：帐号前缀@公众号微信号，帐号前缀最多10个字符，必须是英文、数字字符或者下划线，后缀为公众号微信号，长度不超过30个字符
        :return:
        """
        url = "https://api.weixin.qq.com/customservice/kfaccount/del?access_token=%s" % self.access_token
        wx_data = json2dict(get_contents(url, data=json2dict({"kf_account": kf_account}, trans=False), method='POST'))
        return self.errcode2msg(wx_data)

    def wx_cs_update(self, kf_account, nickname):
        """
        设置客服信息
        :param kf_account: [str] 完整客服帐号，格式为：帐号前缀@公众号微信号，帐号前缀最多10个字符，必须是英文、数字字符或者下划线，后缀为公众号微信号，长度不超过30个字符
        :param nickname: [str] 客服昵称，最长16个字
        :return:
        """
        url = "https://api.weixin.qq.com/customservice/kfaccount/update?access_token=%s" % self.access_token
        account = {'kf_account': kf_account, 'nickname': nickname}
        wx_data = json2dict(get_contents(url, data=json2dict(account, trans=False), method='POST'))
        return self.errcode2msg(wx_data)

    def wx_cs_headimg(self, kf_account, file_path):
        """
        上传客服头像
        :param kf_account: [str] 完整客服帐号，格式为：帐号前缀@公众号微信号
        :param file_path: [str] 上传文件物理路径
        :return:
        """
        # 需要上传文件，函数暂不可用
        url = "https://api.weixin.qq.com/customservice/kfaccount/uploadheadimg?access_token=%s&kf_account=%s"
        file_path = url_absolute(file_path)
        res = self.form_data(file_path)
        if res:
            data_code, header = res
            d = json2dict(get_contents(url % (self.access_token, kf_account), data=data_code, method='POST', header=header))
            return self.errcode2msg(d)
        else:
            return False

    def wx_cs_invite(self, kf_account, invite_wx):
        """
        邀请绑定客服帐号
        :param kf_account: [str] 完整客服帐号，格式为：帐号前缀@公众号微信号
        :param invite_wx: [str] 接收绑定邀请的客服微信号
        :return:
        """
        url = "https://api.weixin.qq.com/customservice/kfaccount/inviteworker?access_token=%s" % self.access_token
        account = {"kf_account": kf_account, "invite_wx": invite_wx}
        wx_data = json2dict(get_contents(url, data=json2dict(account, trans=False), method='POST'))
        return self.errcode2msg(wx_data)

    def upload(self, type, data):
        """
        文件上传到微信服务器,临时素材
        :param type:
        :param data:
        :return:正确情况下的返回JSON数据包结果如下：
            {"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789}
        """
        access_token = self.get_access_token()
        url = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s" % (access_token, type)
        return get_contents(url=url, data=data, method='POST')

    def load(self, media_id):
        """
        获取临时素材,下载临时素材
        :param media_id: 媒体文件ID
        :return: 如果返回的是视频消息素材，则内容如下：
                {
                 "video_url":DOWN_URL
                }
        """
        access_token = self.get_access_token()
        url = "https://api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s" % (access_token, media_id)
        return get_contents(url=url)

    def get_wx_material_list(self, type, offset=0, count=20):
        """
        获取腾讯素材列表
        :param type: [str] 素材的类型，图片（image）、视频（video）、语音 （voice）、图文（news）
        :param offset: [int] 从全部素材的该偏移位置开始返回，0表示从第一个素材 返回
        :param count: [int] 返回素材的数量，取值在1到20之间
        :param get_all: [bool] 是否取全部
        :return:
        """
        # 限制个数与偏移
        count = numeric(count, 1, 20)
        offset = numeric(offset, 0)

        # 类型判断
        if type not in ['image', 'video', 'voice', 'news']:
            return False

        data = {'type': type, 'offset': offset, 'count': count}
        url = 'https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=' + self.access_token
        msg = json2dict(get_contents(url, data=json2dict(data, trans=False), method='POST'))
        return self.errcode2msg(msg)

    def wx_material_news_uploadimg(self, file_path):
        """
        上传图文消息内的图片获取URL
        :param file_path: [str] 上传文件物理路径
        :return:
        """
        file_path = url_absolute(file_path)
        url = "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=%s"

        res = self.form_data(file_path)
        if res:
            data_code, header = res
            d = json2dict(get_contents(url % self.access_token, data=data_code, method='POST', header=header))
            return self.errcode2msg(d)
        else:
            return False

    def wx_material_news_add(self, articles):
        """
        上传图文素材
        :param articles: [list] 图文数据，articles: [{
            "title": TITLE,
            "thumb_media_id": THUMB_MEDIA_ID,
            "author": AUTHOR,
            "digest": DIGEST,
            "show_cover_pic": SHOW_COVER_PIC(0 / 1),
            "content": CONTENT,
            "content_source_url": CONTENT_SOURCE_URL,
            "need_open_comment":1,
            "only_fans_can_comment":1
        }, {...}]
        :return:
        """
        url = "https://api.weixin.qq.com/cgi-bin/material/add_news?access_token=%s" % self.access_token
        res = json2dict(get_contents(url, data=json2dict({'articles': articles}, trans=False), method='POST'))
        return self.errcode2msg(res)

    def wx_material_add(self, type, file_path, title='', introduction=''):
        """
        其他素材上传
        :param type: [str] 媒体文件类型，分别有图片（image）、语音（voice）、视频（video）和缩略图（thumb）
        :param file_path: [str] 上传文件物理路径
        :param title: [str] 标题，上传视频素材时必填
        :param introduction: [str] 描述，上传视频素材时必填
        :return:
        """
        import os
        if type not in ['image', 'voice', 'video', 'thumb']:
            return False

        data = {}
        if type == "video":
            if not (title and introduction):
                return False
            data["description"] = json2dict({"title": title, "introduction": introduction}, trans=False)

        file_path = url_absolute(file_path)
        url = "https://api.weixin.qq.com/cgi-bin/material/add_material?type=%s&access_token=%s"
        res = self.form_data(file_path)
        if res:
            data_code, header = res
            datas = json2dict(get_contents(url % (type, self.access_token), data=data_code, method='POST', header=header))
            return self.errcode2msg(datas)
        else:
            return False

    def wx_material_news_update(self, data):
        """
        修改图文素材
        :param data: [list] 图文数据，articles:{
            "media_id":MEDIA_ID,
            "index":INDEX,
            "articles": {
            "title": TITLE,
            "thumb_media_id": THUMB_MEDIA_ID,
            "author": AUTHOR,
            "digest": DIGEST,
            "show_cover_pic": SHOW_COVER_PIC(0 / 1),
            "content": CONTENT,
            "content_source_url": CONTENT_SOURCE_URL
            }
        }
        :return:
        """
        url = "https://api.weixin.qq.com/cgi-bin/material/update_news?access_token=" + self.access_token
        res = json2dict(get_contents(url, data=json2dict(data, trans=False), method='POST'))
        return self.errcode2msg(res)

    def wx_material_news_del(self, media_id):
        """
        删除永久素材
        :param media_id: [str] 要删除的素材id
        :return:
        """
        url = "https://api.weixin.qq.com/cgi-bin/material/del_material?access_token=" + self.access_token
        return json2dict(get_contents(url, data=json2dict({"media_id": media_id}, trans=False), method='POST'))

    def push_text(self, openid, content):
        """
        发送（推送）客服文本消息
        :param openid: [str] OPENID
        :param content: [str] 要发送的文本内容
        :return:
        """
        data = {"touser": openid, "msgtype": "text", "text": {"content": content}}
        url = 'https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=' + self.access_token
        res = json2dict(get_contents(url, data=json2dict(data, trans=False), method='POST'))
        if res.get('errcode', '') == 0:
            self.message_save(98, content, openid=openid)
        return self.errcode2msg(res)

    def push_news(self, data):
        """

        :param data:
        :return:
        """
        pass

    def push_material(self, openid, id):
        """
        发送（推送）客服消息，推送素材库中的素材,2018年开始不允许推送多图文
        :param openid: [str] OPENID
        :param id: [int] 素材ID
        :return:
        """
        material = self.get_material(id)
        if material['type'] == 2:
            data = {
                    "touser": openid,
                    "msgtype": "text",
                    "text": {"content": material['introduction']}
                }
        elif material['type'] == 0:
            data = {
                "touser": openid,
                "msgtype": "news",
                "news": {
                    "articles": [{
                         "title": material['title'][0],
                         "description": material['introduction'],
                         "url": material['key2url'][0],
                         "picurl": material['picture'][0]
                     }]
                }
            }
        else:
            return False
        url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + self.access_token
        res = json2dict(get_contents(url, data=json2dict(data, trans=False), method='POST'))
        if res.get('errcode', '') == 0:
            self.message_save(99, '向用户推送信息，素材ID：%s' % id, openid=openid)
        return res

    def send_text(self, content):
        """
        发送（回复）被动文本消息
        :param content: [str] 要发送的文本内容
        :return:
        """
        xml_code = '<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[%s]]></Content></xml>'
        return xml_code % (self.xml['FromUserName'], self.xml['ToUserName'], int(self.kg['run_start_time'][1]), content)

    def send_news(self, data):
        """
        发送（回复）被动图文消息
        :param data:
        :return:
        """
        xml_code = '<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[news]]></MsgType><ArticleCount>%s</ArticleCount><Articles>'
        xml_code = xml_code % (self.xml['FromUserName'], self.xml['ToUserName'], int(self.kg['run_start_time'][1]), len(data))
        articles = ''
        for row in data:
            article = '<item><Title><![CDATA[%s]]></Title><Description><![CDATA[%s]]></Description><PicUrl><![CDATA[%s]]></PicUrl><Url><![CDATA[%s]]></Url></item>'
            articles += article % (row[0], row[1], row[2], row[3])
        return xml_code + articles + '</Articles></xml>'

    def send_image(self, mediaid):
        """
        发送（回复）被动图片消息
        :param mediaid: [str] 通过上传多媒体文件，得到的id
        :return:
        """
        xml_code = '<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[image]]></MsgType><Image><MediaId><![CDATA[%s]]></MediaId></Image></xml>'
        return xml_code % (self.xml['FromUserName'], self.xml['ToUserName'], int(self.kg['run_start_time'][1]), mediaid)

    def send_voice(self, mediaid):
        """
        发送（回复）被动语音消息
        :param mediaid: [str] 通过上传多媒体文件，得到的id
        :return:
        """
        xml_code = '<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[voice]]></MsgType><Voice><MediaId><![CDATA[%s]]></MediaId></Voice></xml>'
        return xml_code % (self.xml['FromUserName'], self.xml['ToUserName'], int(self.kg['run_start_time'][1]), mediaid)

    def send_video(self, mediaid, title, description):
        """
        发送（回复）被动视频消息
        :param mediaid: [str] 通过上传多媒体文件，得到的id
        :param title: [str] 通过上传多媒体文件，得到的title标题
        :param description: [str] 通过上传多媒体文件，得到的description描述
        :return:
        """
        xml_code = '<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[video]]></MsgType><Video><MediaId><![CDATA[%s]]></MediaId><Title><![CDATA[%s]]></Title><Description><![CDATA[%s]]></Description></Video></xml>'
        return xml_code % (self.xml['FromUserName'], self.xml['ToUserName'], int(self.kg['run_start_time'][1]), mediaid, title, description)

    def send_music(self, title, content, url):
        """
        发送（回复）被动音乐消息
        :param title: [str] 音乐标题
        :param content: [str] 音乐简介
        :param url: [str] 要发送的音乐地址，高质量和普通音乐均采用该地址，暂不区分
        :return:
        """
        xml_code = '<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>%s</CreateTime><MsgType><![CDATA[music]]></MsgType><Music><Title><![CDATA[%s]]></Title><Description><![CDATA[%s]]></Description><MusicUrl><![CDATA[%s]]></MusicUrl><HQMusicUrl><![CDATA[%s]]></HQMusicUrl></Music><FuncFlag>0</FuncFlag></xml>'
        return xml_code % (self.xml['FromUserName'], self.xml['ToUserName'], int(self.kg['run_start_time'][1]), title, content, url, url)

    def send_material(self, id):
        """
        发送（回复）被动本地素材库中的素材,直接发送，自动判断不同类型的素材，单图文、多图文、文字
        :param id: [int] 素材ID
        :return:
        """
        host = self.kg['server']['HTTP_HOST']
        data = self.get_material(id)
        if data['type'] == 0:
            res = self.send_news([[data['title'][0], data['introduction'], host + data['picture'][0], data['key2url'][0]]])
            self.message_save(99, '自动本地素材推送，素材ID：%s' % id)
        elif data['type'] == 1:
            news_list = []
            for i in range(len(data['title'])):
                news_list.append([data['title'][i], '', host + data['picture'][i], data['key2url'][i]])
            res = self.send_news(news_list)
            self.message_save(99, '自动本地素材推送，素材ID：%s' % id)
        elif data['type'] == 2:
            res = self.send_text(data['introduction'])
            self.message_save(98, data['introduction'])
        else:
            res = self.send_text('Material Does Not Exist.\rID:%s' % id)
        return res

    def send_wx_material(self, id):
        """
        发送（回复）被动微信素材库中的素材
        :param id: [int] 素材id
        :return:
        """
        wx_material = self.db.list(table="wx_material", where=id, shift=1)
        if wx_material:
            data = json2dict(self.get_wx_material(wx_material['media_id']))
            if wx_material['type'] == "news":
                news = []
                for row in data['news_item']:
                    news.append([row['title'], row['digest'], row['thumb_url'], row['url']])
                res = self.send_news(news)
            elif wx_material['type'] == "image":
                res = self.send_image(wx_material['media_id'])
            elif wx_material['type'] == "voice":
                res = self.send_voice(wx_material['media_id'])
            else:
                res = self.send_video(material['media_id'], material['name'], material['description'])
            self.message_save(99, '自动微信素材推送，素材ID：%s' % id)
        else:
            res = self.send_text('Material Does Not Exist.\rID:%s' % id)
        return res

    def wx_material_send_by_tagid(self, type, media_id, tag_id, is_to_all=True, send_ignore_reprint=0, title='', description=''):
        """
        根据标签进行群发
        :param type: [str] 群发的消息类型，图文消息为mpnews，文本消息为text，语音为voice，音乐为music，图片为image，视频为mpvideo，卡券为wxcard
        :param media_id: [str] 用于群发的消息的media_id
        :param tag_id: [int] 发到的标签的tag_id，参见用户管理中用户分组接口，若is_to_all值为true，可不填写tag_id
        :param is_to_all: [bool] 用于设定是否向全部用户发送，值为true或false，选择true该消息群发给所有用户，选择false可根据tag_id发送给指定群组的用户
        :param send_ignore_reprint: [int] 图文消息被判定为转载时，是否继续群发。 1为继续群发（转载），0为停止群发。 该参数默认为0。
        :param title: [str] 消息的标题
        :param description: [str] 消息的描述
        :return:
        """
        if type not in ['mpnews', 'text', 'voice', 'music', 'image', 'mpvideo', 'wxcard']:
            return False
        url = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token=" + self.access_token
        send = {"filter": {"is_to_all": is_to_all, "tag_id": tag_id}, "msgtype": type}
        # 微信卡券
        if type == 'wxcard':
            send['wxcard'] = {"card_id": media_id}
        # 视频
        elif type == 'mpvideo':
            send['mpvideo'] = {'media_id': media_id, 'title': title, 'description': description}
        # 文本
        elif type == 'text':
            send['text'] = {'content': media_id}
        # 图文
        elif type == "mpnews":
            send['mpnews'] = {'media_id': media_id}
            send['send_ignore_reprint'] = send_ignore_reprint
        else:
            send[type] = {'media_id': media_id}
        wx_data = json2dict(get_contents(url, data=json2dict(send, trans=False), method='POST'))
        if wx_data.get('errcode', '') == 0:
            mater = self.db.list(table='wx_material', where='media_id="%s"' % media_id, shift=1)
            m_id = mater['id'] if mater else '该素材暂未同步到本地'
            self.message_save(99, '向用户推送微信素材，素材ID：%s' % m_id)
        return wx_data

    def wx_material_send_by_openid(self, type, media_id, openid, send_ignore_reprint = 0, title='', description=''):
        """
        根据OpenID列表群发
        :param type: [str] 群发的消息类型，图文消息为mpnews，文本消息为text，语音为voice，音乐为music，图片为image，视频为mpvideo，卡券为wxcard
        :param media_id: [str] 用于群发的消息的media_id
        :param openid: [list] 填写图文消息的接收者，一串OpenID列表，OpenID最少2个，最多10000个
        :param send_ignore_reprint: [int] 图文消息被判定为转载时，是否继续群发。 1为继续群发（转载），0为停止群发。 该参数默认为0。
        :param title: [str] 消息的标题
        :param description: [str] 消息的描述
        :return:
        """
        pass

    def wx_material_preview(self, type, media_id, wxname, send_ignore_reprint=0, title='', description=''):
        """
        预览接口
        :param type: [str] 群发的消息类型，图文消息为mpnews，文本消息为text，语音为voice，音乐为music，图片为image，视频为mpvideo，卡券为wxcard
        :param media_id: [str] 用于群发的消息的media_id
        :param wxname: [str] 微信号/QQ号/手机号/openid
        :param send_ignore_reprint: [int] 图文消息被判定为转载时，是否继续群发。 1为继续群发（转载），0为停止群发。 该参数默认为0。
        :param title: [str] 消息的标题
        :param description: [str] 消息的描述
        :return:
        """
        if type not in ['mpnews', 'text', 'voice', 'music', 'image', 'mpvideo', 'wxcard']: return False
        send = {'touser': wxname, 'msgtype': type} if len(wxname) > 20 else {'towxname': wxname, 'msgtype': type}
        # 微信卡券
        if type == 'wxcard':
            send['wxcard'] = {"card_id": media_id}
        # 视频
        elif type == 'mpvideo':
            send['mpvideo'] = {'media_id': media_id, 'title': title, 'description': description}
        # 文本
        elif type == 'text':
            send['text'] = {'content': media_id}
        # 图文
        elif type == "mpnews":
            send['mpnews'] = {'media_id': media_id}
            send['send_ignore_reprint'] = send_ignore_reprint
        else:
            send[type] = {'media_id': media_id}

        url = "https://api.weixin.qq.com/cgi-bin/message/mass/preview?access_token=" + self.access_token
        wx_data = json2dict(get_contents(url, data=json2dict(send, trans=False), method='POST'))
        if wx_data.get('errcode', '') == 0:
            mater = self.db.list(table='wx_material', where='media_id="%s"' % media_id, shift=1)
            m_id = mater['id'] if mater else '该素材暂未同步到本地'
            self.message_save(99, '向用户推送微信素材预览，素材ID：%s' % m_id)
        return wx_data

    def get_material(self, id):
        """
        获取素村，单个指定的素材
        :param id: [int] 元素ID
        :return:
        """
        data = self.db.list(table="material", where=id, shift=1)
        data['title'] = json2dict(data['title'])
        data['picture'] = json2dict(data['picture'])
        data['key2url'] = json2dict(data['key2url'])
        return data

    def get_wx_material(self, media_id, charset="utf-8"):
        """
        获取永久素材
        :param media_id: [str] 素材ID
        :param charset: [str] 编码，因为会直接返回二进制数据，所以把编码设置为可选
        :return:
        """
        url = "https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=%s" % self.access_token
        wx_data = get_contents(url, data=json2dict({"media_id": media_id}, trans=False), method='POST', charset=charset)
        return wx_data

    def get_job_status(self, msg_id):
        """
        查询群发消息发送状态【订阅号与服务号认证后均可用】
        :param string msg_id: 群发返回的msg_id
        :param charset: [str] 编码，因为会直接返回二进制数据，所以把编码设置为可选
        :return: array
        """
        url="https://api.weixin.qq.com/cgi-bin/message/mass/get"
        if msg_id :
            data = {"msg_id": msg_id}
            wx_data = json2dict(get_contents(url, data=json2dict(data, trans=False), method='POST'))
            return wx_data
        else:
            return false

    def questions(self):
        """
        使用关键词回复规则
        :return:
        """
        msg = self.xml['Content']
        self.message_save(1, msg)
        # 精准匹配
        data = self.db.list(
            table='wx_questions',
            where="enable=1 && `rule`= 1 && JSON_CONTAINS(ask, '[\"%s\"]')" % msg,
            order="RAND()",
            shift=1
        )

        # 广泛匹配
        if not data:
            # 完整匹配
            data = self.db.list(
                table='wx_questions',
                where="enable=1 && `rule`= 0 && JSON_CONTAINS(ask, '[\"%s\"]')" % msg,
                order="RAND()",
                shift=1
            )
            # 包含匹配
            if not data:
                data = self.db.list(
                    table='wx_questions',
                    where="enable=1 && `rule`= 0 && ask like '%%%s%%'" % msg,
                    order="RAND()",
                    shift=1
                )

        if not data: return ''
        if data['type'] == 0:
            res = self.send_material(data['mid'])
        elif data['type'] == 1:
            res = self.send_wx_material(data['mid'])
        else:
            res = self.send_text(data['answer'])
        return res

    def create_session(self, kf_account, openid):
        """
        创建客服会话
        :param kf_account: [str] 完整客服帐号，格式为：帐号前缀@公众号微信号
        :param openid: [str] 粉丝的openid
        :return:{
              "errcode" : 0,
              "errmsg"  :  "ok"
           }
        """
        url = "https://api.weixin.qq.com/customservice/kfsession/create?access_token=" + self.access_token
        data = {"kf_account": kf_account, "openid": openid}
        wx_data = json2dict(get_contents(url, data=json2dict(data, trans=False), method='POST'))
        return self.errcode2msg(wx_data)

    def close_session(self, kf_account, openid):
        """
        关闭客服会话
        :param kf_account: [str] 完整客服帐号，格式为：帐号前缀@公众号微信号
        :param openid: [str] 粉丝的openid
        :return:
        """
        url = "https://api.weixin.qq.com/customservice/kfsession/close?access_token=" + self.access_token
        data = {"kf_account": kf_account, "openid": openid}
        wx_data = json2dict(get_contents(url, data=json2dict(data, trans=False), method='POST'))
        return self.errcode2msg(wx_data)

    def get_session(self, openid):
        """
        获取粉丝的会话
        :param openid: [str] 完整客服帐号，格式为：帐号前缀@公众号微信号
        :return:
        """
        url = "https://api.weixin.qq.com/customservice/kfsession/getsession?access_token=%s&openid=%s"
        wx_data = json2dict(get_contents(url % (self.access_token, openid), method='GET'))
        return self.errcode2msg(wx_data)

    def get_code(self, redirect_uri):
        """
        网页授权
        :param redirect_uri: 授权后跳转的网页
        :return:
        """
        weixin_ini = Config('/config/settings.ini').get('weixin')  # 读取配置文件
        redirect_uri = str_replace(redirect_uri, [':', '/'], ['%3A', '%2F'])
        code_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_userinfo&state=STATE&connect_redirect=1#wechat_redirect" % (weixin_ini['appid'], redirect_uri)
        return code_url

    def get_basecode(self, redirect_uri):
        """
        网页授权,此方式无感获取openid
        :param redirect_uri: 授权后跳转的网页
        :return:
        """
        weixin_ini = Config('/config/settings.ini').get('weixin')  # 读取配置文件
        redirect_uri = str_replace(redirect_uri, [':', '/'], ['%3A', '%2F'])
        code_url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=snsapi_base&state=123#wechat_redirect" % (
        weixin_ini['appid'], redirect_uri)
        return code_url

    def get_access_tokens(self, code):
        """
        不同于get_access_token
        通过code换取网页授权access_token
        :param code: 授权网页,用户同意后获取的code
        :return: access_token	网页授权接口调用凭证,注意：此access_token与基础支持的access_token不同
                expires_in	access_token接口调用凭证超时时间，单位（秒）
                refresh_token	用户刷新access_token
                openid	用户唯一标识，请注意，在未关注公众号时，用户访问公众号的网页，也会产生一个用户和公众号唯一的OpenID
                scope	用户授权的作用域，使用逗号（,）分隔
                例如:
                {
                  "access_token":"ACCESS_TOKEN",
                  "expires_in":7200,
                  "refresh_token":"REFRESH_TOKEN",
                  "openid":"OPENID",
                  "scope":"SCOPE"
                }
        """
        weixin_ini = Config('/config/settings.ini').get('weixin')  # 读取配置文件
        token_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code" % (weixin_ini['appid'], weixin_ini['appsecret'], code)
        res = json2dict(get_contents(token_url, method='GET'))
        return self.errcode2msg(res)

    def refresh_access_tokens(self, refresh_token):
        """
        刷新access_tokens
        :param refresh_token:
        :return:正确时返回
            {
              "access_token":"ACCESS_TOKEN",
              "expires_in":7200,
              "refresh_token":"REFRESH_TOKEN",
              "openid":"OPENID",
              "scope":"SCOPE"
            }
        """
        weixin_ini = Config('/config/settings.ini').get('weixin')  # 读取配置文件
        url = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s" % (weixin_ini['appid'], refresh_token)
        res = json2dict(get_contents(url, method='GET'))
        return self.errcode2msg(res)

    def get_userinfo(self, access_tokens, openid):
        """
        拉取用户信息
        :param access_tokens:
        :param openid:
        :return:正确时返回
        {
          "openid":" OPENID",
          "nickname": NICKNAME,
          "sex":"1",
          "province":"PROVINCE",
          "city":"CITY",
          "country":"COUNTRY",
          "headimgurl":       "http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
          "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
          "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
        }
        """
        url = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN" % (access_tokens, openid)
        res = json2dict(get_contents(url, method='GET'))
        return self.errcode2msg(res)

    def check_access_tokens(self, access_tokens, openid):
        """
        校验access_tokens是否有效,可以在拉取用户信息之前校验是否有效,无效则调用refresh_access_tokens函数来刷新
        :param access_tokens:
        :param openid:
        :return:正确时返回
        { "errcode":0,"errmsg":"ok"}
        """
        url = "https://api.weixin.qq.com/sns/auth?access_token=%s&openid=%s" % (access_tokens, openid)
        res = json2dict(get_contents(url, method='GET'))
        return self.errcode2msg(res)

    def research_wx_remind(self, openid, to_url, title, total):
        url = 'https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=' + self.access_token
        data = {
            "touser": openid,
            "template_id": 'gaI0RAQV210gheIjOJGRJfvhlAkMefrcbIMs_P_NGtY',
            "url": to_url,
            "data": {
                "first": {
                    "value": '您的问卷收到一则新提交'
                },
                "keyword1": {
                    "value": title
                },
                "keyword2": {
                    "value": total
                },
                "keyword3": {
                    "value": date(int(self.kg['run_start_time'][1]), '%Y年%m月%d日 %H:%M:%S')
                },
                "remark": {
                    "value": "感谢您的使用！"
                }
            }
        }
        wx_data = json2dict(get_contents(url, data=json2dict(data, trans=False), method='POST'))

        return self.errcode2msg(wx_data)

    def errcode2msg(self, dict_data):
        """
        全局返回码转义为中文说明
        @param dict_data:
        @return:
        """
        d = {
            -1: "系统繁忙，此时请开发者稍候再试",
            0: "请求成功",
            40001: "获取access_token时AppSecret错误，或者access_token无效。请开发者认真比对AppSecret的正确性，或查看是否正在为恰当的公众号调用接口",
            40002: "不合法的凭证类型",
            40003: "不合法的OpenID ，请开发者确认OpenID （该用户）是否已关注公众号，或是否是其他公众号的OpenID",
            40004: "不合法的媒体文件类型",
            40005: "不合法的文件类型",
            40006: "不合法的文件大小",
            40007: "不合法的媒体文件id",
            40008: "不合法的消息类型",
            40009: "不合法的图片文件大小",
            40010: "不合法的语音文件大小",
            40011: "不合法的视频文件大小",
            40012: "不合法的缩略图文件大小",
            40013: "不合法的AppID ，请开发者检查AppID的正确性，避免异常字符，注意大小写",
            40014: "不合法的access_token ，请开发者认真比对access_token的有效性（如是否过期），或查看是否正在为恰当的公众号调用接口",
            40015: "不合法的菜单类型",
            40016: "不合法的按钮个数",
            40017: "不合法的按钮类型",
            40018: "不合法的按钮名字长度",
            40019: "不合法的按钮KEY 长度",
            40020: "不合法的按钮URL 长度",
            40021: "不合法的菜单版本号",
            40022: "不合法的子菜单级数",
            40023: "不合法的子菜单按钮个数",
            40024: "不合法的子菜单按钮类型",
            40025: "不合法的子菜单按钮名字长度",
            40026: "不合法的子菜单按钮 KEY 长度",
            40027: "不合法的子菜单按钮   URL 长度",
            40028: "不合法的自定义菜单使用用户",
            40029: "无效的 oauth_code",
            40030: "不合法的 refresh_token",
            40031: "不合法的 openid 列表",
            40032: "不合法的openid列表长度",
            40033: "不合法的请求字符，不能包含 \\uxxxx格式的字符",
            40035: "不合法的参数",
            40038: "不合法的请求格式",
            40039: "不合法的 URL 长度",
            40048: "无效的url",
            40050: "不合法的分组 id",
            40051: "分组名字不合法",
            40060: "删除单篇图文时，指定的 article_idx 不合法",
            40117: "分组名字不合法",
            40118: "media_id大小不合法",
            40119: "button 类型错误",
            40120: "子 button 类型错误",
            40121: "不合法的media_id类型",
            40125: "无效的appsecret",
            40132: "微信号不合法",
            40137: "不支持的图片格式",
            40155: "请勿添加其他公众号的主页链接",
            40163: "oauth_code已使用",
            41001: "缺少 access_token 参数",
            41002: "缺少 appid 参数",
            41003: "缺少 refresh_token 参数",
            41004: "缺少 secret 参数",
            41005: "缺少多媒体文件数据",
            41006: "缺少 media_id 参数",
            41007: "缺少子菜单数据",
            41008: "缺少 oauth  code",
            41009: "缺少 openid",
            42001: "access_token 超时，请检查 access_token 的有效期，请参考基础支持 - 获取 access_token 中，对 access_token 的详细机制说明",
            42002: "refresh_token 超时",
            42003: "oauth_code 超时",
            42007: "用户修改微信密码， accesstoken 和 refreshtoken 失效，需要重新授权",
            43001: "需要 GET 请求",
            43002: "需要 POST 请求",
            43003: "需要 HTTPS 请求",
            43004: "需要接收者关注",
            43005: "需要好友关系",
            43019: "需要将接收者从黑名单中移除",
            44001: "多媒体文件为空",
            44002: "POST 的数据包为空",
            44003: "图文消息内容为空",
            44004: "文本消息内容为空",
            45001: "多媒体文件大小超过限制",
            45002: "消息内容超过限制",
            45003: "标题字段超过限制",
            45004: "描述字段超过限制",
            45005: "链接字段超过限制",
            45006: "图片链接字段超过限制",
            45007: "语音播放时间超过限制",
            45008: "图文消息超过限制",
            45009: "接口调用超过限制",
            45010: "创建菜单个数超过限制",
            45011: "API 调用太频繁，请稍候再试",
            45015: "回复时间超过限制",
            45016: "系统分组，不允许修改",
            45017: "分组名字过长",
            45018: "分组数量超过上限",
            45047: "客服接口下行条数超过上限",
            45064: "创建菜单包含未关联的小程序",
            45065: "相同 clientmsgid 已存在群发记录，返回数据中带有已存在的群发任务的 msgid",
            45066: "相同 clientmsgid 重试速度过快，请间隔1分钟重试",
            45067: "clientmsgid 长度超过限制",
            46001: "不存在媒体数据",
            46002: "不存在的菜单版本",
            46003: "不存在的菜单数据",
            46004: "不存在的用户",
            47001: "解析 JSON / XML 内容错误",
            48001: "api 功能未授权，请确认公众号已获得该接口，可以在公众平台官网 - 开发者中心页中查看接口权限",
            48002: "粉丝拒收消息（粉丝在公众号选项中，关闭了 “ 接收消息 ” ）",
            48004: "api接口被封禁，请登录 mp.weixin.qq.com 查看详情",
            48005: "api 禁止删除被自动回复和自定义菜单引用的素材",
            48006: "api 禁止清零调用次数，因为清零次数达到上限",
            48008: "没有该类型消息的发送权限",
            50001: "用户未授权该 api",
            50002: "用户受限，可能是违规后接口被封禁",
            50005: "用户未关注公众号",
            61451: "参数错误(invalid parameter)",
            61452: "无效客服账号(invalid kf_account)",
            61453: " 客服帐号已存在(kf_account exsited)",
            61454: "客服帐号名长度超过限制(仅允许10 个英文字符，不包括 @ 及 @ 后的公众号的微信号 )(invalid   kf_acount length)",
            61455: "客服帐号名包含非法字符(仅允许英文 + 数字)(illegal character in kf_account)",
            61456: "客服帐号个数超过限制(10 个客服账号 )(kf_account count exceeded)",
            61457: "无效头像文件类型(invalid  file type)",
            61450: "系统错误(system error)",
            61500: "日期格式错误",
            63001: "部分参数为空",
            63002: "无效的签名",
            65301: "不存在此 menuid 对应的个性化菜单",
            65302: "没有相应的用户",
            65303: "没有默认菜单，不能创建个性化菜单",
            65304: "MatchRule 信息为空",
            65305: "个性化菜单数量受限",
            65306: "不支持个性化菜单的帐号",
            65307: "个性化菜单信息为空",
            65308: "包含没有响应类型的 button",
            65309: "个性化菜单开关处于关闭状态",
            65310: "填写了省份或城市信息，国家信息不能为空",
            65311: "填写了城市信息，省份信息不能为空",
            65312: "不合法的国家信息",
            65313: "不合法的省份信息",
            65314: "不合法的城市信息",
            65316: "该公众号的菜单设置了过多的域名外跳（最多跳转到 3个域名的链接）",
            65317: "不合法的 URL",
            87009: "无效的签名",
            9001001: "POST 数据参数不合法",
            9001002: "远端服务不可用",
            9001003: "Ticket 不合法",
            9001004: "获取摇周边用户信息失败",
            9001005: "获取商户信息失败",
            9001006: "获取 OpenID 失败",
            9001007: "上传文件缺失",
            9001008: "上传素材的文件类型不合法",
            9001009: "上传素材的文件尺寸不合法",
            9001010: "上传失败",
            9001020: "帐号不合法",
            9001021: "已有设备激活率低于 50 % ，不能新增设备",
            9001022: "设备申请数不合法，必须为大于0的数字",
            9001023: "已存在审核中的设备 ID 申请",
            9001024: "一次查询设备 ID 数量不能超过 50",
            9001025: "设备 ID 不合法",
            9001026: "页面 ID 不合法",
            9001027: "页面参数不合法",
            9001028: "一次删除页面 ID 数量不能超过10",
            9001029: "页面已应用在设备中，请先解除应用关系再删除",
            9001030: "一次查询页面  ID 数量不能超过 50",
            9001031: "时间区间不合法",
            9001032: "保存设备与页面的绑定关系参数错误",
            9001033: "门店 ID 不合法",
            9001034: "设备备注信息过长",
            9001035: "设备申请参数不合法",
            9001036: "查询起始值begin 不合法"
        }
        if dict_data.get("errcode", '') in d:
            dict_data['errmsg'] = d[dict_data['errcode']]
        return dict_data



