Update to 7.7.0

Since version 7.7.0, we recommend yours update python to 3.12.

[+] Using nginx technology to load static files improves access speed
[+] Refactor homepage, website, FTP, and database using vue3
[+] Table loading changed to skeleton screen
[+] Add Website statistics-v2 professional plug-in
[+] Add Home page - top 5 resource occupancy
[+] Add protection for Files management (requires Tamper-proof for Enterprise 3.7)
[+] Website, FTP, Databases page add program status
[+] Add FTP log analysis (only supports Centos)
[+] Add password-free login to phpMyAdmin
[+] Add Proxy Project in Website (Supported when web service uses Nginx)
[+] Add WP Toolkit (Pro version only)
[+] Redesigned Docker module
[+] Add WP Toolkit Protection
[+] Add WP Toolkit Backup and Restore
[+] Add WP Toolkit Migrated
[+] Add WP Toolkit Clone site (supports new domain and subdomain)
[+] Add WP Toolkit Create site from backup of other panel
[+] Add WP Toolkit support for Cron automatic backup (only save Local disk)
[+] Add WP Toolkit operation log
[+] Add Integrity check for WP Toolkit
[+] Add WP Toolkit plug-in management and themes management

[*] Optimize phpMyAdmin formula access method
[*] Optimize Home page PHP display problem
[*] Optimize jump to the login interface after the login expires
[*] Optimize automatic renewal of SSL at some times
[*] Optimize Let's Encrypt to increase application success rate

[-] Fix Logs Audit cannot be opened
[-] Fix apache URL rewrite issue
[-] Fix phpmyadmin installation problem
[-] Fix the problem that some servers cannot install software
[-] Fix upload file error
[-] Fix left menu hiding problem
[-] Fix aaPanel Mobile QR code display problem
[-] Fix problem that third-party plug-ins are not displayed in the App Store
[-] Fix issue where the menu bar is blank when opening new tabs
[-] Fixed panel not being accessible in some cases
[-] Fix the issue where Curl warning caused the inability to apply for SSL
[-] Fix Quota issues for Website, FTP, Databases
[-] Fix file interface display problem on mobile terminal
This commit is contained in:
Jack
2024-07-19 11:25:10 +08:00
parent ed34994f45
commit ed55fa708d
1949 changed files with 329210 additions and 26644 deletions
+145
View File
@@ -0,0 +1,145 @@
import json
import os.path
from .weixin_msg import WeiXinMsg
from .mail_msg import MailMsg
from .web_hook_msg import WebHookMsg
from .feishu_msg import FeiShuMsg
from .dingding_msg import DingDingMsg
from .sms_msg import SMSMsg
from .wx_account_msg import WeChatAccountMsg
from .manager import SenderManager
from .util import read_file
from mod.base.push_mod import SenderConfig, PUSH_DATA_PATH
# 把旧地告警系统的信息通道更新
def update_mod_push_msg():
if os.path.exists(PUSH_DATA_PATH + "/update_sender.pl"):
return
with open(PUSH_DATA_PATH + "/update_sender.pl", "w") as f:
f.write("")
WeChatAccountMsg.refresh_config(force=True)
sms_status = False
sc = SenderConfig()
for conf in sc.config:
if conf["sender_type"] == "sms":
sms_status = True
break
if not sms_status:
sc.config.append({
"id": sc.nwe_id(),
"used": True,
"sender_type": "sms",
"data": {},
"original": True # 标记这个通道是该类型 旧有的通道, 同时也是默认通道
})
panel_data_path = "/www/server/panel/data"
# weixin
if os.path.exists(panel_data_path + "/weixin.json"):
try:
weixin_data = json.loads(read_file(panel_data_path + "/weixin.json"))
except:
weixin_data = None
if isinstance(weixin_data, dict) and "weixin_url" in weixin_data:
sc.config.append({
"id": sc.nwe_id(),
"used": True,
"sender_type": "weixin",
"data": {
"url": weixin_data["weixin_url"],
"title": "企业微信" if "title" not in weixin_data else weixin_data["title"]
},
"original": True
})
# mail
stmp_file = panel_data_path + "/stmp_mail.json"
mail_list_file = panel_data_path + "/mail_list.json"
if os.path.exists(stmp_file) and os.path.exists(mail_list_file):
stmp_data = None
try:
stmp_data = json.loads(read_file(stmp_file))
mail_list_data = json.loads(read_file(mail_list_file))
except:
mail_list_data = None
if isinstance(stmp_data, dict):
if 'qq_mail' in stmp_data or 'qq_stmp_pwd' in stmp_data or 'hosts' in stmp_data:
sc.config.append({
"id": sc.nwe_id(),
"used": True,
"sender_type": "mail",
"data": {
"send": stmp_data,
"title": "邮箱",
"receive": [] if not mail_list_data else mail_list_data,
},
"original": True
})
# webhook
webhook_file = panel_data_path + "/hooks_msg.json"
if os.path.exists(stmp_file) and os.path.exists(mail_list_file):
try:
webhook_data = json.loads(read_file(webhook_file))
except:
webhook_data = None
if isinstance(webhook_data, list):
for i in webhook_data:
i["title"] = i["name"]
sc.config.append({
"id": sc.nwe_id(),
"used": True,
"sender_type": "webhook",
"data": i,
})
# feishu
if os.path.exists(panel_data_path + "/feishu.json"):
try:
feishu_data = json.loads(read_file(panel_data_path + "/feishu.json"))
except:
feishu_data = None
if isinstance(feishu_data, dict) and "feishu_url" in feishu_data:
sc.config.append({
"id": sc.nwe_id(),
"used": True,
"sender_type": "feishu",
"data": {
"url": feishu_data["feishu_url"],
"title": "飞书" if "title" not in feishu_data else feishu_data["title"]
},
"original": True
})
# dingding
if os.path.exists(panel_data_path + "/dingding.json"):
try:
dingding_data = json.loads(read_file(panel_data_path + "/dingding.json"))
except:
dingding_data = None
if isinstance(dingding_data, dict) and "dingding_url" in dingding_data:
sc.config.append({
"id": sc.nwe_id(),
"used": True,
"sender_type": "dingding",
"data": {
"url": dingding_data["dingding_url"],
"title": "钉钉" if "title" not in dingding_data else dingding_data["title"]
},
"original": True
})
sc.save_config()
read_file(PUSH_DATA_PATH + "/update_sender.pl", "")
+155
View File
@@ -0,0 +1,155 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: baozi <
# | 消息通道邮箱模块(新)
# +-------------------------------------------------------------------
import re
import json
import requests
import traceback
import socket
import requests.packages.urllib3.util.connection as urllib3_cn
from requests.packages import urllib3
from typing import Optional, Union
from .util import write_push_log, get_test_msg
# 关闭警告
urllib3.disable_warnings()
class DingDingMsg:
def __init__(self, dingding_data):
self.id = dingding_data["id"]
self.config = dingding_data["data"]
def send_msg(self, msg: str, title) -> Optional[str]:
"""
钉钉发送信息
@msg 消息正文
"""
if not self.config:
return '未正确配置钉钉信息'
# user没有时默认为空
if "user" not in self.config:
self.config['user'] = []
if "isAtAll" not in self.config:
self.config['isAtAll'] = []
if not isinstance(self.config['url'], str):
return '钉钉配置错误,请重新配置钉钉机器人'
at_info = ''
for user in self.config['user']:
if re.match(r"^[0-9]{11}$", str(user)):
at_info += '@' + user + ' '
if at_info:
msg = msg + '\n\n>' + at_info
headers = {'Content-Type': 'application/json'}
data = {
"msgtype": "markdown",
"markdown": {
"title": "服务器通知",
"text": msg
},
"at": {
"atMobiles": self.config['user'],
"isAtAll": self.config['isAtAll']
}
}
status = False
error = None
try:
def allowed_gai_family():
family = socket.AF_INET
return family
allowed_gai_family_lib = urllib3_cn.allowed_gai_family
urllib3_cn.allowed_gai_family = allowed_gai_family
response = requests.post(
url=self.config["url"],
data=json.dumps(data),
verify=False,
headers=headers,
timeout=10
)
urllib3_cn.allowed_gai_family = allowed_gai_family_lib
if response.json()["errcode"] == 0:
status = True
except:
error = traceback.format_exc()
status = False
write_push_log("钉钉", status, title)
return error
@classmethod
def check_args(cls, args: dict) -> Union[dict, str]:
if "url" not in args or "title" not in args:
return "信息不完整"
title = args["title"]
if len(title) > 15:
return '备注名称不能超过15个字符'
if "user" in args and isinstance(args["user"], list):
user = args["user"]
else:
user = []
if "atall" in args and isinstance(args["atall"], bool):
atall = args["atall"]
else:
atall = True
data = {
"url": args["url"],
"user": user,
"title": title,
"isAtAll": atall,
}
test_obj = cls({"data": data, "id": None})
test_msg = {
"msg_list": ['>配置状态:<font color=#20a53a>成功</font>\n\n']
}
test_task = get_test_msg("面板消息通道配置提醒")
res = test_obj.send_msg(
test_task.to_dingding_msg(test_msg, test_task.the_push_public_data()),
"面板消息通道配置提醒"
)
if res is None:
return data
return res
def test_send_msg(self) -> Optional[str]:
test_msg = {
"msg_list": ['>配置状态:<font color=#20a53a>成功</font>\n\n']
}
test_task = get_test_msg("面板消息通道配置提醒")
res = self.send_msg(
test_task.to_dingding_msg(test_msg, test_task.the_push_public_data()),
"面板消息通道配置提醒"
)
if res is None:
return None
return res
+139
View File
@@ -0,0 +1,139 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: lx
# | 消息通道飞书通知模块
# +-------------------------------------------------------------------
import re
import json
import requests
import traceback
import socket
import requests.packages.urllib3.util.connection as urllib3_cn
from requests.packages import urllib3
from typing import Optional, Union
from .util import write_push_log, get_test_msg
# 关闭警告
urllib3.disable_warnings()
class FeiShuMsg:
def __init__(self, feishu_data):
self.id = feishu_data["id"]
self.config = feishu_data["data"]
@classmethod
def check_args(cls, args: dict) -> Union[dict, str]:
if "url" not in args or "title" not in args:
return "信息不完整"
title = args["title"]
if len(title) > 15:
return '备注名称不能超过15个字符'
if "user" in args and isinstance(args["user"], list):
user = args["user"]
else:
user = []
if "atall" in args and isinstance(args["atall"], bool):
atall = args["atall"]
else:
atall = True
data = {
"url": args["url"],
"user": user,
"title": title,
"isAtAll": atall,
}
test_obj = cls({"data": data, "id": None})
test_msg = {
"msg_list": ['>配置状态:成功\n\n']
}
test_task = get_test_msg("消息通道配置提醒")
res = test_obj.send_msg(
test_task.to_feishu_msg(test_msg, test_task.the_push_public_data()),
"消息通道配置提醒"
)
if res is None:
return data
return res
def send_msg(self, msg: str, title: str) -> Optional[str]:
"""
飞书发送信息
@msg 消息正文
"""
if not self.config:
return '未正确配置飞书信息。'
reg = '<font.+>(.+)</font>'
tmp = re.search(reg, msg)
if tmp:
tmp = tmp.groups()[0]
msg = re.sub(reg, tmp, msg)
if "isAtAll" not in self.config:
self.config["isAtAll"] = True
if self.config["isAtAll"]:
msg += "<at userid='all'>所有人</at>"
headers = {'Content-Type': 'application/json'}
data = {
"msg_type": "text",
"content": {
"text": msg
}
}
status = False
error = None
try:
def allowed_gai_family():
family = socket.AF_INET
return family
allowed_gai_family_lib = urllib3_cn.allowed_gai_family
urllib3_cn.allowed_gai_family = allowed_gai_family
rdata = requests.post(
url=self.config['url'],
data=json.dumps(data),
verify=False,
headers=headers,
timeout=10
).json()
urllib3_cn.allowed_gai_family = allowed_gai_family_lib
if "StatusCode" in rdata and rdata["StatusCode"] == 0:
status = True
except:
error = traceback.format_exc()
write_push_log("飞书", status, title)
return error
def test_send_msg(self) -> Optional[str]:
test_msg = {
"msg_list": ['>配置状态:<font color=#20a53a>成功</font>\n\n']
}
test_task = get_test_msg("消息通道配置提醒")
res = self.send_msg(
test_task.to_feishu_msg(test_msg, test_task.the_push_public_data()),
"消息通道配置提醒"
)
if res is None:
return None
return res
+135
View File
@@ -0,0 +1,135 @@
#coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: 沐落 <cjx@bt.cn>
# | Author: lx
# | 消息通道邮箱模块
# +-------------------------------------------------------------------
import smtplib
import traceback
from email.mime.text import MIMEText
from email.utils import formataddr
from typing import Tuple, Union, Optional
from mod.base.msg.util import write_push_log, write_mail_push_log, get_test_msg
class MailMsg:
def __init__(self, mail_data):
self.id = mail_data["id"]
self.config = mail_data["data"]
@classmethod
def check_args(cls, args: dict) -> Tuple[bool, Union[dict, str]]:
if "send" not in args or "receive" not in args or len(args["receive"]) < 1:
return False, "信息不完整,必须有发送方和至少一个接收方"
if "title" not in args:
return False, "没有必要的备注信息"
title = args["title"]
if len(title) > 15:
return False, '备注名称不能超过15个字符'
send_data = args["send"]
send = {}
for i in ("qq_mail", "qq_stmp_pwd", "hosts", "port"):
if i not in send_data:
return False, "发送方配置信息不完整"
send[i] = send_data[i].strip()
receive_data = args["receive"]
if isinstance(receive_data, str):
receive_list = [i.strip() for i in receive_data.split("\n") if i.strip()]
else:
receive_list = [i.strip() for i in receive_data if i.strip()]
data = {
"send": send,
"title": title,
"receive": receive_list,
}
test_obj = cls({"data": data, "id": None})
test_msg = {
"msg_list": ['>配置状态:成功<br>']
}
test_task = get_test_msg("消息通道配置提醒")
res = test_obj.send_msg(
test_task.to_mail_msg(test_msg, test_task.the_push_public_data()),
"消息通道配置提醒"
)
if res is None or res.find("部分接收者时失败") != -1:
return True, data
return False, res
def send_msg(self, msg: str, title: str):
"""
邮箱发送
@msg 消息正文
@title 消息标题
"""
if not self.config:
return '未正确配置邮箱信息。'
if 'port' not in self.config['send']:
self.config['send']['port'] = 465
receive_list = self.config['receive']
error_list, success_list = [], []
error_msg_dict = {}
for email in receive_list:
if not email.strip():
continue
try:
data = MIMEText(msg, 'html', 'utf-8')
data['From'] = formataddr((self.config['send']['qq_mail'], self.config['send']['qq_mail']))
data['To'] = formataddr((self.config['send']['qq_mail'], email.strip()))
data['Subject'] = title
if int(self.config['send']['port']) == 465:
server = smtplib.SMTP_SSL(str(self.config['send']['hosts']), int(self.config['send']['port']))
else:
server = smtplib.SMTP(str(self.config['send']['hosts']), int(self.config['send']['port']))
server.login(self.config['send']['qq_mail'], self.config['send']['qq_stmp_pwd'])
server.sendmail(self.config['send']['qq_mail'], [email.strip(), ], data.as_string())
server.quit()
success_list.append(email)
except:
error_list.append(email)
error_msg_dict[email] = traceback.format_exc()
if not error_list and not success_list: # 没有接收者
return "未配置接收邮箱"
if not error_list:
write_push_log("邮箱", True, title, success_list) # 没有失败
return None
if not success_list:
write_push_log("邮箱", False, title, error_list) # 全都失败
return "发送信息失败, 发送失败的接收人:{}".format(error_list)
write_mail_push_log(title, error_list, success_list)
return "发送邮件到部分接收者时失败,包含:{}".format(error_list)
def test_send_msg(self) -> Optional[str]:
test_msg = {
"msg_list": ['>配置状态:<font color=#20a53a>成功</font>\n\n']
}
test_task = get_test_msg("消息通道配置提醒")
res = self.send_msg(
test_task.to_mail_msg(test_msg, test_task.the_push_public_data()),
"消息通道配置提醒"
)
if res is None:
return None
return res
+282
View File
@@ -0,0 +1,282 @@
import time
import traceback
from mod.base.push_mod import SenderConfig
from .weixin_msg import WeiXinMsg
from .mail_msg import MailMsg
from .web_hook_msg import WebHookMsg
from .feishu_msg import FeiShuMsg
from .dingding_msg import DingDingMsg
from .sms_msg import SMSMsg
from .wx_account_msg import WeChatAccountMsg
import json
from mod.base import json_response
from .util import write_file, read_file
import sys,os
sys.path.insert(0, "/www/server/panel/class/")
import public
# 短信会自动添加到 sender 库中的第一个 且通过官方接口更新
# 微信公众号信息通过官网接口更新, 不写入数据库,需要时由文件中读取并序列化
# 其他告警通道本质都类似于web hook 在确认完数据信息无误后,都可以自行添加或启用
class SenderManager:
def __init__(self):
self.custom_parameter_filename = "/www/server/panel/data/mod_push_data/custom_parameter.pl"
def set_sender_conf(self, get):
sender_id = None
try:
if hasattr(get, "sender_id"):
sender_id = get.sender_id.strip()
if not sender_id:
sender_id = None
sender_type = get.sender_type.strip()
args = json.loads(get.sender_data.strip())
except (json.JSONDecoder, AttributeError, TypeError):
return json_response(status=False, msg="参数错误")
sender_config = SenderConfig()
if sender_id is not None:
tmp = sender_config.get_by_id(sender_id)
if tmp is None:
sender_id = None
if sender_type == "weixin":
data = WeiXinMsg.check_args(args)
if isinstance(data, str):
return json_response(status=False, data=data, msg="测试发送失败")
elif sender_type == "mail":
_, data = MailMsg.check_args(args)
if isinstance(data, str):
return json_response(status=False, data=data, msg="测试发送失败")
elif sender_type == "webhook":
custom_parameter = args.get("custom_parameter", {})
if custom_parameter:
try:
public.writeFile(self.custom_parameter_filename, json.dumps(custom_parameter))
except:
pass
# 检查参数
data = WebHookMsg.check_args(args)
if isinstance(data, str):
return json_response(status=False, data=data, msg="测试发送失败")
# 从文件读取并删除文件
try:
if os.path.exists(self.custom_parameter_filename):
custom_parameter = json.loads(public.readFile(self.custom_parameter_filename))
data['custom_parameter'] = custom_parameter
os.remove(self.custom_parameter_filename)
except:
pass
elif sender_type == "feishu":
data = FeiShuMsg.check_args(args)
if isinstance(data, str):
return json_response(status=False, data=data, msg="测试发送失败")
elif sender_type == "dingding":
data = DingDingMsg.check_args(args)
if isinstance(data, str):
return json_response(status=False, data=data, msg="测试发送失败")
else:
return json_response(status=False, msg="当前接口不适应的类型")
# Check if the sender configuration already exists
existing_sender = any(
conf for conf in sender_config.config
if conf['sender_type'] == sender_type and 'title' in conf['data'] and conf['data']['title'] == data['title'] and conf['id'] != sender_id
)
if existing_sender:
return json_response(status=False, msg="同样的发送配置已存在,无法重复添加")
now_sender_id = None
if not sender_id:
now_sender_id = sender_config.nwe_id()
sender_config.config.append(
{
"id": now_sender_id,
"sender_type": sender_type,
"data": data,
"used": True,
})
else:
now_sender_id = sender_id
tmp = sender_config.get_by_id(sender_id)
tmp["data"].update(data)
type_senders = [conf for conf in sender_config.config if conf['sender_type'] == sender_type]
if len(type_senders) == 1:
for conf in sender_config.config:
conf["original"] = (conf['id'] == now_sender_id)
sender_config.save_config()
if sender_type == "webhook":
self.set_default_for_compatible(sender_config.get_by_id(now_sender_id))
return json_response(status=True, msg="保存成功")
@staticmethod
def change_sendr_used(get):
try:
sender_id = get.sender_id.strip()
except (AttributeError, TypeError):
return json_response(status=False, msg="参数错误")
sender_config = SenderConfig()
tmp = sender_config.get_by_id(sender_id)
if tmp is None:
return json_response(status=False, msg="未找到对应发送者")
tmp["used"] = not tmp["used"]
sender_config.save_config()
return json_response(status=True, msg="保存成功")
@staticmethod
def remove_sender(get):
try:
sender_id = get.sender_id.strip()
except (AttributeError, TypeError):
return json_response(status=False, msg="参数错误")
sender_config = SenderConfig()
tmp = sender_config.get_by_id(sender_id)
if tmp is None:
return json_response(status=False, msg="未找到对应发送者")
sender_config.config.remove(tmp)
sender_config.save_config()
return json_response(status=True, msg="删除成功")
@staticmethod
def get_sender_list(get):
# 微信, 飞书, 钉钉, web-hook, 邮箱
refresh = False
try:
if hasattr(get, 'refresh'):
refresh = get.refresh.strip()
if refresh in ("1", "true"):
refresh = True
except (AttributeError, TypeError):
return json_response(status=False, msg="参数错误")
res = []
WeChatAccountMsg.refresh_config(force=refresh)
simple = ("weixin", "mail", "webhook", "feishu", "dingding")
for conf in SenderConfig().config:
if conf["sender_type"] in simple or conf["sender_type"] == "wx_account":
res.append(conf)
elif conf["sender_type"] == "sms":
conf["data"] = SMSMsg(conf).refresh_config(force=refresh)
res.append(conf)
res.sort(key=lambda x: x["sender_type"])
return json_response(status=True, data=res)
@staticmethod
def test_send_msg(get):
try:
sender_id = get.sender_id.strip()
except (json.JSONDecoder, AttributeError, TypeError):
return json_response(status=False, msg="参数错误")
sender_config = SenderConfig()
tmp = sender_config.get_by_id(sender_id)
if tmp is None:
return json_response(status=False, msg="未找到对应发送者")
sender_type = tmp["sender_type"]
if sender_type == "weixin":
sender_obj = WeiXinMsg(tmp)
elif sender_type == "mail":
sender_obj = MailMsg(tmp)
elif sender_type == "webhook":
sender_obj = WebHookMsg(tmp)
elif sender_type == "feishu":
sender_obj = FeiShuMsg(tmp)
elif sender_type == "dingding":
sender_obj = DingDingMsg(tmp)
elif sender_type == "wx_account":
sender_obj = WeChatAccountMsg(tmp)
else:
return json_response(status=False, msg="当前接口不适应的类型")
res = sender_obj.test_send_msg()
if isinstance(res, str):
return json_response(status=False, data=res, msg="测试发送失败")
return json_response(status=True, msg="发送成功")
@staticmethod
def set_default_for_compatible(sender_data: dict):
if sender_data["sender_type"] in ("sms", "wx_account"):
return
panel_data = "/www/server/panel/data"
if sender_data["sender_type"] == "weixin":
weixin_file = "{}/weixin.json".format(panel_data)
write_file(weixin_file, json.dumps({
"state": 1,
"weixin_url": sender_data["data"]["url"],
"title": sender_data["data"]["title"],
"list": {
"default": {
"data": sender_data["data"]["url"],
"title": sender_data["data"]["title"],
"status": 1,
"addtime": int(time.time())
}
}
}))
elif sender_data["sender_type"] == "mail":
stmp_mail_file = "{}/stmp_mail.json".format(panel_data)
mail_list_file = "{}/mail_list.json".format(panel_data)
write_file(stmp_mail_file, json.dumps(sender_data["data"]["send"]))
write_file(mail_list_file, json.dumps(sender_data["data"]["receive"]))
elif sender_data["sender_type"] == "feishu":
feishu_file = "{}/feishu.json".format(panel_data)
write_file(feishu_file, json.dumps({
"feishu_url": sender_data["data"]["url"],
"title": sender_data["data"]["title"],
"isAtAll": True,
"user": []
}))
elif sender_data["sender_type"] == "dingding":
dingding_file = "{}/dingding.json".format(panel_data)
write_file(dingding_file, json.dumps({
"dingding_url": sender_data["data"]["url"],
"title": sender_data["data"]["title"],
"isAtAll": True,
"user": []
}))
elif sender_data["sender_type"] == "webhook":
webhook_file = "{}/hooks_msg.json".format(panel_data)
try:
webhook_data = json.loads(read_file(webhook_file))
except:
webhook_data =[]
target_idx = -1
for idx, i in enumerate(webhook_data):
if i["name"] == sender_data["data"]["title"]:
target_idx = idx
break
else:
sender_data["data"]["name"] = sender_data["data"]["title"]
webhook_data.append(sender_data["data"])
if target_idx != -1:
sender_data["data"]["name"] = sender_data["data"]["title"]
webhook_data[target_idx] = sender_data["data"]
write_file(webhook_file, json.dumps(webhook_data))
+121
View File
@@ -0,0 +1,121 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: baozi
# | 消息通道 短信模块(新)
# +-------------------------------------------------------------------
import json
import os
import time
import traceback
from typing import Union, Optional
from mod.base.push_mod import SenderConfig
from .util import write_push_log, PANEL_PATH, write_file, read_file, public_http_post
class SMSMsg:
API_URL = 'http://www.bt.cn/api/wmsg'
USER_PATH = '{}/data/userInfo.json'.format(PANEL_PATH)
# 构造方法
def __init__(self, msm_data: dict):
self.id = msm_data["id"]
self.data = msm_data["data"]
self.user_info = None
try:
self.user_info = json.loads(read_file(self.USER_PATH))
except:
self.user_info = None
self._PDATA = {
"access_key": "" if self.user_info is None else self.user_info["access_key"],
"data": {}
}
def refresh_config(self, force=False):
if "last_refresh_time" not in self.data:
self.data["last_refresh_time"] = 0
if self.data.get("last_refresh_time") + 60 * 60 * 24 < time.time() or force: # 一天最多更新一次
result = self._request('get_user_sms')
if not isinstance(result, dict) or ("status" in result and not result["status"]):
return {
"count": 0,
"total": 0
}
sc = SenderConfig()
tmp = sc.get_by_id(self.id)
if tmp is not None:
result["last_refresh_time"] = time.time()
tmp["data"] = result
sc.save_config()
else:
result = self.data
return result
def send_msg(self, sm_type: str, sm_args: dict):
"""
@发送短信
@sm_type 预警类型, ssl_end|宝塔SSL到期提醒
@sm_args 预警参数
"""
if not self.user_info:
return "未成功绑定官网账号,无法发送信息,请尝试重新绑定"
tmp = sm_type.split('|')
if "|" in sm_type and len(tmp) >= 2:
s_type = tmp[0]
title = tmp[1]
else:
s_type = sm_type
title = '宝塔告警提醒'
sm_args = self.canonical_data(sm_args)
self._PDATA['data']['sm_type'] = s_type
self._PDATA['data']['sm_args'] = sm_args
print(s_type)
print(sm_args)
result = self._request('send_msg')
u_key = '{}****{}'.format(self.user_info['username'][:3], self.user_info['username'][-3:])
print(result)
if isinstance(result, str):
write_push_log("短信", False, title, [u_key])
return result
if result['status']:
write_push_log("短信", True, title, [u_key])
return None
else:
write_push_log("短信", False, title, [u_key])
return result.get("msg", "发送错误")
@staticmethod
def canonical_data(args):
"""规范数据内容"""
if not isinstance(args, dict):
return args
new_args = {}
for param, value in args.items():
if type(value) != str:
new_str = str(value)
else:
new_str = value.replace(".", "_").replace("+", "")
new_args[param] = new_str
return new_args
def push_data(self, data):
return self.send_msg(data['sm_type'], data['sm_args'])
# 发送请求
def _request(self, d_name: str) -> Union[dict, str]:
pdata = {
'access_key': self._PDATA['access_key'],
'data': json.dumps(self._PDATA['data'])
}
try:
result = public_http_post(self.API_URL + '/' + d_name, pdata)
result = json.loads(result)
return result
except Exception:
return traceback.format_exc()
+105
View File
@@ -0,0 +1,105 @@
[
{
"id": "f4e98e478b85e876",
"used": true,
"sender_type": "sms",
"data": {}
},
{
"id": "fb3e9e409b9d7c27",
"sender_type": "mail",
"data": {
"send": {
"qq_mail": "1191604998@qq.com",
"qq_stmp_pwd": "alvonbfcwhlahbcg",
"hosts": "smtp.qq.com",
"port": "465"
},
"title": "test_mail",
"receive": [
"1191604998@qq.com",
"225326944@qq.com"
]
},
"used": true
},
{
"id": "79900d4fb37fa83d",
"sender_type": "feishu",
"data": {
"url": "https://open.feishu.cn/open-apis/bot/v2/hook/ba6a3f77-0349-4492-a8ad-0b4c99435bf1",
"user": [],
"title": "test_feishu",
"isAtAll": true
},
"used": true
},
{
"id": "8f70de4baa89133e",
"sender_type": "webhook",
"data": {
"title": "webhook",
"url": "http://192.168.69.172:11211",
"query": {},
"headers": {},
"body_type": "json",
"custom_parameter": {},
"method": "POST",
"ssl_verify": null,
"status": true
},
"used": true
},
{
"id": "10bcf5439299d9dd",
"used": true,
"sender_type": "wx_account",
"data": {
"id": "jsbRCBBinMmFjYjczNTQyYmUzxNDiWQw",
"uid": 1228262,
"is_subscribe": 1,
"head_img": "https://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83epBUaqBcCkkxtKwuaOHLy1qjGeDvmf1hZsrkFGNrldyRgSuA3sYB1xlgKv1Z98PUciaxju71PUKchA/132",
"nickname": "沈涛",
"status": 1,
"create_time": "2023-12-27 11:30:15",
"update_time": "2023-12-27 11:30:15",
"remaining": 98,
"title": "沈涛"
}
},
{
"id": "2c7c094eb23ddaae",
"used": true,
"sender_type": "webhook",
"data": {
"url": "http://192.168.69.159:8888/hook?access_key=IUSEViIMMhQio1WyP0ztCyoa8sIBjaWulihhcJX4rRJ4sW79",
"query": {},
"headers": {},
"body_type": "json",
"custom_parameter": {},
"method": "GET",
"ssl_verify": 1,
"status": true,
"name": "aaa",
"title": "aaa"
}
},
{
"id": "63c30845916fa722",
"used": true,
"sender_type": "feishu",
"data": {
"url": "https://open.feishu.cn/open-apis/bot/v2/hook/c6906d9f-01c5-4a74-80bd-3ccda33bf4ec",
"title": "amber"
}
},
{
"id": "6ccf834a95010bed",
"used": true,
"sender_type": "dingding",
"data": {
"url": "https://oapi.dingtalk.com/robot/send?access_token=00732dec605edc1c07f441eb9d470c8bdfa301c4ce89959916fe535d08c09043",
"title": "dd"
}
}
]
+139
View File
@@ -0,0 +1,139 @@
import sys
from typing import Optional, List, Tuple
from mod.base.push_mod import BaseTask, WxAccountMsgBase, WxAccountMsg, get_push_public_data
if "/www/server/panel/class" not in sys.path:
sys.path.insert(0, "/www/server/panel/class")
import public
PANEL_PATH = "/www/server/panel"
public_http_post = public.httpPost
def write_push_log(
module_name: str,
status: bool,
title: str,
user: Optional[List[str]] = None):
"""
记录 告警推送情况
@param module_name: 通道方式
@param status: 是否成功
@param title: 标题
@param user: 推送到的用户,可以为空,如:钉钉 不需要
@return:
"""
if status:
status_str = '<span style="color:#20a53a;">成功</span>'
else:
status_str = '<span style="color:red;">失败</span>'
if not user:
user_str = '[ 默认 ]'
else:
user_str = '[ {} ]'.format(",".join(user))
log = '标题:【{}】,通知方式:【{}】,结果:【{}】,收件人:{}'.format(title, module_name, status_str, user_str)
public.WriteLog('告警通知', log)
return True
def write_mail_push_log(
title: str,
error_user: List[str],
success_user: List[str],
):
"""
记录 告警推送情况
@param title: 标题
@param error_user: 失败的用户
@param success_user: 成功的用户
@return:
"""
e_fmt = '<span style="color:#20a53a;">{}</span>'
s_fmt = '<span style="color:red;">{}</span>'
error_user_msg = ",".join([e_fmt.format(i) for i in error_user])
success_user = ",".join([s_fmt.format(i) for i in success_user])
log = '标题:【{}】,通知方式:【邮箱】,发送失败的收件人:{},发送成功的收件人:{}'.format(
title, error_user_msg, success_user
)
public.WriteLog('告警通知', log)
return True
def write_file(filename: str, s_body: str, mode='w+') -> bool:
"""
写入文件内容
@filename 文件名
@s_body 欲写入的内容
return bool 若文件不存在则尝试自动创建
"""
try:
fp = open(filename, mode=mode)
fp.write(s_body)
fp.close()
return True
except:
try:
fp = open(filename, mode=mode, encoding="utf-8")
fp.write(s_body)
fp.close()
return True
except:
return False
def read_file(filename, mode='r') -> Optional[str]:
"""
读取文件内容
@filename 文件名
return string(bin) 若文件不存在,则返回None
"""
import os
if not os.path.exists(filename):
return None
fp = None
try:
fp = open(filename, mode=mode)
f_body = fp.read()
except:
return None
finally:
if fp and not fp.closed:
fp.close()
return f_body
class _TestMsgTask(BaseTask):
"""
用来测试的短息
"""
@staticmethod
def the_push_public_data():
return get_push_public_data()
def get_keywords(self, task_data: dict) -> str:
pass
def to_sms_msg(self, push_data: dict, push_public_data: dict) -> Tuple[str, dict]:
raise NotImplementedError()
def to_wx_account_msg(self, push_data: dict, push_public_data: dict) -> WxAccountMsg:
msg = WxAccountMsg.new_msg()
msg.thing_type = self.title
msg.msg = "消息通道配置成功"
return msg
def get_test_msg(title: str, task_name="消息通道配置提醒") -> _TestMsgTask:
"""
用来测试的短息
"""
t = _TestMsgTask()
t.title = title
t.template_name = task_name
return t
+221
View File
@@ -0,0 +1,221 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: baozi <baozi@bt.cn>
# | 消息通道HOOK模块
# +-------------------------------------------------------------------
import requests
from typing import Optional, Union
from urllib3.util import parse_url
from .util import write_push_log, get_test_msg
import json
# 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 = hook_data["data"]
def _replace_and_parse(self, value, real_data):
"""替换占位符并递归解析JSON字符串"""
if isinstance(value, str):
value = value.replace("$1", json.dumps(real_data, ensure_ascii=False))
elif isinstance(value, dict):
for k, v in value.items():
value[k] = self._replace_and_parse(v, real_data)
return value
def send_msg(self, msg: str, title:str, push_type: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"
real_data = {
"title": title,
"msg": msg,
"type": push_type,
}
# 处理custom_parameter,将$1替换为real_data内容并递归解析
custom_data = {}
for k, v in self.config.get("custom_parameter", {}).items():
custom_data[k] = self._replace_and_parse(v, real_data)
if custom_data:
real_data = custom_data
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
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,
)
if res.status_code == 200:
status = True
break
else:
status = False
return res.text
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError):
timeout += 5
continue
except requests.exceptions.RequestException as e:
error = str(e)
break
write_push_log("Web Hook", status, title)
return error
@classmethod
def check_args(cls, args) -> Union[str, dict]:
"""配置hook"""
try:
title = args['title']
url = args["url"]
query = args.get("query", {})
headers = args.get("headers", {})
body_type = args.get("body_type", "json")
custom_parameter = args.get("custom_parameter", {})
method = args.get("method", "POST")
ssl_verify = args.get("ssl_verify", None) # null Ture
except (ValueError, KeyError):
return "参数错误"
the_url = parse_url(url)
if the_url.scheme is None or the_url.host is None:
return"url解析错误,这可能不是一个合法的url"
for i in (query, headers, custom_parameter):
if not isinstance(i, dict):
return "参数格式错误"
if body_type not in ('json', 'form_data', 'null'):
return "body_type必须为json,form_data或者null"
if method not in ('GET', 'POST', 'PUT', 'PATCH'):
return "发送方式选择错误"
if ssl_verify not in (True, False, None):
return "是否验证ssl选项错误"
title = title.strip()
if title == "":
return"名称不能为空"
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": ['>配置状态:成功\n\n']
}
test_task = get_test_msg("消息通道配置提醒")
res = test_obj.send_msg(
test_task.to_web_hook_msg(test_msg, test_task.the_push_public_data()),
"消息通道配置提醒",
"消息通道配置提醒"
)
if res is None:
return data
return res
def test_send_msg(self) -> Optional[str]:
test_msg = {
"msg_list": ['>配置状态:<font color=#20a53a>成功</font>\n\n']
}
test_task = get_test_msg("消息通道配置提醒")
res = self.send_msg(
test_task.to_web_hook_msg(test_msg, test_task.the_push_public_data()),
"消息通道配置提醒",
"消息通道配置提醒"
)
if res is None:
return None
return res
+130
View File
@@ -0,0 +1,130 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: baozi <baozi@bt.cn>
# | 消息通道邮箱模块
# +-------------------------------------------------------------------
import re
import json
import requests
import traceback
import socket
import requests.packages.urllib3.util.connection as urllib3_cn
from requests.packages import urllib3
from typing import Optional, Union
from .util import write_push_log, get_test_msg
# 关闭警告
urllib3.disable_warnings()
class WeiXinMsg:
def __init__(self, weixin_data):
self.id = weixin_data["id"]
self.config = weixin_data["data"]
@classmethod
def check_args(cls, args: dict) -> Union[dict, str]:
if "url" not in args or "title" not in args:
return "信息不完整"
title = args["title"]
if len(title) > 15:
return '备注名称不能超过15个字符'
data = {
"url": args["url"],
"title": title,
}
test_obj = cls({"data": data, "id": None})
test_msg = {
"msg_list": ['>配置状态:<font color=#20a53a>成功</font>\n']
}
test_task = get_test_msg("消息通道配置提醒")
res = test_obj.send_msg(
test_task.to_weixin_msg(test_msg, test_task.the_push_public_data()),
"消息通道配置提醒"
)
if res is None:
return data
return res
def send_msg(self, msg: str, title: str) -> Optional[str]:
"""
@name 微信发送信息
@msg string 消息正文(正文内容,必须包含
1、服务器名称
2、IP地址
3、发送时间
)
@to_user string 指定发送人
"""
if not self.config:
return '未正确配置微信信息。'
reg = '<font.+>(.+)</font>'
tmp = re.search(reg, msg)
if tmp:
tmp = tmp.groups()[0]
msg = re.sub(reg, tmp, msg)
data = {
"msgtype": "markdown",
"markdown": {
"content": msg
}
}
headers = {'Content-Type': 'application/json'}
status = False
error = None
try:
def allowed_gai_family():
family = socket.AF_INET
return family
allowed_gai_family_lib = urllib3_cn.allowed_gai_family
urllib3_cn.allowed_gai_family = allowed_gai_family
response = requests.post(
url=self.config["url"],
data=json.dumps(data),
verify=False,
headers=headers,
timeout=10
)
urllib3_cn.allowed_gai_family = allowed_gai_family_lib
if response.json()["errcode"] == 0:
status = True
except:
error = traceback.format_exc()
write_push_log("企业微信", status, title)
return error
def test_send_msg(self) -> Optional[str]:
test_msg = {
"msg_list": ['>配置状态:<font color=#20a53a>成功</font>\n\n']
}
test_task = get_test_msg("消息通道配置提醒")
res = self.send_msg(
test_task.to_weixin_msg(test_msg, test_task.the_push_public_data()),
"消息通道配置提醒",
)
if res is None:
return None
return res
+556
View File
@@ -0,0 +1,556 @@
# coding: utf-8
# +-------------------------------------------------------------------
# | 宝塔Linux面板
# +-------------------------------------------------------------------
# | Copyright (c) 2015-2020 宝塔软件(http://www.bt.cn) All rights reserved.
# +-------------------------------------------------------------------
# | Author: baozi <baozi@bt.cn>
# | 消息通道微信公众号模块
# +-------------------------------------------------------------------
import os, sys
import time, base64
import re
import json
import requests
import traceback
import socket
import requests.packages.urllib3.util.connection as urllib3_cn
from requests.packages import urllib3
from typing import Optional, Union, List, Dict, Any
from .util import write_push_log, get_test_msg, read_file, public_http_post
from mod.base.push_mod import WxAccountMsg, SenderConfig
from mod.base import json_response
# 关闭警告
urllib3.disable_warnings()
class WeChatAccountMsg:
USER_PATH = '/www/server/panel/data/userInfo.json'
need_refresh_file = '/www/server/panel/data/mod_push_data/refresh_wechat_account.tip'
refresh_time = '/www/server/panel/data/mod_push_data/refresh_wechat_account_time.pl'
def __init__(self, *config_data):
if len(config_data) == 0:
self.config = None
elif len(config_data) == 1:
self.config = config_data[0]["data"]
else:
self.config = config_data[0]["data"]
self.config["users"] = [i["data"]['id'] for i in config_data]
self.config["users_nickname"] = [i["data"]['nickname'] for i in config_data]
try:
self.user_info = json.loads(read_file(self.USER_PATH))
except:
self.user_info = None
@classmethod
def get_user_info(cls) -> Optional[dict]:
try:
return json.loads(read_file(cls.USER_PATH))
except:
return None
@classmethod
def last_refresh(cls):
tmp = read_file(cls.refresh_time)
if not tmp:
last_refresh_time = 0
else:
try:
last_refresh_time = int(tmp)
except:
last_refresh_time = 0
return last_refresh_time
@staticmethod
def get_local_ip() -> str:
"""获取内网IP"""
import socket
s = None
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('8.8.8.8', 80))
ip = s.getsockname()[0]
return ip
except:
pass
finally:
if s is not None:
s.close()
return '127.0.0.1'
def send_msg(self, msg: WxAccountMsg) -> Optional[str]:
if self.user_info is None:
return '未获取到用户信息'
msg.set_ip_address(self.user_info["address"], self.get_local_ip())
template_id, msg_data = msg.to_send_data()
url = "https://www.bt.cn/api/v2/user/wx_web/send_template_msg_v3"
wx_account_ids = self.config["users"] if "users" in self.config else [self.config["id"], ]
data = {
"uid": self.user_info["uid"],
"access_key": self.user_info["access_key"],
"data": base64.b64encode(json.dumps(msg_data).encode('utf-8')).decode('utf-8'),
"wx_account_ids": base64.b64encode(json.dumps(wx_account_ids).encode('utf-8')).decode('utf-8'),
}
if template_id != "":
data["template_id"] = template_id
status = False
error = None
user_name = self.config["users_nickname"] if "users_nickname" in self.config else [self.config["nickname"], ]
try:
resp = public_http_post(url, data)
x = json.loads(resp)
if x["success"]:
status = True
else:
status = False
error = x["res"]
except:
error = traceback.format_exc()
write_push_log("微信公众号", status, msg.thing_type, user_name)
return error
@classmethod
def refresh_config(cls, force: bool = False):
if os.path.exists(cls.need_refresh_file):
force = True
os.remove(cls.need_refresh_file)
if force or cls.last_refresh() + 60 * 10 < time.time():
cls._get_by_web()
@classmethod
def _get_by_web(cls) -> Optional[List]:
user_info = cls.get_user_info()
url = "https://www.bt.cn/api/v2/user/wx_web/bound_wx_accounts"
data = {
"uid": user_info["uid"],
"access_key": user_info["access_key"],
"serverid": user_info["serverid"]
}
try:
data = json.loads(public_http_post(url, data))
if not data["success"]:
return None
except:
return None
cls._save_user_info(data["res"])
return data["res"]
@staticmethod
def _save_user_info(user_config_list: List[Dict[str, Any]]):
print(user_config_list)
user_config_dict = {i["hex"]: i for i in user_config_list}
remove_list = []
sc = SenderConfig()
for i in sc.config:
if i['sender_type'] != "wx_account":
continue
if i['data'].get("hex", None) in user_config_dict:
i['data'].update(user_config_dict[i['data']["hex"]])
user_config_dict.pop(i['data']["hex"])
else:
remove_list.append(i)
for r in remove_list:
sc.config.remove(r)
if user_config_dict: # 还有多的
for v in user_config_dict.values():
v["title"] = v["nickname"]
sc.config.append({
"id": sc.nwe_id(),
"used": True,
"sender_type": "wx_account",
"data": v
})
sc.save_config()
@classmethod
def unbind(cls, wx_account_uid: str):
user_info = cls.get_user_info()
if user_info is None:
return json_response(status=True, msg='未获取到用户绑定的信息')
url = "https://www.bt.cn/api/v2/user/wx_web/unbind_wx_accounts"
data = {
"uid": user_info["uid"],
"access_key": user_info["access_key"],
"serverid": user_info["serverid"],
"ids": str(wx_account_uid)
}
try:
datas = json.loads(public_http_post(url, data))
if datas["success"]:
return json_response(status=True, data=datas, msg="解绑成功")
else:
return json_response(status=False, data=datas, msg=datas["res"])
except:
return json_response(status=True, msg="链接云端失败")
@classmethod
def get_auth_url(cls):
user_info = cls.get_user_info()
if user_info is None:
return json_response(status=True, msg='未获取到用户绑定的信息')
url = "https://www.bt.cn/api/v2/user/wx_web/get_auth_url"
data = {
"uid": user_info["uid"],
"access_key": user_info["access_key"],
"serverid": user_info["serverid"],
}
try:
datas = json.loads(public_http_post(url, data))
if datas["success"]:
return json_response(status=True, data=datas)
else:
return json_response(status=False, data=datas, msg=datas["res"])
except:
return json_response(status=True, msg="链接云端失败")
def test_send_msg(self) -> Optional[str]:
test_msg = {
"msg_list": ['>配置状态:<font color=#20a53a>成功</font>\n\n']
}
test_task = get_test_msg("消息通道配置提醒")
res = self.send_msg(
test_task.to_wx_account_msg(test_msg, test_task.the_push_public_data()),
)
if res is None:
return None
return res
# class wx_account_msg:
# __module_name = None
# __default_pl = "{}/data/default_msg_channel.pl".format(panelPath)
# conf_path = '{}/data/wx_account_msg.json'.format(panelPath)
# user_info = None
#
# def __init__(self):
# try:
# self.user_info = json.loads(public.ReadFile("{}/data/userInfo.json".format(public.get_panel_path())))
# except:
# self.user_info = None
# self.__module_name = self.__class__.__name__.replace('_msg', '')
#
# def get_version_info(self, get):
# """
# 获取版本信息
# """
# data = {}
# data['ps'] = '宝塔微信公众号,用于接收面板消息推送'
# data['version'] = '1.0'
# data['date'] = '2022-08-15'
# data['author'] = '宝塔'
# data['title'] = '微信公众号'
# data['help'] = 'http://www.bt.cn'
# return data
#
# def get_local_ip(self):
# '''获取内网IP'''
# 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_config(self, get):
# """
# 微信公众号配置
# """
# if os.path.exists(self.conf_path):
# # 60S内不重复加载
# start_time = int(time.time())
# if os.path.exists("data/wx_account_msg.lock"):
# lock_time = 0
# try:
# lock_time = int(public.ReadFile("data/wx_account_msg.lock"))
# except:
# pass
# # 大于60S重新加载
# if start_time - lock_time > 60:
# public.run_thread(self.get_web_info2)
# public.WriteFile("data/wx_account_msg.lock", str(start_time))
# else:
# public.WriteFile("data/wx_account_msg.lock", str(start_time))
# public.run_thread(self.get_web_info2)
# data = json.loads(public.ReadFile(self.conf_path))
#
# if not 'list' in data: data['list'] = {}
#
# title = '默认'
# if 'res' in data and 'nickname' in data['res']: title = data['res']['nickname']
#
# data['list']['default'] = {'title': title, 'data': ''}
#
# data['default'] = self.__get_default_channel()
# return data
# else:
# public.run_thread(self.get_web_info2)
# return {"success": False, "res": "未获取到配置信息"}
#
# def set_config(self, get):
# """
# @设置默认值
# """
# if 'default' in get and get['default']:
# public.writeFile(self.__default_pl, self.__module_name)
#
# return public.returnMsg(True, '设置成功')
#
# def get_web_info(self, get):
# if self.user_info is None: return public.returnMsg(False, '未获取到用户绑定的信息')
# url = "https://www.bt.cn/api/v2/user/wx_web/info"
# data = {
# "uid": self.user_info["uid"],
# "access_key": self.user_info["access_key"],
# "serverid": self.user_info["serverid"]
# }
# try:
#
# datas = json.loads(public.httpPost(url, data))
#
# if datas["success"]:
# public.WriteFile(self.conf_path, json.dumps(datas))
# return public.returnMsg(True, datas)
# else:
# public.WriteFile(self.conf_path, json.dumps(datas))
# return public.returnMsg(False, datas)
# except:
# public.WriteFile(self.conf_path, json.dumps({"success": False, "res": "链接云端失败,请检查网络"}))
# return public.returnMsg(False, "链接云端失败,请检查网络")
#
# def unbind(self):
# if self.user_info is None:
# return public.returnMsg(False, '未获取到用户绑定的信息')
# url = "https://www.bt.cn/api/v2/user/wx_web/unbind"
# data = {
# "uid": self.user_info["uid"],
# "access_key": self.user_info["access_key"],
# "serverid": self.user_info["serverid"]
# }
# try:
#
# datas = json.loads(public.httpPost(url, data))
#
# if os.path.exists(self.conf_path):
# os.remove(self.conf_path)
#
# if datas["success"]:
# return public.returnMsg(True, datas)
# else:
# return public.returnMsg(False, datas)
# except:
# public.WriteFile(self.conf_path, json.dumps({"success": False, "res": "链接云端失败,请检查网络"}))
# return public.returnMsg(False, "链接云端失败,请检查网络")
#
# def get_web_info2(self):
# if self.user_info is None:
# return public.returnMsg(False, '未获取到用户绑定的信息')
# url = "https://www.bt.cn/api/v2/user/wx_web/info"
# data = {
# "uid": self.user_info["uid"],
# "access_key": self.user_info["access_key"],
# "serverid": self.user_info["serverid"]
# }
# try:
# datas = json.loads(public.httpPost(url, data))
# if datas["success"]:
# public.WriteFile(self.conf_path, json.dumps(datas))
# return public.returnMsg(True, datas)
# else:
# public.WriteFile(self.conf_path, json.dumps(datas))
# return public.returnMsg(False, datas)
# except:
# public.WriteFile(self.conf_path, json.dumps({"success": False, "res": "链接云端失败"}))
# return public.returnMsg(False, "链接云端失败")
#
# def get_send_msg(self, msg):
# """
# @name 处理md格式
# """
# try:
# import re
# title = '宝塔告警通知'
# if msg.find("####") >= 0:
# try:
# title = re.search(r"####(.+)", msg).groups()[0]
# except:
# pass
#
# msg = msg.replace("####", ">").replace("\n\n", "\n").strip()
# s_list = msg.split('\n')
#
# if len(s_list) > 3:
# s_title = s_list[0].replace(" ", "")
# s_list = s_list[3:]
# s_list.insert(0, s_title)
# msg = '\n'.join(s_list)
#
# s_list = []
# for msg_info in msg.split('\n'):
# reg = '<font.+>(.+)</font>'
# tmp = re.search(reg, msg_info)
# if tmp:
# tmp = tmp.groups()[0]
# msg_info = re.sub(reg, tmp, msg_info)
# s_list.append(msg_info)
# msg = '\n'.join(s_list)
# except:
# pass
# return msg, title
#
# def send_msg(self, msg):
# """
# 微信发送信息
# @msg 消息正文
# """
#
# if self.user_info is None:
# return public.returnMsg(False, '未获取到用户信息')
#
# if not isinstance(msg, str):
# return self.send_msg_v2(msg)
#
# msg, title = self.get_send_msg(msg)
# url = "https://www.bt.cn/api/v2/user/wx_web/send_template_msg_v2"
# datassss = {
# "first": {
# "value": "堡塔主机告警",
# },
# "keyword1": {
# "value": "内网IP " + self.get_local_ip() + "\n外网IP " + self.user_info[
# "address"] + " \n服务器别名 " + public.GetConfigValue("title"),
# },
# "keyword2": {
# "value": "堡塔主机告警",
# },
# "keyword3": {
# "value": msg,
# },
# "remark": {
# "value": "如有疑问,请联系宝塔客服",
# },
# }
# data = {
# "uid": self.user_info["uid"],
# "access_key": self.user_info["access_key"],
# "data": base64.b64encode(json.dumps(datassss).encode('utf-8')).decode('utf-8')
# }
#
# try:
# res = {}
# error, success = 0, 0
#
# x = json.loads(public.httpPost(url, data))
# conf = self.get_config(None)['list']
#
# # 立即刷新剩余次数
# public.run_thread(self.get_web_info2)
#
# res[conf['default']['title']] = 0
# if x['success']:
# res[conf['default']['title']] = 1
# success += 1
# else:
# error += 1
#
# try:
# public.write_push_log(self.__module_name, title, res)
# except:
# pass
#
# result = public.returnMsg(True, '发送完成,发送成功{},发送失败{}.'.format(success, error))
# result['success'] = success
# result['error'] = error
# return result
#
# except:
# print(public.get_error_info())
# return public.returnMsg(False, '微信消息发送失败。 --> {}'.format(public.get_error_info()))
#
# def push_data(self, data):
# if isinstance(data, dict):
# return self.send_msg(data['msg'])
# else:
# return self.send_msg_v2(data)
#
# def uninstall(self):
# if os.path.exists(self.conf_path):
# os.remove(self.conf_path)
#
# def send_msg_v2(self, msg):
# from push.base_push import WxAccountMsgBase, WxAccountMsg
# if self.user_info is None:
# return public.returnMsg(False, '未获取到用户信息')
#
# if isinstance(msg, public.dict_obj):
# msg = getattr(msg, "msg", "测试信息")
# if len(msg) >= 20:
# return self.send_msg(msg)
#
# if isinstance(msg, str):
# the_msg = WxAccountMsg.new_msg()
# the_msg.thing_type = msg
# the_msg.msg = msg
# msg = the_msg
#
# if not isinstance(msg, WxAccountMsgBase):
# return public.returnMsg(False, '消息类型错误')
#
# msg.set_ip_address(self.user_info["address"], self.get_local_ip())
#
# template_id, msg_data = msg.to_send_data()
# url = "https://www.bt.cn/api/v2/user/wx_web/send_template_msg_v2"
# data = {
# "uid": self.user_info["uid"],
# "access_key": self.user_info["access_key"],
# "data": base64.b64encode(json.dumps(msg_data).encode('utf-8')).decode('utf-8'),
# }
# if template_id != "":
# data["template_id"] = template_id
#
# try:
# error, success = 0, 0
# resp = public.httpPost(url, data)
# x = json.loads(resp)
# conf = self.get_config(None)['list']
#
# # 立即刷新剩余次数
# public.run_thread(self.get_web_info2)
#
# res = {
# conf['default']['title']: 0
# }
# if x['success']:
# res[conf['default']['title']] = 1
# success += 1
# else:
# error += 1
#
# try:
# public.write_push_log(self.__module_name, msg.thing_type, res)
# except:
# pass
# result = public.returnMsg(True, '发送完成,发送成功{},发送失败{}.'.format(success, error))
# result['success'] = success
# result['error'] = error
# return result
#
# except:
# return public.returnMsg(False, '微信消息发送失败。 --> {}'.format(public.get_error_info()))