宝塔面板自定义设置告警通知webhook接口推送内容

前提

为了能够使用宝塔面板的自定义推送webhook对接到自己的推送系统,特意修改面板代码来支持自定义的推送系统。

环境

宝塔:Linux面板8.1.0

效果

图片bt_webhook.jpeg

步骤

主要修改文件路径如下

/www/server/panel/class/msg/web_hook_msg.py

源文件地址

完整文件链接

新文件改动

    def send_msg(self, msg: str, title, push_type) -> Optional[str]:
        if self._config['status'] is False:
            return "该通道已关闭,不再发送"

        ssl_verify = self._config.get("ssl_verify", None)

        the_url = parse_url(self._config['url'])
        if ssl_verify is None:
            ssl_verify = the_url.scheme == "https"

        real_data = {
            "title": title,
            # 兼容个人webhook
            "text": msg,
            "type": push_type,
        }
        for k, v in self._config.get("custom_parameter", {}).items():
            real_data[k] = v

        data = None
        json_data = None
        headers = self.DEFAULT_HEADERS.copy()
        if self._config["body_type"] == "json":
            json_data = real_data
        elif self._config["body_type"] == "form_data":
            data = real_data

        for k, v in self._config.get("headers", {}).items():
            if not isinstance(v, str):
                v = str(v)
            headers[k] = v

        timeout = 5
        for i in range(3):
            try:
                # 取消https ssl报错
                requests.packages.urllib3.disable_warnings();
                res = requests.request(
                    method=self._config["method"],
                    url=str(the_url),
                    data=data,
                    json=json_data,
                    headers=headers,
                    timeout=timeout,
                    verify=ssl_verify,
                )
                if res.status_code == 200:
                    return None
                else:
                    return res.text
            except requests.exceptions.Timeout:
                timeout += 5
                continue
            except requests.exceptions.RequestException as e:
                return str(e)
            except:
                return "发送失败,疑似是系统环境因素导致"
        return None

    @staticmethod
    def get_send_msg(msg):
        """
        @name 处理md格式
        """
        title = None
        if msg.find("####") >= 0:
            try:
                title = re.search(r"####(.+)\n", msg).groups()[0]
            except KeyError:
                pass
            # 会影响换行
            msg = msg.replace("\n\n", "\n").strip()
        pass
        return msg, title

原文件备份

# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: baozi <baozi@bt.cn>
# | 消息通道HOOK模块
# +-------------------------------------------------------------------
import os
import sys
import json
import re
import copy
import requests
from typing import Optional, List, Dict, Any
from urllib3.util import parse_url

panel_path = "/www/server/panel"
if os.getcwd() != panel_path:
    os.chdir(panel_path)
if panel_path + "/class/" not in sys.path:
    sys.path.insert(0, panel_path + "/class/")

import public


class HooKConfig(object):
    _CONFIG_FILE = '{}/data/hooks_msg.json'.format(panel_path)

    def __init__(self):
        self._config: Optional[List[Dict[str, Any]]] = None

    def __getitem__(self, item: str) -> Optional[Dict[str, Any]]:
        if self._config is None:
            self._read_config()
        for d in self._config:
            if d.get('name', '') == item:
                return d
        return None

    def __setitem__(self, key: str, value: Dict[str, Any]):
        if self._config is None:
            self._read_config()
        if not isinstance(key, str) or not isinstance(value, dict):
            raise ValueError('参数类型错误')

        for idx, d in enumerate(self._config):
            if d.get('name', '') == key:
                target_idx = idx
                break
        else:
            target_idx = -1

        value.update(name=key)
        if target_idx == -1:
            self._config.append(value)
        else:
            self._config[target_idx] = value

        self.save_to_file()

    def __delitem__(self, key):
        if self._config is None:
            self._read_config()

        target_idx = -1
        for i, d in enumerate(self._config):
            if d.get('name', '') == key:
                target_idx = i
                break
        if target_idx != -1:
            del self._config[target_idx]
            self.save_to_file()
        return None

    def _read_config(self):
        data = []
        if os.path.exists(self._CONFIG_FILE):
            js_data = public.readFile(self._CONFIG_FILE)
            if isinstance(js_data, str):
                try:
                    data = json.loads(js_data)
                except json.JSONDecodeError:
                    data = []
        self._config = data

    def to_view(self) -> list:
        if self._config is None:
            self._read_config()

        return copy.deepcopy(self._config)

    def save_to_file(self):
        if self._config is None:
            self._read_config()
        public.writeFile(self._CONFIG_FILE, json.dumps(self._config))

    @staticmethod
    def get_version_info():
        """
        获取版本信息
        """
        data = {
            'ps': '宝塔WEB HOOK消息通道,用于接收面板消息推送',
            'version': '1.0',
            'date': '2023-10-30',
            'author': '宝塔',
            'title': 'WEB HOOK',
            'help': 'https://www.bt.cn/bbs/thread-121791-1-1.html'
        }
        return data

    @classmethod
    def clear_config(cls):
        if os.path.exists(cls._CONFIG_FILE):
            os.remove(cls._CONFIG_FILE)

    def set_all(self, status: bool):
        if self._config is None:
            self._read_config()

        for v in self._config:
            v["status"] = status

        self.save_to_file()

    def all_hook_name(self):
        names = []

        for v in self._config:
            if v["status"] is True:
                names.append(v["name"])

        return names


_cfg = HooKConfig()
# default = {
#     "name": "default",
#     "url": "https://www.bt.cn",
#     "query": {
#         "aaa": "111"
#     },
#     "header": {
#         "AAA": "BBBB",
#     },
#     "body_type": ["json", "form_data", "null"],
#     "custom_parameter": {
#         "rrr": "qqqq"
#     },
#     "method": ["GET", "POST", "PUT", "PATCH"],
#     "ssl_verify": [True, False]
# }
# #
# # 1.自动解析Query参数,拼接并展示给用户  # 可不做
# # 2.自定义Header头 # 必做
# # 3.Body中的内容是: type:str="首页磁盘告警", time:int=168955427, data:str="xxxxxx"  # ?
# # 4.自定义参数: key=value 添加在Body中  # 可不做
# # 5.请求类型自定义 # 必做
#
# # 以上内容需要让用户可测试--!


class RealHook(object):
    DEFAULT_HEADERS = {
        "User-Agent": "BT-Panel",
    }

    def __init__(self, hook_name, name: str = None, config: dict = None):
        if name is not None and config is not None:
            self.name = hook_name
            self._config = config
            return

        if not hook_name:
            raise ValueError("hook_name 不能为空")
        if _cfg[hook_name] is None:
            raise ValueError("没有配置指定的HOOK")

        self.name = hook_name
        self._config = _cfg[hook_name]

    def send_msg(self, msg: str, title, push_type) -> Optional[str]:
        if self._config['status'] is False:
            return "该通道已关闭,不再发送"

        ssl_verify = self._config.get("ssl_verify", None)

        the_url = parse_url(self._config['url'])
        if ssl_verify is None:
            ssl_verify = the_url.scheme == "https"

        real_data = {
            "title": title,
            "msg": msg,
            "type": push_type,
        }
        for k, v in self._config.get("custom_parameter", {}).items():
            real_data[k] = v

        data = None
        json_data = None
        headers = self.DEFAULT_HEADERS.copy()
        if self._config["body_type"] == "json":
            json_data = real_data
        elif self._config["body_type"] == "form_data":
            data = real_data

        for k, v in self._config.get("headers", {}).items():
            if not isinstance(v, str):
                v = str(v)
            headers[k] = v

        timeout = 5
        for i in range(3):
            try:
                res = requests.request(
                    method=self._config["method"],
                    url=str(the_url),
                    data=data,
                    json=json_data,
                    headers=headers,
                    timeout=timeout,
                    verify=ssl_verify,
                )
                if res.status_code == 200:
                    return None
                else:
                    return res.text
            except requests.exceptions.Timeout:
                timeout += 5
                continue
            except requests.exceptions.RequestException as e:
                return str(e)
            except:
                return "发送失败,疑似是系统环境因素导致"
        return None


class web_hook_msg:
    _MODULE_NAME = "hook"

    def __init__(self, name: str = None):
        if name is None:
            self._real_hook = None
        elif _cfg[name] is None:
            self._real_hook = None
        else:
            self._real_hook = RealHook(name)

    @staticmethod
    def get_version_info(get=None):
        """
        获取版本信息
        """
        return _cfg.get_version_info()

    @staticmethod
    def get_config(get=None):
        """
        获取配置
        """
        return _cfg.to_view()

    @staticmethod
    def set_status(status: bool, name=None) -> None:
        if name is None:
            _cfg.set_all(status)
        else:
            if _cfg[name] is not None:
                _cfg[name]["status"] = status

    @staticmethod
    def del_hook_by_name(name: str) -> None:
        del _cfg[name]

    @staticmethod
    def set_config(get):
        """配置hook"""
        try:
            hook_data = get.hook_data
            if isinstance(hook_data, str):
                hook_data = json.loads(hook_data)
            else:
                return ValueError
            name = hook_data['name']
            url = hook_data["url"]
            query = hook_data.get("query", {})
            headers = hook_data.get("headers", {})
            body_type = hook_data.get("body_type", "json")
            custom_parameter = hook_data.get("custom_parameter", {})
            method = hook_data.get("method", "POST")
            ssl_verify = hook_data.get("ssl_verify", None)  # null Ture

            status = bool(hook_data.get("status", True))
        except (ValueError, KeyError, json.JSONDecodeError, AttributeError):
            return public.returnMsg(False, "参数错误")

        test_or_save = int(getattr(get, "test_or_save", "0"))

        try:
            the_url = parse_url(url)  
            if the_url.scheme is None or the_url.host is None:
                # 如果解析结果表明这不是一个有效的URL,则返回错误信息
                return public.returnMsg(False, "URL字段不是一个有效的URL")
        except:
            return public.returnMsg(False, "URL字段不是一个有效的URL")

        for i in (query, headers, custom_parameter):
            if not isinstance(i, dict):
                return public.returnMsg(False, "参数错误")

        if body_type not in ('json', 'form_data', 'null'):
            return public.returnMsg(False, "body_type必须为json,form_data或者null")

        if method not in ('GET', 'POST', 'PUT', 'PATCH'):
            return public.returnMsg(False, "发送方式选择错误")

        if ssl_verify not in (True, False, None):
            return public.returnMsg(False, "是否验证ssl选项错误")

        name = name.strip()
        if name == "":
            return public.returnMsg(False, "名称不能为空")

        if name in ("dingding", "feishu", "mail", "sms", "weixin", "wx_account", "web_hook"):
            return public.returnMsg(False, "不能使用包含歧义的名称")

        the_conf = {
            "url": url,
            "query": query,
            "headers": headers,
            "body_type": body_type,
            "custom_parameter": custom_parameter,
            "method": method,
            "ssl_verify": ssl_verify,
            "status": True
        }

        if test_or_save == 1:
            hook = RealHook(hook_name="", name=name, config=the_conf)
            res = hook.send_msg(
                msg="宝塔面板自定义HOOK通道-测试信息",
                title="测试信息",
                push_type="测试信息"
            )
            if res is None:
                return public.returnMsg(True, "测试信息发送成功")
            else:
                return public.returnMsg(False, "测试信息发送失败")
        else:
            the_conf['status'] = status
            _cfg[name] = the_conf

        return public.returnMsg(True, "配置保存成功")

    @staticmethod
    def get_send_msg(msg):
        """
        @name 处理md格式
        """
        title = None
        if msg.find("####") >= 0:
            try:
                title = re.search(r"####(.+)\n", msg).groups()[0]
            except KeyError:
                pass
            msg = msg.replace("\n\n", "<br>").strip()
        pass
        return msg, title

    def send_msg(self, msg, title: str = '宝塔面板消息推送', push_type: str = 'unknown'):
        """
        触发web_hook, 发送信息
        """
        if self._real_hook is None:
            public.returnMsg(False, "未指定对应的hook用于发送")

        error, success, total = 0, 0, 1
        msg, n_title = self.get_send_msg(msg)

        if n_title:
            title = n_title

        error_msg = self._real_hook.send_msg(msg, title, push_type)

        if error_msg is None:
            status_msg = '<span style="color:#20a53a;">成功</span>'
            success += 1
        else:
            status_msg = '<span style="color:red;">失败</span>'
            error += 1
        log = '标题:【{}】,通知方式:【Api-{}】,发送状态:{}'.format(title, self._real_hook.name, status_msg)
        public.WriteLog('告警通知', log)

        result = public.returnMsg(True, '发送完成,共发送【{}】条,成功【{}】,失败【{}】。'.format(total, success, error))
        result['error_msg'] = error_msg
        result['success'] = success
        result['error'] = error
        return result

    def push_data(self, data):
        if "hook_name" in data:
            self._real_hook = RealHook(data.get("hook_name"))
        if "push_type" in data:
            push_type = data.get("push_type")
            return self.send_msg(data['msg'], data['title'], push_type)
        return self.send_msg(data['msg'], data['title'])

    @staticmethod
    def uninstall():
        _cfg.clear_config()

    @staticmethod
    def get_all_hooks_name():
        return _cfg.all_hook_name()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/582867.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何解决Edge浏览器显示“你的组织浏览器已托管”,导致无法正常打开网页问题?

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

[ACTF2020 新生赛]BackupFile 1 [极客大挑战 2019]BuyFlag 1 [护网杯 2018]easy_tornado 1

目录 [ACTF2020 新生赛]BackupFile 1 1.打开页面&#xff0c;叫我们去找源文件 2.想到用disearch扫描&#xff0c;发现源文件index.php.bak 3.访问这个文件&#xff0c;下载一个文件&#xff0c;用记事本打开 4.翻译php代码 5.构造payload url/?key123&#xff0c;得到fl…

《与 Apollo 共创生态:我和 Apollo 7周年大会的心路历程》

目录 前言7周年大会开放协同写在最后 前言 Apollo开放平台的企业生态计划是一个激动人心的举措&#xff0c;它展现了Apollo团队长期以来的努力和成就。通过与全球开发者和合作伙伴的紧密合作&#xff0c;Apollo开放平台已经成为一个创新和技术交流的重要平台。企业生态计划的推…

LeetCode 104.二叉树的最大深度

题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a; 输入&#xff1a;root [1,null,…

优化大型语言模型交互:提升查询和提示效果的26条原则

推荐下arxiv挂的一个提示词教程&#xff1a; https://github.com/VILA-Lab/ATLAS https://arxiv.org/abs/2312.16171 它提出了一套26条指导原则&#xff0c;改善和优化与大型语言模型&#xff08;LLMs&#xff09;的交互过程。通过这些原则&#xff0c;旨在简化对LLMs的查询和…

【软考高项】二十八、进度管理基础内容

一、管理基础 小型项目中&#xff0c;定义活动、排列活动顺序、估算活动持续时间及制定进度模型形成进度计划等过程的联系非常密切&#xff0c;可以视为一个过程&#xff0c;可以由一个人在较短时间内完成管理新实践 具有未完成项的迭代型进度计划:适应型生命周期的滚动式…

信息系统管理

目录 一、信息系统管理范围 1、规划和组织 2、设计和实施 ①、信息系统架构 Ⅰ、集中式架构 Ⅱ、分布式架构 Ⅲ、SOA&#xff08;面向服务的系统架构&#xff09; 3、运维和服务 ①、运行管理和控制 ②、IT服务管理 ③、运行与监控 Ⅰ、运行监控 Ⅱ、安全监控 4、…

Docker本地部署overleaf后,挖掘用户加密逻辑

overleaf的用户信息&#xff0c;保存在mongo数据库的users集合中。 用户密码则存在hashedPassword字段中 从开源的代码services\web\app\src\Features\Authentication\AuthenticationManager.js第303行可以找到密码加密逻辑。 本地可以通过下面的代码生成overleaf用户密码信息…

JAVA实现easyExcel批量导入

注解类型描述ExcelProperty导入指定当前字段对应excel中的那一列。可以根据名字或者Index去匹配。当然也可以不写&#xff0c;默认第一个字段就是index0&#xff0c;以此类推。千万注意&#xff0c;要么全部不写&#xff0c;要么全部用index&#xff0c;要么全部用名字去匹配。…

投资蓄能之际,九安医疗如何进一步稳固主业“压舱石”?

体外诊断行业的消费环境变化&#xff0c;正从相关企业的发展中体现。 据梳理&#xff0c;随着疫情检测需求回落&#xff0c;2023年以来&#xff0c;菲鹏生物、雅睿生物、中翰生物等体外诊断公司&#xff0c;陆续主动撤回上市申请。 而已上市公司也正处于周期性调整阶段。4月2…

MobileNetV4 论文学习

论文地址&#xff1a;https://arxiv.org/abs/2404.10518 代码地址&#xff1a;https://github.com/tensorflow/models/blob/master/official/vision/modeling/backbones/mobilenet.py 解决了什么问题&#xff1f; 边端设备的高效神经网络不仅能带来实时交互的体验&#xff0c…

(学习日记)2024.05.10:UCOSIII第六十四节:常用的结构体(os.h文件)第三部分

之前的章节都是针对某个或某些知识点进行的专项讲解&#xff0c;重点在功能和代码解释。 回到最初开始学μC/OS-III系统时&#xff0c;当时就定下了一个目标&#xff0c;不仅要读懂&#xff0c;还要读透&#xff0c;改造成更适合中国宝宝体质的使用方式。在学完野火的教程后&a…

设计模式——保护性暂停

同步模式之保护性暂停 文章目录 同步模式之保护性暂停定义实现应用带超时版 GuardedObject扩展——原理之join扩展——多任务版 GuardedObject 定义 即 Guarded Suspension&#xff0c;用在一个线程等待另一个线程的执行结果 要点 有一个结果需要从一个线程传递到另一个线程&…

【逆向百例】百度翻译js逆向

关注它&#xff0c;不迷路。 本文章中所有内容仅供学习交流&#xff0c;不可用于任何商业用途和非法用途&#xff0c;否则后果自负&#xff01; 前言 目标 分析某度翻译接口&#xff0c;使用python获取翻译结果&#xff0c;并用pyinstaller打包成单文件可执行程序。 工具 ch…

python自定义交叉熵损失,再和pytorch api对比

背景 我们知道&#xff0c;交叉熵本质上是两个概率分布之间差异的度量&#xff0c;公式如下 其中概率分布P是基准&#xff0c;我们知道H(P,Q)>0&#xff0c;那么H(P,Q)越小&#xff0c;说明Q约接近P。 损失函数本质上也是为了度量模型和完美模型的差异&#xff0c;因此可以…

理解红黑树结构

红黑树的特性 节点是红色或黑色根是黑色叶子节点&#xff08;外部节点&#xff0c;空节点&#xff09;都是黑色&#xff0c;这里的叶子节点指的是最底层的空节点&#xff08;外部节点&#xff09;&#xff0c;下图中的那些null节点才是叶子节点&#xff0c;null节点的父节点在…

偏微分方程算法之五点菱形差分法

目录 一、研究目标 二、理论推导 三、算例实现 四、结论 一、研究目标 上个专栏我们介绍了双曲型偏微分方程的主要算法及实现。从今天开始&#xff0c;我们在新的专栏介绍另一种形式偏微分方程-椭圆型的解法。 研究目标选取经典的二维椭圆型方程&#xff08;也称泊松Poisso…

选对伪原创改写软件,文章写作不犯难!

文章写作在当下火热的自媒体的行业中是一项非常重要的技能&#xff0c;只要是参与做自媒体的朋友&#xff0c;想要在内容输出方面不出现困难的情况&#xff0c;那么文章写作的技能基本要具备&#xff0c;但是我们依然能看到有很多不擅长写作的朋友也做起了自媒体&#xff0c;并…

高扬程水泵的性能与应用领域 /恒峰智慧科技

在现代社会中&#xff0c;科技的发展为我们的生活带来了无数便利和可能性。其中&#xff0c;高扬程水泵作为一种高效能的水泵&#xff0c;其独特的设计使其在各个领域都有着广泛的应用&#xff0c;尤其是在森林消防中。 一、高扬程水泵的性能 1. 高扬程&#xff1a;高扬程水泵…

SpringCloud-Seata分布式事务的环境搭建搭建

目录 一、版本说明 二、建立Seata Server数据库&#xff08;TC-带头大哥的数据库&#xff09; 三、业务库建表 四、安装Seata-Server 4.1 虚拟机里新建一个/opt/seate/seata-server文件夹&#xff0c;在seate文件夹下新建一个docker-compose.yml 文件 4.2 运行容器 4.3 在na…
最新文章