#coding: utf-8
# +-------------------------------------------------------------------
# | YakPanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2016 YakPanel(www.yakpanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@yakpanel.com>
# | Author: lx
# | 消息推送管理
# | 对外方法 get_modules_list、install_module、uninstall_module、get_module_template、set_push_config、get_push_config、del_push_config
# +-------------------------------------------------------------------

import os, sys

panelPath = "/www/server/panel"
os.chdir(panelPath)
sys.path.insert(0,panelPath + "/class/")
import public,re,json,time
try:
    from YakPanel import session
except :
    pass
class panelPush:

    __conf_path =  "{}/class/push/push.json".format(panelPath)
    def __init__(self):
        spath = '{}/class/push'.format(panelPath)
        if not os.path.exists(spath): os.makedirs(spath)

    """
    @获取推送模块列表
    """
    def get_modules_list(self,get):
        cpath = '{}/class/push/push_list.json'.format(panelPath)
        try:
            spath = os.path.dirname(cpath)
            if not os.path.exists(spath): os.makedirs(spath)

            if 'force' in get or not os.path.exists(cpath):
                if not 'download_url' in session: session['download_url'] = public.get_url()
                public.downloadFile('{}/linux/panel/push/push_list.json'.format(session['download_url']),cpath)
        except : pass

        if not os.path.exists(cpath):
            return {}

        data = {}
        push_list = self._get_conf()
        module_list = public.get_modules('class/push')

        configs = json.loads(public.readFile(cpath))
        for p_info in configs:
            p_info['data'] = {}
            p_info['setup'] = False
            p_info['info'] = False
            key = p_info['name']
            try:
                if hasattr(module_list, key):
                    p_info['setup'] = True
                    # if key in module_list:
                    #     print(dir(module_list))
                    #     print(dir(module_list[key]))
                    #     print(dir(getattr(module_list[key], key)))
                    push_module = getattr(module_list[key], key)()
                    p_info['info'] = push_module.get_version_info(None);
                    #格式化消息通道
                    if key in push_list:
                        p_info['data'] = self.__get_push_list(push_list[key])
                    #格式化返回执行周期
                    if hasattr(push_module,'get_push_cycle'):
                        p_info['data'] = push_module.get_push_cycle(p_info['data'])
            except :
                return public.get_error_object(None)
            data[key] = p_info
        return data

    """
    安装/更新消息通道模块
    @name 需要安装的模块名称
    """
    def install_module(self,get):
        module_name = get.name
        down_url = public.get_url()

        local_path = '{}/class/push'.format(panelPath)
        if not os.path.exists(local_path): os.makedirs(local_path)

        sfile = '{}/{}.py'.format(local_path,module_name)
        public.downloadFile('{}/linux/panel/push/{}.py'.format(down_url,module_name),sfile)
        if not os.path.exists(sfile): return public.returnMsg(False, '[{}] Module installation failed'.format(module_name))
        if os.path.getsize(sfile) < 1024: return public.returnMsg(False, '[{}] Module installation failed'.format(module_name))

        sfile = '{}/class/push/{}.html'.format(panelPath,module_name)
        public.downloadFile('{}/linux/panel/push/{}.html'.format(down_url,module_name),sfile)

        return public.returnMsg(True, '[{}] Module installed successfully.'.format(module_name))

    """
    卸载消息通道模块
    @name 需要卸载的模块名称
    """
    def uninstall_module(self,get):
        module_name = get.name
        sfile = '{}/class/push/{}.py'.format(panelPath,module_name)
        if os.path.exists(sfile): os.remove(sfile)

        return public.returnMsg(True, '[{}] Module uninstalled successfully'.format(module_name))


    """
    @获取模块执行日志
    """
    def get_module_logs(self,get):
        module_name = get.name
        id = get.id
        return []

    """
    获取模块模板
    """
    def get_module_template(self,get):
        sfile = '{}/class/push/{}.html'.format(panelPath,get.module_name)

        if not os.path.exists(sfile):
            return public.returnMsg(False, 'template file does not exist!')

        shtml = public.readFile(sfile)
        return public.returnMsg(True, shtml)


    """
    @获取模块推送参数，如：panel_push ssl到期，服务停止
    """
    def get_module_config(self,get):
        module = get.name
        p_list = public.get_modules('class/push')
        push_module = getattr(p_list[module], module)()

        if not module in p_list:
            return public.returnMsg(False, 'The specified module [{}] is not installed!'.format(module))

        if not hasattr(push_module,'get_module_config'):
            return public.returnMsg(False, 'No get_module_config method exists for the specified module [{}].'.format(module))
        return push_module.get_module_config(get)



    """
    @获取模块配置项
    @优先调用模块内的get_push_config
    """
    def get_push_config(self,get):
        module = get.name
        id = get.id
        p_list = public.get_modules('class/push')
        if not module in p_list:
            return public.returnMsg(False, 'The specified module [{}] is not installed.'.format(module))

        result = None
        push_module = getattr(p_list[module], module)()
        if not hasattr(push_module,'get_push_config'):
            push_list = self._get_conf()

            res_data = public.returnMsg(False, 'The specified configuration was not found!')
            res_data['code'] = 100
            if not module in push_list:
                return res_data
            if not id in push_list[module]:
                return res_data

            result = push_list[module][id]
        else:
            result = push_module.get_push_config(get)
        return self.get_push_user(result)

    def get_push_user(self,result):

        #获取发送给谁
        if not 'to_user' in result:
            result['to_user'] = {}
            if 'module' in result:
                for s_module in result['module'].split(','):
                    result['to_user'][s_module] = 'default'
            else:
                return False

        info = {}
        for s_module in result['module'].split(','):
            msg_obj = public.init_msg(s_module)
            if not msg_obj: continue

            info[s_module] = {}
            data = msg_obj.get_config(None)

            if 'list' in data:
                for key in result['to_user'][s_module].split(','):
                    if not key in data['list']:
                        continue
                    info[s_module][key] = data['list'][key]
        result['user_info'] = info
        return result

    """
    @设置推送配置
    @优先调用模块内的set_push_config
    """
    def set_push_config(self,get):
        module = get.name
        id = get.id
        p_list = public.get_modules('class/push')

        if not module in p_list:
            return public.returnMsg(False, 'The specified module [{}] is not installed.'.format(module))

        pdata = json.loads(get.data)
        if not 'module' in pdata or not pdata['module']:
            return public.returnMsg(False, 'The specified alarm method is not set, please select again.')
        if module == "load_balance_push":
            pdata = self.__get_args(pdata,'cycle', "500|502|503|504")
        else:
            pdata = self.__get_args(pdata, 'cycle', 1)
        pdata = self.__get_args(pdata,'count',1)
        pdata = self.__get_args(pdata,'interval',600)
        pdata = self.__get_args(pdata,'key','')
        pdata = self.__get_args(pdata,'push_count',0)

        nData = {}
        for skey in ['key','type','cycle','count','interval','module','title','project','status','index','push_count']:
            if skey in pdata:
                nData[skey] = pdata[skey]

        public.set_module_logs('set_push_config',nData['type'])
        class_obj = getattr(p_list[module], module)()
        if hasattr(class_obj,'set_push_config'):
            get['data'] = json.dumps(nData)
            result = class_obj.set_push_config(get)
            if 'status' in result: return result

            data = result
        else:
            data = self._get_conf()
            if not module in data:data[module] = {}
            data[module][id] = nData


        public.writeFile(self.__conf_path,json.dumps(data))
        return public.returnMsg(True, 'Saved successfully')

    """
    @设置推送状态
    """
    def set_push_status(self,get):
        id = get.id
        module = get.name

        data = self._get_conf()
        if not module in data: return public.returnMsg(True, 'module name does not exist!')
        if not id in data[module]: return public.returnMsg(True, 'The specified push task does not exist!')

        status = int(get.status)
        if status:
            data[module][id]['status'] = True
        else:
            data[module][id]['status'] =  False
        public.writeFile(self.__conf_path,json.dumps(data))
        return public.returnMsg(True, 'Successful operation.')
    """
    @删除指定配置
    """
    def del_push_config(self,get):
        id = get.id
        module = get.name

        p_list = public.get_modules('class/push')
        if not module in p_list:
            return public.returnMsg(False, 'The specified module {} is not installed.'.format(module))
        push_module = getattr(p_list[module], module)()
        if not hasattr(push_module,'del_push_config'):
            data = self._get_conf()
            del data[module][id]
            public.writeFile(self.__conf_path,json.dumps(data))
            return public.returnMsg(True, 'successfully deleted.')

        return push_module.del_push_config(get)

    """
    获取消息通道配置列表
    """
    def get_push_msg_list(self,get):
        data = {}
        msgs = self.__get_msg_list()
        from panelMessage import panelMessage
        pm = panelMessage()
        for x in msgs:
            x['setup'] = False
            key = x['name']
            try:
                obj =  pm.init_msg_module(key)
                if obj:
                    x['setup'] = True
                    if key == 'sms':x['title'] = '{}<a title="Please make sure there are enough SMS messages, otherwise you will not be able to receive notifications." href="javascript:;" class="bt-ico-ask">?</a>'.format(x['title'])
            except :
                pass
            data[key] = x
        return data

    """
    @ 获取消息推送配置
    """
    def _get_conf(self):
        data = {}
        try:
            if os.path.exists(self.__conf_path):
                data = json.loads(public.readFile(self.__conf_path))
                self.update_config(data)
        except:pass
        return data

    """
    @ 获取插件版本信息
    """
    def get_version_info(self):
        """
        获取版本信息
        """
        data = {}
        data['ps'] = ''
        data['version'] = '1.0'
        data['date'] = '2020-07-14'
        data['author'] = 'YakPanel'
        data['help'] = 'http://www.yakpanel.com'
        return data

    """
    @格式化推送对象
    """
    def format_push_data(self,push = ['dingding','weixin','feishu'], project = '', type = ''):
        item = {
            'title':'',
            'project':project,
            'type':type,
            'cycle':1,
            'count':1,
            'keys':[],
            'helps':[],
            'push':push
        }
        return item



    def push_message_immediately(self, channel_data):
        """推送消息到指定的消息通道，即时

        Args:
            channel_data(dict):
                key: msg_channel, 消息通道名称，多个用逗号相连
                value: msg obj, 每种消息通道的消息内容格式，可能包含标题

        Returns:
            {
                status: True/False,
                msg: {
                    "email": {"status": msg},
                    ...
                }
            }
        """
        if type(channel_data) != dict:
            return public.returnMsg(False, "The parameter is wrong")

        from panelMessage import panelMessage
        pm = panelMessage()
        channel_res = {}
        res = {
            "status": False,
            "msg": channel_res
        }

        for module, msg in channel_data.items():
            modules = []
            if module.find(",") != -1:
                modules = module.split(",")
            else:
                modules.append(module)
            for m_module in modules:
                msg_obj = pm.init_msg_module(m_module)
                if not msg_obj:continue
                ret = msg_obj.push_data(msg)
                if ret and "status" in ret and ret['status']:
                    res["status"] = True
                    channel_res[m_module] = ret
                else:
                    msg = "Message push failed."
                    if "msg" in ret:
                        msg = ret["msg"]
                    channel_res[m_module] = public.returnMsg(False, msg)
        return res

    """
    @格式为消息通道格式
    """
    def format_msg_data(self):
        data = {
            'title':'',
            'to_email':'',
            'sms_type':'',
            'sms_argv':{},
            'msg':''
        }
        return data

    def __get_msg_list(self):
        """
        获取消息通道列表
        """
        data = []
        cpath = '{}/data/msg.json'.format(panelPath)
        if not os.path.exists(cpath):
            return data
        try:
            conf = public.readFile(cpath)
            data = json.loads(conf)
        except :
            try:
                time.sleep(0.5)
                conf = public.readFile(cpath)
                data = json.loads(conf)
            except:pass

        return data

    def __get_args(self,data,key,val = ''):
        """
        @获取默认参数
        """
        if not key in data: data[key] = val
        if type(data[key]) != type(val):
            data[key] = val
        return data


    def __get_push_list(self,data):
        """
        @格式化列表数据
        """
        m_data = {}
        result = {}
        for x in self.__get_msg_list(): m_data[x['name']] = x

        for skey in data:
            result[skey] = data[skey]

            m_list = []
            for x in data[skey]['module'].split(','):
                if x in m_data: m_list.append(m_data[x]['title'])
            result[skey]['m_title'] = '、'.join(m_list)

            m_cycle =[]
            if data[skey]['cycle'] > 1:
                m_cycle.append('every {} seconds'.format(data[skey]['cycle']))
            m_cycle.append('{} times, with an interval of {} seconds'.format(data[skey]['count'],data[skey]['interval']))
            result[skey]['m_cycle'] = ''.join(m_cycle)

            # 兼容旧版本没有返回project项，导致前端无法编辑问题
            if "project" not in result[skey] and "type" in result[skey]:
                if result[skey]["type"]  == "services":
                    services = ['nginx','apache',"pure-ftpd",'mysql','php-fpm','memcached','redis']
                    _title = result[skey]['title']
                    for s in services:
                        if _title.find(s)!=-1:
                            result[skey]["project"] = s
                else:
                    result[skey]["project"] = result[skey]["type"]
            if "project" in result[skey]:
                if result[skey]["project"] == "FTP server":
                    result[skey]["project"] ="pure-ftpd"
        return result


    #************************************************推送
    """
    @推送data/push目录的所有文件
    """
    def push_messages_from_file(self):

        path = "{}/data/push".format(panelPath)
        if not os.path.exists(path): os.makedirs(path)

        from panelMessage import panelMessage
        pm = panelMessage()

        for x in os.listdir(path):
            try:
                spath = '{}/{}'.format(path,x)
                if os.path.isdir(spath): continue
                data = json.loads(public.readFile(spath))

                msg_obj = pm.init_msg_module(data['module'])
                if not msg_obj:continue

                ret = msg_obj.push_data(data)
                if ret['status']: pass

                os.remove(spath)
            except :
                print(public.get_error_info())

    """
    @消息推送线程
    """
    def start(self):

        total = 0
        interval = 5

        tips = '{}/data/push/tips'.format(public.get_panel_path())
        if not os.path.exists(tips): os.makedirs(tips)

        try:
            if True:
                # 推送文件
                self.push_messages_from_file()

                # 调用推送子模块
                data = {}
                is_write = False
                path = "{}/class/push/push.json".format(panelPath)

                if os.path.exists(path):
                    data = public.readFile(path)
                    data = json.loads(data)

                p = public.get_modules('class/push')
                for skey in data:
                    if len(data[skey]) <= 0: continue
                    if skey in ['panelLogin_push','panel_login']: continue #面板登录主动触发

                    total = None
                    obj = getattr(p[skey], skey)()

                    for x in data[skey]:
                        try:

                            item = data[skey][x]
                            item['id'] = x
                            if not item['status']: continue
                            if not item['module']: continue
                            if not 'index' in item: item['index'] = 0

                            if time.time() - item['index'] < item['interval']:
                                print('{} Interval not reached, skip.'.format(item['title']))
                                continue

                            #验证推送次数
                            push_record = {}
                            tips_path = '{}/{}'.format(tips,x)
                            if 'push_count' in item and item['push_count'] > 0:
                                item['tips_list'] = []
                                try:
                                    push_record = json.loads(public.readFile(tips_path))
                                except:pass
                                for k in push_record:
                                    if push_record[k] < item['push_count']:
                                        continue
                                    item['tips_list'].append(k)

                            #获取推送数据
                            if not total: total = obj.get_total()
                            rdata = obj.get_push_data(item,total)
                            if not rdata:
                                continue
                            push_status = False
                            for m_module in item['module'].split(','):
                                if not m_module in rdata:
                                    continue

                                msg_obj = public.init_msg(m_module)
                                if not msg_obj:continue

                                if 'to_user' in item and m_module in item['to_user']:
                                    rdata[m_module]['to_user'] = item['to_user'][m_module]

                                ret = msg_obj.push_data(rdata[m_module])
                                data[skey][x]['index'] = rdata['index']
                                is_write = True
                                push_status = True

                            #获取是否推送成功.
                            if push_status:
                                if 'push_keys' in rdata:
                                    for k in rdata['push_keys']:
                                        if not k in push_record: push_record[k] = 0
                                        push_record[k] += 1
                                    public.writeFile(tips_path,json.dumps(push_record))
                        except :
                            print(public.get_error_info())

                if is_write:
                    public.writeFile(path,json.dumps(data))
                #time.sleep(interval)
        except :

            print(public.get_error_info())


    def __get_login_panel_info(self):
        """
        @name 获取面板登录列表
        @auther cjxin
        @date 2022-09-29
        """
        import config
        c_obj = config.config()
        send_type = c_obj.get_login_send(None)['msg']
        if not send_type:
            return False
        return {"type":"panel_login","module":send_type,"interval":600,"status":True,"title":"Panel Login Alert","cycle":1,"count":1,"key":"","module_type":'site_push'}


    def __get_ssh_login_info(self):
        """
        @name 获取SSH登录列表
        @auther cjxin
        @date 2022-09-29
        """
        import ssh_security
        c_obj = ssh_security.ssh_security()
        send_type = c_obj.get_login_send(None)['msg']
        if not send_type or send_type in ['error']:
            return False

        return {"type":"ssh_login","module":send_type,"interval":600,"status":True,"title":"SSH login warning","cycle":1,"count":1,"key":"","module_type":'site_push'}



    def get_push_list(self,get):
        """
        @获取所有推送列表
        """
        conf = self._get_conf()
        for key in conf.keys():
            for x in conf[key]:
                data = conf[key][x]
                data['module_type'] = key

                conf[key][x] = self.get_push_user(data)

        if not 'site_push' in conf: conf['site_push'] = {}

        data = conf['site_push']
        for skey in ['panel_login','ssh_login']:
            info = None
            if skey in data:
                del data[skey]
            if skey in ['panel_login']:
                info = self.__get_login_panel_info()
            elif skey in ['ssh_login']:
                info = self.__get_ssh_login_info()

            if info:
                data[skey] = info
        conf['site_push'] = data
        return conf

    def get_push_logs(self,get):
        """
        @name 获取推送日志
        """

        p = 1
        limit = 15
        if 'p' in get: p = get.p
        if 'limit' in get: limit = get.limit

        where = "type = 'Alarm notification'"
        sql = public.M('logs')

        if hasattr(get, 'search'):
            where = " and logs like '%{search}%' ".format(search=get.search)

        count = sql.where(where,()).count()
        data = public.get_page(count,int(p),int(limit))
        pattern = r"href='(?:/v2)?/push.*?\?p=(\d+)'"
        # 使用re.sub进行替换
        data['page'] = re.sub(pattern, r"href='\1'", data['page'])

        data['data'] = public.M('logs').where(where,()).limit('{},{}'.format(data['shift'], data['row'])).order('id desc').select()

        return public.return_message(0, 0,  data)

    # 兼容旧版本的告警
    def update_config(self, config):
        if "site_push" not in config:
            config["site_push"] = {}
        if "panel_push" in config:
            for k, v in config["panel_push"].items():
                if v["type"] != "endtime":
                    config["site_push"][k] = v
                if "push_count" not in v:
                    v["push_count"] = 1 if v["type"] == "ssl" else 0
            del config["panel_push"]
            public.writeFile(self.__conf_path, json.dumps(config))


if __name__ == '__main__':
    panelPush().start()
