# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Windows面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2022 宝塔软件(https://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# +-------------------------------------------------------------------

# --------------------------------
# 宝塔公共库
# --------------------------------

import json, os, sys, time, re, winreg, psutil, xmltodict, psutil, socket, base64, shutil

import win32net, win32netcon, win32security, win32netcon, win32service
import win32serviceutil as service
from xml.dom import minidom


file_read = 1179785
file_exec = 1179817
file_modify = 1245631
file_all = 2032127
server_err = None

from importlib import reload


def M(table):
    """
        @name 访问面板数据库
        @author hwliang<hwl@bt.cn>
        @table 被访问的表名(必需)
        @return db.Sql object

        ps: 默认访问data/default.db
    """
    import db
    with db.Sql() as sql:
        # sql = db.Sql()
        return sql.table(table)



def http_get_home(url, timeout, ex):
    """
    @name Get方式使用优选节点访问官网
    @param url 当前官网URL地址
    @param timeout 用于测试超时时间
    @param ex 上一次错误的响应内容
    @return string 响应内容

    如果已经是优选节点，将直接返回ex

    """

    try:
        home = 'www.bt.cn'
        if url.find(home) == -1: return ex
        hosts_file = "config/hosts.json"
        if not os.path.exists(hosts_file): return ex
        hosts = json.loads(readFile(hosts_file))
        headers = {"host": home}
        for host in hosts:
            new_url = url.replace(home, host)
            res = HttpGet(new_url, timeout, headers)
            if res:
                writeFile("data/home_host.pl", host)
                set_home_host(host)
                return res
        return ex
    except:
        return ex

def print_log(_info,_level = 'DEBUG'):
    '''
        @name 写入日志
        @author hwliang<2021-08-12>
        @param _info<string> 要写入到日志文件的信息
        @param _level<string> 日志级别
        @return void
    '''
    print(_info)


def set_home_host(host):
    pass


def HttpGet(url, timeout=60, headers={}):
    """
        @name 发送GET请求
        @author hwliang<hwl@bt.cn>
        @url 被请求的URL地址(必需)
        @timeout 超时时间默认60秒
        @return string
    """
    import http_requests
    res = http_requests.get(url, timeout=timeout, headers=headers)
    if res.status_code == 0:
        if headers: return False
        s_body = res.text
        return s_body
    s_body = res.text
    del res
    return s_body


def httpGet(url, timeout=60):
    return HttpGet(url, timeout)


def HttpPost(url, data, timeout=60, headers={}):
    """
    发送POST请求
    @url 被请求的URL地址(必需)
    @data POST参数，可以是字符串或字典(必需)
    @timeout 超时时间默认60秒
    return string
    """
    import http_requests
    res = http_requests.post(url, data=data, timeout=timeout, headers=headers)

    if res.status_code == 0:
        s_body = res.text
        return s_body
    s_body = res.text
    return s_body


def httpPost(url, data, timeout=20):
    if timeout < 5: timeout = 5
    return HttpPost(url, data, timeout)


def http_post_home(url, data, timeout, ex):
    """
        @name POST方式使用优选节点访问官网
        @param url(string) 当前官网URL地址
        @param data(dict) POST数据
        @param timeout(int) 用于测试超时时间
        @param ex(string) 上一次错误的响应内容
        @return string 响应内容

        如果已经是优选节点，将直接返回ex
    """
    try:
        home = 'www.bt.cn'
        if url.find(home) == -1: return ex
        hosts_file = "config/hosts.json"
        if not os.path.exists(hosts_file): return ex
        hosts = json.loads(readFile(hosts_file))
        headers = {"host": home}
        for host in hosts:
            new_url = url.replace(home, host)
            res = HttpPost(new_url, data, timeout, headers)
            if res:
                writeFile("data/home_host.pl", host)
                set_home_host(host)
                return res
        return ex

    except:
        return ex


def Md5(strings):
    """
        @name 生成MD5
        @author hwliang<hwl@bt.cn>
        @param strings 要被处理的字符串
        @return string(32)
    """
    if type(strings) != bytes:
        strings = strings.encode()
    import hashlib
    m = hashlib.md5()
    m.update(strings)
    return m.hexdigest()


def md5(strings):
    return Md5(strings)


def FileMd5(filename):
    """
    生成文件的MD5
    @filename 文件名
    return string(32) or False
    """
    if not os.path.isfile(filename): return False;
    import hashlib;
    my_hash = hashlib.md5()
    f = open(filename, 'rb')
    while True:
        b = f.read(8096)
        if not b:
            break
        my_hash.update(b)
    f.close()
    return my_hash.hexdigest();


def GetRandomString(length):
    """
       取随机字符串
       @length 要获取的长度
       return string(length)
    """
    from random import Random
    strings = ''
    chars = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789'
    chrlen = len(chars) - 1
    random = Random()
    for i in range(length):
        strings += chars[random.randint(0, chrlen)]
    return strings


def GetRandomString1(length):
    """
       取随机数字
       @length 要获取的长度
       return string(length)
    """

    from random import Random
    strings = ''
    chars = '0123456789'
    chrlen = len(chars) - 1
    random = Random()
    for i in range(length):
        strings += chars[random.randint(0, chrlen)]
    return strings


def GetRandomString2(length):
    """
       取随机特殊字符串
       @length 要获取的长度
       return string(length)
    """

    from random import Random
    strings = ''
    chars = '!@#$%^&*()_+.,?[]-='
    chrlen = len(chars) - 1
    random = Random()
    for i in range(length):
        strings += chars[random.randint(0, chrlen)]
    return strings


def bt_print(msg):
    """
    处理非utf8字符print输出
    return 无
    """
    try:
        print(msg)
    except:
        try:
            print(msg, msg.encode('utf-8'))
        except:pass


def get_system_lang():
    ret = ExecShell('chcp')[0]
    tmp = re.search('(\d+)', ret)
    if tmp:
        return int(tmp.group(1))
    return 936


def ReturnJson(status, msg, args=()):
    """
    取通用Json返回
    @status  返回状态
    @msg  返回消息
    return string(json)
    """
    return GetJson(ReturnMsg(status, msg, args));


def returnJson(status, msg, args=()):
    return ReturnJson(status, msg, args)


def ReturnMsg(status, msg, args=()):
    """
        @name 取通用dict返回
        @param status  返回状态
        @param msg  返回消息
        @return dict  {"status":bool,"msg":string}
    """
    log_message = json.loads(ReadFile(get_panel_path() + '/BTPanel/static/language/' + GetLanguage() + '/public.json'));
    keys = log_message.keys();
    if type(msg) == str:
        if msg in keys:
            msg = log_message[msg];
            for i in range(len(args)):
                rep = '{' + str(i + 1) + '}'
                msg = msg.replace(rep, args[i]);
    return {'status': status, 'msg': msg}


def returnMsg(status, msg, args=()):
    return ReturnMsg(status, msg, args)


def GetJson(data):
    """
    将对象转换为JSON
    @data 被转换的对象(dict/list/str/int...)
    """
    from json import dumps
    if data == bytes: data = data.decode('utf-8')
    try:
        return dumps(data, ensure_ascii=False)
    except:
        return dumps(returnMsg(False, "错误的响应: %s" % str(data)))


def getJson(data):
    return GetJson(data)


def get_str_code(f_body):
    from chardet.universaldetector import UniversalDetector
    detector = UniversalDetector()
    detector.feed(f_body)
    detector.close()
    char = detector.result
    return char;


def decode_data(srcBody):
    """
    遍历解码字符串
    """
    arrs = ['utf-8', 'GBK', 'ANSI', 'BIG5']
    for encoding in arrs:
        try:
            data = srcBody.decode(encoding)
            return encoding, data
        except:
            pass
    return False, None


def ReadFile(filename, mode='r'):
    """
    读取文件内容
    @filename 文件名
    return string(bin) 若文件不存在，则返回None
    """

    import os, chardet
    if not os.path.exists(filename): return False
    if not os.path.isfile(filename): return False

    f_body = '';
    try:
        fp = open(filename, mode)
        f_body = fp.read()
    except:
        try:
            fp.close()
        except:
            pass

        try:
            encoding = 'utf8'
            fp = open(filename, mode, encoding=encoding)
            f_body = fp.read()
        except:
            try:
                fp.close()
            except:
                pass
            try:
                encoding = 'gbk'
                fp = open(filename, mode, encoding=encoding)
                f_body = fp.read()
            except:
                try:
                    fp.close()
                except:
                    pass

                encoding = 'ansi'
                fp = open(filename, mode, encoding=encoding)
                f_body = fp.read()

    try:
        if f_body[0] == '\ufeff':
            # 处理带bom格式
            new_code = chardet.detect(f_body.encode(encoding))["encoding"]
            f_body = f_body.encode(encoding).decode(new_code);
    except:
        pass

    fp.close()
    return f_body


def readFile(filename, mode='r'):
    return ReadFile(filename, mode)


def readFile2(filename, mode='r'):
    """
    读取文件二进制文件
    @filename 文件名
    @return bin 返回二进制文件
    """
    import os
    if not os.path.exists(filename): return False
    if not os.path.isfile(filename): return False

    fp = open(filename, mode)
    f_body = fp.read()
    fp.close()
    return f_body


def WriteFile(filename, s_body, mode='w+', encoding='utf-8'):
    """
    写入文件内容
    @filename 文件名
    @s_body 欲写入的内容
    return bool 若文件不存在则尝试自动创建
    """
    try:
        fp = open(filename, mode, encoding="utf-8")
        fp.write(s_body)
        fp.close()
        return True
    except:
        try:
            fp = open(filename, mode)
            fp.write(s_body)
            fp.close()
            return True
        except:
            return False


def writeFile(filename, s_body, mode='w+', encoding='utf-8'):
    return WriteFile(filename, s_body, mode, encoding)


def writeFile2(filename, s_body, mode='w+'):
    """
    写入字节文件内容
    @filename 文件名
    @s_body 欲写入的内容

    """
    try:
        fp = open(filename, mode);
        fp.write(s_body)
        fp.close()
        return True
    except:
        return False


def WriteLog(type, logMsg, args=()):
    """
    报错操作日志
    @type 操作日志类型
    @logMsg 日志内容
    @args 日志内容中的参数
    """
    # 写日志
    # try:

    uid = 1
    username = 'system'
    try:
        from BTPanel import session
        if 'username' in session:
            username = session['username']
            uid = session['uid']
            if session.get('debug') == 1: return
    except:
        pass

    import time, db, json
    logMessage = json.loads(readFile('BTPanel/static/language/' + get_language() + '/log.json'));
    keys = logMessage.keys()
    if logMsg in keys:
        logMsg = logMessage[logMsg]
        for i in range(len(args)):
            rep = '{' + str(i + 1) + '}'
            logMsg = logMsg.replace(rep, args[i])
    if type in keys: type = logMessage[type]

    try:
        if 'login_address' in session:
            logMsg = '{} {}'.format(session['login_address'], logMsg)
    except:pass
    sql = db.Sql()
    mDate = time.strftime('%Y-%m-%d %X', time.localtime())
    data = (uid, username, type, logMsg, mDate)
    result = sql.table('logs').add('uid,username,type,log,addtime', data)
    # except:
    # pass


def GetLanguage():
    '''
    取语言
    '''
    return "Simplified_Chinese"


def get_language():
    return GetLanguage()


def GetConfigValue(key):
    '''
    取配置值
    @key 配置项的key
    '''
    config = GetConfig()
    if not key in config.keys(): return None
    return config[key]


def SetConfigValue(key, value):
    """
    设置面板配置
    @key 配置项的key
    @value 配置项的值
    """
    config = GetConfig()
    config[key] = value
    WriteConfig(config)


def GetConfig():
    '''
    取所有配置项
    return json格式
    '''
    path = get_panel_path() + "/config/config.json"

    if not os.path.exists(path): return {}
    f_body = ReadFile(path)
    if not f_body: return {}
    return json.loads(f_body)


def WriteConfig(config):
    """
    保存所有配置
    @config json格式配置文件
    """
    path = "config/config.json"
    WriteFile(path, json.dumps(config))


def GetLan(key):
    """
    取提示消息
    """
    log_message = json.loads(ReadFile('BTPanel/static/language/' + GetLanguage() + '/template.json'));
    keys = log_message.keys();
    msg = None;
    if key in keys:
        msg = log_message[key];
    return msg;


def getLan(key):
    return GetLan(key)


def GetMsg(key, args=()):
    """
    根据key获取内置消息返回
    @key 指定消息的key
    @args 消息内容中的参数
    """
    try:
        log_message = json.loads(ReadFile('BTPanel/static/language/' + GetLanguage() + '/public.json'));
        keys = log_message.keys();
        msg = None;
        if key in keys:
            msg = log_message[key];
            for i in range(len(args)):
                rep = '{' + str(i + 1) + '}'
                msg = msg.replace(rep, args[i]);
        return msg;
    except:
        return key


def getMsg(key, args=()):
    return GetMsg(key, args)


def get_error_info():
    """
    获取面板详细错误
    """
    import traceback
    errorMsg = traceback.format_exc();
    return errorMsg


def kill(name, username=None):
    """
    结束指定进程
    @name 进程名
    @username 按进程和运行用户结束
    """
    shell = "taskkill /f /t /im %s" % name
    if username:
        shell = 'taskkill /f /t /fi "username eq %s" /im %s ' % (username, name)
    os.system(shell);


def GetWebServer():
    """
    获取Web服务器
    """
    webserver = None;
    if get_server_status('nginx') >= 0:
        webserver = 'nginx'
    elif get_server_status('apache') >= 0:
        webserver = 'apache';
    else:
        if os.path.exists('data/iis.setup'): webserver = 'iis'

    return webserver;


def get_webserver():
    return GetWebServer()


def get_ppids(pid, name, ppids=[]):
    """
    获取进程ppid
    """
    proc = psutil.Process(pid)
    if proc.name() == name:
        if not proc.pid in ppids: ppids.append(proc.pid)
        get_ppids(proc.ppid(), name, ppids)
    return True


def ServiceReload(siteName=None):
    """
    重载Web服务配置
    @siteName 网站名称，IIS专用，重启指定IIS网站
    """
    webserver = get_webserver()
    result = '';
    if webserver == 'nginx':
        ser = psutil.win_service_get(webserver)
        if ser: ExecShell('taskkill /pid {} -t -f'.format(ser.pid()))

        isError = checkWebConfig()
        if isError != True:
            if isError.find('0.0.0.0:80') >= 0: os.system("net stop http /y")
        num = 0
        while num < 5:
            if get_server_status(webserver) == 1: break
            os.system('net start nginx')
            time.sleep(0.2);
            num += 1
    elif webserver == 'apache':

        ser = psutil.win_service_get(webserver)
        if ser: ExecShell('taskkill /pid {} -t -f'.format(ser.pid()))
        isError = checkWebConfig()
        if isError != True:
            if isError.find('0.0.0.0:80') >= 0: os.system("net stop http /y")

        ExecShell('taskkill /f /im php-cig.exe')
        num = 0
        while num < 5:
            if get_server_status(webserver) == 1: break
            os.system('net start apache')
            time.sleep(0.2);
            num += 1
    else:
        if siteName:
            appcmd = "C:\\windows\\system32\\inetsrv\\appcmd.exe"
            ExecShell(appcmd + ' stop apppool /apppool.name:"%s"' % (siteName))

            ExecShell(appcmd + ' start apppool /apppool.name:"%s"' % (siteName))
        else:
            result = ExecShell('iisreset')
    return result;


def serviceReload(siteName=None):
    return ServiceReload(siteName)


def clear_chcp(msg):
    """
    @name 清除活动代码
    """
    msg = re.sub('Active code page: \d+', '', msg)
    return msg


def ExecProcess(path, fio=None, env=None):
    """
    进程通信
    @env 环境变量
    @data post参数 id=123&name=admin
    @path 进程路径
    """
    import subprocess
    sub = subprocess.Popen(path, env=env, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    if not sub: return None, None

    if fio:
        buff_size = 1024
        while True:
            tip_data = fio.read(buff_size)
            if not tip_data: break
            sub.stdin.write(tip_data)
    return sub.stdout


def ExecShell(cmdstring, cwd=None, timeout=None, shell=True, output=False):
    """
    通过管道执行SHELL
    @cmdstring 需要执行的cmd命令
    @cwd 设置工作目录
    @timeout 超时时间
    @shell 是否通过shell输出
    @output 是否通过文件重定向输出结果（当结果数据量过大必须使用此参数，否则造成管道阻塞）
    """
    import shlex
    import datetime
    import subprocess
    import time, tempfile

    if output:
        ExecShell("del /s {}\panel\data\*.clog".format(to_path(GetConfigValue('setup_path'))))
        cmd_name = GetRandomString(8)
        sucess_path = 'data/{}_sucess.clog'.format(cmd_name)
        error_path = 'data/{}_error.clog'.format(cmd_name)

        shell = '{} 1>{} 2>{}'.format(cmdstring, sucess_path, error_path)
        os.system(shell)
        time.sleep(0.01)
        sucess = ''
        error = ''
        if os.path.exists(sucess_path): sucess = readFile(sucess_path)
        if os.path.exists(error_path): error = readFile(error_path)

        return clear_chcp(sucess), error
    else:
        a = ''
        e = ''
        try:
            rx = md5(cmdstring)
            succ_f = tempfile.SpooledTemporaryFile(max_size=4096000, mode='wb+', suffix='_succ', prefix='btex_' + rx,
                                                   dir='data')
            err_f = tempfile.SpooledTemporaryFile(max_size=4096000, mode='wb+', suffix='_err', prefix='btex_' + rx,
                                                  dir='data')

            sub = subprocess.Popen(cmdstring, shell=shell, bufsize=128, stdout=succ_f, stderr=err_f)
            sub.wait()

            err_f.seek(0)
            succ_f.seek(0)
            a = succ_f.read()
            e = err_f.read()

            if not err_f.closed: err_f.close()
            if not succ_f.closed: succ_f.close()
        except:
            pass

        if type(a) == bytes:
            try:
                a = a.decode('utf-8')
            except:
                try:
                    a = a.decode('gb2312')
                except:
                    try:
                        a = a.decode('utf-16')
                    except:
                        a = a.decode('gb2312', 'ignore')

        if type(e) == bytes:
            try:
                e = e.decode('utf-8')
            except:
                try:
                    a = a.decode('utf-16')
                except:
                    e = e.decode('gb2312', 'ignore')
        return a, e


def ExecPowerShell(cmdstring, cwd=None, timeout=None, shell=True):
    """
    通过管道执行PowerShell
    @cmdstring 需要执行的cmd命令
    @cwd 设置工作目录
    @timeout 超时时间
    @shell 是否通过shell输出
    """

    try:
        # 通过管道执行SHELL
        import shlex
        import datetime
        import subprocess
        import time

        if shell:
            cmdstring_list = cmdstring
        else:
            cmdstring_list = shlex.split(cmdstring)

        if timeout:
            end_time = datetime.datetime.now() + datetime.timedelta(seconds=timeout)

        sub = subprocess.Popen(cmdstring_list, cwd=cwd, shell=shell, bufsize=4096, stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE)

        while sub.poll() is None:
            time.sleep(0.1)
            if timeout:
                if end_time <= datetime.datetime.now():
                    raise Exception("Timeout：%s" % cmdstring)
        a, e = sub.communicate()
        if type(a) == bytes: a = a.decode('gb2312')
        if type(e) == bytes: e = e.decode('gb2312')
    except:
        if not a:
            e = ''
            a = os.popen(cmdstring).read()
    return a, e


def GetLocalIp():
    #取本地外网IP
    try:
        filename = 'data/iplist.txt'
        ipaddress = readFile(filename)
        if not ipaddress:
            url = GetConfigValue('home') + '/Api/getIpAddress'
            m_str = HttpGet(url)
            ipaddress = re.search(r"^\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}$",m_str).group(0)
            WriteFile(filename,ipaddress)
        c_ip = check_ip(ipaddress)
        if not c_ip: return GetHost()
        return ipaddress
    except:
        try:
            url = GetConfigValue('home') + '/Api/getIpAddress'
            return HttpGet(url)
        except:
            return GetHost()


def GetHost(port=False):
    """
    获取面板当前访问的Host
    @port 面板端口
    """
    from flask import request
    host_tmp = request.headers.get('host')

    if not host_tmp:
        if request.url_root:
            tmp = re.findall("(https|http)://([\w:\.-]+)", request.url_root)
            if tmp: host_tmp = tmp[0][1]
    if not host_tmp:
        host_tmp = GetLocalIp() + ':' + readFile('data/port.pl').strip()
    try:
        if host_tmp.find(':') == -1: host_tmp += ':80';
    except:
        host_tmp = "127.0.0.1:8888"
    h = host_tmp.split(':')
    if port: return h[1]
    return h[0]


def GetClientIp():
    from flask import request
    ipaddr =  request.remote_addr.replace('::ffff:','')
    if not check_ip(ipaddr): return '未知IP地址'
    return ipaddr


def phpReload(version):
    # 重载PHP配置
    serviceReload()


def mod_reload(mode):
    """
    重载指定模块
    @mode 模块对象
    """
    if not mode: return False
    try:
        if sys.version_info[0] == 2:
            reload(mode)
        else:
            import imp
            imp.reload(mode)
        return True
    except:
        return False


"""
@获取远程桌面端口
"""


def get_ssh_port():
    import winreg
    try:
        key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE,
                             r"SYSTEM\CurrentControlSet\Control\Terminal Server\Wds\rdpwd\Tds\tcp")
        port, type = winreg.QueryValueEx(key, "PortNumber")
    except:
        port = 3389
    return port


def checkInput(data):
    """
    过滤字符串输入
    @data 输入的字符串
    """
    if not data: return data;
    if type(data) != str: return data;
    checkList = [
        {'d': '<', 'r': '＜'},
        {'d': '>', 'r': '＞'},
        {'d': '\'', 'r': '‘'},
        {'d': '"', 'r': '“'},
        {'d': '&', 'r': '＆'},
        {'d': '#', 'r': '＃'},
        {'d': '<', 'r': '＜'}
    ]
    for v in checkList:
        data = data.replace(v['d'], v['r']);
    return data;


# 取文件指定尾行数
def GetNumLines(path, num, p=1):
    pyVersion = sys.version_info[0]
    max_len = 1024 * 128
    try:
        from html import escape
        if not os.path.exists(path): return ""
        start_line = (p - 1) * num
        count = start_line + num
        fp = open(path, 'rb')

        buf = ""
        fp.seek(-1, 2)
        if fp.read(1) == "\n": fp.seek(-1, 2)
        data = []
        total_len = 0
        b = True
        n = 0

        for i in range(count):
            while True:
                newline_pos = str.rfind(str(buf), "\n")

                pos = fp.tell()
                if newline_pos != -1:
                    if n >= start_line:
                        line = buf[newline_pos + 1:]
                        line_len = len(line)
                        total_len += line_len
                        sp_len = total_len - max_len
                        if sp_len > 0:
                            line = line[sp_len:]
                        try:
                            data.insert(0, escape(line))
                        except:
                            pass
                    buf = buf[:newline_pos]
                    n += 1
                    break
                else:
                    if pos == 0:
                        b = False
                        break
                    to_read = min(4096, pos)
                    fp.seek(-to_read, 1)
                    t_buf = fp.read(to_read)
                    if pyVersion == 3:
                        try:
                            if type(t_buf) == bytes: t_buf = t_buf.decode('utf-8')
                        except:
                            try:
                                if type(t_buf) == bytes: t_buf = t_buf.decode('gbk')
                            except:
                                t_buf = str(t_buf)
                    buf = t_buf + buf
                    fp.seek(-to_read, 1)
                    if pos - to_read == 0:
                        buf = "\n" + buf
                if total_len >= max_len: break
            if not b: break
        fp.close()
        result = "\n".join(data)

        if not result: raise Exception('null')
    except:
        result = ''  # ExecShell("tail -n {} {}".format(num,path))[0]
        if len(result) > max_len:
            result = result[-max_len:]

    try:
        try:
            result = json.dumps(result)
            return json.loads(result).strip()
        except:
            if pyVersion == 2:
                result = result.decode('utf8', errors='ignore')
            else:
                result = result.encode('utf-8', errors='ignore').decode("utf-8", errors="ignore")
        return result.strip()
    except:
        return ""


def GetNumLines2(path, num, p=1):
    """
    取文件指定尾行数
    @path 文件路径
    @num 每次取多少行
    @p 倒数第几页
    """
    pyVersion = sys.version_info[0]
    data = []
    try:
        from html import escape
        if not os.path.exists(path): return "";
        start_line = (p - 1) * num;
        count = start_line + num;
        fp = open(path, 'rb')
        buf = ""
        fp.seek(-1, 2)
        if fp.read(1) == "\n": fp.seek(-1, 2)

        b = True
        n = 0;
        for i in range(count):
            while True:
                newline_pos = str.rfind(str(buf), "\n")
                pos = fp.tell()
                if newline_pos != -1:
                    if n >= start_line:
                        line = buf[newline_pos + 1:]
                        try:
                            n_line = escape(line, quote=False)
                            data.insert(0, n_line)
                        except:
                            try:
                                data.insert(0, n_line)
                            except:
                                pass
                    buf = buf[:newline_pos]
                    n += 1;
                    break;
                else:
                    if pos == 0:
                        b = False
                        break
                    to_read = min(4096, pos)
                    fp.seek(-to_read, 1)
                    t_buf = fp.read(to_read)
                    if pyVersion == 3:
                        try:
                            if type(t_buf) == bytes: t_buf = t_buf.decode('utf-8')
                        except:
                            try:
                                if type(t_buf) == bytes: t_buf = t_buf.decode('gbk')
                            except:
                                t_buf = ''
                    buf = t_buf + buf
                    fp.seek(-to_read, 1)
                    if pos - to_read == 0:
                        buf = "\n" + buf
            if not b: break;
        fp.close()
    except:
        if os.path.getsize(path) == 0: return '';
        return ExecShell("tail -n {} {}".format(num, path))[0]
    return "\n".join(data)


def getPanelAddr():
    """
    获取面板地址
    """
    from flask import request
    protocol = 'https://' if os.path.exists("data/ssl.pl") else 'http://'
    return protocol + request.headers.get('host')


def format_date(format="%Y-%m-%d %H:%M:%S", times=None):
    """
    格式化指定时间戳
    @format 时间格式
    @times 时间戳
    return 字符串时间格式
    """
    if not times: times = int(time.time())
    time_local = time.localtime(int(times))
    return time.strftime(format, time_local)


def to_date(format="%Y-%m-%d %H:%M:%S", times=None):
    """
    日期转时间戳
    """

    ts = time.strptime(times, "%Y-%m-%d %H:%M:%S")
    return int(time.mktime(ts))


def to_size(size):
    """
    字节单位转换
    @size 字节大小
    return 返回带单位的格式(如：1 GB)
    """
    if not size: return '0.00 b'
    size = float(size)

    d = ('b', 'KB', 'MB', 'GB', 'TB');
    s = d[0];
    for b in d:
        if size < 1024: return ("%.2f" % size) + ' ' + b;
        size = size / 1024;
        s = b;
    return ("%.2f" % size) + ' ' + b;


def get_size(size):
    """
    字节单位转换
    @size 字节大小
    return 返回带单位的格式(如：1 G)
    """
    d = ('b', 'K', 'M', 'G', 'T');
    s = d[0];
    for b in d:
        if size < 1024: return str(round(size, 2)) + ' ' + b;
        size = size / 1024;
        s = b;
    return str(round(size, 2)) + ' ' + b;


def error_403(e):
    from BTPanel import Response
    errorStr = '''<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx</center>
</body>
</html>'''
    headers = {
        "Content-Type": "text/html"
    }
    return Response(errorStr, status=403, headers=headers)


def error_404(e):
    from BTPanel import Response
    errorStr = '''<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>'''
    headers = {
        "Content-Type": "text/html"
    }
    return Response(errorStr, status=404, headers=headers)


def checkCode(code, outime=120):
    """
    校验验证码
    @code 验证码
    @outime 超时时间
    """

    from BTPanel import session, cache
    try:
        codeStr = cache.get('codeStr')
        cache.delete('codeStr')
        if not codeStr:
            session['login_error'] = GetMsg('CODE_TIMEOUT')
            return False

        if md5(code.lower()) != codeStr:
            session['login_error'] = GetMsg('CODE_ERR')
            return False
        return True
    except:
        session['login_error'] = GetMsg('CODE_NOT_EXISTS')
        return False


def writeSpeed(title, used, total, speed=0):
    """
    设置事件进度
    @title 事件标题
    @used 已完成进度
    @total 总进度
    @speed 速度
    """
    import json
    if not title:
        data = {'title': None, 'progress': 0, 'total': 0, 'used': 0, 'speed': 0}
    else:
        progress = int((100.0 * used / total));
        data = {'title': title, 'progress': progress, 'total': total, 'used': used, 'speed': speed}
    writeFile('data/panelSpeed.pl', json.dumps(data));
    return True;


def getSpeed():
    """
    取事件进度

    """
    import json;
    data = readFile('data/panelSpeed.pl');
    if not data:
        data = json.dumps({'title': None, 'progress': 0, 'total': 0, 'used': 0, 'speed': 0})
        writeFile('data/panelSpeed.pl', data);
    return json.loads(data);


def downloadFileByWget(url, filename):
    """
    wget下载文件
    @url 下载地址
    @filename 本地文件路径
    @说明： 部分系统
    """
    loacl_path = '{}/script/wget.exe'.format(os.getenv('BT_PANEL'))
    if not os.path.exists(loacl_path): downloadFile(get_url() + '/win/panel/data/wget.exe', loacl_path)

    shell = "{} {} -O {} -t 5 -T 60  --no-check-certificate --auth-no-challenge --force-directorie ".format(loacl_path, url, filename)
    os.system(shell)

    num = 0
    re_size = 0
    while num <= 20:
        if os.path.exists(filename):
            cr_size = os.path.getsize(filename)
            if re_size > 0 and re_size == cr_size:
                break
            else:
                re_size = cr_size
        time.sleep(0.5)
        num += 1

    # if not os.path.exists(filename):
    #    downloadFile(url,filename)

    else:
        if os.path.exists(filename):
            if os.path.getsize(filename) < 1:os.remove(filename)
            # downloadFile(url,filename)


def get_requests_headers():
    return {"Content-type": "application/x-www-form-urlencoded", "User-Agent": "BT-Panel"}


def downloadFile(url, filename):
    try:
        import requests
        from requests.packages.urllib3.exceptions import InsecureRequestWarning
        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
        res = requests.get(url, headers=get_requests_headers(), timeout=120, stream=True, verify=False)

        with open(filename, "wb") as f:
            for _chunk in res.iter_content(chunk_size=8192):
                f.write(_chunk)
    except:
        downloadFileByWget(url, filename)


def downloadFile_backup(url, filename):
    """
    下载文件
    @url 下载地址
    @filename 本地文件路径
    说明：重试3次，http尝试下载2次，失败尝试用wget下载一次
    """
    try:
        bt_print('准备下载文件[' + os.path.basename(filename) + ']')
        import urllib.request
        urllib.request.urlretrieve(url, filename=filename, reporthook=downloadHook)
    except Exception as ex:
        nUrl = url;
        download = url
        try:
            tmp = re.match('(.+)(/win/.+)', url)
            if tmp:
                bt_print("节点[%s]连接失败，无法下载文件[%s]" % (tmp.groups()[0], url))
                download = get_url(0.5)
                nUrl = url.replace(tmp.groups()[0], download)
            else:
                home = 'www.bt.cn'
                host_home = 'data/home_host.pl'
                if url.find(home) != -1:
                    if os.path.exists(host_home):
                        download = readFile(host_home)
                        nUrl = url.replace(home, download)

            bt_print('重新选择下载节点[%s]，准备下载文件[%s]' % (download, os.path.basename(filename)))

            import urllib.request
            urllib.request.urlretrieve(nUrl, filename=filename, reporthook=downloadHook)
        except Exception as e:
            try:
                time.sleep(0.5)
                downloadFileByWget(url, filename)
            except Exception as exx:
                err_info = '节点1：无法下载[%s] 错误详情->> %s \r\n 节点2： 无法下载文件[%s] 错误详情->> %s' % (url, str(ex), nUrl, str(exx))
                submit_panel_bug(err_info)


def downloadHook(count, blockSize, totalSize):
    """
    下载回调
    @count
    @totalSize 总大小
    @blockSize 未完成大小
    """
    speed = {'total': totalSize, 'block': blockSize, 'count': count}
    rate = int(100.0 * count * blockSize / totalSize)
    if rate > 100: rate = 100
    bt_print(to_size(count * blockSize) + '.......... .......... .......... ..........' + str(rate) + '%')


def inArray(arrays, searchStr):
    """
    搜索数据中是否存在
    @arrays 数组列表
    @searchStr 查找字符串
    """
    for key in arrays:
        if key == searchStr: return True

    return False


def get_vhost_path():
    '''
        @name 取虚拟主机目录
        @author hwliang<2021-08-14>
        @return string
    '''
    return '{}/vhost'.format(get_panel_path())


def listen_ipv6():
    '''
        @name 是否监听ipv6
        @author hwliang<2021-08-12>
        @return bool
    '''
    ipv6_file = '{}/data/ipv6.pl'.format(get_panel_path())
    return os.path.exists(ipv6_file)


def get_logs_path():
    '''
        @name 取日志目录
        @author hwliang<2021-07-14>
        @return string
    '''
    return os.getenv('BT_SETUP') + "/wwwlogs"


#
def checkWebConfig():
    """
    检查Web服务器配置文件是否有错误
    """
    serverType = get_webserver()
    if serverType == 'nginx':
        shell = "%s && cd %s/nginx && nginx.exe -t" % (os.getenv("BT_SETUP")[0:2], os.getenv("BT_SETUP"))
        result = ExecShell(shell);

        searchStr = 'successful'
    elif serverType == 'apache':
        shell = os.getenv("BT_SETUP") + '/apache/bin/httpd.exe -t'
        result = ExecShell(shell);
        searchStr = 'Syntax OK'
    else:
        return True
    if result[1].find(searchStr) == -1 and result[1] != '':
        WriteLog("TYPE_SOFT", 'CONF_CHECK_ERR', (result[1].replace('\\', '/'),));
        return result[1];
    return True;


def checkIp(ip):
    """
    检查是否为IPv4地址
    @ip ipv4地址
    """
    p = re.compile('^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$')
    if p.match(ip):
        return True
    else:
        return False


def checkPort(port):
    """
    检查端口是否合法
    @port 端口号
    """
    try:
        if not re.match('^\d+$', port):  return False

        ports = ['21', '25', '443', '8080', '888', '8888', '8443'];
        if port in ports: return False;
        intport = int(port);
        if intport < 1 or intport > 65535: return False;
        return True;
    except:
        return False;


def getStrBetween(startStr, endStr, srcStr):
    """
    字符串取中间
    @startStr 截取开始字符串
    @endStr 截取结束字符串
    @srcStr 源字符串
    """
    start = srcStr.find(startStr)
    if start == -1: return None
    end = srcStr.find(endStr)
    if end == -1: return None
    return srcStr[start + 1:end]


def getCpuType():
    """
    取CPU类型
    """
    try:
        cpuType = ReadReg(r'HARDWARE\DESCRIPTION\System\CentralProcessor\0', 'ProcessorNameString')
    except:
        cpuType = ''
    return cpuType;


def IsRestart():
    """
    检查是否允许重启
    """
    num = M('tasks').where('status!=?', ('1',)).count();
    if num > 0: return False;
    return True;


def hasPwd(password):
    """
    加密密码字符
    @password  密码
    """
    import crypt;
    return crypt.crypt(password, password);


def get_timeout(url, timeout=3):
    """
    获取url超时事件
    @url url地址
    @timeout 超时时间
    """
    try:
        start = time.time()
        result = int(httpGet(url, timeout))
        return result, int((time.time() - start) * 1000 - 500)
    except:
        return 0, False


def get_url(timeout=0.5):
    """
    节点测速
    @timeout 超时时间
    """
    import json
    try:

        return  get_home_node('http://download.bt.cn')

        src_node = None
        default_path = '{}/data/default_node.pl'.format(get_panel_path())
        try:
            node = json.loads(readFile(default_path))
            node['net'], node['ping'] = get_timeout(node['protocol'] + node['address'] + ':' + node['port'] + '/net_test', 1)

            if node['ping'] < 1000 and node['net'] > 1500:
                 return "https://" + node['address']
        except:pass



        nodeFile = 'data/node.json'
        node_list = json.loads(readFile(nodeFile))
        mnode1 = []
        mnode2 = []
        mnode3 = []
        for node in node_list:
            node['net'], node['ping'] = get_timeout(node['protocol'] + node['address'] + ':' + node['port'] + '/net_test', 1)

            if not node['ping']: continue
            if node['ping'] < 0: continue

            if node['ping'] < 100:  # 当响应时间<100ms且可用带宽大于1500KB时
                if node['net'] > 1500:
                    mnode1.append(node)
                elif node['net'] > 1000:
                    mnode3.append(node)
            else:
                if node['net'] > 1000:  # 当响应时间>=100ms且可用带宽大于1000KB时
                    mnode2.append(node)
            if node['ping'] < 100:
                if node['net'] > 3000: break  # 有节点可用带宽大于3000时，不再检查其它节点
        if mnode1:  # 优选低延迟高带宽
            mnode = sorted(mnode1, key=lambda x: x['net'], reverse=True)
        elif mnode3:  # 备选低延迟，中等带宽
            mnode = sorted(mnode3, key=lambda x: x['net'], reverse=True)
        else:  # 终选中等延迟，中等带宽
            mnode = sorted(mnode2, key=lambda x: x['ping'], reverse=False)

        print('                                                 ')
        print('Waiting for connection:' + node['address'] + ':' + node['port'])
        print('speed:{} ms ,idle : {}'.format(node['ping'], node['net']))
        print('-------------------------------------------------')

        if not mnode: return 'https://download.bt.cn'

        writeFile(default_path,json.dumps(mnode[0]))

        return "https://" + mnode[0]['address']
        # return mnode[0]['protocol'] + mnode[0]['address'] + ':' + mnode[0]['port']
    except:
        return 'http://download.bt.cn'


def getDate(format='%Y-%m-%d %X'):
    """
    取格式时间
    """
    return time.strftime(format, time.localtime())


def CheckPort(port, other=None):
    """
    检查端口是否合法
    @port 端口号（1-65535）
    @other 是否检测是面板使用的端口
    """
    if type(port) == str: port = int(port)
    if port < 1 or port > 65535: return False
    if other:
        checks = [22, 20, 21, 8888, 3306, 11211, 888, 25]
        if port in checks: return False
    return True


# 获取Token
def GetToken():
    try:
        tokenFile = 'data/token.json';
        if not os.path.exists(tokenFile): return False;
        token = json.loads(readFile(tokenFile));
        return token;
    except:
        return False


# 解密数据
def auth_decode(data):
    token = GetToken()
    # 是否有生成Token
    if not token: return returnMsg(False, 'REQUEST_ERR')

    # 校验access_key是否正确
    if token['access_key'] != data['btauth_key']: return returnMsg(False, 'REQUEST_ERR')

    # 解码数据
    import binascii, hashlib, urllib, hmac, json
    tdata = binascii.unhexlify(data['data']);

    # 校验signature是否正确
    signature = binascii.hexlify(hmac.new(token['secret_key'], tdata, digestmod=hashlib.sha256).digest());
    if signature != data['signature']: return returnMsg(False, 'REQUEST_ERR');

    # 返回
    return json.loads(urllib.unquote(tdata));


def auth_encode(data):
    """
    数据加密
    @data 需要加密的数据
    """
    token = GetToken()
    pdata = {}

    # 是否有生成Token
    if not token: return returnMsg(False, 'REQUEST_ERR')

    # 生成signature
    import binascii, hashlib, urllib, hmac, json
    tdata = urllib.quote(json.dumps(data));
    # 公式  hex(hmac_sha256(data))
    pdata['signature'] = binascii.hexlify(hmac.new(token['secret_key'], tdata, digestmod=hashlib.sha256).digest());

    # 加密数据
    pdata['btauth_key'] = token['access_key'];
    pdata['data'] = binascii.hexlify(tdata);
    pdata['timestamp'] = time.time()

    # 返回
    return pdata;


def checkToken(get):
    """
    检查Token
    """
    tempFile = 'data/tempToken.json'
    if not os.path.exists(tempFile): return False
    import json, time
    tempToken = json.loads(readFile(tempFile))
    if time.time() > tempToken['timeout']: return False
    if get.token != tempToken['token']: return False
    return True;


def process_exists(pname, exe=None):
    """
    检查进程是否存在
    @pname 进程名称
    @exe 进程路径
    """
    try:
        pids = psutil.pids()
        for pid in pids:
            try:
                p = psutil.Process(pid)
                if p.name() == pname:
                    if not exe:
                        return True;
                    else:
                        if p.exe() == exe: return True
            except:
                pass
        return False
    except:
        return True


def get_sys_version():
    """
    获取系统版本
    """
    import platform
    v = platform.version()
    arrs = v.split('.')
    arrs.append(v)
    return arrs


def restart_panel():
    """
    重启面板
    """
    import system
    return system.system().ReWeb(None)


def get_mac_address():
    """
    获取mac
    """
    import uuid
    mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
    return ":".join([mac[e:e + 2] for e in range(0, 11, 2)])


def to_string(lites):
    """
    字符串转码
    @lites 转码内容
    """
    if type(lites) != list: lites = [lites]
    m_str = ''
    for mu in lites:
        m_str += chr(mu)
    return m_str


def WriteReg(path, key, value, type=winreg.REG_SZ):
    """
    写入/创建注册表
    @path 注册表路径
    @key 注册表键值
    @value 注册表值
    @type 注册表值类型
    """
    import winreg
    try:
        newKey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path, 0, winreg.KEY_ALL_ACCESS)
    except:
        newKey = winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, path)
    try:
        # SetValue 只支持字符串，其他类型使用SetValueEx
        if isinstance(value, int):
            winreg.SetValueEx(newKey, key, 0, winreg.REG_DWORD, value)
        else:
            winreg.SetValueEx(newKey, key, 0, type, value)
        return True
    except Exception as ex:

        return False


def ReadReg(path, key):
    """
    读取注册表
    @path 注册表路径
    @key 注册表键值
    """
    import winreg
    try:
        newKey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path)
        value, type = winreg.QueryValueEx(newKey, key)
        return value
    except:
        return False


def DelReg(path, key):
    """
    删除注册表
    @path 注册表路径
    @key 注册表键值
    """
    import winreg
    try:
        newKey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path)
        winreg.DeleteKey(newKey, key)
        return True
    except:
        return False


def format_path(path):
    """
    @path 路径
    """
    return path.replace('\\', '/')


def to_path(path):
    """
    格式化命令行运行
    @path 路径
    """
    return path.replace('/', '\\')


def set_system_path(key, val):
    """
    设置系统环境变量
    @key 环境变量key
    @环境变量的值
    """
    ExecShell('setx /m ' + key + ' "' + val + '"')
    if os.environ.get(key):
        return True
    return False


def create_server(name, disName, path, args=None, description=None, userName=None, password=None):
    """
    创建系统服务
    @name 服务名称
    @disName 显示名称
    @path exe文件路径
    @args 服务参数
    @description 服务描述
    @userName 启动用户
    @password 用户密码
    """
    try:
        import win32service
        if userName and userName.find('.\\') < 0:
            userName = '.\\{}'.format(userName)

        path = to_path(path)
        service.InstallService(name, name, disName, win32service.SERVICE_AUTO_START, errorControl=None,
                               bRunInteractive=0, serviceDeps=None, userName=userName, password=password, exeName=path,
                               perfMonIni=None, perfMonDll=None, exeArgs=args, description=description,
                               delayedstart=None)
        if get_server_status(name) > -1:
            set_server_status(name, 'start')
            return True, ''
        else:
            return False, ''
    except:
        return False, get_error_info()


def change_server_start_type(name, type):
    """
    修改服务启动类型
    @name 服务名称
    @type 启动类型（1：自动 0：手动 -1：禁用）
    """
    try:
        startType = None
        if type == -1:
            startType = win32service.SERVICE_DISABLED
        elif type == 1:
            startType = win32service.SERVICE_AUTO_START
        else:
            startType = win32service.SERVICE_DEMAND_START

        scm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
        if scm:
            svc = win32service.OpenService(scm, name, win32service.SC_MANAGER_ALL_ACCESS)
            if svc:
                win32service.ChangeServiceConfig(svc, win32service.SERVICE_NO_CHANGE, startType,
                                                 win32service.SERVICE_NO_CHANGE, None, None, 0, None, None, None, None)
                return True, ''
        return False, '无法打开服务管理器'
    except:
        return False, get_error_info()


def change_server_users(name, user='LocalSystem', password=None):
    """
    修改服务启动 用户
    @name 服务名称
    @user 启动用户（未设置代表管理员用户）
    @password 用户密码
    """
    try:
        if user and user.find('.\\') < 0:  user = '.\\{}'.format(user)

        import win32service
        scm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
        if scm:
            svc = win32service.OpenService(scm, name, win32service.SC_MANAGER_ALL_ACCESS)
            if svc:
                win32service.ChangeServiceConfig(svc, win32service.SERVICE_NO_CHANGE, win32service.SERVICE_AUTO_START,
                                                 win32service.SERVICE_NO_CHANGE, None, None, 0, None, user, password,
                                                 None)
                return True

        return False, "无法打开服务管理器."
    except:
        return False, get_error_info()


def query_server_config(name):
    """
    查询服务配置
    @name 服务名称
    return 第1位：3（手动） 2（自动）
    """
    status = get_server_status(name)
    if status > -1:
        scm = win32service.OpenSCManager(None, None, win32service.SC_MANAGER_ALL_ACCESS)
        if scm:
            svc = win32service.OpenService(scm, name, win32service.SC_MANAGER_ALL_ACCESS)
            if svc:
                config = win32service.QueryServiceConfig(svc)
                return config
    return None


def get_server_status(name):
    """
    获取服务状态
    @name  服务名称
    return （-1：未安装 0：未启动 1：已启动）
    """
    try:
        serviceStatus = service.QueryServiceStatus(name)
        if serviceStatus[1] == 4:
            return 1
        return 0
    except:
        return -1


def start_service(name):
    """
    启动服务
    @name 服务名称
    return array（状态，错误详情）
    """
    error = None
    try:
        timeout = 0;
        while get_server_status(name) == 0:
            try:
                service.StartService(name)
                time.sleep(1);
            except:
                time.sleep(1);
            timeout += 1

            otime = 180
            if name in ['apache', 'nginx']: otime = 300

            if timeout > otime: break

        if get_server_status(name) != 0:
            return True, error
        return False, '操作失败，{}秒内未完成启动服务【{}】'.format(otime, name)
    except:
        return False, get_error_info()


def stop_service(name):
    """
    启动服务
    @name 服务名称
    return array（状态，错误详情）
    """
    error = None
    try:
        timeout = 0;
        while get_server_status(name) == 1:
            try:
                service.StopService(name)
                time.sleep(1);
            except:
                time.sleep(1);
            timeout += 1

            otime = 180
            if name in ['apache', 'nginx']: otime = 300

            if timeout > otime: break

        if get_server_status(name) != 1:
            return True, error
        return False, '操作失败，{}秒内未完成启动服务【{}】'.format(otime, name)
    except:
        return False, get_error_info()


def set_server_status(name, status):
    """
    设置服务状态
    @name 服务名称
    @status 服务状态（start）
    return array(状态，详情)
    """
    old_status = get_server_status(name)
    if old_status > -1:
        try:
            if status == 'reload' or status == 'restart':
                ret, error = stop_service(name)
                if not ret: ret, error

                ret, error = start_service(name)
                if not ret: return ret, error

                return True, ''
            else:
                if status == 'stop':
                    ret, error = stop_service(name)
                    if not ret: ret, error
                else:
                    ret, error = start_service(name)
                    if not ret: return ret, error
            return True, ''
        except:
            return False, get_error_info()
    return False, "{}服务未安装.".format(name)


def delete_server(name):
    """
    删除系统服务
    @name 服务名称
    return array(状态，详情)
    """
    try:
        stop_service(name)
        service.RemoveService(name)
        return True, ''
    except:
        return False, get_error_info()


def get_server_path(name):
    """
    获取服务路径
    @name 服务名称
    return 服务路径
    """
    data = psutil.win_service_get(name).as_dict()
    path = data['binpath'].split(' ')[0].replace('"', '')
    return path


def is_bind():
    return not not get_user_info()


def is_64bitos():
    """
    判断是否x64系统
    """
    import platform
    bites = {'AMD64': 64, 'x86_64': 64, 'i386': 32, 'x86': 32}
    info = platform.uname()
    if bites.get(info.machine) == 64:
        return True
    return False


def get_appSetup(path, arrs):
    """
    批量查找软件是否安装
    @path 注册表路径
    @arrs 软件列表
    """
    key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, path)
    countkey = winreg.QueryInfoKey(key)[0]
    for i in range(int(countkey)):
        try:
            oKey = winreg.EnumKey(key, i)
            val = ReadReg(path + '\\' + oKey, 'DisplayName')
            if not val: continue;
            num = 0
            for name in arrs:
                if val.find(name) >= 0:
                    num += 1
                else:
                    num = 0;
            if len(arrs) == num: return True;
        except:
            continue
    return False


def isAppSetup(arrs):
    """
    根据多个内容匹配查找软件是否安装
    @arrs 软件名称列表
    """
    rRet = get_appSetup(r'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall', arrs)
    if not rRet:
        if is_64bitos(): rRet = get_appSetup(r'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall', arrs)
    return rRet


def get_vc_version(phpVersion):
    """
    根据php版本获取所需vc版本
    @phpVersion php版本
    return vc++版本
    """
    version = '2008';
    if phpVersion == '71' or phpVersion == '70':
        version = '2015'
    elif phpVersion == '56' or phpVersion == '55':
        version = '2012'
    elif phpVersion == '72' or phpVersion == '73' or phpVersion == '74':
        version = '2015'
    return version


# 替换官网节点
def get_home_node(url):
    try:
        parsed_url = urlparse(url)
        domain = parsed_url.netloc
        if domain in ['www.bt.cn', 'api.bt.cn', 'download.bt.cn']:
            if parsed_url.path in ['/api/index']:
                return url
            sfile = '{}/data/node_url.pl'.format(get_panel_path())
            node_info = json.loads(readFile(sfile))
            if domain == 'www.bt.cn':
                if not node_info['www-node']:
                    return url
                return urlunparse(parsed_url._replace(netloc=node_info['www-node']['url']))
            elif domain == 'api.bt.cn':
                if not node_info['api-node']:
                    return url
                return urlunparse(parsed_url._replace(netloc=node_info['api-node']['url']))
            elif domain == 'download.bt.cn':
                if not node_info['down-node']:
                    return url
                return urlunparse(parsed_url._replace(netloc=node_info['down-node']['url']))
    except:
        pass
    return url

# 检测vc版本
def check_setup_vc(phpVersion, bit='x86'):
    """
    根据php版本判断是否安装所需vc++
    @phpVersion php版本
    @bit 系统位数
    return bool 是否安装
    """
    # bit = ' x64' if is_64bitos() else ' x86'
    arrs = ['Visual C++', bit]
    if phpVersion == '71' or phpVersion == '70':
        arrs.append("2015")
    elif phpVersion == '56' or phpVersion == '55':
        arrs.append("2012")
    elif phpVersion == '73' or phpVersion == '73' or phpVersion == '74':
        arrs.append("2015")
    elif phpVersion == '80':
        arrs.append("2019")
    else:
        arrs.append("2008")
    return isAppSetup(arrs)


def setup_vc(version, bit='x86'):
    """
    安装vc++
    @version vc版本号
    @bit 系统位数
    """
    download_url = get_url()
    tmpPath = 'C:/temp'
    if not os.path.exists(tmpPath): os.makedirs(tmpPath)
    temp = tmpPath + '/vc' + version + '_' + bit + '.exe'
    try:
        downloadFileByWget(download_url + '/win/vc/vcredist_' + version + '_' + bit + '.exe', temp)
    except:
        downloadFile(download_url + '/win/vc/vcredist_' + version + '_' + bit + '.exe', temp)
    if not os.path.exists(temp):
        return False
    ExecShell(temp + ' /q')
    return True


def loads_json(path):
    """
    从文件读取xml转为json对象
    @path 文件路径
    """
    data = {}
    try:
        conf = readFile(path)
        data = xmltodict.parse(conf)
    except:
        pass
    return data


def dumps_json(conf):
    """
    json对象转为xml文件
    @conf json对象
    return str xml文件内容
    """
    xmlstr = xmltodict.unparse(conf)
    return xmlstr


def format_xml(conf):
    """
    格式化xml
    @conf 文件内容
    """
    b = minidom.parseString(conf)
    c = b.toprettyxml(indent="\t")
    return c


def get_file_list(path, flist):
    """
    递归获取目录所有文件列表
    @path 目录路径
    @flist 返回文件列表
    """
    if os.path.exists(path):
        files = os.listdir(path)
        flist.append(path)
        for file in files:
            if os.path.isdir(path + '/' + file):
                get_file_list(path + '/' + file, flist)
            else:
                flist.append(path + '/' + file)


def get_paths(path, flist):
    """
    获取根目录列表
    @path 文件路径
    @flist 文件列表
    """
    try:
        path = path.replace('\\', '/')
        path = os.path.dirname(path)
        if not path in flist:
            flist.append(path)
            get_paths(path, flist)
    except:
        pass


def SetFileAccess(filename, user, access):
    """
    设置文件权限(安装脚本使用)
    @filename 文件路径
    @user 用户名
    @access 用户权限
    """
    try:
        import win32security
        sd = win32security.GetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION)
        dacl = sd.GetSecurityDescriptorDacl()
        ace_count = dacl.GetAceCount()

        for i in range(ace_count, 0, -1):
            try:
                data = {}
                data['rev'], data['access'], usersid = dacl.GetAce(i - 1)
                data['user'], data['group'], data['type'] = win32security.LookupAccountSid('', usersid)
                if data['user'].lower() == user.lower(): dacl.DeleteAce(i - 1)  # 删除旧的dacl
            except:
                dacl.DeleteAce(i - 1)
        try:
            userx, domain, type = win32security.LookupAccountName("", user)
        except:
            try:
                userx, domain, type = win32security.LookupAccountName("", 'IIS APPPOOL\\' + user)
            except:
                return False, "【{}】用户不存在，无法设置权限。".format(user)
        if access > 0:  dacl.AddAccessAllowedAceEx(win32security.ACL_REVISION, 3, access, userx)

        sd.SetSecurityDescriptorDacl(1, dacl, 0)
        win32security.SetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION, sd)
        return True, None
    except:
        return False, get_error_info()


# 设置文件权限和所有者（level:继承到子目录）
def set_file_access(filename, user, access, level=True):
    # 是否继承
    if level:
        if os.path.isdir(filename):
            filelist = []
            get_file_list(filename, filelist)
            for new_path in filelist:
                status, info = SetFileAccess(new_path, user, access)
                if not status: return status, info
            return True, None
        else:
            return SetFileAccess(filename, user, access)
    else:
        return SetFileAccess(filename, user, access)


# 删除文件权限
def del_file_access(filename, user):
    import win32security
    sd = win32security.GetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION)
    dacl = sd.GetSecurityDescriptorDacl()
    ace_count = dacl.GetAceCount()

    for i in range(ace_count, 0, -1):
        try:
            data = {}
            data['rev'], data['access'], usersid = dacl.GetAce(i - 1)
            data['user'], data['group'], data['type'] = win32security.LookupAccountSid('', usersid)

            if data['user'].lower() == user.lower():
                dacl.DeleteAce(i - 1)
        except:
            try:
                # 处理拒绝访问
                dacl.DeleteAce(i - 1)
            except:
                pass
    sd.SetSecurityDescriptorDacl(1, dacl, 0)
    win32security.SetFileSecurity(filename, win32security.DACL_SECURITY_INFORMATION, sd)
    return True


def get_login_type():
    path = '{}/data/login_type.pl'.format(get_panel_path())
    if not os.path.exists(path):
        data = {'sms': 1, 'pwd': 1, 'app': 1}
    else:
        data = json.loads(readFile(path))
    return data


def time_to_int(time):
    """
    时间转为int
    @time 时间格式 字符串
    return int N秒
    """
    if time.find('.') >= 0:
        d, t = time.strip().split(".")
    else:
        d = 0;
        t = time
    h, m, s = t.strip().split(":")
    return int(d) * 86400 + int(h) * 3600 + int(m) * 60 + int(s)


def int_to_time(seconds):
    """
    秒数转为具体天数
    @seconds 秒数
    return str 10:10
    """
    m, s = divmod(seconds, 60)
    h, m = divmod(m, 60)
    d, h = divmod(h, 24)

    result = "%s.%02d:%02d:%02d" % (d, h, m, s)
    return result


def get_pip_list():
    """
    获取pip安装模块列表
    return list
    """
    rRet = ExecShell(get_run_pip('[PIP] list'))
    list = rRet[0].split('\r\n')
    import re
    data = {}
    i = 0;
    for x in list:
        if i > 1:
            temp = re.search('([\w-]+)\s+([\d\.]+)', x)
            if temp:
                key = temp.groups()[0]
                version = temp.groups()[1]

                item = {}
                item['version'] = version
                data[key] = item
        i += 1
    return data


def write_request_log():
    """
    写面板日志
    """
    try:
        from BTPanel import request, g, session
        if session.get('debug') == 1: return

        log_path = 'logs/request'
        log_file = getDate(format='%Y-%m-%d') + '.json'
        if not os.path.exists(log_path): os.makedirs(log_path)

        from flask import request
        log_data = {}
        log_data['date'] = getDate()
        log_data['ip'] = GetClientIp() + ':' + str(request.environ.get('REMOTE_PORT'))
        log_data['method'] = request.method
        log_data['uri'] = request.full_path
        log_data['user-agent'] = request.headers.get('User-Agent')

        log_data['argv'] = "{}"
        if request.method == 'POST':
            args = str(request.form.to_dict())
            if len(args) < 2048 and args.find('pass') == -1 and args.find('user') == -1:
                log_data['argv'] = args

        WriteFile(log_path + '/' + log_file, json.dumps(log_data) + "\n", 'a+')
    except:
        pass


def get_menus():
    '''
        @name 获取菜单列表
        @author hwliang<2020-08-31>
        @return list
    '''
    from BTPanel import session
    data = json.loads(ReadFile('config/menu.json'))
    hide_menu = ReadFile('config/hide_menu.json')
    debug = session.get('debug')
    if hide_menu:
        hide_menu = json.loads(hide_menu)
        show_menu = []
        for i in range(len(data)):
            if data[i]['id'] in hide_menu: continue
            if data[i]['id'] == "memuAxterm":
                if debug: continue
            show_menu.append(data[i])
        data = show_menu
        del (hide_menu)
        del (show_menu)
    menus = sorted(data, key=lambda x: x['sort'])
    return menus


def get_path_size(path):
    """
    根据路径获取文件/目录大小
    @path 文件或者目录路径
    return int
    """
    if not os.path.exists(path): return 0;
    if not os.path.isdir(path): return os.path.getsize(path)
    size_total = 0
    for nf in os.walk(path):
        for f in nf[2]:
            filename = nf[0] + '/' + f
            if not os.path.exists(filename): continue;
            if os.path.islink(filename): continue;
            size_total += os.path.getsize(filename)
    return size_total


def get_database_character(db_name):
    """
    获取mysql字符集
    @db_name 数据库名称
    return str 字符集
    """
    try:
        db_obj = get_mysql_obj(db_name)
        tmp = db_obj.query("show create database `%s`" % db_name.strip())
        c_type = str(re.findall(r"SET\s+([\w\d-]+)\s",tmp[0][1])[0])
        c_types = ['utf8','utf-8','gbk','big5','utf8mb4']
        if not c_type.lower() in c_types: return 'utf8'
        return c_type
    except:
        return 'utf8'

# 取mysql数据库对象
def get_mysql_obj(db_name):
    is_cloud_db = False
    if db_name:
        db_find = M('databases').where("name=?" ,db_name).find()
        if db_find['sid']:
            return get_mysql_obj_by_sid(db_find['sid'])
        is_cloud_db = db_find['db_type'] in ['1',1]


    if is_cloud_db:
        import db_mysql
        db_obj = db_mysql.panelMysql()
        conn_config = json.loads(db_find['conn_config'])
        try:
            db_obj = db_obj.set_host(conn_config['db_host'],conn_config['db_port'],conn_config['db_name'],conn_config['db_user'],conn_config['db_password'])
        except Exception as e:
            raise PanelError(GetMySQLError(e))
    else:
        import panelMysql
        db_obj = panelMysql.panelMysql()
    return db_obj

# 取mysql数据库对像 By sid
def get_mysql_obj_by_sid(sid = 0,conn_config = None):
    if type(sid) == str:
        try:
            sid = int(sid)
        except :sid = 0

    if sid:
        if not conn_config: conn_config = M('database_servers').where("id=?" ,sid).find()
        import db_mysql
        db_obj = db_mysql.panelMysql()

        try:
            db_obj = db_obj.set_host(conn_config['db_host'],conn_config['db_port'],None,conn_config['db_user'],conn_config['db_password'])
        except Exception as e:
            raise PanelError(GetMySQLError(e))
    else:

        import panelMysql
        db_obj = panelMysql.panelMysql()
    return db_obj

def GetMySQLError(e):
    res = ''
    if e.args[0] == 1045:
        res = '数据库用户名或密码错误!'
    if e.args[0] == 1049:
        res = '数据库不存在!'
    if e.args[0] == 1044:
        res = '没有指定数据库的访问权限，或指定数据库不存在!'
    if e.args[0] == 1062:
        res = '数据库已存在!'
    if e.args[0] == 1146:
        res = '数据表不存在!'
    if e.args[0] == 2003:
        res = '数据库服务器连接失败!'
    if e.args[0] == 1142:
        res = '指定用户权限不足!'
    if res:
        res = res + "<pre>" + str(e) + "</pre>"
    else:
        res = str(e)
    return res

def get_database_codestr(codeing):
    wheres = {
        'utf8': 'utf8_general_ci',
        'utf8mb4': 'utf8mb4_general_ci',
        'gbk': 'gbk_chinese_ci',
        'big5': 'big5_chinese_ci'
    }
    return wheres[codeing]


def get_database_size(name=None):
    """
    @获取数据库大小
    """
    data = {}
    try:
        mysql_obj = get_mysql_obj(name)
        tables = mysql_obj.query("select table_schema, (sum(DATA_LENGTH)+sum(INDEX_LENGTH)) as data from information_schema.TABLES group by table_schema")
        if type(tables) == list:
            for x in tables:
                if len(x) < 2:continue
                if x[1] == None:continue
                data[x[0]] = int(x[1])
    except: return data
    return data

def get_database_size_by_name(name):
    """
    @获取数据库大小
    """
    data = 0
    try:
        mysql_obj = get_mysql_obj(name)

        tables = mysql_obj.query("select table_schema, (sum(DATA_LENGTH)+sum(INDEX_LENGTH)) as data from information_schema.TABLES WHERE table_schema='{}' group by table_schema".format(name))
        data = tables[0][1]
        if not data: data = 0
    except: return data
    return data

def get_database_size_by_id(id):
    """
    @获取数据库大小
    """
    data = 0
    name = M('databases').where('id=?',id).getField('name')

    mysql_obj = get_mysql_obj(name)

    try:
        tables = mysql_obj.query("select table_schema, (sum(DATA_LENGTH)+sum(INDEX_LENGTH)) as data from information_schema.TABLES WHERE table_schema='{}' group by table_schema".format(name))
        print(tables)
        data = tables[0][1]
        if not data: data = 0
    except :pass
    return data


def check_win_path(path):
    """
    检查Windows路径是否含有特殊字符
    @path 需检查的文件路径
    return bool
    """
    import re
    tmp = re.match('[a-zA-Z]:(.+)', path)
    if not tmp: return False
    if re.search('[\*\?\<\>\:\|\"\：]', tmp.groups()[0]): return False
    return True


def submit_panel_bug(err_msg=None):
    """
    提交面板错误日志
    @err_msg 错误内容
    """
    try:
        from BTPanel import request
        import system
        if not err_msg: err_msg = get_error_info()

        pdata = {}
        pdata['err_info'] = err_msg
        pdata['path_full'] = request.full_path

        pdata['version'] = 'Windows-Panel-%s' % version()
        pdata['os'] = system.system().GetSystemVersion()
        pdata['py_version'] = sys.version
        pdata['install_date'] = int(os.stat('class/common.py').st_mtime)
        httpPost("https://www.bt.cn/api/panel/s_error", pdata, timeout=3)
    except:
        pass


# 获取debug日志
def get_debug_log():
    from BTPanel import request

    remote_port = str(request.environ.get('REMOTE_PORT'))
    address = GetClientIp()
    if address.strip() == '127.0.0.1':
        try:
            port = get_ssh_port()
            ret = ExecShell('netstat -aon|findstr ":{}"'.format(port))[0]
            tmps = re.search('TCP\s+.+:{}\s+(.+):(\d+)\s+ESTABLISHED'.format(port), ret)
            if tmps:
                tmp = tmps.groups()
                address = tmp[0]
                remote_port = tmp[1]
        except:
            pass

    http_type = readFile('{}/data/http_type.pl'.format(get_panel_path()))
    if http_type == 'curl':
        #curl错误日志会截断
        return address + ':' + remote_port + '|' + str(int(time.time())) + '|'
    return address + ':' + remote_port + '|' + str(int(time.time())) + '|' + get_error_info()


def version():
    """
    获取面板版本
    """
    try:
        version = readFile('{}/data/panel.version'.format(get_panel_path()))
    except:
        version = '6.0.0'
    return version


def path_safe_check(path, force=True):
    """
    校验路径安全
    @path 校验路径
    @force
    """
    if len(path) > 256: return False
    checks = ['..', './', '\\', '%', '$', '^', '&', '*', '~', '#', '"', "'", ';', '|', '{', '}', '`']
    for c in checks:
        if path.find(c) != -1: return False
    if force:
        rep = r"^[\w\s\.\/-]+$"
        if not re.match(rep, path): return False
    return True


def get_pip_url():
    """
    自动适配pip源(os.getenv('APPDATA') + '/pip')
    """
    try:
        path = os.getenv('APPDATA') + '/pip'
        if not os.path.exists(path): os.makedirs(path)
        if not os.path.exists(path + '/pip.ini'):
            nodes = ['https://mirrors.aliyun.com/pypi/simple', 'https://pypi.doubanio.com/simple', 'https://pypi.tuna.tsinghua.edu.cn/simple']
            curr_node = None
            for node in nodes:
                start = time.time();
                ping = 10000
                ret = httpGet(node + '/0/', 3)
                if ret.find(".whl") >= 0:  ping = time.time() - start

                if not curr_node: curr_node = {'ping': 10000, 'url': node}
                if ping < curr_node['ping']: curr_node = {'ping': ping, 'url': node}
                if curr_node['ping'] < 50: break

            tmp = re.search('\/\/(.+?)\/', curr_node['url'])
            if tmp:
                host = tmp.groups()[0]
                config = '''[global]
    index-url = %s

    [install]
    trusted-host = %s''' % (curr_node['url'], host)
                writeFile(path + '/pip.ini', config)
                return True
    except :pass

    return False


def copytree(src, dst):
    """
    重新拷贝目录方法
    @src 源目录
    @dst 新目录
    """
    import shutil
    names = os.listdir(src)
    if not os.path.exists(dst):  os.makedirs(dst)
    for name in names:
        srcname = os.path.join(src, name)
        dstname = os.path.join(dst, name)
        try:
            if os.path.isdir(srcname):
                copytree(srcname, dstname)
            else:
                shutil.copy2(srcname, dstname)
        except:
            pass


def move(sfile, dfile):
    """
    重写文件移动
    @sfile 源文件路径
    @dfile 新文件路径
    """
    dfile = dfile.replace('//', '/')
    sfile = sfile.replace('//', '/')
    import shutil
    if not os.path.exists(sfile): return False
    if dfile.rstrip('/') == sfile.rstrip('/'):  return False

    dfile.rstrip()
    if sfile.lower() == dfile.lower():
        os.rename(sfile, dfile);
        return True;

    is_dir = os.path.isdir(sfile)
    if not os.path.exists(dfile) or not is_dir:
        try:
            if os.path.exists(dfile): os.remove(dfile)
        except:
            ExecShell("attrib -r {}".format(to_path(dfile)))
            os.remove(dfile)

        shutil.move(sfile, dfile)
    else:
        copytree(sfile, dfile)
        if os.path.exists(sfile):
            if is_dir:
                shutil.rmtree(sfile, True)
            else:
                try:
                    os.remove(sfile)
                except:
                    ExecShell("attrib -r {}".format(to_path(dfile)))
                    os.remove(sfile)
    return True


def rmdir(sfile, mkdir=False):
    """
    @删除文件/目录
    """
    if not os.path.exists(sfile):
        if mkdir: os.makedirs(sfile)
        return False
    import shutil
    path = os.path.dirname(sfile)
    try:
        dfile = '{}/{}.del'.format(path, GetRandomString(12))
        try:
            if os.path.isfile(sfile):
                os.remove(sfile)
            else:
                shutil.rmtree(sfile, True)
        except : pass
        if os.path.exists(sfile): os.rename(sfile, dfile)
    except:
        pass

    for x in os.listdir(path):
        try:
            if x[-4:] == '.del':
                tpath = '{}/{}'.format(path, x)
                if os.path.isfile(tpath):
                    os.remove(tpath)
                else:
                    shutil.rmtree(tpath, True)
        except:
            pass

    if mkdir: os.makedirs(sfile)


def rmlog():
    log_file = get_panel_path() + '/data/panelShell.pl'
    rmdir(log_file)
    return log_file


def format_error(msg):
    """
    格式化错误
    """
    if msg.find('页面文件太小') >= 0:
        msg = "系统内存不足."
    elif msg.find('No space left on device') >= 0:
        msg = '无法完成此操作，磁盘容量不足，请先清理或扩充.'

    return msg


def sync_date(self, get):
    """
    同步系统时间
    """
    dateStr = HttpGet(GetConfigValue('home') + '/api/index/get_win_date')
    arrs = dateStr.split(' ')
    os.system('date {} && time {}'.format(arrs[0], arrs[1]))


def set_phpmyadmin_conf(port):
    """
    设置phpmyadmin连接端口
    @port mysql端口
    """
    dir_name = readFile('data/phpmyadminDirName.pl')
    if not dir_name: return True

    path = os.getenv("BT_SETUP") + '\\phpmyadmin\\' + dir_name
    conf = ''
    if not os.path.exists(path + '/config.inc.php'):
        if os.path.exists(path + '/config.sample.inc.php'): conf = readFile(path + '/config.sample.inc.php')
    else:
        conf = readFile(path + '/config.inc.php')

    if conf.find("$cfg['Servers'][$i]['port']") >= 0:
        conf = re.sub("\$cfg\[\'Servers\'\]\[\$i\]\[\'port\'\].+", "$cfg['Servers'][$i]['port'] = '{}';".format(port),
                      conf)
    else:
        conf = conf.replace('''$cfg['Servers'][$i]['host'] = 'localhost';''', '''$cfg['Servers'][$i]['host'] = 'localhost';
$cfg['Servers'][$i]['port'] = '%s';''' % (port))

    writeFile(path + '/config.inc.php', conf)


def get_run_python(shell):
    """
    获取运行python脚本
    @shell 执行命令
    """
    shell = shell.replace('[PYTHON]', '"C:/Program Files/python/python.exe"')
    shell = to_path(shell)
    return shell


def get_run_pip(shell):
    """
    获取运行pip脚本
    @shell 执行命令
    """
    shell = shell.replace('[PIP]', '"C:\Program Files\python\python.exe" -m pip')
    return shell


def tran_lang(msg, to_lang, from_lang="CN"):
    """
    转换语言
    @msg 要转换的内容
    @to_lang 需要转换的语言
    @from_lang 原内容语言
    """
    try:
        from translate import Translator
    except:
        os.system(get_run_pip("[PIP] install translate"))
        from translate import Translator

    translator = Translator(from_lang=from_lang, to_lang=to_lang)
    translation = translator.translate(msg)

    return translation


# 中文路径处理
def en_punycode_path(path):
    path = path.lower()
    if sys.version_info[0] == 2: path = path.encode('utf-8')
    if os.path.exists(path): return path
    import re;
    match = re.search(u"[\x80-\xff]+", path)
    if not match: return path
    npath = ''
    for ph in path.split('/'):
        npath += '/' + en_punycode(ph)
    return npath.replace('//', '/')


def en_punycode(domain):
    """
    中文域名转punycode
    @domain 要转换的域名
    """
    tmp = domain.lower().split('.')
    newdomain = ''
    for dkey in tmp:
        # 匹配非ascii字符
        match = re.search(u"[\x80-\xff]+", dkey)
        if not match: match = re.search(u"[\u4e00-\u9fa5]+", dkey)
        if not match:
            newdomain += dkey + '.'
        else:
            newdomain += 'xn--' + dkey.encode('punycode').decode('utf-8') + '.'
    return newdomain[0:-1]


def de_punycode(domain):
    """
    punycode 转中文域名
    @domain 要转换的域名
    """
    tmp = domain.lower().split('.')
    newdomain = ''
    for dkey in tmp:
        if dkey.find('xn--') >= 0:
            newdomain += dkey.replace('xn--', '').encode('utf-8').decode('punycode') + '.'
        else:
            newdomain += dkey + '.'
    return newdomain[0:-1]


def import_cdn_plugin():
    """
    引用静态加速插件
    """
    plugin_path = '{}/plugin/static_cdn'.format(get_panel_path())
    if not os.path.exists(plugin_path):
        return True
    try:
        import static_cdn_main
    except:
        sys.path.insert(0, plugin_path)
        import static_cdn_main


def get_cdn_hosts():
    """
    获取静态加速插件节点列表
    """
    if import_cdn_plugin(): return []
    import static_cdn_main
    return static_cdn_main.static_cdn_main().get_hosts(None)


def get_cdn_url():
    """
    获取当前选择的静态加速节点
    """
    try:

        from BTPanel import cache
        cdn_url = cache.get('cdn_url')
        if cdn_url: return cdn_url
        if import_cdn_plugin(): return False
        import static_cdn_main
        cdn_url = static_cdn_main.static_cdn_main().get_url(None)

        cache.set('cdn_url', cdn_url, 3)
        return cdn_url
    except:
        return False


def set_cdn_url(cdn_url):
    """
    设置当前静态加速节点
    @cdn_url 当前选择的cdn节点
    """
    if not cdn_url: return False
    import_cdn_plugin()
    get = dict_obj()
    get.cdn_url = cdn_url
    import static_cdn_main
    static_cdn_main.static_cdn_main().set_url(get)
    return True


def get_page(count, p=1, rows=12, callback='', result='1,2,3,4,5,8'):
    """
    构造分页
    @count 总数量
    @p 当前第几页
    @rows 每页行数
    @callback 回调函数
    @result 构造结果
    """
    import page

    fullurl = "/"
    try:
        from BTPanel import request
        fullurl = request.full_path
    except:
        pass

    page = page.Page()
    info = {'count': count, 'row': rows, 'p': p, 'return_js': callback, 'uri': fullurl}
    data = {'page': page.GetPage(info, result), 'shift': str(page.SHIFT), 'row': str(page.ROW)}
    return data


def is_domain(domain):
    reg = "^([\w\-\*]{1,100}\.){1,10}([\w\-]{1,24}|[\w\-]{1,24}\.[\w\-]{1,24})$"
    if re.match(reg, domain): return True
    return False


def is_ipv4(ip):
    """
    判断是否IPV4地址
    @ip ip地址
    return bool
    """
    try:
        socket.inet_pton(socket.AF_INET, ip)
    except AttributeError:
        try:
            socket.inet_aton(ip)
        except socket.error:
            return False
        return ip.count('.') == 3
    except socket.error:
        return False
    return True


def is_ipv6(ip):
    """
    判断是否IPV6地址
    @ip ip地址
    return bool
    """
    try:
        socket.inet_pton(socket.AF_INET6, ip)
    except socket.error:
        return False
    return True


def get_line_ips(_line):
    """
    @name 格式化行内ip
    """
    ips = []
    tmps = re.findall('(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)',_line)
    for tmp in tmps:
        ips.append('.'.join(tmp))
    return ips

def check_ip(ip):
    """
    判断是否IP地址(ipv4+ipv6)
    @ip ip地址
    return bool
    """
    return is_ipv4(ip) or is_ipv6(ip)


def url_encode(data):
    if type(data) == str: return data
    import urllib
    if sys.version_info[0] != 2:
        pdata = urllib.parse.urlencode(data).encode('utf-8')
    else:
        pdata = urllib.urlencode(data)
    return pdata


def url_decode(data):
    if type(data) == str: return data
    import urllib
    if sys.version_info[0] != 2:
        pdata = urllib.parse.urldecode(data).encode('utf-8')
    else:
        pdata = urllib.urldecode(data)
    return pdata


def unzip(src_path, dst_path):
    """
    解压目录
    @src_path zip文件路径
    @dst_path 解压路径
    """
    import zipfile
    zip_file = zipfile.ZipFile(src_path)
    for names in zip_file.namelist():
        zip_file.extract(names, dst_path)
    zip_file.close()
    return True


def trim(data):
    """
    @去除所有空格
    """
    return data.replace(' ','').strip()

def set_func(key,count = 0):
    """
    设置指定key的操作时间
    @key 面板访问函数
    """
    path = 'data/func.json'
    data = {}
    try:
        data = json.loads(readFile(path))
    except:
        pass
    if not key in data:
        data[key] = {}
        data[key]['time'] = 0
        data[key]['count'] = 0
    data[key]['time'] = int(time.time())
    if count > 0:
        data[key]['count'] = count
    else:
        data[key]['count'] += 1
    writeFile(path, json.dumps(data))


def get_func(key):
    """
    获取指定功能的操作时间
    @key 面板函数
    """
    path = 'data/func.json'
    ret = {}
    ret['count'] = 0
    ret['time'] = 0
    if not os.path.exists(path): return ret
    data = {}
    try:
        data = json.loads(readFile(path))
    except:
        pass
    if not key in data: return ret
    return data[key]


def set_cache_func(key,info):
    """
    设置指定key的操作时间
    @key 缓存的key函数
    """
    data = {}
    path = '{}/data/cache_func.json'.format(get_panel_path())

    try:
        data = json.loads(readFile(path))
    except:
        pass
    if not key in data: data[key] = {}

    data[key]['time'] = int(time.time())
    data[key]['data'] = info

    writeFile(path, json.dumps(data))


def get_cache_func(key):
    """
    获取指定功能的操作时间
    @key 缓存的key函数
    """

    ret = {}
    data = {}
    ret['data'] = ''
    ret['time'] = 0
    path = '{}/data/cache_func.json'.format(get_panel_path())
    if not os.path.exists(path):
        return ret
    try:
        data = json.loads(readFile(path))
    except:
        pass
    if not key in data: return ret
    return data[key]

def find_user(name):
    """
    查询系统用户是否存在
    @name 系统用户
    """
    resume = 0
    while True:
        data, total, resume = win32net.NetUserEnum(None, 3, win32netcon.FILTER_NORMAL_ACCOUNT, resume)
        for user in data:
            if user['name'].lower() == name.lower():
                return user
        if not resume: break

    return False


def add_user(name, pwd, ps=''):
    """
    添加系统用户
    @name 用户名
    @pwd 密码
    @ps 备注
    """
    try:
        d = {}
        d['name'] = name.lower()
        d['password'] = pwd
        d['comment'] = ps
        d['flags'] = win32netcon.UF_NORMAL_ACCOUNT | win32netcon.UF_SCRIPT
        d['priv'] = win32netcon.USER_PRIV_USER
        win32net.NetUserAdd(None, 1, d)

        # 设置用户允许登录服务
        handle = win32security.LsaOpenPolicy(None, win32security.POLICY_ALL_ACCESS)
        sid_obj, domain, tmp = win32security.LookupAccountName(None, name.lower())
        win32security.LsaAddAccountRights(handle, sid_obj, ('SeServiceLogonRight',))
        win32security.LsaClose(handle)

        return True, None
    except:
        return False, get_error_info()


def change_user_pwd(name, pwd):
    """
    修改用户密码
    @name 系统用户
    @pwd 新密码
    """
    try:
        name = name.lower()
        data = find_user(name)
        if data:
            data['password'] = pwd
            win32net.NetUserSetInfo(None, name, 1, data)
            return True, None
        return False, '【{}】用户不存在'.format(name)
    except:
        return False, get_error_info()


def del_user(name):
    """
    删除系统用户
    @name 系统用户名
    """
    name = name.lower()
    bt_user = ["administrator"]
    if name in bt_user: return False, '系统用户不允许删除.'
    try:
        win32net.NetUserDel(None, name)
        return True, None
    except:
        return False, get_error_info()


def init_msg(module):
    """
    初始化消息通道
    @module 消息通道模块名称
    """
    import os, sys
    if not os.path.exists('class/msg'): os.makedirs('class/msg')
    panelPath = get_panel_path()

    sfile = 'class/msg/{}_msg.py'.format(module)
    if not os.path.exists(sfile): return False
    sys.path.insert(0, "{}/class/msg".format(panelPath))

    msg_main = __import__('{}_msg'.format(module))
    try:
        mod_reload(msg_main)
    except:
        pass
    return eval('msg_main.{}_msg()'.format(module));


# 获取证书哈希
def get_cert_data(path):
    import panelSSL
    get = dict_obj()
    get.certPath = path
    data = panelSSL.panelSSL().GetCertName(get)
    return data


def set_error_num(key, empty=False, expire=3600):
    '''
        @name 设置失败次数(每调用一次+1)
        @author hwliang<2020-08-21>
        @param key<string> 索引
        @param empty<bool> 是否清空计数
        @param expire<int> 计数器生命周期(秒)
        @return bool
    '''
    from BTPanel import cache
    key = md5(key)
    num = cache.get(key)
    if not num:
        num = 0
    else:
        if empty:
            cache.delete(key)
            return True
    cache.set(key, num + 1, expire)
    return True


def get_error_num(key, limit=False):
    '''
        @name 获取失败次数
        @author hwliang<2020-08-21>
        @param key<string> 索引
        @param limit<False or int> 如果为False，则直接返回失败次数，否则与失败次数比较，若大于失败次数返回True，否则返回False
        @return int or bool
    '''
    from BTPanel import cache
    key = md5(key)
    num = cache.get(key)
    if not num: num = 0
    if not limit:
        return num
    if limit > num:
        return True
    return False


def long2ip(ips):
    '''
        @name 将整数转换为IP地址
        @author hwliang<2020-06-11>
        @param ips string(ip地址整数)
        @return ipv4
    '''
    i1 = int(ips / (2 ** 24))
    i2 = int((ips - i1 * (2 ** 24)) / (2 ** 16))
    i3 = int(((ips - i1 * (2 ** 24)) - i2 * (2 ** 16)) / (2 ** 8))
    i4 = int(((ips - i1 * (2 ** 24)) - i2 * (2 ** 16)) - i3 * (2 ** 8))
    return "{}.{}.{}.{}".format(i1, i2, i3, i4)


def ip2long(ip):
    '''
        @name 将IP地址转换为整数
        @author hwliang<2020-06-11>
        @param ip string(ipv4)
        @return long
    '''
    ips = ip.split('.')
    if len(ips) != 4: return 0
    iplong = 2 ** 24 * int(ips[0]) + 2 ** 16 * int(ips[1]) + 2 ** 8 * int(ips[2]) + int(ips[3])
    return iplong


# 提交关键词
def submit_keyword(keyword):
    pdata = {"keyword": keyword}
    httpPost(GetConfigValue('home') + '/api/panel/total_keyword', pdata)


# 统计关键词
def total_keyword(keyword):
    import threading
    p = threading.Thread(target=submit_keyword, args=(keyword,))
    p.setDaemon(True)
    p.start()


# 获取sessionid
def get_session_id():
    from BTPanel import request, app
    session_id = request.cookies.get(app.config['SESSION_COOKIE_NAME'], '')
    if not re.findall(r"^([\w\.-]{64,64})$", session_id):
        return GetRandomString(64)
    return session_id


def chdck_salt():
    '''
        @name 检查所有用户密码是否加盐，若没有则自动加上
        @author hwliang<2020-07-08>
        @return void
    '''


    u_list = M('users').where('salt is NULL', ()).field('id,username,password,salt').select()
    for u_info in u_list:
        salt = GetRandomString(12)  # 12位随机
        pdata = {}
        pdata['password'] = md5(md5(u_info['password'] + '_bt.cn') + salt)
        pdata['salt'] = salt
        M('users').where('id=?', (u_info['id'],)).update(pdata)


def get_login_token():
    token_s = readFile('data/login_token.pl')
    if not token_s: return GetRandomString(32)

    return token_s


def get_sess_key():
    from BTPanel import session
    return md5(get_login_token() + session.get('request_token_head', ''))


def auto_backup_panel():
    """
    @naem 自动备份面板数据
    """
    try:
        panel_path = get_panel_path()

        paths = panel_path + '/data/not_auto_backup.pl'
        if os.path.exists(paths): return False
        b_path = '{}/backup/panel'.format(get_soft_path())
        backup_path = '{}/{}/panel'.format(b_path, format_date('%Y-%m-%d'))

        if os.path.exists("{}.zip".format(backup_path)): return True

        if os.path.getsize(panel_path + '/data/default.db') > 104857600 * 5:
            return False

        dst_path = backup_path + '/data'
        if not os.path.exists(dst_path):
            os.makedirs(dst_path, 384)

        if not os.path.exists(backup_path):
            os.makedirs(backup_path, 384)

        copytree(panel_path + '/config', backup_path + '/config')
        copytree(panel_path + '/vhost', backup_path + '/vhost')
        s_path = panel_path + '/data'
        for name in os.listdir(s_path):
            #过滤大文件
            if name in ['scan']: continue
            try:
                filename = '{}/{}'.format(s_path, name)
                if os.path.isdir(filename):
                    copytree(filename, backup_path + '/data/' + name)
                else:
                    shutil.copy(filename, backup_path + '/data/')
            except:pass

        flist = []
        get_file_list(backup_path, flist)
        import zipfile
        f = zipfile.ZipFile(backup_path + '.zip', 'w', zipfile.ZIP_DEFLATED)
        for item in flist:
            s_path = item.replace(backup_path, '')
            if s_path: f.write(item, s_path)
        f.close()
        rmdir(backup_path)
        time_now = time.time() - (86400 * 15)
        for f in os.listdir(b_path):
            if time.mktime(time.strptime(f, "%Y-%m-%d")) < time_now:
                path = b_path + '/' + f
                if os.path.exists(path): shutil.rmtree(path)
    except:
        pass

def password_salt(password, username=None, uid=None):
    '''
        @name 为指定密码加盐
        @author hwliang<2020-07-08>
        @param password string(被md5加密一次的密码)
        @param username string(用户名) 可选
        @param uid int(uid) 可选
        @return string
    '''
    chdck_salt()
    if not uid:
        if not username:
            raise Exception('username或uid必需传一项')
        uid = M('users').where('username=?', (username,)).getField('id')
    salt = M('users').where('id=?', (uid,)).getField('salt')
    return md5(md5(password + '_bt.cn') + salt)

def get_mysql_info():
    """
    获取以安装数据库信息
    """
    data = {}
    try:
        path = get_server_path('mysql')
        name = re.search('([MySQL|MariaDB-]+\d+\.\d+)', path).groups()[0]
        myconf = readFile(GetConfigValue('setup_path') + '/mysql/' + name + '/my.ini')
        rep = "port\s*=\s*([0-9]+)"

        data['port'] = int(re.search(rep, myconf).groups()[0])
        data['name'] = name
        data['path'] = GetConfigValue('setup_path') + '/mysql/' + data['name']
        data['version'] = re.search("(\d*\.\d*)", name).groups()[0]
        return data
    except :
        data['path'] = ''
        return data

def get_net_version():
    """
    获取net版本
    """
    netV = '45'
    if os.path.exists('data/net'):
        netV = readFile('data/net')
    return netV


def get_py_version():
    """
    获取加密插件版本
    """
    import sys
    v = 'cp{}{}'.format(sys.version_info.major, sys.version_info.minor)

    return v


def clear_is_read(path):
    if os.path.isdir(path):
        ExecShell("attrib -r {}/*.* /s /d".format(to_path(path)))
    else:
        ExecShell("attrib -r {} /s /d".format(to_path(path)))


def get_update_file():
    local_path = '{}/class/panel_update.py'.format(os.getenv('BT_PANEL'))
    downloadFileByWget('{}/win/install/panel_update.py'.format(get_url()), local_path)
    if os.path.getsize(local_path) < 64: return False

    update_obj = __import__('panel_update')
    mod_reload(update_obj)

    return True


def file_unlock(filename):
    """
    解除文件占用
    @filename 文件路径
    """
    for proc in psutil.process_iter():
        if proc.pid < 200: continue  # 1000以内为系统进程
        try:
            for x in proc.open_files():
                if format_path(filename.strip()) == format_path(x.path.strip()):
                    proc.kill()
        except:
            pass


def get_registry_value(key, subkey, value):
    """
    读取注册表信息
    @key 注册表类型
    @subkey 注册表路径
    @value 注册表具体key值
    """
    import winreg
    key = getattr(winreg, key)
    handle = winreg.OpenKey(key, subkey)
    (value, type) = winreg.QueryValueEx(handle, value)
    return value


def get_system_version(info=False):
    """
    取操作系统版本
    """
    try:
        import platform
        bit = 'x86'
        if is_64bitos(): bit = 'x64'

        def get(key):
            return get_registry_value("HKEY_LOCAL_MACHINE", "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", key)

        os = get("ProductName")
        build = get("CurrentBuildNumber")

        version = "%s (build %s) %s (Py%s)" % (os, build, bit, platform.python_version())
        if info: return version

        tmp = re.search('Windows\s+Server\s+(\d+)', version)
        if tmp: return tmp.groups()[0]

        return None
    except Exception as ex:
        WriteLog('获取系统版', '获取系统版本失败,注册表无法打开,错误详情 ->>' + str(ex));
        return None


def aes_encrypt(data, key):
    import panelAes
    aes_obj = panelAes.aescrypt_py3(key)
    return aes_obj.aesencrypt(data)


def aes_decrypt(data, key):
    import panelAes
    aes_obj = panelAes.aescrypt_py3(key)
    return aes_obj.aesdecrypt(data)

def get_net_addrs():
    """
    @获取网卡ip地址
    """
    res = []
    nets = psutil.net_if_addrs()
    for key, addrs in nets.items():
        status = {"ip" : "","type" : "ipv4","addrs":key,"mac" : ""}
        for addr in addrs:
            status["ip"] =  addr.address
            if addr.family == socket.AF_INET6:
                status["type"] = 'ipv6'
            if addr.family == psutil.AF_LINK:
                status["mac"] = addr.address
        res.append(status)
    return res

def get_ipaddress():
    '''
        @name 获取本机IP地址
        @author hwliang<2020-11-24>
        @return list
    '''
    result = []
    for item in get_net_addrs():
        if item['ip'] in ['127.0.0.1']: continue
        if re.search('^192\.168\.', item['ip']): continue
        if re.search('^10\.', item['ip']): continue

        result.append(item['ip'])
    return result


def run_thread(fun, args=(), daemon=False):
    '''
        @name 使用线程执行指定方法
        @author heliang<2020-10-27>
        @param fun {def} 函数对像
        @param args {tuple} 参数元组
        @param daemon {bool} 是否守护线程
        @return bool
    '''
    import threading
    p = threading.Thread(target=fun, args=args)
    p.setDaemon(daemon)
    p.start()
    return True


# 加密字符串
def en_crypt(key, strings):
    try:
        if type(strings) != bytes: strings = strings.encode('utf-8')
        from cryptography.fernet import Fernet
        f = Fernet(key)
        result = f.encrypt(strings)
        return result.decode('utf-8')
    except:
        # print(get_error_info())
        return strings


# 解密字符串
def de_crypt(key, strings):
    try:
        if type(strings) != bytes: strings = strings.decode('utf-8')
        from cryptography.fernet import Fernet
        f = Fernet(key)
        result = f.decrypt(strings).decode('utf-8')
        return result
    except:
        # print(get_error_info())
        return strings


def check_domain_cloud(domain):
    run_thread(cloud_check_domain, (domain,))


def cloud_check_domain(domain):
    '''
        @name 从云端验证域名的可访问性,并将结果保存到文件
        @author hwliang<2020-12-10>
        @param domain {string} 被验证的域名
        @return void
    '''
    try:
        check_domain_path = 'data/check_domain/'
        if not os.path.exists(check_domain_path):
            os.makedirs(check_domain_path, 384)
        result = httpPost('https://www.bt.cn/api/panel/check_domain', {"domain": domain})
        cd_file = check_domain_path + domain + '.pl'
        writeFile(cd_file, result)
    except:
        pass


def isOpen(port):
    # 检查端口是否占用
    import socket
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        s.connect(('127.0.0.1', int(port)))
        s.shutdown(2)
        return True
    except:
        return False


def get_root_domain(domain_name):
    '''
        @name 根据域名查询根域名和记录值
        @author cjxin<2020-12-17>
        @param domain {string} 被验证的根域名
        @return void
    '''
    top_domain_list = ['.ac.cn', '.ah.cn', '.bj.cn', '.com.cn', '.cq.cn', '.fj.cn', '.gd.cn',
                       '.gov.cn', '.gs.cn', '.gx.cn', '.gz.cn', '.ha.cn', '.hb.cn', '.he.cn',
                       '.hi.cn', '.hk.cn', '.hl.cn', '.hn.cn', '.jl.cn', '.js.cn', '.jx.cn',
                       '.ln.cn', '.mo.cn', '.net.cn', '.nm.cn', '.nx.cn', '.org.cn', '.cn.com','.edu.cn']
    old_domain_name = domain_name
    top_domain = "." + ".".join(domain_name.rsplit('.')[-2:])
    new_top_domain = "." + top_domain.replace(".", "")
    is_tow_top = False
    if top_domain in top_domain_list:
        is_tow_top = True
        domain_name = domain_name[:-len(top_domain)] + new_top_domain

    if domain_name.count(".") > 1:
        zone, middle, last = domain_name.rsplit(".", 2)
        if is_tow_top:
            last = top_domain[1:]
        root = ".".join([middle, last])
    else:
        zone = ""
        root = old_domain_name
    return root, zone


def query_dns(domain, dns_type='A', is_root=False):
    '''
        @name 查询域名DNS解析
        @author cjxin<2020-12-17>
        @param domain {string} 被验证的根域名
        @param dns_type {string} dns记录
        @param is_root {bool} 是否查询根域名
        @return void
    '''
    try:
        import dns.resolver
    except:
        os.system(get_run_pip('[PIP] install dnspython'))
        import dns.resolver

    if is_root: domain, zone = get_root_domain(domain)
    try:

        ret = dns.resolver.query(domain, dns_type)
        data = []
        for i in ret.response.answer:
            for j in i.items:
                tmp = {}
                if dns_type == 'A':
                    tmp['value'] = j.address
                elif dns_type == 'CNAME':
                    tmp['value'] = j.to_text()
                elif dns_type == 'NS':
                    tmp['value'] = j.to_text()
                elif dns_type == 'CAA':
                    tmp['flags'] = j.flags
                    tmp['tag'] = j.tag.decode()
                    tmp['value'] = j.value.decode()
                data.append(tmp)
        return data
    except:
        return False


def re_python_pro(port):
    """
    @port 占用进程
    """
    for proc in psutil.process_iter():
        if proc.name() == 'python.exe':
            for x in proc.connections():
                if x.laddr.port == port:
                    try:
                        proc.kill()
                    except:
                        pass
                    time.sleep(1)


# 检查App和小程序的绑定
def check_app(check='app'):
    path = os.getenv('BT_PANEL') + '/'
    if check == 'app':
        try:
            if not os.path.exists(path+"/plugin/btapp"): return False
            if not os.path.exists(path + 'config/api.json'): return False
            if os.path.exists(path + 'config/api.json'):
                btapp_info = json.loads(readFile(path + 'config/api.json'))
                if not btapp_info['open']: return False
                if not 'apps' in btapp_info: return False
                if not btapp_info['apps']: return False
                return True
            return False
        except:
            return False
    elif check == 'app_bind':
        if not os.path.exists(path + "/plugin/btapp"): return False
        if not os.path.exists(path + 'config/api.json'): return False
        btapp_info = json.loads(readFile(path + 'config/api.json'))
        if not btapp_info: return False
        if not btapp_info['open']: return False
        return True
    elif check == 'wxapp':
        if not os.path.exists(path + 'plugin/app/user.json'): return False
        app_info = json.loads(readFile(path + 'plugin/app/user.json'))
        if not app_info: return False
        return True


def get_oem_name():
    '''
        @name 获取OEM名称
        @author hwliang<2021-03-24>
        @return string
    '''
    oem = ''
    oem_file = '{}/data/o.pl'.format(get_panel_path())
    if os.path.exists(oem_file):
        oem = readFile(oem_file)
        if oem: oem = oem.strip()
    return oem


def get_mac_address():
    import uuid
    mac = uuid.UUID(int=uuid.getnode()).hex[-12:]
    return ":".join([mac[e:e + 2] for e in range(0, 11, 2)])


def get_user_info():
    '''
        @name 获取用户信息
        @author cjxin<2022-12-05>
        @return dict
    '''
    user_file = '{}/data/userInfo.json'.format(get_panel_path())
    if not os.path.exists(user_file): return {}
    userInfo = {}
    try:
        userTmp = json.loads(readFile(user_file))
        if not 'serverid' in userTmp or len(userTmp['serverid']) != 64:
            import panelAuth
            userTmp = panelAuth.panelAuth().create_serverid(None)

        userInfo['uid'] = userTmp['uid']
        userInfo['access_key'] = userTmp['access_key']
        userInfo['username'] = userTmp['username']
        userInfo['serverid'] = userTmp['serverid']
        userInfo['oem'] = get_oem_name()
        userInfo['o'] = userInfo['oem']
        userInfo['mac'] = get_mac_address()
    except:
        pass
    return userInfo


def get_plugin_title(plugin_name):
    '''
        @name 获取插件标题
        @author hwliang<2021-06-24>
        @param plugin_name<string> 插件名称
        @return string
    '''

    info_file = '{}/plugin/{}/info.json'.format(get_panel_path(), plugin_name)
    try:
        return json.loads(readFile(info_file))['title']
    except:
        return plugin_name


def get_plugin_replace_rules():
    '''
        @name 获取插件文件内容替换规则
        @author hwliang<2021-06-28>
        @return list
    '''
    return [
        {
            "find": "{{PATH}}",
            "replace": format_path(os.getenv('BT_SETUP'))
        },
        {
            "find": "[PATH]",
            "replace": format_path(os.getenv('BT_SETUP'))
        }
    ]


def get_error_object(plugin_title=None, plugin_name=None):
    '''
        @name 获取格式化错误响应对像
        @author hwliang<2021-06-21>
        @return Resp
    '''
    if not plugin_title: plugin_title = get_plugin_title(plugin_name)
    from BTPanel import request, Resp
    ss = '''404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

During handling of the above exception, another exception occurred:'''
    error_info = get_error_info().strip().split(ss)[-1].strip()
    request_info = '''REQUEST_DATE: {request_date}
 PAN_VERSION: {panel_version}
  OS_VERSION: {os_version}
 REMOTE_ADDR: {remote_addr}
 REQUEST_URI: {method} {full_path}
REQUEST_FORM: {request_form}
  USER_AGENT: {user_agent}'''.format(
        request_date=getDate(),
        remote_addr=GetClientIp(),
        method=request.method,
        full_path=request.full_path,
        request_form=request.form.to_dict(),
        user_agent=request.headers.get('User-Agent'),
        panel_version=version(),
        os_version=get_system_version(True)
    )

    result = readFile('BTPanel/templates/default/plugin_error.html').format(
        plugin_name=plugin_title,
        request_info=request_info,
        error_title=error_info.split("\n")[-1],
        error_msg=error_info
    )
    return Resp(result, 500)


# xss 防御
def xssencode(text):
    from html import escape, unescape
    list = ['`', '~', '&', '#', '*', '$', '@', '<', '>', '\"', '\'', ';', '%', ',', '\\u']
    ret = []
    for i in text:
        if i in list:
            i = ''
        ret.append(i)
    str_convert = ''.join(ret)
    text2 = escape(str_convert, quote=True)
    return text2


# xss 防御
def xssencode2(text):
    from html import escape, unescape
    text2 = escape(text, quote=True)
    return text2


#xss 防御
def xsssec(text):
    return text.replace('&', '&amp;').replace('"', '&quot;').replace('<', '&lt;').replace('>', '&gt;')

def exists_args(args, get):
    '''
        @name 检查参数是否存在
        @author hwliang<2021-06-08>
        @param args<list or str> 参数列表 允许是列表或字符串
        @param get<dict_obj> 参数对像
        @return bool 都存在返回True，否则抛出KeyError异常
    '''
    if type(args) == str:
        args = args.split(',')
    for arg in args:
        if not arg in get:
            raise KeyError('缺少必要参数: {}'.format(arg))
    return True


def get_dos_disk():
    import win32file, system
    diskIo = system.system().GetDiskInfo()
    data = {}
    for x in diskIo:
        d = x['path'][:2]
        try:
            device = win32file.QueryDosDevice(d).replace('\x00\x00', '')
            data[device] = d
        except:
            pass
    return data


def flush_plugin_list():
    '''
        @name 刷新插件列表
        @author hwliang<2021-07-22>
        @return bool
    '''
    skey = 'TNaMJdG3mDHKRS6Y'
    from BTPanel import cache
    from pluginAuth import Plugin
    if cache.get(skey): cache.delete(skey)
    Plugin(False).get_plugin_list(True)
    return True


# 取缓存
def cache_get(key):
    from BTPanel import cache
    return cache.get(key)


# 设置缓存
def cache_set(key, value, timeout=None):
    from BTPanel import cache
    return cache.set(key, value, timeout)


# 删除缓存
def cache_remove(key):
    from BTPanel import cache
    return cache.delete(key)


# 取session值
def sess_get(key):
    from BTPanel import session
    if key in session: return session[key]
    return None


# 设置或修改session值
def sess_set(key, value):
    from BTPanel import session
    session[key] = value
    return True


# 删除指定session值
def sess_remove(key):
    from BTPanel import session
    if key in session: del (session[key])
    return True


def pkcs12_to_pem(pfx_buff, password=None):
    """
    pfx转pem
    """
    from OpenSSL import crypto
    try:
        p12 = crypto.load_pkcs12(pfx_buff, password)
    except:
        p12 = crypto.load_pkcs12(pfx_buff)

    data = {}
    data['key'] = crypto.dump_privatekey(crypto.FILETYPE_PEM, p12.get_privatekey()).decode()
    data['cert'] = crypto.dump_certificate(crypto.FILETYPE_PEM, p12.get_certificate()).decode()

    ca_list = []
    for x in p12.get_ca_certificates():
        ca_list.insert(0, crypto.dump_certificate(crypto.FILETYPE_PEM, x).decode().strip())
    data['ca'] = '\n'.join(ca_list)

    return data


# 清除pfx证书密码
def clear_pkcs12_pwd(pfx_buff, password):
    ret = pkcs12_to_pem(pfx_buff, password)
    new_buffer = dump_pkcs12(ret['key'], '{}\n{}'.format(ret['cert'], ret['ca']), ret['cert'])

    return new_buffer


# 证书转为pkcs12
def dump_pkcs12(key_pem=None, cert_pem=None, ca_pem=None, friendly_name=None):
    import OpenSSL

    p12 = OpenSSL.crypto.PKCS12()
    if cert_pem:
        p12.set_certificate(OpenSSL.crypto.load_certificate(
            OpenSSL.crypto.FILETYPE_PEM, cert_pem.encode()))
    if key_pem:
        p12.set_privatekey(OpenSSL.crypto.load_privatekey(
            OpenSSL.crypto.FILETYPE_PEM, key_pem.encode()))
    if ca_pem:
        p12.set_ca_certificates((OpenSSL.crypto.load_certificate(
            OpenSSL.crypto.FILETYPE_PEM, ca_pem.encode()),))
    if friendly_name:
        p12.set_friendlyname(friendly_name.encode())
    return p12.export()

def write_push_log(module,msg,res):
    """
    @name 写推送日志
    @module string 模块名称
    @msg string 消息内容
    @res dict 推送结果
    """
    user = ''
    for key in res:
        status = '<span style="color:#20a53a;">成功</span>'
        if res[key] == 0: status = '<span style="color:red;">失败</span>'
        user += '【 {}：{} 】 '.format(key,status)

    if not user: user = '[ 默认 ] '

    try:
        msg_obj = init_msg(module)
        if msg_obj: module = msg_obj.get_version_info(None)['title']
    except:
        print(get_error_info())

    log = '标题：【{}】，通知方式：【{}】，收件人：{}'.format(xsssec(msg),module,user)

    WriteLog('告警通知',log)
    return True

# 登陆告警
def login_send_body(is_type, username, login_ip, port):
    panelPath = os.getenv('BT_PANEL')
    path = "{}/class/push/push.json".format(panelPath)
    data = {}
    try:
        if os.path.exists(path):
            data = json.loads(readFile(path))
    except:pass

    login_aera_status,login_aera = check_login_area(login_ip = login_ip,login_type = 'panel')
    if 'panelLogin_push' in data and 'login' in data['panelLogin_push']:

        s_login = data['panelLogin_push']['login']
        if not s_login['status']: return

        for m_module in s_login['module'].split(','):
            msg_obj = init_msg(m_module)
            if not msg_obj: continue

            login_time = time.strftime('%Y-%m-%d %X', time.localtime())
            if m_module in ['sms']:
                sm_args = {'name': '[' + GetConfigValue('title') + ']', 'time': login_time, 'type': '['+is_type+']','user': username}
                rdata = msg_obj.send_msg('login_panel', check_sms_argv(sm_args))
            else:
                p_msg = get_push_info('堡塔面板登录提醒',[
                        '>登录方式：' +is_type,
                        '>登录账号：' + username,
                        '>登录IP：' + login_ip + ':' + str(port),
                        '>归属地：' + login_aera['login_ip_area'],
                        '>登录状态：<font color=#20a53a>成功</font>'
                    ])['msg']

                rdata = msg_obj.send_msg(p_msg)


def get_free_ips_area(ips):
    '''
    @name 免费IP库 获取ip地址所在地
    @author cjxin
    @param ips<list>
    @return list
    '''
    import PluginLoader

    args = dict_obj()
    args.model_index = 'safe'
    args.ips = ips
    res = PluginLoader.module_run('freeip','get_ip_area',args)
    return res

def get_free_ip_info(address):
    '''
    @name 免费IP库 获取ip地址所在地
    @param ip<string>
    @return dict
    '''
    ip = address.split(':')[0]
    if not is_ipv4(ip):
        return {'info':'未知归属地'}
    if is_local_ip(ip):
        return {'info':'内网地址','local':True}

    ip_info = {}
    sfile = '{}/data/ip_area.json'.format(get_panel_path())
    try:
        ip_info = json.loads(readFile(sfile))
    except: pass

    if ip in ip_info:
        return ip_info[ip]
    try:
        param = get_user_info()
        param['ip'] = address
        res = json.loads(httpPost('https://www.bt.cn/api/ip/info',param))

        if address in res:
            info =  res[address]
            ip_info[ip] = info
            ip_info[ip]['info'] = '{} {} {} {}'.format(info['carrier'],info['country'],info['province'],info['city']).strip()
            ip_info[ip]['ip'] = ip
            writeFile(sfile,json.dumps(ip_info))
            return res[address]
    except: pass

    return {'info':'未知归属地'}


def is_login_area(login_ip):
    """
    @name 验证是否是登录地区
    """
    sfile = '{}/data/limit_login_area.pl'.format(get_panel_path())
    if not os.path.exists(sfile): return True

    limit_login_area = []
    try:
        limit_login_area = json.loads(readFile(sfile))
    except:pass

    #未设置登录地区限制
    if len(limit_login_area) <= 0:
        return True

    #内网地址跳过检测
    #if is_local_ip(key):
    #    return True

    login_ip_area = ''
    ip_info = get_free_ip_info(login_ip)

    if not 'city' in ip_info:
        login_ip_area = ip_info['info']

    for key in limit_login_area:
        if login_ip_area.find(key) >= 0:
            return True
    return False


def check_login_area(login_ip,login_type = 'panel'):
    """
    @name 检测登录地区
    @login_type 登录类型 panel:宝塔面板登录, ssh:ssh登录
    """

    login_ip_area = ''
    ip_info = get_ips_area([login_ip])

    if 'status' in ip_info:
        login_ip_area = '****（开通企业版可查看）'
    else :
        ip_info = ip_info[login_ip]
        if not 'city' in ip_info:
            login_ip_area = ip_info['info']

    data = {}
    status = False
    sfile =  '{}/data/{}_login_area.pl'.format(get_panel_path(),login_type)
    s_conf = '{}/data/{}_login_area.json'.format(get_panel_path(),login_type)
    if os.path.exists(sfile):
        status = True

    data = {}
    try:
        data = json.loads(readFile(s_conf))
    except:pass

    if not login_ip_area and 'city' in ip_info:
        city = ip_info['city']
        login_ip_area = ip_info['info']

        if not city in data:
            data[city] = 0

        if data[city] < 3:
            login_ip_area += '（<font color=red>异地</font>）'
        data[city] += 1

        writeFile(s_conf,json.dumps(data))

    data['login_ip_area'] = login_ip_area
    return status,data

#实例化定目录下的所有模块
class get_modules:

    def __contains__(self, key):
        return self.get_attr(key)

    def __setitem__(self, key, value):
        setattr(self,key,value)

    def get_attr(self,key):
        '''
            尝试获取模块，若为字符串，则尝试实例化模块，否则直接返回模块对像
        '''
        res = getattr(self,key)
        if isinstance(res,str):
            try:
                tmp_obj = __import__(key)
                reload(tmp_obj)
                setattr(self,key,tmp_obj)
                return tmp_obj
            except:
                raise Exception(get_error_info())
        return res

    def __getitem__(self, key):
        return self.get_attr(key)

    def __delitem__(self,key):
        delattr(self,key)

    def __delattr__(self, key):
        delattr(self,key)

    def get_items(self):
        return self

    def __init__(self,path = "class",limit = None):
        '''
            @name 加载指定目录下的模块
            @author hwliang<2020-08-03>
            @param path<string> 指定目录，可指定绝对目录，也可指定相对于/www/server/panel的相对目录 默认加载class目录
            @param limit<string/list/tuple> 指定限定加载的模块名称，默认加载path目录下的所有模块
            @param object

            @example
                p = get_modules('class')
                if 'public' in p:
                    md5_str = p.public.md5('test')
                    md5_str = p['public'].md5('test')
                    md5_str = getattr(p['public'],'md5')('test')
                else:
                    print(p.__dict__)
        '''
        os.chdir(get_panel_path())
        exp_files = ['__init__.py','__pycache__']
        if not path in sys.path:
            sys.path.insert(0,path)
        for fname in os.listdir(path):
            if fname in exp_files: continue
            filename = '/'.join([path,fname])
            if os.path.isfile(filename):
                if not fname[-3:] in ['.py','.so']: continue
                mod_name = fname[:-3]
            else:
                c_file = '/'.join((filename,'__init__.py'))
                if not os.path.exists(c_file):
                    continue
                mod_name = fname

            if limit:
                if not isinstance(limit,list) and not isinstance(limit,tuple):
                    limit = (limit,)
                if not mod_name in limit:
                    continue

            setattr(self,mod_name,mod_name)


#取通用对象
re_key_match = re.compile(r'^[\w\s\[\]\-]+$')
re_key_match2 = re.compile(r'^\.?__[\w\s[\]\-]+__\.?$')
key_filter_list = ['get','set','get_items','exists','__contains__','__setitem__','__getitem__','__delitem__','__delattr__','__setattr__','__getattr__','__class__']
class dict_obj:
    def __contains__(self, key):
        return getattr(self,key,None)
    def __setitem__(self, key, value):
        if key in key_filter_list:
            raise PanelError("错误的字段名")
        #if not re_key_match.match(key) or re_key_match2.match(key):
        #    raise PanelError("错误的字段名")
        setattr(self,key,value)
    def __getitem__(self, key): return getattr(self,key,None)
    def __delitem__(self,key): delattr(self,key)
    def __delattr__(self, key): delattr(self,key)
    def get_items(self): return self
    def exists(self,keys):
        return exists_args(keys,self)
    def set(self,key,value):
        if not isinstance(value,str) or not isinstance(key,str): return False
        if key in key_filter_list:
            raise PanelError("错误的字段名")
        #if not re_key_match.match(key) or re_key_match2.match(key):
        #    raise PanelError("错误的字段名")
        return setattr(self,key,value)

    def get(self,key,default='',format='',limit = []):
        '''
            @name 获取指定参数
            @param key<string> 参数名称，允许在/后面限制参数格式，请参考参数值格式(format)
            @param default<string> 默认值，默认空字符串
            @param format<string>  参数值格式(int|str|port|float|json|xss|path|url|ip|ipv4|ipv6|letter|mail|phone|正则表达式|>1|<1|=1)，默认为空
            @param limit<list> 限制参数值内容
            @param return mixed
        '''
        if key.find('/') != -1:
            key,format = key.split('/')
        result = getattr(self,key,default)
        if isinstance(result,str): result = result.strip()
        if format:
            if format in ['str','string','s']:
                result = str(result)
            elif format in ['int','d']:
                try:
                    result = int(result)
                except:
                    raise ValueError("参数：{}，要求int类型数据".format(key))
            elif format in ['float','f']:
                try:
                    result = float(result)
                except:
                    raise ValueError("参数：{}，要求float类型数据".format(key))
            elif format in ['json','j']:
                try:
                    result = json.loads(result)
                except:
                    raise ValueError("参数：{}, 要求JSON字符串".format(key))
            elif format in ['xss','x']:
                result = xssencode(result)
            elif format in ['path','p']:
                if not path_safe_check(result):
                    raise ValueError("参数：{}，要求正确的路径格式".format(key))
                result = result.replace('//','/')
            elif format in ['url','u']:
                regex = re.compile(
                     r'^(?:http|ftp)s?://'
                     r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|'
                     r'localhost|'
                     r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
                     r'(?::\d+)?'
                     r'(?:/?|[/?]\S+)$', re.IGNORECASE)
                if not re.match(regex,result):
                    raise ValueError('参数：{}，要求正确的URL格式'.format(key))
            elif format in ['ip','ipaddr','i','ipv4','ipv6']:
                if format == 'ipv4':
                    if not is_ipv4(result):
                        raise ValueError('参数：{}，要求正确的ipv4地址'.format(key))
                elif format == 'ipv6':
                    if not is_ipv6(result):
                        raise ValueError('参数：{}，要求正确的ipv6地址'.format(key))
                else:
                    if not is_ipv4(result) and not is_ipv6(result):
                        raise ValueError('参数：{}，要求正确的ipv4/ipv6地址'.format(key))
            elif format in ['w','letter']:
                if not re.match(r'^\w+$',result):
                    raise ValueError('参数：{}，要求只能是英文字母或数据组成'.format(key))
            elif format in ['email','mail','m']:
                if not re.match(r"^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$",result):
                    raise ValueError("参数：{}，要求正确的邮箱地址格式".format(key))
            elif format in ['phone','mobile','p']:
                if not re.match("^[0-9]{11,11}$",result):
                    raise ValueError("参数：{}，要求手机号码格式".format(key))
            elif format in ['port']:
                result_port = int(result)
                if result_port > 65535 or result_port < 0:
                    raise ValueError("参数：{}，要求端口号为0-65535".format(key))
                result = result_port
            elif re.match(r"^[<>=]\d+$",result):
                operator = format[0]
                length = int(format[1:].strip())
                result_len = len(result)
                error_obj = ValueError("参数：{}，要求长度为{}".format(key,format))
                if operator == '=':
                    if result_len != length:
                        raise error_obj
                elif operator == '>':
                    if result_len < length:
                        raise error_obj
                else:
                    if result_len > length:
                        raise error_obj
            elif format[0] in ['^','(','[','\\','.'] or format[-1] in ['$',')',']','+','}']:
                if not re.match(format,result):
                    raise ValueError("指定参数格式不正确, {}:{}".format(key,format))

        if limit:
            if not result in limit:
                raise ValueError("指定参数值范围不正确, {}:{}".format(key,limit))
        return result



def is_debug():
    return os.path.exists('{}/data/debug.pl'.format(get_panel_path()))


# 清理大日志文件
def clean_max_log(log_file, max_size=100, old_line=100):
    if not os.path.exists(log_file): return False
    max_size = 1024 * 1024 * max_size
    if os.path.getsize(log_file) > max_size:
        try:
            old_body = GetNumLines(log_file, old_line)
            writeFile(log_file, old_body)
        except:
            pass


class PanelError(Exception):
    '''
        @name 宝塔通用异常对像
        @author hwliang<2021-06-25>
    '''

    def __init__(self, value):
        self.value = value

    def __str__(self):
        return ("面板运行时发生错误: {}".format(repr(self.value)))





def download_main(upgrade_plugin_name, upgrade_version):
    '''
        @name 下载插件主程序文件
        @author hwliang<2021-06-25>
        @param upgrade_plugin_name<string> 插件名称
        @param upgrade_version<string> 插件版本
        @return void
    '''
    import requests, shutil
    plugin_path = '{}/plugin'.format(get_panel_path())
    tmp_path = '{}/temp'.format(get_panel_path())
    download_d_main_url = 'https://api.bt.cn/down/download_plugin_main'
    pdata = get_user_info()
    pdata['name'] = upgrade_plugin_name
    pdata['version'] = upgrade_version
    pdata['os'] = 'Windows'
    download_res = requests.post(download_d_main_url, pdata, timeout=30, headers=get_requests_headers())
    filename = '{}/{}.py'.format(tmp_path, upgrade_plugin_name)
    if not os.path.exists(tmp_path): os.makedirs(tmp_path)

    with open(filename, 'wb+') as save_script_f:
        save_script_f.write(download_res.content)
        save_script_f.close()
    if md5(download_res.content) != download_res.headers['Content-md5']:
        raise PanelError('插件安装包HASH校验失败')
    dst_file = '{plugin_path}/{plugin_name}/{plugin_name}_main.py'.format(plugin_path=plugin_path,
                                                                          plugin_name=upgrade_plugin_name)
    shutil.copyfile(filename, dst_file)
    if os.path.exists(filename): os.remove(filename)
    WriteLog('软件管理', "检测到插件[{}]程序文件异常，已尝试自动修复!".format(get_plugin_info(upgrade_plugin_name)['title']))


def get_plugin_bin(plugin_save_file, plugin_timeout):
    list_body = None
    if not os.path.exists(plugin_save_file): return list_body
    s_time = time.time()
    m_time = os.stat(plugin_save_file).st_mtime
    if s_time - m_time < plugin_timeout or not plugin_timeout:
        list_body = readFile(plugin_save_file, 'rb')
    return list_body


def get_plugin_main_object(plugin_name, sys_path):
    os_file = sys_path + '/' + plugin_name + '_main.py'
    php_file = sys_path + '/index.php'
    is_php = False
    if os.path.exists(os_file):  # 是否为编译后的so文件
        plugin_obj = __import__(plugin_name + '_main')

    elif os.path.exists(php_file):  # 是否为PHP代码
        import panelPHP
        is_php = True
        plugin_obj = panelPHP.panelPHP(plugin_name)

    return plugin_obj, is_php

def get_plugin_script(plugin_name, plugin_path):
    # 优先运行py文件

    plugin_file = '{plugin_path}/{name}/{name}_main.py'.format(plugin_path=plugin_path, name=plugin_name)

    if not os.path.exists(plugin_file): return '', True
    plugin_body = readFile(plugin_file, 'rb')

    if plugin_body.find(b'import') != -1:
        return plugin_body, True

    if len(plugin_body) > 1024 and plugin_body.find(b'+') != -1 and plugin_body.find(b'/') != -1:
        return plugin_body,False

    # 无py文件再运行so
    plugin_file_so = '{plugin_path}/{name}/{name}_main.pyd'.format(plugin_path=plugin_path, name=plugin_name)
    if os.path.exists(plugin_file_so): return '', True

    return plugin_body, False


def re_download_main(plugin_name, plugin_path):
    plugin_file = '{plugin_path}{name}/{name}_main.py'.format(plugin_path=plugin_path, name=plugin_name)
    version = get_plugin_info(plugin_name)['versions']
    download_main(plugin_name, version)
    plugin_body = readFile(plugin_file, 'rb')
    return plugin_body


def get_sysbit():
    '''
        @name 获取操作系统位数
        @author hwliang<2021-07-07>
        @return int 32 or 64
    '''
    return 64



def push_argv(msg):
    """
    @name 格式化推送参数
    """
    if type(msg) != str:
        return msg
    if is_ipv4(msg):
        tmp1 = msg.split('.')
        msg = '{}.***.***.{}'.format(tmp1[0], tmp1[3])
    else:
        if is_domain(msg):
            msg = msg.replace('.', '_')
    return msg


def get_push_title():
    """
    @name 获取推送标题
    """
    title = push_argv(GetConfigValue('title'))
    return title



def get_push_address():
    """
    @name 获取推送ip
    """
    ip = push_argv(GetLocalIp())
    return ip


def get_network_ip():
    """
    @name 获取本机ip
    @return string
    """
    import socket
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        s.connect(('8.8.8.8', 80))
        ip = s.getsockname()[0]
        return ip
    finally:
        s.close()
    return '127.0.0.1'

def get_push_info(title,slist = []):
    """
    @name 获取推送信息
    @param title<str> 推送标题
    @param slist<list> 推送追加的列表
            如：slist = ['>发送内容:xxx']
    @return dict
    """
    data = {}
    data['title'] = title
    data['ip'] = GetLocalIp()
    data['local_ip'] = get_network_ip()
    data['time'] = format_date()
    data['server_name'] = GetConfigValue('title')

    dlist = [
        "#### {}".format(data['title']),
        ">服务器："+ data['server_name'] ,
        ">IP地址：{}(外) {}(内)".format(data['ip'],data['local_ip']) ,
        ">发送时间：" + data['time']
    ]
    dlist.extend(slist)
    msg = "\n\n".join(dlist)
    data['msg'] = msg
    data['list'] = dlist
    return data

def get_ip_info():
    """
    @name 获取ip信息
    @return dict
    """
    data = {}
    data['ip'] = GetLocalIp()
    data['local_ip'] = get_network_ip()
    data['time'] = format_date()
    data['server_name'] = GetConfigValue('title')
    return data

def get_panel_path():
    '''
        @name 取面板根目录
        @author hwliang<2021-07-14>
        @return string
    '''
    return os.getenv('BT_PANEL')


def get_soft_path(spath=None):
    '''
        @name 取软件根目录
        @author hwliang<2021-07-14>
        @return string
    '''
    path = os.getenv('BT_SETUP')
    if spath: path = to_path(path + '/' + spath)
    return path


def get_setup_path(spath = None):
    return get_soft_path(spath)

def get_os(_os = 'windows'):
    """
    @验证系统版本
    """
    src_os = 'windows'
    if os.path.exists('/www/server/panel'):src_os = 'linux'
    if src_os == _os: return True
    return False

def get_plugin_path(plugin_name=None):
    '''
        @name 取指定插件目录
        @author hwliang<2021-07-14>
        @param plugin_name<string> 插件名称 不传则返回插件根目录
        @return string
    '''

    root_path = "{}/plugin".format(get_panel_path())
    if not plugin_name: return root_path
    return "{}/{}".format(root_path, plugin_name)


def get_class_path():
    '''
        @name 取类库所在路径
        @author hwliang<2021-07-14>
        @return string
    '''
    return "{}/class".format(get_panel_path())


def read_config(config_name, ext_name='json'):
    '''
        @name 读取指定配置文件
        @author hwliang<2021-07-14>
        @param config_name<string> 配置文件名称(不含扩展名)
        @param ext_name<string> 配置文件扩展名，默认为json
        @return string 如果发生错误，将抛出PanelError异常
    '''
    config_file = "{}/config/{}.{}".format(get_panel_path(), config_name, ext_name)
    if not os.path.exists(config_file):
        raise PanelError('指定配置文件{} 不存在'.format(config_name))

    config_str = readFile(config_file)
    if ext_name == 'json':
        try:
            config_body = json.loads(config_str)
        except Exception as ex:
            raise PanelError('配置文件不是标准的可解析JSON内容!\n{}'.format(ex))
        return config_body
    return config_str


def save_config(config_name, config_body, ext_name='json'):
    '''
        @name 保存配置文件
        @author hwliang<2021-07-14>
        @param config_name<string> 配置文件名称(不含扩展名)
        @param config_body<mixed> 被保存的内容, ext_name为json，请传入可解析为json的参数类型，如list,dict,int,str等
        @param ext_name<string> 配置文件扩展名，默认为json
        @return string 如果发生错误，将抛出PanelError异常
    '''

    config_file = "{}/config/{}.{}".format(get_panel_path(), config_name, ext_name)
    if ext_name == 'json':
        try:
            config_body = json.dumps(config_body)
        except Exception as ex:
            raise PanelError('配置内容无法被转换为json格式!\n{}'.format(ex))

    return writeFile(config_file, config_body)


def get_config_value(config_name, key, default='', ext_name='json'):
    '''
        @name 获取指定配置文件的指定配置项
        @author hwliang<2021-07-14>
        @param config_name<string> 配置文件名称(不含扩展名)
        @param key<string> 配置项
        @param default<mixed> 获不存在则返回的默认值，默认为空字符串
        @param ext_name<string> 配置文件扩展名，默认为json
        @return mixed 如果发生错误，将抛出PanelError异常
    '''
    config_data = read_config(config_name, ext_name)
    return config_data.get(key, default)


def set_config_value(config_name, key, value, ext_name='json'):
    '''
        @name 设置指定配置文件的指定配置项
        @author hwliang<2021-07-14>
        @param config_name<string> 配置文件名称(不含扩展名)
        @param key<string> 配置项
        @param value<mixed> 配置值
        @param ext_name<string> 配置文件扩展名，默认为json
        @return mixed  如果发生错误，将抛出PanelError异常
    '''
    config_data = read_config(config_name, ext_name)
    config_data[key] = value
    return save_config(config_name, config_data, ext_name)


def return_data(status, data, status_code=None, error_msg=None):
    '''
        @name 格式化响应内容
        @author hwliang<2021-07-14>
        @param status<bool> 状态
        @param data<mixed> 响应数据
        @param status_code<int> 状态码
        @param error_msg<string> 错误消息内容
        @return dict

    '''
    if status_code is None:
        status_code = 1 if status else 0
    if error_msg is None:
        error_msg = '' if status else '未知错误'

    result = {
        'status': status,
        "status_code": status_code,
        'error_msg': str(error_msg),
        'data': data
    }
    return result


def return_error(error_msg, status_code=-1, data=[]):
    '''
        @name 格式化错误响应内容
        @author hwliang<2021-07-15>
        @param error_msg<string> 错误消息
        @param status_code<int> 状态码，默认为-1
        @param data<mixed> 响应数据
        @return dict
    '''
    return return_data(False, data, status_code, str(error_msg))


def return_status_code(status_code, format_body, data=[]):
    '''
        @name 按状态码返回
        @author hwliang<2021-07-15>
        @param status_code<int> 状态码
        @param format_body<string> 错误内容
        @param data<mixed> 响应数据
        @return dict
    '''
    error_msg = get_config_value('status_code', str(status_code))
    if not error_msg: raise PanelError('指定状态码不存在!')
    return return_data(error_msg[0], data, status_code, error_msg[1].format(format_body))


def to_dict_obj(data):
    '''
        @name 将dict转换为dict_obj
        @author hwliang<2021-07-15>
        @param data<dict> 要被转换的数据
        @return dict_obj
    '''
    if not isinstance(data, dict):
        raise PanelError('错误的数据类型，只支持将dict转换为dict_obj')
    pdata = dict_obj()
    for key in data.keys():
        pdata[key] = data[key]
    return pdata


def get_script_object(filename):
    '''
        @name 从脚本文件获取对像
        @author hwliang<2021-07-19>
        @param filename<string> 文件名
        @return object
    '''
    _obj = sys.modules.get(filename, None)
    if _obj: return _obj
    from types import ModuleType
    _obj = sys.modules.setdefault(filename, ModuleType(filename))
    _code = readFile(filename)
    _code_object = compile(_code, filename, 'exec')
    _obj.__file__ = filename
    _obj.__package__ = ''
    exec(_code_object, _obj.__dict__)
    return _obj


def check_hooks():
    '''
        @name 自动注册HOOK
        @author hwliang<2021-07-19>
        @return void
    '''
    hooks_path = '{}/hooks'.format(get_panel_path())
    if not os.path.exists(hooks_path):
        return
    for hook_name in os.listdir(hooks_path):
        if hook_name[-3:] != '.py': continue
        filename = os.path.join(hooks_path, hook_name)
        _obj = get_script_object(filename)
        _main = getattr(_obj, 'main', None)
        if not _main: continue
        _main()


def register_hook(hook_index, hook_def):
    '''
        @name 注册HOOK
        @author hwliang<2021-07-15>
        @param hook_index<string> HOOK位置
        @param hook_def<def> HOOK函数对像
        @return void
    '''
    from BTPanel import hooks
    hook_keys = hooks.keys()
    if not hook_index in hook_keys:
        hooks[hook_index] = []
    if not hook_def in hooks[hook_index]:
        hooks[hook_index].append(hook_def)


def exec_hook(hook_index, data):
    '''
        @name 执行HOOk
        @author hwliang<2021-07-15>
        @param hook_index<string> HOOK索引位置，格式限制：^\w+$
        @param data<mixed> 运行数据
        @return mixed
    '''
    try:
        from BTPanel import hooks
        hook_keys = hooks.keys()
        if not hook_index in hook_keys:
            return data

        for hook_def in hooks[hook_index]:
            data = hook_def(data)
    except : pass

    return data


def get_hook_index(mod_name, def_name):
    '''
        @name 获取HOOK位置
        @author hwliang<2021-07-19>
        @param mod_name<string> 模块名称
        @param def_name<string> 方法名称
        @return tuple
    '''
    mod_name = mod_name.upper()
    def_name = def_name.upper()
    last_index = '{}_{}_LAST'.format(mod_name, def_name)
    end_index = '{}_{}_END'.format(mod_name, def_name)
    return last_index, end_index


def rename_config():
    flag = False
    try:
        webserver = get_webserver()
        if webserver == 'iis':
            __appcmd = 'C:/Windows/system32/inetsrv/appcmd.exe'
            os.system(__appcmd + " uninstall module waf_iis")
        else:
            path = '{}/{}/conf/vhost/btwaf.conf'.format(get_soft_path(), webserver)
            if os.path.exists(path):
                os.rename(path, path.replace('.conf', 'config'))
                serviceReload()
    except:
        pass


def is_error_path():
    if os.path.exists("data/error_pl.pl"):
        rename_config()
        return True
    return False


def check_tcp(ip,port):
    '''
        @name 使用TCP的方式检测指定IP:端口是否能连接
        @author hwliang<2021-06-01>
        @param ip<string> IP地址
        @param port<int> 端口
        @return bool
    '''
    import socket
    try:
        s = socket.socket()
        s.settimeout(5)
        s.connect((ip.strip(),int(port)))
        s.close()
    except:
        return False
    return True



def get_full_session_file():
    '''
        @name 获取临时SESSION文件
        @author hwliang<2021-12-28>
        @return string
    '''
    from BTPanel import app
    full_session_key = app.config['SESSION_KEY_PREFIX'] + get_session_id()
    sess_path = get_panel_path() + '/data/session/'
    return sess_path + '/' + md5(full_session_key)

def is_api_limit_ip(ip_list,client_ip):
    '''
        @name 判断IP是否在限制列表中
        @author hwliang<2022-02-10>
        @param ip_list<list> 限制IP列表
        @param client_ip<string> 客户端IP
        @return bool
    '''
    iplong_list = []
    for limit_ip in ip_list:
        if not limit_ip: continue
        if limit_ip in ['*','all','0.0.0.0','0.0.0.0/0','0.0.0.0/24','0.0.0.0/32']: return True
        limit_ip = limit_ip.split('-')
        iplong = {}
        iplong['min'] = ip2long(limit_ip[0])
        if len(limit_ip) > 1:
            iplong['max'] = ip2long(limit_ip[1])
        else:
            iplong['max'] = iplong['min']
        iplong_list.append(iplong)

    client_ip_long = ip2long(client_ip)
    for limit_ip in iplong_list:
        if client_ip_long >= limit_ip['min'] and client_ip_long <= limit_ip['max']:
            return True
    return False

#获取服务器IP
def get_ip():
    iplist_file = '{}/data/iplist.txt'.format(get_panel_path())
    if os.path.exists(iplist_file):
        data=ReadFile(iplist_file)
        return data.strip()
    else:return '127.0.0.1'

def is_apache_nginx():
    '''
        @name 是否是apache或nginx
        @author hwliang<2021-08-17>
        @return bool
    '''
    web = get_webserver()
    if web in ['nginx','apache']:
        return True
    return False


def error_conn_cloud(text):
    '''
        @name 连接云端失败
        @author hwliang<2021-12-18>
        @return void
    '''
    code_msg = ''
    if text.find("502 Bad Gateway") != -1:
        code_msg = '502 Bad Gateway'
    if text.find("504 Bad Gateway") != -1:
        code_msg = '504 Bad Gateway'
    elif text.find("Connection refused") != -1:
        code_msg = 'Connection refused'
    elif text.find("Connection timed out") != -1:
        code_msg = 'Connection timed out'
    elif text.find("Connection reset by peer") != -1:
        code_msg = 'Connection reset by peer'
    elif text.find("Name or service not known") != -1:
        code_msg = 'Name or service not known'
    elif text.find("No route to host") != -1:
        code_msg = 'No route to host'
    elif text.find("No such file or directory") != -1:
        code_msg = 'No such file or directory'
    elif text.find("404 Not Found") != -1:
        code_msg = '404 Not Found'
    elif text.find("403 Forbidden") != -1:
        code_msg = '403 Forbidden'
    elif text.find("401 Unauthorized") != -1:
        code_msg = '401 Unauthorized'
    elif text.find("400 Bad Request") != -1:
        code_msg = '400 Bad Request'
    elif text.find("Remote end closed connection without response") != -1:
        code_msg = 'Remote end closed connection'
    err_template_file = '{}/BTPanel/templates/default/error_connect.html'.format(get_panel_path())
    msg = readFile(err_template_file)
    msg = msg.format(code=code_msg)
    return msg


def set_module_logs(mod_name,fun_name,count = 1):
    """
    @模块使用次数
    @mod_name 模块名称
    @fun_name 函数名
    """
    import datetime
    data = {}
    path = '{}/data/mod_log.json'.format(get_panel_path())
    if os.path.exists(path):
        try:
            data = json.loads(readFile(path))
        except : pass

    key = datetime.datetime.now().strftime("%Y-%m-%d")
    if not key in data: data[key] = {}

    if not mod_name in data[key]:
        data[key][mod_name] = {}

    if not fun_name in data[key][mod_name]:
        data[key][mod_name][fun_name] = 0

    data[key][mod_name][fun_name] += count

    writeFile(path,json.dumps(data))
    return True

def sys_path_append(path):
    '''
        @name 追加引用路径
        @author hwliang<2021-07-07>
        @param path<string> 路径
        @return void
    '''
    if not path in sys.path:
        sys.path.insert(0,path)


def get_plugin_pid(plugin_name):
    '''
        @name 获取指定插件的pid
        @author hwliang<2021-06-15>
        @param plugin_name<string> 插件名称
        @return string
    '''
    plugin_info = get_plugin_find(plugin_name)
    if not plugin_info: return 0
    if 'pid' in plugin_info:
        return plugin_info['pid']
    return 0


def get_plugin_find(upgrade_plugin_name = None):
    '''
        @name 获取指定软件信息
        @author hwliang<2021-06-15>
        @param upgrade_plugin_name<string> 插件名称
        @return dict
    '''
    import panelPlugin
    plugin_list_data = panelPlugin.panelPlugin().get_cloud_list()

    for p_data_info in plugin_list_data['list']:
        if p_data_info['name'] == upgrade_plugin_name:
            upgrade_plugin_name = p_data_info['name']
            return p_data_info

    return get_plugin_info(upgrade_plugin_name)


def get_plugin_value(plugin_name,key):
    '''
        @name 获取插件配置值
        @author hwliang
        @param plugin_name<string> 插件名称
        @param key<string> 字段名
        @return mixed
    '''
    plugin_info = get_plugin_find(plugin_name)
    return plugin_info.get(key,None)



def get_plugin_info(upgrade_plugin_name):
    '''
        @name 获取插件信息
        @author hwliang<2021-06-15>
        @param upgrade_plugin_name<string> 插件名称
        @return dict
    '''
    plugin_path = '{}/plugin'.format(get_panel_path())
    plugin_info_file = '{}/{}/info.json'.format(plugin_path, upgrade_plugin_name)
    if not os.path.exists(plugin_info_file): return {}
    info_body = readFile(plugin_info_file)
    if not info_body: return {}
    plugin_info = json.loads(info_body)
    return plugin_info

def set_search_history(mod_name,key,val):
    """
    @保存搜索历史
    @mod_name 模块名称
    @key 关键字
    @val string 搜索内容
    """
    if not val: return False

    max = 10
    p_file = get_panel_path()
    m_file = p_file + '/data/search.limit'
    d_file = p_file + '/data/search.json'
    try:
         sdata = int(readFile(m_file))
         if sdata: max = sdata
    except: pass

    result = {}
    try:
        result = json.loads(readFile(d_file))
    except :pass

    if not mod_name in result: result[mod_name] = {}
    if not key in result[mod_name]:  result[mod_name][key] = []

    n_list = []
    for item in result[mod_name][key]:
        if item['val'].strip() != val.strip(): n_list.append(item)

    n_list.append({'val':val,'time':int(time.time())})
    result[mod_name][key] = n_list[0:max]

    writeFile(d_file,json.dumps(result))
    return True

def get_search_history(mod_name,key):
    """
    @获取搜索历史
    @mod_name string 模块名称
    @key string 关键字
    """
    result = []
    d_file = get_panel_path() + '/data/search.json'
    try:
        result = json.loads(readFile(d_file))[mod_name][key]
    except :pass

    result = sorted(result, key=lambda x: x['time'], reverse=True)
    return result


def set_dir_history(mod_name,key,val):
    """
    @设置目录打开历史
    @mod_name string 模块名称
    @key string 函数名
    @val string 路径
    """
    if not val:  return False

    max = 10
    result = {}
    d_file = get_panel_path() + '/data/dir_history.json'
    try:
        result = json.loads(readFile(d_file))
    except :pass

    if not mod_name in result: result[mod_name] = {}
    if not key in result[mod_name]:  result[mod_name][key] = []

    data = result[mod_name][key]
    for info in data:
        if val.find(info['val']) >= 0:
            if time.time() - info['time'] < 15:
                data.remove(info)

    data.append({'val':val,'time':int(time.time())})
    result[mod_name][key] = data[0:max]
    writeFile(d_file,json.dumps(result))
    return True


def get_dir_history(mod_name,key):
    """
    @获取目录打开历史
    @mod_name string 模块名称
    @key string 关键字
    """
    result = []
    d_file = get_panel_path() + '/data/dir_history.json'
    try:
        result = json.loads(readFile(d_file))[mod_name][key]
    except :pass
    return result


def install_pip(shell):
    """
    @name 安装pip模块
    @author cjxin
    @param shell<string> 安装命令
    """
    if get_os('windows'):
        os.system(get_run_pip(shell.replace('pip','[PIP]')))
    else:
        os.system(shell.replace('pip','btpip'))

def check_sms_argv(data):
    """
    @name 批量处理短信参数，否则会被拦截
    @param data<dict> 参数
    """
    for key in data:
       data[key] = push_argv(data[key])
    return data


def check_chinese(data):
    """
    @name 检查是否有中文
    @param data<string> 检查的字符串
    @return bool
    """
    if re.search('[\u4e00-\u9fa5]',data):
        return True
    return False



def check_firewall_rule(port):
    '''
    @name 检测防火墙是否已经添加规则
    @author cjxin
    @param port int 端口号
    '''
    import PluginLoader
    args = dict_obj()
    args.model_index = 'safe'
    args.port = port

    res = PluginLoader.module_run("firewall","check_firewall_rule",args)
    return res

def add_firewall_rule(port,protocol = 'tcp',types = 'accept',address = '0.0.0.0/0',brief = None):
    """
    @name 添加防火墙规则
    @author cjxin
    @param port int 端口号
    @param protocol string 协议类型 tcp udp
    @param types string 添加类型 accept reject
    @param address string 地址
    @param brief string 描述
    """
    import PluginLoader

    args = dict_obj()
    args.model_index = 'safe'
    args.port = port
    args.protocol = protocol
    args.types = types
    args.address = address
    args.brief = brief
    if not brief: args.brief = str(port)

    res = PluginLoader.module_run("firewall","create_rules",args)
    return res

def del_firewall_rule(port,protocol = 'tcp',types = 'accept',address = '0.0.0.0/0'):
    '''
    @name 删除防火墙规则
    @author cjxin
    @param port int 端口号
    @param protocol str 协议
    @param types str 类型
    @param address str 地址
    '''

    import PluginLoader

    args = dict_obj()
    args.model_index = 'safe'
    args.port = port
    args.protocol = protocol
    args.types = types
    args.address = address

    res = PluginLoader.module_run("firewall","remove_rules",args)
    return res

def is_local_ip(ip):
    '''
        @name 判断是否为本地(内网)IP地址
        @author hwliang<2021-03-26>
        @param ip string(ipv4)
        @return bool
    '''
    patt = r"^(192\.168|127|10|172\.(16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31))\."
    if re.match(patt,ip): return True
    return False


def get_ips_area(ips):
    '''
    @name 获取ip地址所在地
    @author cjxin
    @param ips<list>
    @return list
    '''
    import PluginLoader

    args = dict_obj()
    args.model_index = 'safe'
    args.ips = ips

    res = PluginLoader.module_run("ips","get_ip_area",args)
    return res

def return_area(result,key):
    """
    @name 格式化返回带IP归属地的数组
    @param result<list> 数据数组
    @param key<str> ip所在字段
    @return list
    """
    tmps = []
    for data in result:
        data['area'] = ''
        tmps.append(data[key])

    res = get_ips_area(tmps)
    if 'status' in res: return result

    for data in result:
        if data[key] in res:
            data['area'] = res[data[key]]
    return result



def check_auth_ip():
    """
    @name 检测api和www的服务器ip是否一致
    @auther cjxin 2022-09-13
    @return dict
    """
    import http_requests
    result = {'www':'','api':''}
    res = http_requests.post('https://www.bt.cn/api/getIpAddress', data={}, timeout=5, headers={})
    if res.status_code == 200:
        result['www'] = res.text

    res1 = http_requests.post('https://api.bt.cn/api/getIpAddress', data={}, timeout=5, headers={})
    if res1.status_code == 200:
        result['api'] = res1.text

    return result



def error_not_login(e = None,_src = None):
    '''
        @name 未登录时且未输入正确的安全入口时的响应
        @author hwliang<2021-12-16>
        @return Response
    '''
    from BTPanel import Response,render_template,redirect

    try:
        abort_code = read_config('abort')
        if not abort_code in [None,1,0,'0','1']:
            if abort_code == 404: return error_404(e)
            if abort_code == 403: return error_403(e)
            return Response(status=int(abort_code))
    except:
        pass

    if e in ['/login']:
        return redirect(e)

    if _src:
        return e
    else:
        return render_template('autherr.html')


def get_password_config():
    '''
        @name 获取密码安全配置
        @author hwliang<2021-10-18>
        @return int
    '''
    import config
    return config.config().get_password_config(None)


def password_expire_check():
    '''
        @name 密码过期检查
        @author hwliang<2021-10-18>
        @return bool
    '''
    p_config = get_password_config()
    if p_config['expire'] == 0: return True
    if time.time() > p_config['expire_time']: return False
    return True

def is_spider():
    '''
        @name 判断是否为爬虫
        @return bool
    '''
    from BTPanel import request
    import panelDefense
    p = panelDefense.bot_safe()
    return not p.spider(request.headers.get('User-Agent'),request.remote_addr)


def get_rsa_public_key():
    '''
        @name 获取RSA公钥内容
        @author hwliang
        @return str
    '''
    from BTPanel import session
    pub_key = 'rsa_public_key'
    public_key = session.get(pub_key)
    if not public_key:
        create_rsa_key()
        public_key = session.get(pub_key)
    return public_key

    # path = get_rsa_public_key_file()
    # if not os.path.exists(path): create_rsa_key()
    # if not os.path.exists(path): return ''
    # return readFile(path)

#设置用户组
def set_own(filename,user,group=None):
    pass

def get_rsa_private_key():
    '''
        @name 获取RSA私钥内容
        @author hwliang
        @return str
    '''
    from BTPanel import session
    prv_key = 'rsa_private_key'
    private_key = session.get(prv_key)
    if not private_key:
        create_rsa_key()
        private_key = session.get(prv_key)
    return private_key


def create_rsa_key():
    '''
        @name 创建RSA密钥
        @author hwliang
        @return bool
    '''
    try:
        # private_key_file = get_rsa_private_key_file()
        # public_key_file = get_rsa_public_key_file()
        # if os.path.exists(private_key_file) and os.path.exists(public_key_file): return True
        from BTPanel import session
        pub_key = 'rsa_public_key'
        prv_key = 'rsa_private_key'
        if pub_key in session and prv_key in session:
            return True
        from Crypto.PublicKey import RSA
        key = RSA.generate(1024)
        private_key = key.exportKey("PEM")
        public_key = key.publickey().exportKey("PEM")
        session[pub_key] = public_key.decode('utf-8')
        session[prv_key] = private_key.decode('utf-8')

        # writeFile(private_key_file,private_key,'wb+')
        # writeFile(public_key_file,public_key,'wb+')
        return True
    except:
        print_log(get_error_info())
        return False



def rsa_encrypt(data):
    '''
        @name RSA加密数据
        @param data str 要加密的数据
        @return str
    '''
    # 分片长度 1024 / 8 - 11 = 117
    split_length = 117
    try:
        from Crypto.PublicKey import RSA
        from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs

        # 初始化RSA加密对象
        public_key = get_rsa_public_key()
        cipher_public = Cipher_pkcs.new(RSA.importKey(public_key))

        # 分片加密
        data = data.encode('utf-8')
        encrypted_arr = []
        for i in range(0,len(data),split_length):
            d = data[i:i+split_length]
            encrypted_data = cipher_public.encrypt(d)
            encrypted_base64 = base64.b64encode(encrypted_data).decode()
            encrypted_arr.append(encrypted_base64)

        # 用换行符拼接
        return "\n".join(encrypted_arr)
    except:
        print_log(get_error_info())
        return ''

def rsa_decrypt(data):
    '''
        @name RSA解密数据
        @param data str 要解密的数据
        @return str
    '''
    try:
        from Crypto.PublicKey import RSA
        from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs

        # 初始化RSA解密对象
        private_key = get_rsa_private_key()
        cipher_private = Cipher_pkcs.new(RSA.importKey(private_key))

        # 分片解密
        decrypted_str = b""
        for d in data.split("\n"):
            res = base64.b64decode(d)
            decrypted_data = cipher_private.decrypt(res, None)
            decrypted_str += decrypted_data
        return decrypted_str.decode('utf-8')
    except:
        print_log(get_error_info())
        return ''


def rsa_encrypt_for_private_key(data):
    '''
        @name RSA私钥加密数据
        @author hwliang
        @param data str 要加密的数据
        @return str
    '''
    # 分片长度 1024 / 8 - 11 = 117
    split_length = 117
    try:
        from Crypto.PublicKey import RSA
        from Crypto.Cipher import PKCS1_v1_5 as Cipher_pkcs

        # 初始化RSA加密对象
        private_key = get_rsa_private_key()
        cipher_private = Cipher_pkcs.new(RSA.importKey(private_key))

        # 分片加密
        data = data.encode('utf-8')
        encrypted_arr = []
        for i in range(0,len(data),split_length):
            d = data[i:i+split_length]
            encrypted_data = cipher_private.encrypt(d)
            encrypted_base64 = base64.b64encode(encrypted_data).decode()
            encrypted_arr.append(encrypted_base64)

        # 用换行符拼接
        return "\n".join(encrypted_arr)
    except:
        print_log(get_error_info())
        return ''



def get_admin_path():
    '''
        @name 取安全入口
        @author hwliang
        @return string
    '''
    login_path = '/login'
    path = '{}/data/admin_path.pl'.format(get_panel_path())
    if not os.path.exists(path): return login_path
    admin_path = readFile(path)
    if not admin_path: return login_path
    admin_path = admin_path.strip()
    if admin_path in ['','/']:
        return login_path
    return admin_path

def get_client_hash():
    '''
        @name 获取客户端HASH
        @author hwliang
        @return str
    '''
    #from flask import request
    #client_hash = md5(request.headers.get('User-Agent') +'_'+ request.remote_addr)
    #return client_hash

    from flask import session,request
    is_tmp_login = session.get('tmp_login')
    if is_tmp_login:
        client_hash = md5(request.remote_addr)
    else:
        #client_hash = md5(request.headers.get('User-Agent') +'_'+ request.remote_addr)
        client_hash = md5(request.headers.get('User-Agent'))
    return client_hash


def check_client_hash():
    '''
        @name 验证客户端HASH
        @author hwliang
        @return bool
    '''
    # 是否关闭验证
    not_tip = '{}/data/not_check_ip.pl'.format(get_panel_path())
    if os.path.exists(not_tip): return True
    from BTPanel import session

    skey = 'client_hash'
    client_hash = get_client_hash()
    if not skey in session:
        session[skey] = client_hash
        return True

    if session[skey] != client_hash:
        return False
    return True


def is_ssl():
    '''
        @name 是否开启SSL
        @author hwliang
        @return bool
    '''
    return os.path.exists(get_panel_path() + '/data/ssl.pl')

def get_cookie(key,default=None):
    '''
        @name 获取指定Cookie值
        @author hwliang
        @param key<str> Cookie键
        @param default<str> 默认值
        @return str
    '''
    from flask import request
    return request.cookies.get(key,default)

def get_csrf_cookie_token_key():
    '''
        @name 获取CSRF Cookie Key
        @author hwliang
        @return string
    '''
    if is_ssl():
        token_key = 'request_token'
    else:
        token_key = 'request_token'
    return token_key


def get_csrf_cookie_token_value():
    '''
        @name 获取CSRF Cookie Value
        @author hwliang
        @return string
    '''
    token_key = get_csrf_cookie_token_key()
    return get_cookie(token_key)


def get_csrf_html_token_key():
    '''
        @name 获取CSRF HTML Key
        @author hwliang
        @return string
    '''
    if is_ssl():
        token_key = 'request_token_head'
    else:
        token_key = 'request_token_head'
    return token_key

def get_csrf_html_token_value():
    '''
        @name 获取CSRF HTML Value
        @author hwliang
        @return string
    '''
    token_key = get_csrf_html_token_key()
    return get_cookie(token_key)

def get_csrf_sess_html_token_value():
    '''
        @name 从SESSION获取CSRF HTML value
        @author hwliang
        @return string
    '''
    from flask import session
    return session.get(get_csrf_html_token_key(),"")

def get_csrf_sess_cookie_token_value():
    '''
        @name 从SESSION获取CSRF Cookie value
        @author hwliang
        @return string
    '''
    from flask import session
    return session.get(get_csrf_cookie_token_key(),"")


# 2024/1/24 上午 10:38 通用响应对象
def returnResult(status=True, msg="OK", data=None, timestamp=None, code=0, args=None):
    '''
    通用响应对象
    @param code: 0:成功 1:失败 2:警告 ...
    @param status:
    @param msg: 只传msg,不传需要前端处理的数据
    @param data: 只传需要前端处理的数据
    @param timestamp: 秒级时间戳
    @return:

    使用示例：
    成功：return dp.returnResult(data=data)
    失败：return dp.returnResult(code=1, status=False, msg="获取失败!", data=[])
    失败：return dp.returnResult(code=1, status=False, msg="获取失败!")
    警告：return dp.returnResult(code=2, status=False, msg="警告，xxxxxxxxxxx!")
    ...
    '''
    import time
    if timestamp is None:
        timestamp = int(time.time())

    try:
        log_message = json.loads(ReadFile('BTPanel/static/language/' + GetLanguage() + '/public.json'))
        keys = log_message.keys()
    except:
        log_message = {}
        keys = []

    if type(msg) == str:
        if msg in keys:
            msg = log_message[msg]
            for i in range(len(args)):
                rep = '{' + str(i + 1) + '}'
                msg = msg.replace(rep, args[i])

    return {
        "code": code,
        "status": status,
        "msg": msg,
        "data": data,
        "timestamp": timestamp
    }


# 2024/1/24 上午 11:56 取指定模型目录
def get_mod_path(mod_name=None):
    '''
        @name 取指定插件目录
        @author hwliang<2021-07-14>
        @param mod_name<string> 模型名称 不传则返回模型根目录
        @return string
    '''

    root_path = "{}/mod/project".format(get_panel_path())
    if not mod_name: return root_path
    return "{}/{}".format(root_path, mod_name)


class session_obj:
    def __contains__(self, key):
        return getattr(self,key,None)
    def __setitem__(self, key, value):
        setattr(self, key, value)
        cache.set(key, value,86400)
    def __getitem__(self, key):
        return cache.get(key)
    def __delitem__(self,key): cache.delete(key)
    def __delattr__(self, key): cache.delete(key)
try:
    import FileCache
    cache = FileCache.FileCache()

    import FileSession
    session = FileSession.FileSession(session_expiry=86400)


except:
    cache = None
    session = None
