Files
aaPanel/mod/base/msg/web_hook_msg.py
T
2026-04-16 18:17:07 +08:00

280 lines
9.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# coding: utf-8
# +-------------------------------------------------------------------
# | aapanel
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 aapanel(http://www.aapanel.com) All rights reserved.
# +-------------------------------------------------------------------
# | Author: baozi <baozi@bt.cn>
# | 消息通道HOOK模块
# +-------------------------------------------------------------------
import copy
import requests
from typing import Optional, Union
from urllib3.util import parse_url
from .util import write_push_log, get_test_msg
import json
import public
# config = {
# "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 WebHookMsg(object):
DEFAULT_HEADERS = {
"User-Agent": "BT-Panel",
}
def __init__(self, hook_data: dict):
self.id = hook_data["id"]
self.config = copy.deepcopy(hook_data["data"])
def send_msg(self, msg: str, title: str) -> Optional[str]:
the_url = parse_url(self.config['url'])
ssl_verify = self.config.get("ssl_verify", None)
if ssl_verify is None:
ssl_verify = the_url.scheme == "https"
else:
ssl_verify = bool(int(ssl_verify)) # 转换为布尔值
custom_parameter = self.config.get("custom_parameter", {})
if not isinstance(custom_parameter, dict):
custom_parameter = {} # 如果 custom_parameter 不是字典,则设置为空字典
real_data = self._build_real_data(msg, title, custom_parameter=custom_parameter)
headers = self.DEFAULT_HEADERS.copy()
for k, v in self.config.get("headers", {}).items():
if not isinstance(v, str):
v = str(v)
headers[k] = v
data = None
json_data = None
if self.config["body_type"] == "json":
json_data = real_data
elif self.config["body_type"] == "form_data":
data = real_data
status = False
error = None
timeout = 10
if data:
for k, v in data.items():
if isinstance(v, str):
continue
else:
data[k]=json.dumps(v)
for i in range(3):
try:
if json_data is not None:
res = requests.request(
method=self.config["method"],
url=str(the_url),
json=json_data,
headers=headers,
timeout=timeout,
verify=ssl_verify,
)
else:
res = requests.request(
method=self.config["method"],
url=str(the_url),
data=data,
headers=headers,
timeout=timeout,
verify=ssl_verify,
)
text_lower = res.text.lower()
if "error" in text_lower or "invalid" in text_lower or "fail" in text_lower:
status = False
return res.text
if "success" in text_lower:
status = True
error = None
break
if res.status_code == 200:
status = True
error = None
break
else:
status = False
return res.text
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError):
timeout += 5
error = "time out"
continue
except requests.exceptions.RequestException as e:
error = str(e)
break
write_push_log("Web Hook", status, title)
return error if error else None
@staticmethod
def _build_real_data(msg: str, title:str, push_type:str = None, custom_parameter: dict = None):
if not custom_parameter:
custom_parameter = {}
if not push_type:
push_type = title
default_data = {
"title": title,
"msg": msg,
"type": push_type,
}
_build_by_replace = False
def _replace(tmp_data: Union[str, list, dict,]):
nonlocal _build_by_replace
if isinstance(tmp_data, str):
if "$1" in tmp_data:
_build_by_replace = True
tmp_data = tmp_data.replace("$1", json.dumps(default_data, ensure_ascii=False))
if "$msg" in tmp_data:
_build_by_replace = True
tmp_data = tmp_data.replace("$msg", msg)
if "$title" in tmp_data:
_build_by_replace = True
tmp_data = tmp_data.replace("$title", title)
if "$type" in tmp_data:
_build_by_replace = True
tmp_data = tmp_data.replace("$type", push_type)
return tmp_data
elif isinstance(tmp_data, list):
new_data = []
for i in tmp_data:
new_data.append(_replace(i))
return new_data
elif isinstance(tmp_data, dict):
new_data = {}
for k, v in tmp_data.items():
new_data[k] = _replace(v)
return new_data
else:
return tmp_data
real_data = _replace(custom_parameter)
if _build_by_replace:
return real_data
else:
custom_parameter["title"] = title
custom_parameter["msg"] = msg
custom_parameter["type"] = push_type
return custom_parameter
@classmethod
def check_args(cls, args):
"""配置hook"""
try:
title = args['title']
url = args["url"]
query = args.get("query", {})
headers = args.get("headers", {})
if headers == "":
headers = {}
headers = json.loads(headers) if isinstance(headers, str) else headers
body_type = args.get("body_type", "json")
if body_type == "application/json":
body_type = "json"
custom_parameter = args.get("custom_parameter", {})
if custom_parameter == "":
custom_parameter = {}
custom_parameter = json.loads(custom_parameter) if isinstance(custom_parameter, str) else custom_parameter
method = args.get("method", "POST")
ssl_verify = args.get("ssl_verify", None) # null Ture
except Exception as e:
return False, str(e)
the_url = parse_url(url)
if the_url.scheme is None or the_url.host is None:
return False, "URL parsing error, which may not be a legitimate URL"
for i in (query, headers, custom_parameter):
if not isinstance(i, dict):
return False, public.lang('Parameter format error')
if body_type not in ('json', 'form_data', 'null'):
return False, public.lang('The body type must be json,form data, or null')
if method not in ('GET', 'POST', 'PUT', 'PATCH'):
return False, public.lang('The sending method is incorrect')
if ssl_verify not in (True, False, None):
return False, public.lang('Verify if the SSL option is wrong')
title = title.strip()
if title == "":
return False, "The name cannot be empty"
data = {
"title": title,
"url": url,
"query": query,
"headers": headers,
"body_type": body_type,
"custom_parameter": custom_parameter,
"method": method,
"ssl_verify": ssl_verify,
"status": True
}
test_obj = cls({"data": data, "id": None})
test_msg = {
"msg_list": ['>configuration state: Success\n\n']
}
test_task = get_test_msg("Message channel configuration reminders")
res = test_obj.send_msg(
test_task.to_web_hook_msg(test_msg, test_task.the_push_public_data()),
"Message channel configuration reminders"
)
if res is None:
return True, data
return False, res
def test_send_msg(self) -> Optional[str]:
test_msg = {
"msg_list": ['>configuration state: Success \n\n']
}
test_task = get_test_msg("Message channel configuration reminders")
res = self.send_msg(
test_task.to_web_hook_msg(test_msg, test_task.the_push_public_data()),
"Message channel configuration reminders"
)
if res is None:
return None
return res