Initial YakPanel commit
This commit is contained in:
168
mod/base/msg/__init__.py
Normal file
168
mod/base/msg/__init__.py
Normal file
@@ -0,0 +1,168 @@
|
||||
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 .tg_msg import TgMsg
|
||||
from .manager import SenderManager
|
||||
from .util import read_file,write_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
|
||||
# else:
|
||||
# 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
|
||||
|
||||
# sms 取消自动添加
|
||||
# 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": "weixin" 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": "mail",
|
||||
"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": "feishu" 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": "dingding" if "title" not in dingding_data else dingding_data["title"]
|
||||
},
|
||||
"original": True
|
||||
})
|
||||
|
||||
# tg
|
||||
if os.path.exists(panel_data_path + "/tg_bot.json"):
|
||||
try:
|
||||
tg_data = json.loads(read_file(panel_data_path + "/tg_bot.json"))
|
||||
except:
|
||||
tg_data = None
|
||||
|
||||
if isinstance(tg_data, dict) and "bot_token" in tg_data:
|
||||
sc.config.append({
|
||||
"id": sc.nwe_id(),
|
||||
"used": True,
|
||||
"sender_type": "tg",
|
||||
"data": {
|
||||
"my_id": tg_data["my_id"],
|
||||
"bot_token": tg_data["bot_token"],
|
||||
"title": "tg" if "title" not in tg_data else tg_data["title"],
|
||||
},
|
||||
"original": True
|
||||
})
|
||||
|
||||
sc.save_config()
|
||||
write_file(PUSH_DATA_PATH + "/update_sender.pl", "")
|
||||
BIN
mod/base/msg/__pycache__/sms_msg.cpython-314.pyc
Normal file
BIN
mod/base/msg/__pycache__/sms_msg.cpython-314.pyc
Normal file
Binary file not shown.
BIN
mod/base/msg/__pycache__/wx_account_msg.cpython-314.pyc
Normal file
BIN
mod/base/msg/__pycache__/wx_account_msg.cpython-314.pyc
Normal file
Binary file not shown.
156
mod/base/msg/dingding_msg.py
Normal file
156
mod/base/msg/dingding_msg.py
Normal file
@@ -0,0 +1,156 @@
|
||||
# coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | yakpanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 yakpanel(https://www.yakpanel.com) 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
|
||||
import public
|
||||
|
||||
# 关闭警告
|
||||
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 public.lang('DingTalk information is not correctly configured')
|
||||
|
||||
# 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 public.lang('The DingTalk configuration is incorrect, please reconfigure the DingTalk robot')
|
||||
|
||||
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": "Server notifications",
|
||||
"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("dingding", 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 public.lang('Incomplete information')
|
||||
|
||||
title = args["title"]
|
||||
if len(title) > 15:
|
||||
return public.lang('Note names cannot be longer than 15 characters')
|
||||
|
||||
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": ['>configuration state: <font color=#20a53a> Success </font>\n\n']
|
||||
}
|
||||
|
||||
test_task = get_test_msg("Message channel configuration reminders")
|
||||
|
||||
res = test_obj.send_msg(
|
||||
test_task.to_dingding_msg(test_msg, test_task.the_push_public_data()),
|
||||
"Message channel configuration reminders"
|
||||
)
|
||||
if res is None:
|
||||
return data
|
||||
|
||||
return res
|
||||
|
||||
def test_send_msg(self) -> Optional[str]:
|
||||
|
||||
test_msg = {
|
||||
"msg_list": ['>configuration state: <font color=#20a53a> Success </font>\n\n']
|
||||
}
|
||||
test_task = get_test_msg("Message channel configuration reminders")
|
||||
res = self.send_msg(
|
||||
test_task.to_dingding_msg(test_msg, test_task.the_push_public_data()),
|
||||
"Message channel configuration reminders"
|
||||
)
|
||||
if res is None:
|
||||
return None
|
||||
return res
|
||||
140
mod/base/msg/feishu_msg.py
Normal file
140
mod/base/msg/feishu_msg.py
Normal file
@@ -0,0 +1,140 @@
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | yakpanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 yakpanel(http://www.yakpanel.com) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: lx
|
||||
# | 消息通道飞书通知模块
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
import re
|
||||
import json
|
||||
import requests
|
||||
import traceback
|
||||
import socket
|
||||
import public
|
||||
|
||||
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 public.lang('Incomplete information')
|
||||
|
||||
title = args["title"]
|
||||
if len(title) > 15:
|
||||
return public.lang('Note names cannot be longer than 15 characters')
|
||||
|
||||
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": ['>configuration state: Success\n\n']
|
||||
}
|
||||
|
||||
test_task = get_test_msg("Message channel configuration reminders")
|
||||
|
||||
res = test_obj.send_msg(
|
||||
test_task.to_feishu_msg(test_msg, test_task.the_push_public_data()),
|
||||
"Message channel configuration reminders"
|
||||
)
|
||||
if res is None:
|
||||
return data
|
||||
|
||||
return res
|
||||
|
||||
def send_msg(self, msg: str, title: str) -> Optional[str]:
|
||||
"""
|
||||
飞书发送信息
|
||||
@msg 消息正文
|
||||
"""
|
||||
if not self.config:
|
||||
return public.lang('Feishu information is not configured correctly.')
|
||||
|
||||
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'>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("feishu", status, title)
|
||||
|
||||
return error
|
||||
|
||||
def test_send_msg(self) -> Optional[str]:
|
||||
test_msg = {
|
||||
"msg_list": ['>configuration state: <font color=#20a53a> Success </font>\n\n']
|
||||
}
|
||||
test_task = get_test_msg("Message channel configuration reminders")
|
||||
res = self.send_msg(
|
||||
test_task.to_feishu_msg(test_msg, test_task.the_push_public_data()),
|
||||
"Message channel configuration reminders"
|
||||
)
|
||||
if res is None:
|
||||
return None
|
||||
return res
|
||||
158
mod/base/msg/mail_msg.py
Normal file
158
mod/base/msg/mail_msg.py
Normal file
@@ -0,0 +1,158 @@
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | yakpanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 yakpanel(http://www.yakpanel.com) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: 沐落 <cjx@yakpanel.com>
|
||||
# | Author: lx
|
||||
# | 消息通道邮箱模块
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
import smtplib
|
||||
import traceback
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import formataddr
|
||||
from typing import Tuple, Union, Optional
|
||||
import public
|
||||
|
||||
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, "Incomplete information, there must be a sender and at least one receiver"
|
||||
|
||||
if "title" not in args:
|
||||
return False, "There is no necessary remark information"
|
||||
|
||||
title = args["title"]
|
||||
if len(title) > 15:
|
||||
return False, 'Note names cannot be longer than 15 characters'
|
||||
|
||||
send_data = args["send"]
|
||||
send = {}
|
||||
for i in ("qq_mail", "qq_stmp_pwd", "hosts", "port"):
|
||||
if i not in send_data:
|
||||
return False, "The sender configuration information is incomplete"
|
||||
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": ['>configuration state: Success<br>']
|
||||
}
|
||||
|
||||
test_task = get_test_msg("Message channel configuration reminders")
|
||||
|
||||
res = test_obj.send_msg(
|
||||
test_task.to_mail_msg(test_msg, test_task.the_push_public_data()),
|
||||
"Message channel configuration reminders"
|
||||
)
|
||||
if res is None or res.find("Failed to send mail to some recipients") != -1:
|
||||
return True, data
|
||||
|
||||
return False, res
|
||||
|
||||
def send_msg(self, msg: str, title: str):
|
||||
"""
|
||||
邮箱发送
|
||||
@msg 消息正文
|
||||
@title 消息标题
|
||||
"""
|
||||
if not self.config:
|
||||
return public.lang('Mailbox information is not configured correctly')
|
||||
|
||||
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
|
||||
server = None
|
||||
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
|
||||
|
||||
port = int(self.config['send']['port'])
|
||||
host = str(self.config['send']['hosts'])
|
||||
user = self.config['send']['qq_mail']
|
||||
pwd = self.config['send']['qq_stmp_pwd']
|
||||
|
||||
if port == 465:
|
||||
# SSL direct connection
|
||||
server = smtplib.SMTP_SSL(host, port, timeout=10)
|
||||
else:
|
||||
# Standard connection, possibly with STARTTLS
|
||||
server = smtplib.SMTP(host, port, timeout=10)
|
||||
try:
|
||||
# Attempt to upgrade to a secure connection
|
||||
server.starttls()
|
||||
except smtplib.SMTPNotSupportedError:
|
||||
# The server does not support STARTTLS, proceed with an insecure connection
|
||||
pass
|
||||
|
||||
server.login(user, pwd)
|
||||
server.sendmail(user, [email.strip(), ], data.as_string())
|
||||
success_list.append(email)
|
||||
except:
|
||||
err_msg = traceback.format_exc()
|
||||
# public.print_log(f"邮件发送失败: {err_msg}")
|
||||
error_list.append(email)
|
||||
error_msg_dict[email] = err_msg
|
||||
finally:
|
||||
if server:
|
||||
server.quit()
|
||||
|
||||
if not error_list and not success_list:
|
||||
return public.lang('The receiving mailbox is not configured')
|
||||
if not error_list:
|
||||
write_push_log("mail", True, title, success_list)
|
||||
return None
|
||||
if not success_list:
|
||||
write_push_log("mail", False, title, error_list)
|
||||
first_error_msg = list(error_msg_dict.values())[0]
|
||||
# 修复 IndexError
|
||||
return public.lang('Failed to send message, Recipient of failed to send:{}, Error: {}', error_list, first_error_msg)
|
||||
|
||||
write_mail_push_log(title, error_list, success_list)
|
||||
|
||||
return public.lang('Failed to send mail to some recipients, including:{}',error_list)
|
||||
|
||||
def test_send_msg(self) -> Optional[str]:
|
||||
test_msg = {
|
||||
"msg_list": ['>configuration state: <font color=#20a53a> Success </font>\n\n']
|
||||
}
|
||||
test_task = get_test_msg("Message channel configuration reminders")
|
||||
res = self.send_msg(
|
||||
test_task.to_mail_msg(test_msg, test_task.the_push_public_data()),
|
||||
"Message channel configuration reminders"
|
||||
)
|
||||
if res is None:
|
||||
return None
|
||||
return res
|
||||
|
||||
362
mod/base/msg/manager.py
Normal file
362
mod/base/msg/manager.py
Normal file
@@ -0,0 +1,362 @@
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from mod.base.push_mod import SenderConfig
|
||||
from .weixin_msg import WeiXinMsg
|
||||
from .mail_msg import MailMsg
|
||||
from .tg_msg import TgMsg
|
||||
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"
|
||||
self.init_default_sender()
|
||||
def set_sender_conf(self, get):
|
||||
|
||||
args = json.loads(get.sender_data.strip())
|
||||
|
||||
try:
|
||||
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=public.lang('The parameter is incorrect'))
|
||||
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=public.lang('Test send failed'))
|
||||
|
||||
|
||||
elif sender_type == "mail":
|
||||
_, data = MailMsg.check_args(args)
|
||||
if isinstance(data, str):
|
||||
return json_response(status=False, data=data, msg=public.lang('Test send failed'))
|
||||
|
||||
|
||||
elif sender_type == "tg":
|
||||
_, data = TgMsg.check_args(args)
|
||||
|
||||
if isinstance(data, str):
|
||||
return json_response(status=False, data=data, msg=data)
|
||||
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=public.lang('Test send failed'))
|
||||
|
||||
# 从文件读取并删除文件
|
||||
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=public.lang('Test send failed'))
|
||||
|
||||
elif sender_type == "dingding":
|
||||
data = DingDingMsg.check_args(args)
|
||||
if isinstance(data, str):
|
||||
return json_response(status=False, data=data, msg=public.lang('Test send failed'))
|
||||
else:
|
||||
return json_response(status=False, msg=public.lang('A type that is not supported by the current interface'))
|
||||
# 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
|
||||
)
|
||||
|
||||
|
||||
# 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:
|
||||
# public.print_log('000 -{}'.format(conf['sender_type']))
|
||||
# public.print_log('000 -{}'.format(sender_type))
|
||||
#
|
||||
# public.print_log('111 conf -{}'.format(conf['sender_type']))
|
||||
# public.print_log('111 -{}'.format(sender_type))
|
||||
#
|
||||
# public.print_log('222 conf -{}'.format(conf['data']['title']))
|
||||
# public.print_log('222 data -{}'.format(data['title']))
|
||||
#
|
||||
# public.print_log('333 conf -{}'.format(conf['id']))
|
||||
# public.print_log('333 -{}'.format(sender_id))
|
||||
|
||||
if existing_sender:
|
||||
return json_response(status=False, msg=public.lang('The same send configuration already exists and cannot be added repeatedly'))
|
||||
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=public.lang('Saved successfully'))
|
||||
except:
|
||||
public.print_log('Error:{}'.format(str(public.get_error_info())))
|
||||
|
||||
@staticmethod
|
||||
def change_sendr_used(get):
|
||||
try:
|
||||
sender_id = get.sender_id.strip()
|
||||
except (AttributeError, TypeError):
|
||||
return json_response(status=False, msg=public.lang('The parameter is incorrect'))
|
||||
|
||||
sender_config = SenderConfig()
|
||||
tmp = sender_config.get_by_id(sender_id)
|
||||
if tmp is None:
|
||||
return json_response(status=False, msg=public.lang('Corresponding sender not found'))
|
||||
tmp["used"] = not tmp["used"]
|
||||
|
||||
sender_config.save_config()
|
||||
|
||||
return json_response(status=True, msg=public.lang('Saved successfully'))
|
||||
|
||||
@staticmethod
|
||||
def remove_sender(get):
|
||||
try:
|
||||
sender_id = get.sender_id.strip()
|
||||
except (AttributeError, TypeError):
|
||||
return json_response(status=False, msg=public.lang('The parameter is incorrect'))
|
||||
|
||||
sender_config = SenderConfig()
|
||||
tmp = sender_config.get_by_id(sender_id)
|
||||
if tmp is None:
|
||||
return json_response(status=False, msg=public.lang('Corresponding sender not found'))
|
||||
sender_config.config.remove(tmp)
|
||||
sender_config.save_config()
|
||||
|
||||
return json_response(status=True, msg=public.lang('Successfully delete'))
|
||||
|
||||
@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=public.lang('The parameter is incorrect'))
|
||||
|
||||
res = []
|
||||
# WeChatAccountMsg.refresh_config(force=refresh)
|
||||
simple = ("weixin", "mail", "webhook", "feishu", "dingding", "tg")
|
||||
|
||||
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=public.lang('The parameter is incorrect'))
|
||||
|
||||
sender_config = SenderConfig()
|
||||
tmp = sender_config.get_by_id(sender_id)
|
||||
if tmp is None:
|
||||
return json_response(status=False, msg=public.lang('Corresponding sender not found'))
|
||||
|
||||
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 == "tg":
|
||||
sender_obj = TgMsg(tmp)
|
||||
# elif sender_type == "wx_account":
|
||||
# sender_obj = WeChatAccountMsg(tmp)
|
||||
else:
|
||||
return json_response(status=False, msg=public.lang('A type that is not supported by the current interface'))
|
||||
|
||||
res = sender_obj.test_send_msg()
|
||||
if isinstance(res, str):
|
||||
return json_response(status=False, data=res, msg=public.lang('Test send failed'))
|
||||
return json_response(status=True, msg=public.lang('The sending was successful'))
|
||||
|
||||
@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"] == "tg":
|
||||
tg_file = "{}/tg_bot.json".format(panel_data)
|
||||
write_file(tg_file, json.dumps({
|
||||
"my_id": sender_data["data"]["my_id"],
|
||||
"bot_token": sender_data["data"]["bot_token"],
|
||||
"title": sender_data["data"]["title"]
|
||||
}))
|
||||
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))
|
||||
|
||||
|
||||
def init_default_sender(self):
|
||||
|
||||
|
||||
import os,sys
|
||||
sys.path.insert(0, "/www/server/panel/mod/project/push")
|
||||
import msgconfMod
|
||||
sender_config = SenderConfig()
|
||||
sender_types = set(conf['sender_type'] for conf in sender_config.config)
|
||||
all_types = {"feishu", "dingding", "weixin", "mail", "webhook"} # 所有可能的类型
|
||||
|
||||
for sender_type in sender_types:
|
||||
type_senders = [conf for conf in sender_config.config if conf['sender_type'] == sender_type]
|
||||
|
||||
# 检查是否已有默认通道
|
||||
has_default = any(conf.get('original', False) for conf in type_senders)
|
||||
if has_default:
|
||||
continue
|
||||
|
||||
if len(type_senders) == 1:
|
||||
# 只有一个通道,设置为默认通道
|
||||
for conf in type_senders:
|
||||
get = public.dict_obj()
|
||||
get['sender_id'] = conf['id']
|
||||
get['sender_type'] = conf['sender_type']
|
||||
self.set_default_sender(get)
|
||||
else:
|
||||
# 有多个通道,根据添加时间设置默认通道
|
||||
sorted_senders = sorted(type_senders, key=lambda x: x['data'].get('create_time', ''))
|
||||
if sorted_senders:
|
||||
get = public.dict_obj()
|
||||
get['sender_id'] = sorted_senders[0]['id']
|
||||
get['sender_type'] = sorted_senders[0]['sender_type']
|
||||
self.set_default_sender(get)
|
||||
|
||||
# 检查没有通道的类型,并删除对应文件
|
||||
missing_types = all_types - sender_types
|
||||
for missing_type in missing_types:
|
||||
file_path = f"/www/server/panel/data/{missing_type}.json"
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
123
mod/base/msg/sms_msg.py
Normal file
123
mod/base/msg/sms_msg.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | yakpanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 yakpanel(http://www.yakpanel.com) 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 = 'https://www.yakpanel.com/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 'B' * 32,
|
||||
"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|YakPanel 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 = 'YakPanel 告警提醒'
|
||||
|
||||
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:
|
||||
import public
|
||||
api_root = public.GetConfigValue('home').rstrip('/') + '/api/wmsg'
|
||||
result = public_http_post(api_root + '/' + d_name, pdata)
|
||||
result = json.loads(result)
|
||||
return result
|
||||
except Exception:
|
||||
return traceback.format_exc()
|
||||
105
mod/base/msg/test.json
Normal file
105
mod/base/msg/test.json
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
252
mod/base/msg/tg_msg.py
Normal file
252
mod/base/msg/tg_msg.py
Normal file
@@ -0,0 +1,252 @@
|
||||
# coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | YakPanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: jose <zhw@yakpanel.com>
|
||||
# | 消息通道电报模块
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
import sys, os, re, public, json, requests
|
||||
|
||||
try:
|
||||
import telegram
|
||||
except:
|
||||
public.ExecShell("btpip install -I python-telegram-bot")
|
||||
import telegram
|
||||
|
||||
panelPath = "/www/server/panel"
|
||||
os.chdir(panelPath)
|
||||
sys.path.insert(0, panelPath + "/class/")
|
||||
from requests.packages import urllib3
|
||||
|
||||
# 关闭警告
|
||||
urllib3.disable_warnings()
|
||||
|
||||
from typing import Union, Optional
|
||||
|
||||
from mod.base.msg.util import write_push_log, get_test_msg
|
||||
|
||||
|
||||
class TgMsg:
|
||||
conf_path = "{}/data/tg_bot.json".format(panelPath)
|
||||
__tg_info = None
|
||||
__module_name = None
|
||||
__default_pl = "{}/data/default_msg_channel.pl".format(panelPath)
|
||||
|
||||
def __init__(self, conf):
|
||||
self.conf = conf
|
||||
self.bot_token = self.conf['data']['bot_token']
|
||||
self.my_id = self.conf['data']['my_id']
|
||||
|
||||
def get_version_info(self, get):
|
||||
"""
|
||||
获取版本信息
|
||||
"""
|
||||
data = {}
|
||||
data['ps'] = 'Use telegram bots to send receive panel notifications'
|
||||
data['version'] = '1.0'
|
||||
data['date'] = '2022-08-10'
|
||||
data['author'] = 'YakPanel'
|
||||
data['title'] = 'Telegram'
|
||||
data['help'] = 'http://www.yakpanel.com'
|
||||
return data
|
||||
|
||||
def get_config(self, get):
|
||||
"""
|
||||
获取tg配置
|
||||
"""
|
||||
data = {}
|
||||
if self.__tg_info:
|
||||
data = self.__tg_info
|
||||
|
||||
data['default'] = self.__get_default_channel()
|
||||
|
||||
return data
|
||||
|
||||
def set_config(self, get):
|
||||
"""
|
||||
设置tg bot
|
||||
@my_id tg id
|
||||
@bot_token 机器人token
|
||||
"""
|
||||
|
||||
if not hasattr(get, 'my_id') or not hasattr(get, 'bot_token'):
|
||||
return public.returnMsg(False, public.lang("Please fill in the complete information"))
|
||||
|
||||
title = 'Default'
|
||||
if hasattr(get, 'title'):
|
||||
title = get.title
|
||||
if len(title) > 7:
|
||||
return public.returnMsg(False, public.lang("Note name cannot exceed 7 characters"))
|
||||
|
||||
self.__tg_info = {"my_id": get.my_id.strip(), "bot_token": get.bot_token, "title": title}
|
||||
|
||||
try:
|
||||
info = public.get_push_info('Notification Configuration Reminder',
|
||||
['>Configuration status:<font color=#20a53a>successfully</font>\n\n'])
|
||||
ret = self.send_msg(info['msg'], get.my_id.strip(), get.bot_token)
|
||||
except:
|
||||
ret = self.send_msg('YakPanel alarm test', get.my_id.strip(), get.bot_token)
|
||||
if ret:
|
||||
|
||||
if 'default' in get and get['default']:
|
||||
public.writeFile(self.__default_pl, self.__module_name)
|
||||
|
||||
public.writeFile(self.conf_path, json.dumps(self.__tg_info))
|
||||
return public.returnMsg(True, public.lang("successfully set"))
|
||||
else:
|
||||
return ret
|
||||
|
||||
def get_send_msg(self, msg):
|
||||
"""
|
||||
@name 处理md格式
|
||||
"""
|
||||
try:
|
||||
title = 'YakPanel notifications'
|
||||
if msg.find("####") >= 0:
|
||||
try:
|
||||
title = re.search(r"####(.+)", msg).groups()[0]
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
info = public.get_push_info('Notification Configuration Reminder', ['>Send Content: ' + msg])
|
||||
msg = info['msg']
|
||||
except:
|
||||
pass
|
||||
return msg, title
|
||||
|
||||
async def send_msg_async(self, bot_token, chat_id, msg):
|
||||
"""
|
||||
tg发送信息
|
||||
@msg 消息正文
|
||||
"""
|
||||
|
||||
bot = telegram.Bot(token=bot_token)
|
||||
|
||||
await bot.send_message(chat_id=chat_id, text=msg, parse_mode='MarkdownV2')
|
||||
|
||||
# 外部也调用
|
||||
def send_msg(self, msg, title):
|
||||
"""
|
||||
tg发送信息
|
||||
@msg 消息正文
|
||||
"""
|
||||
|
||||
bot_token = self.bot_token
|
||||
chat_id = self.my_id
|
||||
|
||||
msg = msg.strip()
|
||||
msg = self.escape_markdown_v2(msg)
|
||||
import asyncio
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
try:
|
||||
loop.run_until_complete(self.send_msg_async(bot_token, chat_id, msg))
|
||||
write_push_log("Telegram", True, title)
|
||||
|
||||
public.print_log('message sent successfully!')
|
||||
loop.close()
|
||||
|
||||
return None
|
||||
|
||||
|
||||
except Exception as e:
|
||||
public.print_log('tg sent error:{}'.format(str(public.get_error_info())))
|
||||
write_push_log("Telegram", False, title)
|
||||
|
||||
return public.lang("Telegram Failed to send {}",e)
|
||||
|
||||
def escape_markdown_v2(self, text):
|
||||
"""
|
||||
Escape special characters for Telegram's MarkdownV2 mode.
|
||||
"""
|
||||
# 所有需要转义的 MarkdownV2 字符
|
||||
escape_chars = r'\_*[]()~`>#+-=|{}.!'
|
||||
for ch in escape_chars:
|
||||
text = text.replace(ch, '\\' + ch)
|
||||
return text
|
||||
|
||||
@classmethod
|
||||
def check_args(cls, args: dict) -> Union[dict, str]:
|
||||
|
||||
my_id = args.get('my_id', None).strip()
|
||||
bot_token = args.get('bot_token', None)
|
||||
if not my_id or not bot_token:
|
||||
return public.lang('Incomplete information')
|
||||
|
||||
title = args.get('title', 'Default')
|
||||
if len(title) > 15:
|
||||
return public.lang('Note name cannot exceed 15 characters')
|
||||
|
||||
data = {
|
||||
"my_id": my_id,
|
||||
"bot_token": bot_token,
|
||||
"title": title
|
||||
}
|
||||
conf = {
|
||||
"data": data
|
||||
}
|
||||
|
||||
# 调用TgMsg的方法
|
||||
tg = TgMsg(conf)
|
||||
try:
|
||||
|
||||
test_msg = {
|
||||
"msg_list": ['>configuration state: <font color=#20a53a> Success </font>\n\n']
|
||||
}
|
||||
test_task = get_test_msg("Message channel configuration reminders")
|
||||
ret = tg.send_msg(
|
||||
test_task.to_tg_msg(test_msg, test_task.the_push_public_data()),
|
||||
"Message channel configuration reminders"
|
||||
)
|
||||
|
||||
|
||||
except:
|
||||
ret = tg.send_msg('YakPanel alarm test', "Message channel configuration reminders")
|
||||
|
||||
# 测试失败也添加
|
||||
if ret:
|
||||
return False, ret
|
||||
else:
|
||||
return True, data
|
||||
|
||||
def test_send_msg(self) -> Optional[str]:
|
||||
test_msg = {
|
||||
"msg_list": ['>configuration state: <font color=#20a53a> Success </font>\n\n']
|
||||
}
|
||||
test_task = get_test_msg("Message channel configuration reminders")
|
||||
res = self.send_msg(
|
||||
test_task.to_tg_msg(test_msg, test_task.the_push_public_data()),
|
||||
"Message channel configuration reminders"
|
||||
)
|
||||
if res is None:
|
||||
return None
|
||||
return res
|
||||
|
||||
def push_data(self, data):
|
||||
"""
|
||||
@name 统一发送接口
|
||||
@data 消息内容
|
||||
{"module":"mail","title":"标题","msg":"内容","to_email":"xx@qq.com","sm_type":"","sm_args":{}}
|
||||
"""
|
||||
|
||||
return self.send_msg(data['msg'])
|
||||
|
||||
def __get_default_channel(self):
|
||||
"""
|
||||
@获取默认消息通道
|
||||
"""
|
||||
try:
|
||||
if public.readFile(self.__default_pl) == self.__module_name:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
def uninstall(self):
|
||||
if os.path.exists(self.conf_path):
|
||||
os.remove(self.conf_path)
|
||||
139
mod/base/msg/util.py
Normal file
139
mod/base/msg/util.py
Normal 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;"> Success </span>'
|
||||
else:
|
||||
status_str = '<span style="color:red;">Fail</span>'
|
||||
|
||||
if not user:
|
||||
user_str = '[ default ]'
|
||||
else:
|
||||
user_str = '[ {} ]'.format(",".join(user))
|
||||
|
||||
log = 'Title: [{}], Notification: [{}], Result: [{}], Addressee: {}'.format(title, module_name, status_str, user_str)
|
||||
public.WriteLog('Alarm notification', 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 = 'Title: [{}], notification method: [Email], send failed recipients: {}, send successful recipients: {}'.format(
|
||||
title, error_user_msg, success_user
|
||||
)
|
||||
public.WriteLog('Alarm notification', 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 = "The message channel was configured successfully"
|
||||
return msg
|
||||
|
||||
|
||||
def get_test_msg(title: str, task_name="Message channel configuration reminders") -> _TestMsgTask:
|
||||
"""
|
||||
用来测试的短息
|
||||
"""
|
||||
t = _TestMsgTask()
|
||||
|
||||
t.title = title
|
||||
t.template_name = task_name
|
||||
return t
|
||||
227
mod/base/msg/web_hook_msg.py
Normal file
227
mod/base/msg/web_hook_msg.py
Normal file
@@ -0,0 +1,227 @@
|
||||
# coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | yakpanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 yakpanel(http://www.yakpanel.com) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: baozi <baozi@yakpanel.com>
|
||||
# | 消息通道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
|
||||
import public
|
||||
# config = {
|
||||
# "name": "default",
|
||||
# "url": "https://www.yakpanel.com",
|
||||
# "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": "Yak-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"
|
||||
else:
|
||||
ssl_verify = bool(int(ssl_verify)) # 转换为布尔值
|
||||
|
||||
|
||||
real_data = {
|
||||
"title": title,
|
||||
"msg": msg,
|
||||
"type": push_type,
|
||||
}
|
||||
custom_parameter = self.config.get("custom_parameter", {})
|
||||
if not isinstance(custom_parameter, dict):
|
||||
custom_parameter = {} # 如果 custom_parameter 不是字典,则设置为空字典
|
||||
# 处理custom_parameter,将$1替换为real_data内容并递归解析
|
||||
custom_data = {}
|
||||
for k, v in 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 public.lang('The parameter is incorrect')
|
||||
|
||||
the_url = parse_url(url)
|
||||
if the_url.scheme is None or the_url.host is None:
|
||||
return"URL parsing error, which may not be a legitimate URL"
|
||||
|
||||
for i in (query, headers, custom_parameter):
|
||||
if not isinstance(i, dict):
|
||||
return public.lang('Parameter format error')
|
||||
|
||||
if body_type not in ('json', 'form_data', 'null'):
|
||||
return public.lang('The body type must be json,form data, or null')
|
||||
|
||||
if method not in ('GET', 'POST', 'PUT', 'PATCH'):
|
||||
return public.lang('The sending method is incorrect')
|
||||
|
||||
if ssl_verify not in (True, False, None):
|
||||
return public.lang('Verify if the SSL option is wrong')
|
||||
|
||||
title = title.strip()
|
||||
if title == "":
|
||||
return"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",
|
||||
"Message channel configuration reminders"
|
||||
)
|
||||
if res is None:
|
||||
return data
|
||||
|
||||
return res
|
||||
|
||||
def test_send_msg(self) -> Optional[str]:
|
||||
test_msg = {
|
||||
"msg_list": ['>configuration state: <font color=#20a53a> Success </font>\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",
|
||||
"Message channel configuration reminders"
|
||||
)
|
||||
if res is None:
|
||||
return None
|
||||
return res
|
||||
|
||||
131
mod/base/msg/weixin_msg.py
Normal file
131
mod/base/msg/weixin_msg.py
Normal file
@@ -0,0 +1,131 @@
|
||||
# coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | yakpanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 yakpanel(http://www.yakpanel.com) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: baozi <baozi@yakpanel.com>
|
||||
# | 消息通道邮箱模块
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
import re
|
||||
import json
|
||||
import requests
|
||||
import traceback
|
||||
import socket
|
||||
import public
|
||||
|
||||
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 public.lang('Incomplete information')
|
||||
|
||||
title = args["title"]
|
||||
if len(title) > 15:
|
||||
return public.lang('Note names cannot be longer than 15 characters')
|
||||
|
||||
data = {
|
||||
"url": args["url"],
|
||||
"title": title,
|
||||
}
|
||||
|
||||
test_obj = cls({"data": data, "id": None})
|
||||
test_msg = {
|
||||
"msg_list": ['>configuration state: <font color=#20a53a> Success </font>\n']
|
||||
}
|
||||
|
||||
test_task = get_test_msg("Message channel configuration reminders")
|
||||
|
||||
res = test_obj.send_msg(
|
||||
test_task.to_weixin_msg(test_msg, test_task.the_push_public_data()),
|
||||
"Message channel configuration reminders"
|
||||
)
|
||||
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 public.lang('WeChat information is not configured correctly')
|
||||
|
||||
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("weixin", status, title)
|
||||
return error
|
||||
|
||||
def test_send_msg(self) -> Optional[str]:
|
||||
test_msg = {
|
||||
"msg_list": ['>configuration state: <font color=#20a53a> Success </font>\n\n']
|
||||
}
|
||||
test_task = get_test_msg("Message channel configuration reminders")
|
||||
res = self.send_msg(
|
||||
test_task.to_weixin_msg(test_msg, test_task.the_push_public_data()),
|
||||
"Message channel configuration reminders",
|
||||
)
|
||||
if res is None:
|
||||
return None
|
||||
return res
|
||||
|
||||
|
||||
|
||||
|
||||
565
mod/base/msg/wx_account_msg.py
Normal file
565
mod/base/msg/wx_account_msg.py
Normal file
@@ -0,0 +1,565 @@
|
||||
# coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | yakpanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 yakpanel(http://www.yakpanel.com) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: baozi <baozi@yakpanel.com>
|
||||
# | 消息通道微信公众号模块
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
import os, sys
|
||||
import time, base64
|
||||
|
||||
import re
|
||||
import json
|
||||
import requests
|
||||
import traceback
|
||||
import socket
|
||||
import public
|
||||
|
||||
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 public.lang('No user information was obtained')
|
||||
if public.is_self_hosted():
|
||||
return public.lang('WeChat official account cloud features are not available in self-hosted mode.')
|
||||
|
||||
msg.set_ip_address(self.user_info["address"], self.get_local_ip())
|
||||
template_id, msg_data = msg.to_send_data()
|
||||
url = "https://wafapi2.yakpanel.com/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": 'B' * 32,
|
||||
"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("wx_account", 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()
|
||||
if user_info is None:
|
||||
return None
|
||||
if public.is_self_hosted():
|
||||
return None
|
||||
url = "https://wafapi2.yakpanel.com/api/v2/user/wx_web/bound_wx_accounts"
|
||||
data = {
|
||||
"uid": user_info["uid"],
|
||||
"access_key": 'B' * 32,
|
||||
"serverid": user_info["server_id"]
|
||||
}
|
||||
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=public.lang('The user binding information was not obtained'))
|
||||
url = "https://wafapi2.yakpanel.com/api/v2/user/wx_web/unbind_wx_accounts"
|
||||
data = {
|
||||
"uid": user_info["uid"],
|
||||
"access_key": 'B' * 32,
|
||||
"serverid": user_info["server_id"],
|
||||
"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=public.lang('The unbinding is successful'))
|
||||
else:
|
||||
return json_response(status=False, data=datas, msg=datas["res"])
|
||||
except:
|
||||
return json_response(status=True, msg=public.lang('Failed to link to the cloud'))
|
||||
|
||||
@classmethod
|
||||
def get_auth_url(cls):
|
||||
user_info = cls.get_user_info()
|
||||
if user_info is None:
|
||||
return json_response(status=True, msg=public.lang('The user binding information was not obtained'))
|
||||
if public.is_self_hosted():
|
||||
return json_response(status=False, msg=public.lang('WeChat official account cloud features are not available in self-hosted mode.'))
|
||||
url = "https://wafapi2.yakpanel.com/api/v2/user/wx_web/get_auth_url"
|
||||
data = {
|
||||
"uid": user_info["uid"],
|
||||
"access_key": 'B' * 32,
|
||||
"serverid": user_info["server_id"],
|
||||
}
|
||||
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=public.lang('Failed to link to the cloud'))
|
||||
|
||||
def test_send_msg(self) -> Optional[str]:
|
||||
test_msg = {
|
||||
"msg_list": ['>configuration state: <font color=#20a53a> Success </font>\n\n']
|
||||
}
|
||||
test_task = get_test_msg("Message channel configuration reminders")
|
||||
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'] = 'YakPanel 微信公众号,用于接收面板消息推送'
|
||||
# data['version'] = '1.0'
|
||||
# data['date'] = '2022-08-15'
|
||||
# data['author'] = 'YakPanel'
|
||||
# data['title'] = '微信公众号'
|
||||
# data['help'] = 'http://www.yakpanel.com'
|
||||
# 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, 'The user binding information was not obtained')
|
||||
# url = "https://wafapi2.yakpanel.com/api/v2/user/wx_web/info"
|
||||
# data = {
|
||||
# "uid": self.user_info["uid"],
|
||||
# "access_key": self.user_info["access_key"],
|
||||
# "serverid": self.user_info["server_id"]
|
||||
# }
|
||||
# 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, 'The user binding information was not obtained')
|
||||
# url = "https://wafapi2.yakpanel.com/api/v2/user/wx_web/unbind"
|
||||
# data = {
|
||||
# "uid": self.user_info["uid"],
|
||||
# "access_key": self.user_info["access_key"],
|
||||
# "serverid": self.user_info["server_id"]
|
||||
# }
|
||||
# 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, 'The user binding information was not obtained')
|
||||
# url = "https://wafapi2.yakpanel.com/api/v2/user/wx_web/info"
|
||||
# data = {
|
||||
# "uid": self.user_info["uid"],
|
||||
# "access_key": self.user_info["access_key"],
|
||||
# "serverid": self.user_info["server_id"]
|
||||
# }
|
||||
# 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 = 'YakPanel 告警通知'
|
||||
# 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=public.lang('\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=public.lang('\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://wafapi2.yakpanel.com/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": "如有疑问,请联系YakPanel 支持",
|
||||
# },
|
||||
# }
|
||||
# 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://wafapi2.yakpanel.com/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()))
|
||||
Reference in New Issue
Block a user