# coding: utf-8
import os, sys, json,time,string,subprocess

from dict_obj import dict_obj
import db

def M(table):

    with db.Sql() as sql:
        return sql.table(table)

# 生成MD5
def Md5(strings):
    if type(strings) != bytes:
        strings = strings.encode()
    import hashlib
    m = hashlib.md5()
    m.update(strings)
    return m.hexdigest()

def FileMd5(filename):
    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 IsRestart():
    num = M('tasks').where('status!=?', ('1',)).count()
    if num > 0: return False
    return True

# 检查端口是否合法
def CheckPort(port):
    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
    
def get_requests_headers():
    return {"Content-type": "application/x-www-form-urlencoded", "User-Agent": "BT-Panel"}

# 发送GET请求
def HttpGet(url, timeout=60, headers={}):
    import requests
    try:
        response = requests.get(url, timeout=timeout, headers=headers)
        response.raise_for_status()
        return response.text
    except requests.RequestException as e:
        return str(e)

#发送POST请求
def HttpPost(url, data=None, json_data=None, timeout=60, headers={}):
    import requests
    try:
        response = requests.post(url, data=data, json=json_data, timeout=timeout, headers=headers)
        response.raise_for_status()
        return response.text
    except requests.RequestException as e:
        return str(e)

#  取随机字符串
def GetRandomString(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):

    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):

    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

#取通用Json返回
def ReturnJson(status, msg, args=()):
    return GetJson(ReturnMsg(status, msg, args))

def return_data(status, data={}, status_code=None, error_msg=None):
    if status_code == None:
        status_code = 1 if status else 0
    if error_msg == 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=[]):
    if not data: data = error_msg
    return return_data(False, data, status_code, str(error_msg))

def get_config_value(config_name, key, default='', ext_name='json'):
    config_data = read_config(config_name, ext_name)
    return config_data.get(key, default)

def read_config(config_name, ext_name='json'):

    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 return_status_code(status_code, format_body, data=[]):

    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))


# 2024/1/24 上午 10:38 通用响应对象
def returnResult(status=True, msg="OK", data=None, timestamp=None, code=0, args=None):
    import time
    if timestamp is None:
        timestamp = int(time.time())

    try:
        lang_file = "{}/languages/Simplified_Chinese.json".format(get_panel_path())
        log_message = json.loads(ReadFile(lang_file))
        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
    }


def GetJson(data):
    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)))

# 取通用dict返回
def ReturnMsg(status, msg, args=()):
    return {'status': status, 'msg': msg}

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

# 设置面板配置
def SetConfigValue(key, value):
    config = GetConfig()
    config[key] = value
    WriteConfig(config)

# 取所有配置项
def GetConfig():
    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 get_panel_path():
    if os.name == 'nt':
        return os.getenv('BT_PANEL')
    return "/www/server/panel"

# 获取软件安装路径
def get_soft_path():
    if os.name == 'nt':
        return os.getenv('BT_SETUP')
    return "/www/server"

def get_setup_path():
    if os.name == 'nt':
        return os.getenv('BT_SETUP')
    return "/www/server"

def get_plugin_path(plugin_name=None):

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

def get_plugin_info(upgrade_plugin_name):
  
    plugin_path = get_plugin_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 getStrBetween(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 gen_password(length=8, chars=string.ascii_letters + string.digits):
    from random import choice
    return ''.join([choice(chars) for i in range(length)])

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

def to_size(size):
    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 getDate(format='%Y-%m-%d %X'):
    return time.strftime(format, time.localtime())

# 递归获取目录所有文件列表
def get_file_list(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 ReadFile(filename, mode='r'):

    if not os.path.exists(filename) or not os.path.isfile(filename):
        return False
    try:
        # 尝试直接读取文件内容
        with open(filename, mode) as fp:
            f_body = fp.read()
    except Exception:
        # 如果直接读取失败，尝试使用不同编码格式读取
        for encoding in ['utf-8', 'gbk', 'ansi']:
            try:
                with open(filename, mode, encoding=encoding) as fp:
                    f_body = fp.read()
                    break
            except Exception:
                continue
        else:
            return False
    # 处理带 BOM 的文件
    try:
        if f_body and f_body[0] == '\ufeff':
            detected_encoding = chardet.detect(f_body.encode())["encoding"]
            f_body = f_body.encode().decode(detected_encoding)
    except Exception:
        pass

    return f_body


# 写入文件内容
def WriteFile(filename, s_body, mode='w+', encoding='utf-8'):
    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

# 通过管道执行SHELL命令
def ExecShell(cmdstring, cwd=None, timeout=None, shell=True, output=False):
    try:
        # 创建临时文件用于存储标准输出和错误输出
        rx = Md5(cmdstring)
        import tempfile
        with tempfile.SpooledTemporaryFile(max_size=4096000, mode='wb+', suffix='_succ', prefix='btex_' + rx, dir='data') as succ_f, \
             tempfile.SpooledTemporaryFile(max_size=4096000, mode='wb+', suffix='_err', prefix='btex_' + rx, dir='data') as err_f:

            # 执行命令
            sub = subprocess.Popen(
                cmdstring,
                shell=shell,
                cwd=cwd,
                bufsize=128,
                stdout=succ_f,
                stderr=err_f
            )
            sub.wait(timeout=timeout)

            # 读取输出内容
            succ_f.seek(0)
            err_f.seek(0)
            stdout = succ_f.read()
            stderr = err_f.read()

        # 解码输出内容
        stdout = decode_bytes(stdout)
        stderr = decode_bytes(stderr)

        return stdout, stderr

    except subprocess.TimeoutExpired:
        return "", "Command timed out"
    except Exception as e:
        return "", f"Command execution failed: {str(e)}"

#解码字节数据为字符串
def decode_bytes(data):
    if isinstance(data, bytes):
        for encoding in ['utf-8', 'gb2312', 'utf-16']:
            try:
                return data.decode(encoding)
            except:
                continue
        return data.decode('gb2312', 'ignore')
    return data

def GetMsg(key, args=()):
    """
    根据key获取内置消息返回
    @key 指定消息的key
    @args 消息内容中的参数
    """
    try:
        lang_file = "{}/languages/Simplified_Chinese.json".format(get_panel_path())


        log_message = json.loads(ReadFile(lang_file))
        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 Exception as ex:
        return key

def process_exists(pname, exe=None):
    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 writeSpeed(title, used, total, speed=0):
    import json
    if not title:
        data = {'title': None, 'progress': 0, 'total': 0, 'used': 0, 'speed': 0}
    else:
        try:
            progress = int((100.0 * used / total))
        except:
            progress = 0
        data = {'title': title, 'progress': progress, 'total': total, 'used': used, 'speed': speed}
    WriteFile('/tmp/panelSpeed.pl', json.dumps(data))
    return True


# 取进度
def getSpeed():
    import json
    data = ReadFile('/tmp/panelSpeed.pl')
    if not data:
        data = json.dumps({'title': None, 'progress': 0, 'total': 0, 'used': 0, 'speed': 0})
        WriteFile('/tmp/panelSpeed.pl', data)
    return json.loads(data)
  
# xss 防御
def xsssec(text):
    return xsssec3(text) 

# xss 防御
def xsssec2(text):
    return text.replace('<', '&lt').replace('>', '&gt')

def xsssec3(text):
    sub_list = {
        '<': '＜',
        '>': '＞',
        '"': '＂',
        "'": '＇'
    }
    for s in sub_list.keys():
        text = text.replace(s, sub_list[s])
    return text

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
    
def xss_version(text):
    try:
        if not text or not isinstance(text, str): return text
        text = text.strip()
        list = ['`', '~', '&', '#', '/', '*', '$', '@', '<', '>', '\"', '\'', '', '%', ',', '\\u']
        ret = []
        for i in text:
            if i in list:
                i = ''
            ret.append(i)
        str_convert = ''.join(ret)
        return str_convert
    except:
        return text.replace('&', '&amp').replace('"', '&quot').replace('<', '&lt').replace('>', '&gt')

def xssencode2(text):
    try:
        from cgi import html
        text2 = html.escape(text, quote=True)
        return text2
    except:
        return text.replace('&', '&amp').replace('"', '&quot').replace('<', '&lt').replace('>', '&gt')

# 名称输入系列化
def xssdecode(text):
    try:
        cs = {"&quot": '"', "&#x27": "'"}
        for c in cs.keys():
            text = text.replace(c, cs[c])

        str_convert = text
        if sys.version_info[0] == 3:
            import html
            text2 = html.unescape(str_convert)
        else:
            text2 = cgi.unescape(str_convert)
        return text2
    except:
        return text



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_tasks_run(data):
 
    spath = '{}/data/tasks'.format(get_panel_path())
    if not os.path.exists(spath): os.makedirs(spath, 384)

    task_file = '{}/{}'.format(spath, Md5(str(time.time())))
    WriteFile(task_file, json.dumps(data))
    return returnMsg(True, task_file)

def exists_args(args, get):

    if type(args) == str:
        args = args.split(',')
    for arg in args:
        if not arg in get:
            raise KeyError('缺少必要参数: {}'.format(arg))
    return True


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 软件列表
    """
    import winreg
    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_error_info():
    """
    获取面板详细错误
    """
    import traceback
    errorMsg = traceback.format_exc()
    return errorMsg

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

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

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