Initial YakPanel commit
This commit is contained in:
BIN
class/msg/__pycache__/sms_msg.cpython-314.pyc
Normal file
BIN
class/msg/__pycache__/sms_msg.cpython-314.pyc
Normal file
Binary file not shown.
BIN
class/msg/__pycache__/wx_account_msg.cpython-314.pyc
Normal file
BIN
class/msg/__pycache__/wx_account_msg.cpython-314.pyc
Normal file
Binary file not shown.
143
class/msg/dingding.html
Normal file
143
class/msg/dingding.html
Normal file
@@ -0,0 +1,143 @@
|
||||
<div class="conter_box box_dingding">
|
||||
<!-- <div style="padding-bottom: 12px; margin-bottom: 18px; border-bottom: #ccc 1px dashed;">
|
||||
<div class="flex" style="align-item: center; height: 32px;">
|
||||
<span class="tname" style="width: 99px; line-height: 30px; padding-right: 20px; text-align: right;"><i class="total_tips">?</i>设为默认</span>
|
||||
<div>
|
||||
<input class="btswitch btswitch-ios" id="default_setting" type="checkbox" />
|
||||
<label style="position: relative;top: 5px;" class="btswitch-btn" for="default_setting"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="bt-form">
|
||||
<div class="line">
|
||||
<span class="tname">Notify everyone</span>
|
||||
<div class="info-r" style="height:28px; margin-left:125px">
|
||||
<input class="btswitch btswitch-ios" id="panel_alert_all" type="checkbox" disabled="disabled" checked>
|
||||
<label style="position: relative;top: 5px;" title="Only supports notify everyone." class="btswitch-btn" for="panel_alert_all"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line">
|
||||
<span class="tname">Dingding URL</span>
|
||||
<div class="info-r">
|
||||
<textarea name="channel_dingding_value" class="bt-input-text mr5" type="text" placeholder="Please enter Dingding url" style="width: 300px; height:90px; line-height:20px"></textarea>
|
||||
</div>
|
||||
<button class="btn btn-success btn-sm dingding_submit" style="margin: 10px 0 0 125px;">Save</button>
|
||||
</div>
|
||||
<div class="line">
|
||||
<ul class="help-info-text c7">
|
||||
<li>Notify everyone, Immutable</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style type="text/css">
|
||||
.total_tips {
|
||||
border: 1px solid #cbcbcb;
|
||||
border-radius: 8px;
|
||||
color: #cbcbcb;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-family: arial;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
margin-right: 5px;
|
||||
text-align: center;
|
||||
width: 14px;
|
||||
}
|
||||
</style>
|
||||
<!--钉钉模块-->
|
||||
<script type="text/javascript">
|
||||
var dingding = {
|
||||
all_info: {},
|
||||
init: function () {
|
||||
var that = this;
|
||||
this.all_info = $('.alarm-view .bt-w-menu p.bgw').data('data'); //设置全局数据
|
||||
this.get_dingding_data();
|
||||
|
||||
// 设置默认
|
||||
$('#default_setting').change(function () {
|
||||
var _default = $(this).prop('checked');
|
||||
var _url = that.all_info.data.dingding_url;
|
||||
if (!_url) {
|
||||
layer.msg('Dingding URL is not configured', { icon: 2 })
|
||||
$(this).prop('checked', !_default);
|
||||
return
|
||||
}
|
||||
var loadTs = layer.msg('Dingding configuration is being set, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=set_default_channel&channel=dingding', { default: _default }, function (res) {
|
||||
layer.close(loadTs);
|
||||
layer.msg(res.msg, { icon: res.status ? 1 : 2 })
|
||||
if (res.status) that.refresh_data();
|
||||
});
|
||||
});
|
||||
|
||||
var showTips = ''
|
||||
$('.total_tips').hover(function(){
|
||||
showTips = setTimeout(function(){
|
||||
layer.tips('After setting as default, message notifications will be sent using this message channel first.', $('.total_tips'), {
|
||||
tips: [1, '#20a53a'],
|
||||
time: 0,
|
||||
success:function(layero,indexs){
|
||||
layero.css("left", $('.total_tips').offset().left - 10);
|
||||
}})
|
||||
},200)
|
||||
},function(){
|
||||
clearTimeout(showTips);
|
||||
layer.closeAll('tips');
|
||||
})
|
||||
},
|
||||
/**
|
||||
*@description 获取钉钉url,保存按钮添加事件
|
||||
*/
|
||||
get_dingding_data: function () {
|
||||
var that = this;
|
||||
var data = this.all_info.data;
|
||||
|
||||
if (data) {
|
||||
var url = data.dingding_url || '';
|
||||
var _default = data.hasOwnProperty('default') ? data.default : false
|
||||
|
||||
$('textarea[name=channel_dingding_value]').val(url);
|
||||
$('#default_setting').prop('checked', _default);
|
||||
}
|
||||
// 钉钉信息设置
|
||||
$('.dingding_submit').click(function () {
|
||||
that.set_submit_ding();
|
||||
})
|
||||
},
|
||||
/**
|
||||
*@description 设置钉钉url信息,保存按钮
|
||||
*/
|
||||
set_submit_ding: function () {
|
||||
var that = this;
|
||||
var _url = $('textarea[name=channel_dingding_value]').val();
|
||||
if (_url == '') return layer.msg('Please enter Dingding url', { icon: 2 })
|
||||
var loadT = layer.msg('Dingding is being set up, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=set_msg_config&name=dingding', { url: _url, atall: 'True' }, function (rdata) {
|
||||
layer.close(loadT);
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 })
|
||||
if (rdata.status) that.refresh_data();
|
||||
})
|
||||
},
|
||||
refresh_data: function () {
|
||||
var that = this
|
||||
$.post('/config?action=get_msg_configs', function (rdata) {
|
||||
$.each(rdata, function (key, item) {
|
||||
var $el = $('.alarm-view .bt-w-menu .men_' + key);
|
||||
if (item.data && item.data.default) {
|
||||
$el.html($el.text() + '<span class="show-default"></span>');
|
||||
} else {
|
||||
$el.find('span').remove();
|
||||
}
|
||||
$el.data('data', item);
|
||||
if (key === 'dingding') {
|
||||
that.all_info = item
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
224
class/msg/dingding_msg.py
Normal file
224
class/msg/dingding_msg.py
Normal file
@@ -0,0 +1,224 @@
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | YakPanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: 沐落 <cjx@yakpanel.com>
|
||||
# | Author: lx
|
||||
# | 消息通道邮箱模块
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
import os, sys, public, base64, json, re,requests
|
||||
import sys, os
|
||||
panelPath = "/www/server/panel"
|
||||
os.chdir(panelPath)
|
||||
sys.path.insert(0,panelPath + "/class/")
|
||||
import public, json, requests
|
||||
from requests.packages import urllib3
|
||||
# 关闭警告
|
||||
|
||||
urllib3.disable_warnings()
|
||||
import socket
|
||||
import requests.packages.urllib3.util.connection as urllib3_cn
|
||||
|
||||
class dingding_msg:
|
||||
|
||||
conf_path = 'data/dingding.json'
|
||||
__dingding_info = None
|
||||
__module_name = None
|
||||
__default_pl = "{}/data/default_msg_channel.pl".format(panelPath)
|
||||
def __init__(self):
|
||||
try:
|
||||
self.__dingding_info = json.loads(public.readFile(self.conf_path))
|
||||
if not 'dingding_url' in self.__dingding_info or not 'isAtAll' in self.__dingding_info or not 'user' in self.__dingding_info:
|
||||
self.__dingding_info = None
|
||||
except :
|
||||
self.__dingding_info = None
|
||||
self.__module_name = self.__class__.__name__.replace('_msg','')
|
||||
|
||||
def get_version_info(self,get):
|
||||
"""
|
||||
获取版本信息
|
||||
"""
|
||||
data = {}
|
||||
data['ps'] = 'Dingding is used to receive panel message push'
|
||||
data['version'] = '1.2'
|
||||
data['date'] = '2022-08-10'
|
||||
data['author'] = 'YakPanel'
|
||||
data['title'] = 'Dingding'
|
||||
data['help'] = 'http://www.yakpanel.com'
|
||||
return data
|
||||
|
||||
def get_config(self,get):
|
||||
"""
|
||||
获取钉钉配置
|
||||
"""
|
||||
data = {}
|
||||
if self.__dingding_info :
|
||||
data = self.__dingding_info
|
||||
|
||||
if not 'list' in data: data['list'] = {}
|
||||
|
||||
title = 'Default'
|
||||
if 'title' in data: title = data['title']
|
||||
|
||||
data['list']['default'] = {'title':title,'data':data['dingding_url']}
|
||||
data['default'] = self.__get_default_channel()
|
||||
|
||||
return data
|
||||
|
||||
def set_config(self,get):
|
||||
"""
|
||||
设置钉钉配置
|
||||
@url 钉钉URL
|
||||
@atall 默认@全体成员
|
||||
@user
|
||||
"""
|
||||
|
||||
if not hasattr(get, 'url') or not hasattr(get, 'atall'):
|
||||
return public.returnMsg(False, public.lang("Please fill in the complete information"))
|
||||
|
||||
user = []
|
||||
status = 1
|
||||
atall = False
|
||||
if 'status' in get: status = int(get.status)
|
||||
if 'user' in get: user = get.user.split('\n')
|
||||
|
||||
if 'atall' in get and get.atall == 'True':
|
||||
atall = True
|
||||
|
||||
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.__dingding_info = {"dingding_url": get.url.strip(),"isAtAll": atall, "user":user,"title":title}
|
||||
|
||||
try:
|
||||
info = public.get_push_info('Message channel configuration reminder',['>configuration status: <font color=#20a53a>Success</font>\n\n'])
|
||||
ret = self.send_msg(info['msg'])
|
||||
except:
|
||||
ret = self.send_msg('YakPanel alarm test')
|
||||
|
||||
if ret['status']:
|
||||
if 'default' in get and get['default']:
|
||||
public.writeFile(self.__default_pl, self.__module_name)
|
||||
|
||||
if ret['success'] <= 0:
|
||||
return public.returnMsg(False, public.lang("Failed to add, please check whether the URL is correct"))
|
||||
|
||||
public.writeFile(self.conf_path, json.dumps(self.__dingding_info))
|
||||
return public.returnMsg(True, public.lang("Notification set successfully"))
|
||||
else:
|
||||
return ret
|
||||
|
||||
|
||||
def get_send_msg(self,msg):
|
||||
"""
|
||||
@name 处理md格式
|
||||
"""
|
||||
try:
|
||||
title = 'YakPanel alarm notification'
|
||||
if msg.find("####") >= 0:
|
||||
try:
|
||||
title = re.search(r"####(.+)", msg).groups()[0]
|
||||
except:pass
|
||||
else:
|
||||
info = public.get_push_info('Alarm Mode Configuration Reminder',['>Send Content: ' + msg])
|
||||
msg = info['msg']
|
||||
except:pass
|
||||
return msg,title
|
||||
|
||||
def send_msg(self,msg,to_user = 'default'):
|
||||
"""
|
||||
钉钉发送信息
|
||||
@msg 消息正文
|
||||
"""
|
||||
|
||||
if not self.__dingding_info :
|
||||
return public.returnMsg(False, public.lang("DingTalk information is incorrectly configured."))
|
||||
|
||||
if isinstance(self.__dingding_info['user'],int):
|
||||
return public.returnMsg(False, public.lang("DingTalk configuration error, please reconfigure the DingTalk robot."))
|
||||
|
||||
at_info = ''
|
||||
for user in self.__dingding_info['user']:
|
||||
if re.match("^[0-9]{11,11}$",str(user)): at_info += '@'+user+' '
|
||||
|
||||
msg,title = self.get_send_msg(msg)
|
||||
|
||||
if at_info: msg = msg + '\n\n>' + at_info
|
||||
|
||||
data = {
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"title": "server notification",
|
||||
"text": msg
|
||||
},
|
||||
"at": {
|
||||
"atMobiles": self.__dingding_info['user'],
|
||||
"isAtAll": self.__dingding_info['isAtAll']
|
||||
}
|
||||
}
|
||||
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
|
||||
error,success = 0,0
|
||||
conf = self.get_config(None)['list']
|
||||
|
||||
res = {}
|
||||
for to_key in to_user.split(','):
|
||||
if not to_key in conf: continue
|
||||
try:
|
||||
allowed_gai_family_lib = urllib3_cn.allowed_gai_family
|
||||
def allowed_gai_family():
|
||||
family = socket.AF_INET
|
||||
return family
|
||||
urllib3_cn.allowed_gai_family = allowed_gai_family
|
||||
x = requests.post(url = conf[to_key]['data'], data = json.dumps(data),verify=False, headers=headers,timeout=10)
|
||||
urllib3_cn.allowed_gai_family=allowed_gai_family_lib
|
||||
|
||||
if x.json()["errcode"] == 0:
|
||||
success += 1
|
||||
res[conf[to_key]['title']] = 1
|
||||
else:
|
||||
error += 1
|
||||
res[conf[to_key]['title']] = 0
|
||||
except:
|
||||
error += 1
|
||||
res[conf[to_key]['title']] = 0
|
||||
try:
|
||||
public.write_push_log(self.__module_name,title,res)
|
||||
except:pass
|
||||
|
||||
ret = public.returnMsg(True,'Send completed, send successfully{}, send failed{}.'.format(success,error))
|
||||
ret['success'] = success
|
||||
ret['error'] = error
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
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)
|
||||
142
class/msg/feishu.html
Normal file
142
class/msg/feishu.html
Normal file
@@ -0,0 +1,142 @@
|
||||
<div class="conter_box box_feishu">
|
||||
<!-- <div style="padding-bottom: 12px; margin-bottom: 18px; border-bottom: #ccc 1px dashed;">
|
||||
<div class="flex" style="align-item: center; height: 32px;">
|
||||
<span class="tname" style="width: 99px; line-height: 30px; padding-right: 20px; text-align: right;"><i class="total_tips">?</i>设为默认</span>
|
||||
<div>
|
||||
<input class="btswitch btswitch-ios" id="default_setting" type="checkbox" />
|
||||
<label style="position: relative;top: 5px;" class="btswitch-btn" for="default_setting"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="bt-form">
|
||||
<div class="line">
|
||||
<span class="tname">Notify everyone</span>
|
||||
<div class="info-r" style="height:28px; margin-left:125px">
|
||||
<input class="btswitch btswitch-ios" id="panel_alert_all" type="checkbox" >
|
||||
<label style="position: relative;top: 5px;" title="Only supports notify everyone." class="btswitch-btn" for="panel_alert_all"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line">
|
||||
<span class="tname">Feishu/Lark URL</span>
|
||||
<div class="info-r">
|
||||
<textarea name="channel_feishu_value" class="bt-input-text mr5" type="text" placeholder="Please enter Feishu/Lark url" style="width: 300px; height:90px; line-height:20px"></textarea>
|
||||
</div>
|
||||
<button class="btn btn-success btn-sm feishu_submit" style="margin: 10px 0 0 125px;">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style type="text/css">
|
||||
.total_tips {
|
||||
border: 1px solid #cbcbcb;
|
||||
border-radius: 8px;
|
||||
color: #cbcbcb;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-family: arial;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
margin-right: 5px;
|
||||
text-align: center;
|
||||
width: 14px;
|
||||
}
|
||||
</style>
|
||||
<!--飞书模块-->
|
||||
<script type="text/javascript">
|
||||
var feishu = {
|
||||
all_info: {},
|
||||
init: function () {
|
||||
var that = this;
|
||||
this.all_info = $('.alarm-view .bt-w-menu p.bgw').data('data'); //设置全局数据
|
||||
this.get_feishu_data();
|
||||
|
||||
$('#panel_alert_all').attr('checked',this.all_info.data.isAtAll)
|
||||
|
||||
// 设置默认
|
||||
$('#default_setting').change(function () {
|
||||
var _default = $(this).prop('checked');
|
||||
var _url = that.all_info.data.feishu_url;
|
||||
if (!_url) {
|
||||
layer.msg('Feishu/Lark is not configured URL', { icon: 2 })
|
||||
$(this).prop('checked', !_default);
|
||||
return
|
||||
}
|
||||
var loadTs = layer.msg('Feishu/Lark configuration is being set, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=set_default_channel&channel=feishu', { default: _default }, function (res) {
|
||||
layer.close(loadTs);
|
||||
layer.msg(res.msg, { icon: res.status ? 1 : 2 })
|
||||
if (res.status) that.refresh_data();
|
||||
});
|
||||
});
|
||||
|
||||
var showTips = ''
|
||||
$('.total_tips').hover(function(){
|
||||
showTips = setTimeout(function(){
|
||||
layer.tips('After setting as default, message notifications will be sent using this message channel first.', $('.total_tips'), {
|
||||
tips: [1, '#20a53a'],
|
||||
time: 0,
|
||||
success:function(layero,indexs){
|
||||
layero.css("left", $('.total_tips').offset().left - 10);
|
||||
}})
|
||||
},200)
|
||||
},function(){
|
||||
clearTimeout(showTips)
|
||||
layer.closeAll('tips');
|
||||
})
|
||||
},
|
||||
/**
|
||||
*@description 获取飞书url,保存按钮添加事件
|
||||
*/
|
||||
get_feishu_data: function () {
|
||||
var that = this;
|
||||
var data = this.all_info.data;
|
||||
|
||||
if (data) {
|
||||
var url = data.feishu_url || '';
|
||||
var _default = data.hasOwnProperty('default') ? data.default : false
|
||||
|
||||
$('textarea[name=channel_feishu_value]').val(url);
|
||||
$('#default_setting').prop('checked', _default);
|
||||
}
|
||||
// 飞书信息设置
|
||||
$('.feishu_submit').click(function () {
|
||||
that.set_submit_ding();
|
||||
});
|
||||
},
|
||||
/**
|
||||
*@description 设置飞书url信息,保存按钮
|
||||
*/
|
||||
set_submit_ding: function () {
|
||||
var that = this;
|
||||
|
||||
var _url = $('textarea[name=channel_feishu_value]').val(),
|
||||
_isAll = $('#panel_alert_all').prop('checked');
|
||||
if (_url == '') return layer.msg('Please enter Feishu/Lark url', { icon: 2 })
|
||||
var loadT = layer.msg('Feishu/Lark is being set up, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=set_msg_config&name=feishu', { url: _url, atall: _isAll?'True':'False' }, function (rdata) {
|
||||
layer.close(loadT);
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 })
|
||||
if (rdata.status) that.refresh_data();
|
||||
})
|
||||
},
|
||||
refresh_data: function () {
|
||||
var that = this
|
||||
$.post('/config?action=get_msg_configs', function (rdata) {
|
||||
$.each(rdata, function (key, item) {
|
||||
var $el = $('.alarm-view .bt-w-menu .men_' + key);
|
||||
if (item.data && item.data.default) {
|
||||
$el.html($el.text() + '<span class="show-default"></span>');
|
||||
} else {
|
||||
$el.find('span').remove();
|
||||
}
|
||||
$('.alarm-view .bt-w-menu .men_' + key).data('data', item);
|
||||
if (key === 'feishu') {
|
||||
that.all_info = item
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
214
class/msg/feishu_msg.py
Normal file
214
class/msg/feishu_msg.py
Normal file
@@ -0,0 +1,214 @@
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | YakPanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: lx
|
||||
# | 消息通道飞书通知模块
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
import os, sys, public, json, requests
|
||||
import sys, os
|
||||
panelPath = '/www/server/panel'
|
||||
os.chdir(panelPath)
|
||||
sys.path.insert(0,panelPath + "/class/")
|
||||
import public, json, requests
|
||||
from requests.packages import urllib3
|
||||
# 关闭警告
|
||||
|
||||
urllib3.disable_warnings()
|
||||
import socket
|
||||
import requests.packages.urllib3.util.connection as urllib3_cn
|
||||
class feishu_msg:
|
||||
|
||||
conf_path = 'data/feishu.json'
|
||||
__feishu_info = None
|
||||
|
||||
__module_name = None
|
||||
__default_pl = "{}/data/default_msg_channel.pl".format(panelPath)
|
||||
|
||||
def __init__(self):
|
||||
try:
|
||||
self.__feishu_info = json.loads(public.readFile(self.conf_path))
|
||||
if not 'feishu_url' in self.__feishu_info or not 'isAtAll' in self.__feishu_info or not 'user' in self.__feishu_info:
|
||||
self.__feishu_info = None
|
||||
except :
|
||||
self.__feishu_info = None
|
||||
|
||||
self.__module_name = self.__class__.__name__.replace('_msg','')
|
||||
|
||||
def get_version_info(self,get):
|
||||
"""
|
||||
获取版本信息
|
||||
"""
|
||||
data = {}
|
||||
data['ps'] = 'Feishu is used to receive panel message push'
|
||||
data['version'] = '1.2'
|
||||
data['date'] = '2022-08-10'
|
||||
data['author'] = 'YakPanel'
|
||||
data['title'] = 'Feishu'
|
||||
data['help'] = 'http://www.yakpanel.com'
|
||||
return data
|
||||
|
||||
|
||||
def __get_default_channel(self):
|
||||
"""
|
||||
@获取默认消息通道
|
||||
"""
|
||||
try:
|
||||
if public.readFile(self.__default_pl) == self.__module_name:
|
||||
return True
|
||||
except:pass
|
||||
return False
|
||||
|
||||
def get_config(self,get):
|
||||
"""
|
||||
获取飞书配置
|
||||
"""
|
||||
data = {}
|
||||
if self.__feishu_info :
|
||||
data = self.__feishu_info
|
||||
|
||||
if not 'list' in data: data['list'] = {}
|
||||
|
||||
title = 'Default'
|
||||
if 'title' in data: title = data['title']
|
||||
|
||||
data['list']['default'] = {'title':title,'data':data['feishu_url']}
|
||||
|
||||
data['default'] = self.__get_default_channel()
|
||||
|
||||
return data
|
||||
|
||||
def set_config(self,get):
|
||||
"""
|
||||
设置飞书配置
|
||||
@url 飞书URL
|
||||
@atall 默认@全体成员
|
||||
@user
|
||||
"""
|
||||
if not hasattr(get, 'url'):
|
||||
return public.returnMsg(False, public.lang("Please fill in the complete information"))
|
||||
|
||||
isAtAll = True
|
||||
if hasattr(get, "atall"):
|
||||
if get.atall.lower() == "false":
|
||||
isAtAll = False
|
||||
|
||||
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.__feishu_info = {"feishu_url": get.url.strip(), "isAtAll": isAtAll, "user":1,"title":title}
|
||||
ret = self.send_msg('YakPanel alarm test')
|
||||
if ret['status']:
|
||||
if 'default' in get and get['default']:
|
||||
public.writeFile(self.__default_pl, self.__module_name)
|
||||
|
||||
if ret['success'] <= 0:
|
||||
return public.returnMsg(False, public.lang("Failed to add, please check whether the URL is correct"))
|
||||
|
||||
public.writeFile(self.conf_path, json.dumps(self.__feishu_info))
|
||||
return public.returnMsg(True, public.lang("successfully set"))
|
||||
else:
|
||||
return public.returnMsg(False, public.lang("Failed to add, please check whether the URL is correct"))
|
||||
|
||||
|
||||
def get_send_msg(self,msg):
|
||||
"""
|
||||
@name 处理md格式
|
||||
"""
|
||||
try:
|
||||
import re
|
||||
title = 'YakPanel warning notification'
|
||||
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[1:]
|
||||
s_list.insert(0,s_title)
|
||||
msg = '\n'.join(s_list)
|
||||
|
||||
reg = '<font.+>(.+)</font>'
|
||||
tmp = re.search(reg,msg)
|
||||
if tmp:
|
||||
tmp = tmp.groups()[0]
|
||||
msg = re.sub(reg,tmp,msg)
|
||||
except:pass
|
||||
return msg,title
|
||||
|
||||
def send_msg(self,msg,to_user = 'default'):
|
||||
"""
|
||||
飞书发送信息
|
||||
@msg 消息正文
|
||||
"""
|
||||
if not self.__feishu_info :
|
||||
return public.returnMsg(False, public.lang("Feishu information is not configured correctly."))
|
||||
|
||||
msg,title = self.get_send_msg(msg)
|
||||
if self.__feishu_info["isAtAll"]:
|
||||
msg += "<at userid='all'>Everyone</at>"
|
||||
|
||||
data = {
|
||||
"msg_type": "text",
|
||||
"content": {
|
||||
"text": msg
|
||||
}
|
||||
}
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
res = {}
|
||||
|
||||
error,success = 0,0
|
||||
conf = self.get_config(None)['list']
|
||||
|
||||
for to_key in to_user.split(','):
|
||||
if not to_key in conf: continue
|
||||
try:
|
||||
|
||||
allowed_gai_family_lib=urllib3_cn.allowed_gai_family
|
||||
def allowed_gai_family():
|
||||
family = socket.AF_INET
|
||||
return family
|
||||
urllib3_cn.allowed_gai_family = allowed_gai_family
|
||||
rdata = requests.post(url = conf[to_key]['data'], data = json.dumps(data),verify=False, headers=headers,timeout=10).json()
|
||||
urllib3_cn.allowed_gai_family=allowed_gai_family_lib
|
||||
|
||||
# x = requests.post(url = conf[to_key]['data'], data=json.dumps(data), headers=headers,verify=False,timeout=10)
|
||||
# rdata = x.json()
|
||||
|
||||
if "StatusCode" in rdata and rdata["StatusCode"] == 0:
|
||||
success += 1
|
||||
res[conf[to_key]['title']] = 1
|
||||
else:
|
||||
error += 1
|
||||
res[conf[to_key]['title']] = 0
|
||||
except:
|
||||
public.print_log(public.get_error_info())
|
||||
error += 1
|
||||
res[conf[to_key]['title']] = 0
|
||||
|
||||
try:
|
||||
public.write_push_log(self.__module_name,title,res)
|
||||
except:pass
|
||||
|
||||
ret = public.returnMsg(True,'Send completed, send successfully {}, send failed {}.'.format(success,error))
|
||||
ret['success'] = success
|
||||
ret['error'] = error
|
||||
|
||||
return ret
|
||||
|
||||
def push_data(self,data):
|
||||
return self.send_msg(data['msg'])
|
||||
|
||||
def uninstall(self):
|
||||
if os.path.exists(self.conf_path):
|
||||
os.remove(self.conf_path)
|
||||
253
class/msg/mail.html
Normal file
253
class/msg/mail.html
Normal file
@@ -0,0 +1,253 @@
|
||||
<div class="conter_box active box_mail">
|
||||
<div class="bt-form">
|
||||
<!-- <div style="padding-bottom: 12px; margin-bottom: 18px; border-bottom: #ccc 1px dashed;">
|
||||
<div class="flex" style="align-item: center; height: 32px;">
|
||||
<span class="tname" style="width: 99px; line-height: 30px; padding-right: 20px; text-align: right;"><i class="total_tips">?</i>设为默认</span>
|
||||
<div>
|
||||
<input class="btswitch btswitch-ios" id="default_setting" type="checkbox" />
|
||||
<label style="position: relative;top: 5px;" class="btswitch-btn" for="default_setting"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="line">
|
||||
<div class="tab-nav recipient_nav relative">
|
||||
<span class="on" data-ctype="0" style="line-height: 30px;">Recipient setting</span>
|
||||
<span data-ctype="1" style="line-height: 30px;">Sender setting</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line recipient_view">
|
||||
<div class="line relative">
|
||||
<textarea name="recipient_textarea" class="bt-input-text mr5" type="text" style="width: 300px; height:120px; line-height:20px"></textarea>
|
||||
<div class="placeholder c9 reci_tips" style="position: absolute;top: 25px;left: 25px; display:none">Fill in one email address per line, for example:<br>xxx@163.com<br>xxx@qq.com</div>
|
||||
</div>
|
||||
<button class="btn btn-success btn-sm recipient_submit" style="/* margin-left: 100px; */">Save</button>
|
||||
</div>
|
||||
<div class="line sender_view" style="display:none">
|
||||
<div class="line">
|
||||
<span class="tname">Sender Email</span>
|
||||
<div class="info-r">
|
||||
<input name="sender_mail_value" class="bt-input-text mr5" type="text" style="width: 300px">
|
||||
</div>
|
||||
</div>
|
||||
<div class="line">
|
||||
<span class="tname">SMTP Password</span>
|
||||
<div class="info-r">
|
||||
<input name="sender_mail_password" class="bt-input-text mr5" type="password" style="width: 300px">
|
||||
</div>
|
||||
</div>
|
||||
<div class="line">
|
||||
<span class="tname">SMTP Server</span>
|
||||
<div class="info-r">
|
||||
<input name="sender_mail_server" class="bt-input-text mr5" type="text" style="width: 300px">
|
||||
</div>
|
||||
</div>
|
||||
<div class="line">
|
||||
<span class="tname">SMTP Port</span>
|
||||
<div class="info-r">
|
||||
<input name="sender_mail_port" class="bt-input-text mr5" type="text" style="width: 300px">
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-success btn-sm sender_submit" style="margin-left: 125px;">Save</button>
|
||||
<ul class="help-info-text c7">
|
||||
<li>Recommended use port 465, and the protocol is SSL/TLS</li>
|
||||
<li>Port 25 is SMTP protocol, port 587 is STARTTLS protocol</li>
|
||||
<li>Not support Gmail, Outlook, Yahoo</li>
|
||||
<li><a href="https://www.yakpanel.com/bbs/thread-71298-1-1.html" target="_blank" class="btlink">Tutorial</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<style type="text/css">
|
||||
.total_tips {
|
||||
border: 1px solid #cbcbcb;
|
||||
border-radius: 8px;
|
||||
color: #cbcbcb;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-family: arial;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
margin-right: 5px;
|
||||
text-align: center;
|
||||
width: 14px;
|
||||
}
|
||||
</style>
|
||||
<!--邮箱模块-->
|
||||
<script type="text/javascript">
|
||||
var mail = {
|
||||
all_mail_info: {},
|
||||
init: function () {
|
||||
var that = this;
|
||||
this.all_mail_info = $('.alarm-view .bt-w-menu p.bgw').data('data'); //设置全局数据
|
||||
this.gat_info();
|
||||
this.init_default();
|
||||
// 选项卡切换时
|
||||
$('.recipient_nav span').click(function () {
|
||||
var _type = $(this).attr('data-ctype');
|
||||
$(this).addClass('on').siblings().removeClass('on')
|
||||
switch (_type) {
|
||||
case '0':
|
||||
$('.recipient_view').show();
|
||||
$('.sender_view').hide();
|
||||
break;
|
||||
case '1':
|
||||
$('.recipient_view').hide();
|
||||
$('.sender_view').show();
|
||||
that.get_sender_data();
|
||||
break;
|
||||
}
|
||||
})
|
||||
// 收件者保存按钮
|
||||
$('.recipient_submit').click(function () {
|
||||
that.recipient_submit();
|
||||
})
|
||||
// 发送者信息设置
|
||||
$('.sender_submit').click(function () {
|
||||
that.sender_submit();
|
||||
})
|
||||
|
||||
// 设置默认
|
||||
$('#default_setting').change(function () {
|
||||
var _default = $(this).prop('checked');
|
||||
var _send = that.all_mail_info.data.send;
|
||||
if (!_send) {
|
||||
layer.msg('Email sender settings not configured', { icon: 2 })
|
||||
$(this).prop('checked', !_default);
|
||||
return
|
||||
}
|
||||
var loadTs = layer.msg('Setting up Email configuration, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=set_default_channel&channel=mail', { default: _default }, function (res) {
|
||||
layer.close(loadTs);
|
||||
layer.msg(res.msg, { icon: res.status ? 1 : 2 })
|
||||
if (res.status) that.refresh_data();
|
||||
});
|
||||
});
|
||||
|
||||
var showTips = ''
|
||||
$('.total_tips').hover(function(){
|
||||
showTips = setTimeout(function(){
|
||||
layer.tips('After setting as default, message notifications will be sent using this message channel first.', $('.total_tips'), {
|
||||
tips: [1, '#20a53a'],
|
||||
time: 0,
|
||||
success:function(layero,indexs){
|
||||
layero.css("left", $('.total_tips').offset().left - 10);
|
||||
}})
|
||||
},200)
|
||||
},function(){
|
||||
clearTimeout(showTips)
|
||||
layer.closeAll('tips');
|
||||
})
|
||||
},
|
||||
/**
|
||||
*@description 获取邮箱信息、设置收件者提示语事件
|
||||
*/
|
||||
gat_info: function () {
|
||||
var _tips = $('textarea[name=recipient_textarea]');
|
||||
var msg = ''
|
||||
if (!$.isEmptyObject(this.all_mail_info['data']['receive'])) {
|
||||
msg = mail.all_mail_info['data']['receive'] ? mail.all_mail_info['data']['receive'].join('\n') : ''
|
||||
}
|
||||
_tips.html(msg)
|
||||
// 设置收件者tips
|
||||
if (_tips.val() == '') $('.reci_tips.placeholder').show();
|
||||
$('.placeholder').click(function () { $(this).hide().siblings('textarea').focus() })
|
||||
_tips.focus(function () {
|
||||
$('.reci_tips.placeholder').hide()
|
||||
})
|
||||
_tips.blur(function () {
|
||||
_tips.val() == '' ? $('.reci_tips.placeholder').show() : $('.reci_tips.placeholder').hide()
|
||||
});
|
||||
},
|
||||
|
||||
init_default: function () {
|
||||
var data = this.all_mail_info.data;
|
||||
if (!$.isEmptyObject(data)) {
|
||||
var _default = data.hasOwnProperty('default') ? data.default : false;
|
||||
$('#default_setting').prop('checked', _default);
|
||||
}
|
||||
},
|
||||
/**
|
||||
*@description 设置发送者信息
|
||||
*/
|
||||
get_sender_data: function () {
|
||||
var that = this;
|
||||
var data = this.all_mail_info.data;
|
||||
|
||||
if (!$.isEmptyObject(data) && !$.isEmptyObject(data.send)) {
|
||||
var send = data.send;
|
||||
|
||||
var mail_ = send.qq_mail || '',
|
||||
stmp_pwd_ = send.qq_stmp_pwd || '',
|
||||
hosts_ = send.hosts || '',
|
||||
port_ = send.port || '';
|
||||
|
||||
$('input[name=sender_mail_value]').val(mail_)
|
||||
$('input[name=sender_mail_password]').val(stmp_pwd_)
|
||||
$('input[name=sender_mail_server]').val(hosts_)
|
||||
$('input[name=sender_mail_port]').val(port_)
|
||||
} else {
|
||||
$('input[name=sender_mail_port]').val('465')
|
||||
}
|
||||
},
|
||||
/**
|
||||
*@description 设置收件者邮箱,保存按钮
|
||||
*/
|
||||
recipient_submit: function () {
|
||||
var that = this;
|
||||
var reci_ = $('textarea[name=recipient_textarea]').val();
|
||||
var loadTs = layer.msg('Please wait while the recipient email is being set...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=set_msg_config&name=mail', { mails: reci_ }, function (res) {
|
||||
layer.close(loadTs);
|
||||
layer.msg(res.msg, { icon: res.status ? 1 : 2 })
|
||||
if (res.status) that.refresh_data();
|
||||
})
|
||||
},
|
||||
/**
|
||||
*@description 设置发送者邮箱,保存按钮
|
||||
*/
|
||||
sender_submit: function () {
|
||||
var that = this;
|
||||
var _email = $('input[name=sender_mail_value]').val(),
|
||||
_passW = $('input[name=sender_mail_password]').val(),
|
||||
_server = $('input[name=sender_mail_server]').val(),
|
||||
_port = $('input[name=sender_mail_port]').val();
|
||||
|
||||
if (_email == '') return layer.msg('Email address cannot be empty!', { icon: 2 });
|
||||
if (_passW == '') return layer.msg('STMP password cannot be empty!', { icon: 2 });
|
||||
if (_server == '') return layer.msg('STMP server address cannot be empty!', { icon: 2 });
|
||||
if (_port == '') return layer.msg('Please enter valid port number', { icon: 2 });
|
||||
|
||||
var loadTs = layer.msg('The Email notification is being generated, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=set_msg_config&name=mail', {
|
||||
send: 1,
|
||||
qq_mail: _email,
|
||||
qq_stmp_pwd: _passW,
|
||||
hosts: _server,
|
||||
port: _port
|
||||
}, function (rdata) {
|
||||
layer.close(loadTs);
|
||||
if (rdata.status) that.refresh_data();
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 });
|
||||
});
|
||||
},
|
||||
refresh_data: function () {
|
||||
var that = this
|
||||
$.post('/config?action=get_msg_configs', function (rdata) {
|
||||
$.each(rdata, function (key, item) {
|
||||
var $el = $('.alarm-view .bt-w-menu .men_' + key);
|
||||
if (item.data && item.data.default) {
|
||||
$el.html($el.text() + '<span class="show-default"></span>');
|
||||
} else {
|
||||
$el.find('span').remove();
|
||||
}
|
||||
$('.alarm-view .bt-w-menu .men_' + key).data('data', item);
|
||||
if (key === 'mail') {
|
||||
that.all_mail_info = item;
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
229
class/msg/mail_msg.py
Normal file
229
class/msg/mail_msg.py
Normal file
@@ -0,0 +1,229 @@
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | YakPanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: 沐落 <cjx@yakpanel.com>
|
||||
# | Author: lx
|
||||
# | 消息通道邮箱模块
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
import os, sys, public, base64, json, re
|
||||
import sys, os
|
||||
panelPath = "/www/server/panel"
|
||||
os.chdir(panelPath)
|
||||
sys.path.insert(0,panelPath + "/class/")
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import formataddr
|
||||
|
||||
class mail_msg:
|
||||
__module_name = None
|
||||
__default_pl = "{}/data/default_msg_channel.pl".format(panelPath)
|
||||
|
||||
__mail_send_conf = '/www/server/panel/data/stmp_mail.json'
|
||||
__mail_receive_conf = '/www/server/panel/data/mail_list.json'
|
||||
__mail_config = None
|
||||
def __init__(self):
|
||||
self.__mail_config = {}
|
||||
if os.path.exists('data/stmp_mail.json'):
|
||||
self.__mail_config['send'] = json.loads(public.readFile(self.__mail_send_conf))
|
||||
|
||||
if os.path.exists('data/mail_list.json'):
|
||||
self.__mail_config['receive'] = json.loads(public.readFile(self.__mail_receive_conf))
|
||||
|
||||
if not 'send' in self.__mail_config: self.__mail_config['send'] = {}
|
||||
if not 'receive' in self.__mail_config: self.__mail_config['receive'] = {}
|
||||
|
||||
if 'qq_mail' not in self.__mail_config['send'] or 'qq_stmp_pwd' not in self.__mail_config['send'] or 'hosts' not in self.__mail_config['send']: self.__mail_config = None
|
||||
|
||||
self.__module_name = self.__class__.__name__.replace('_msg','')
|
||||
|
||||
def __get_default_channel(self):
|
||||
"""
|
||||
@获取默认消息通道
|
||||
"""
|
||||
try:
|
||||
if public.readFile(self.__default_pl) == self.__module_name:
|
||||
return True
|
||||
except:pass
|
||||
return False
|
||||
|
||||
def get_version_info(self,get):
|
||||
"""
|
||||
获取版本信息
|
||||
"""
|
||||
data = {}
|
||||
data['ps'] = 'Email is used to receive panel message push'
|
||||
data['version'] = '1.1'
|
||||
data['date'] = '2022-08-10'
|
||||
data['author'] = 'YakPanel'
|
||||
data['title'] = 'Email'
|
||||
data['help'] = 'http://www.yakpanel.com'
|
||||
return data
|
||||
|
||||
def get_config(self,get):
|
||||
"""
|
||||
获取QQ邮箱配置
|
||||
"""
|
||||
data = {}
|
||||
if self.__mail_config:
|
||||
data = self.__mail_config
|
||||
if "send" in data:
|
||||
if not os.path.exists(self.__mail_send_conf):
|
||||
public.writeFile(self.__mail_send_conf,json.dumps(data["send"]))
|
||||
if "receive" in data:
|
||||
if not os.path.exists(self.__mail_receive_conf):
|
||||
public.writeFile(self.__mail_receive_conf, json.dumps(data["receive"]))
|
||||
|
||||
data['default'] = self.__get_default_channel()
|
||||
return data
|
||||
|
||||
def set_config(self,get):
|
||||
"""
|
||||
设置邮箱配置
|
||||
@send 带有此参数表示设置发送者设置
|
||||
@qq_mail 发送者邮箱
|
||||
@qq_stmp_pwd 发送者密码
|
||||
@hosts 发送stmp
|
||||
@发送端口port
|
||||
|
||||
@mails 接收者配置,一行一个(如:111@qq.com\n222@qq.com)
|
||||
"""
|
||||
|
||||
if not self.__mail_config:
|
||||
self.__mail_config = {'send':{},'receive':[]}
|
||||
|
||||
if hasattr(get, 'send'):
|
||||
if not hasattr(get, 'qq_mail') or not hasattr(get, 'qq_stmp_pwd') or not hasattr(get, 'hosts') or not hasattr(get, 'port'): return public.returnMsg(False, public.lang("Please fill in the complete information"))
|
||||
mail_config = {
|
||||
"qq_mail": get.qq_mail.strip(),
|
||||
"qq_stmp_pwd": get.qq_stmp_pwd.strip(),
|
||||
"hosts": get.hosts.strip(),
|
||||
"port": get.port
|
||||
}
|
||||
self.__mail_config['send'] = mail_config
|
||||
|
||||
else:
|
||||
mails = ''
|
||||
if hasattr(get, 'mails'):
|
||||
mails = get.mails.strip()
|
||||
|
||||
arrs = []
|
||||
for mail in mails.split('\n'):
|
||||
if not mail.strip(): continue
|
||||
arrs.append(mail.strip())
|
||||
|
||||
self.__mail_config['receive'] = arrs
|
||||
|
||||
#首次配置同步接收者配置
|
||||
receive_list = self.__mail_config['receive']
|
||||
mail_address = self.__mail_config['send']['qq_mail']
|
||||
if not mail_address in receive_list and len(receive_list) == 0:
|
||||
if not 'receive' in self.__mail_config:
|
||||
self.__mail_config['receive'] = []
|
||||
self.__mail_config['receive'].append(mail_address)
|
||||
|
||||
ret = self.send_msg("YakPanel 测试邮件")
|
||||
if ret['status']:
|
||||
|
||||
if ret['success'] <= 0:
|
||||
return public.returnMsg(False, public.lang("Sending failed, please check whether the sender configuration or receiver information is correct."))
|
||||
|
||||
public.writeFile(self.__mail_send_conf, json.dumps(self.__mail_config['send']))
|
||||
public.writeFile(self.__mail_receive_conf,json.dumps(self.__mail_config['receive']))
|
||||
|
||||
return public.returnMsg(True, public.lang("successfully set"))
|
||||
else:
|
||||
return ret
|
||||
|
||||
|
||||
def get_send_msg(self,msg):
|
||||
"""
|
||||
@name 处理md格式
|
||||
"""
|
||||
try:
|
||||
title = None
|
||||
if msg.find("####") >= 0:
|
||||
try:
|
||||
title = re.search(r"####(.+)\n", msg).groups()[0]
|
||||
except:pass
|
||||
msg = msg.replace("\n\n","<br>").strip()
|
||||
|
||||
except:pass
|
||||
return msg,title
|
||||
|
||||
def send_msg(self,msg , title = 'YakPanel panel message push',to_email = None):
|
||||
"""
|
||||
邮箱发送
|
||||
@msg 消息正文
|
||||
@title 消息标题
|
||||
@to_email 发送给谁,默认发送所有人
|
||||
"""
|
||||
if not self.__mail_config :
|
||||
return public.returnMsg(False, public.lang("Email information is not configured correctly."))
|
||||
|
||||
if not 'port' in self.__mail_config['send']: self.__mail_config['send']['port'] = 465
|
||||
|
||||
receive_list = []
|
||||
if to_email:
|
||||
for x in to_email.split(','):
|
||||
receive_list.append(x)
|
||||
else:
|
||||
receive_list = self.__mail_config['receive']
|
||||
|
||||
res = {}
|
||||
ret_msg = {}
|
||||
error ,sucess,total = 0,0,0
|
||||
msg,n_title = self.get_send_msg(msg)
|
||||
if n_title: title = n_title
|
||||
|
||||
for email in receive_list:
|
||||
if not email: continue
|
||||
|
||||
try:
|
||||
data = MIMEText(msg, 'html', 'utf-8')
|
||||
data['From'] = formataddr([self.__mail_config['send']['qq_mail'], self.__mail_config['send']['qq_mail']])
|
||||
data['To'] = formataddr([self.__mail_config['send']['qq_mail'], email.strip()])
|
||||
data['Subject'] = title
|
||||
if int(self.__mail_config['send']['port']) == 465:
|
||||
server = smtplib.SMTP_SSL(str(self.__mail_config['send']['hosts']), str(self.__mail_config['send']['port']))
|
||||
else:
|
||||
server = smtplib.SMTP(str(self.__mail_config['send']['hosts']), str(self.__mail_config['send']['port']))
|
||||
server.login(self.__mail_config['send']['qq_mail'], self.__mail_config['send']['qq_stmp_pwd'])
|
||||
server.sendmail(self.__mail_config['send']['qq_mail'], [email.strip(), ], data.as_string())
|
||||
server.quit()
|
||||
sucess += 1
|
||||
res[email] = 1
|
||||
except :
|
||||
error += 1
|
||||
res[email] = 0
|
||||
ret_msg[email] = public.get_error_info()
|
||||
total += 1
|
||||
|
||||
try:
|
||||
if res:
|
||||
public.write_push_log(self.__module_name,title,res)
|
||||
else:
|
||||
public.WriteLog('Alarm notification','[Email] No sender configured, please configure first.')
|
||||
except:pass
|
||||
|
||||
result = public.returnMsg(True,'The sending is complete, a total of [{}] items were sent, [{}] succeeded, and [{}] failed.'.format(total,sucess,error))
|
||||
result['list'] = ret_msg
|
||||
result['success'] = sucess
|
||||
result['error'] = error
|
||||
return result
|
||||
|
||||
def push_data(self,data):
|
||||
to_email = data.get("to_email", None)
|
||||
if 'to_user' in data:
|
||||
to_email = data['to_user']
|
||||
|
||||
return self.send_msg(data['msg'],data['title'],to_email)
|
||||
|
||||
def uninstall(self):
|
||||
if os.path.exists(self.__mail_send_conf):
|
||||
os.remove(self.__mail_send_conf)
|
||||
if os.path.exists(self.__mail_receive_conf):
|
||||
os.remove(self.__mail_receive_conf)
|
||||
104
class/msg/sms.html
Normal file
104
class/msg/sms.html
Normal file
@@ -0,0 +1,104 @@
|
||||
<div class="conter_box box_sms">
|
||||
<div class="bt-form">
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-success" id="smsTotalNumber" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 100%;">
|
||||
<div class="progress_text"><span>总条数:</span><span class="sm_total">{{ data.get("total", -1) }}</span><span> 剩余条数:</span><span class="sm_count">{{ data.get("count", -1) }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<!--<div class="progress_text"><span>总条数:</span><span class="sm_total">0</span><span> 剩余条数:</span><span class="sm_count">0</span></div>-->
|
||||
<ul class="help-info-text c7">
|
||||
<li><span style="color:red">重要:开启短信登录【必须开启安全入口】,否则存在【安全风险】</span></li>
|
||||
<li>当前短信仅支持面板消息推送</li>
|
||||
<li>如需面板部分功能需要增加短信推送,请联系客服</li>
|
||||
<li>如需购买短信条数,请联系微信客服<a class="btlink" onclick="bt.onlineService()">微信客服</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/static/js/jquery.qrcode.min.js" defer=""></script>
|
||||
<!--短信模块-->
|
||||
<script type="text/javascript">
|
||||
var sms = {
|
||||
all_info: {},
|
||||
init: function () {
|
||||
this.all_info = $('.alarm-view .bt-w-menu p.bgw').data('data'); //设置全局数据
|
||||
//this.get_sms_data();
|
||||
},
|
||||
/**
|
||||
*@description 获取短信消息,保存按钮添加事件
|
||||
*/
|
||||
get_sms_data: function () {
|
||||
var that = this;
|
||||
if (sms.all_info['data']['count']) {
|
||||
var info_data = sms.all_info['data'];
|
||||
var data = (info_data['count'] / info_data['total']) * 100
|
||||
$('.sm_count').text(info_data['count'])
|
||||
$('.sm_total').text(info_data['total'])
|
||||
$('#smsTotalNumber').css('width', data.toFixed(2) + '%')
|
||||
// $('#smsTotalNumber').text(data.toFixed(2) + '%')
|
||||
$('#panel_login').prop('checked',info_data['login'] == 1 ?true:false);
|
||||
}
|
||||
$('.sms_label').click(function(){
|
||||
var _ev = $('#panel_login'),tips = '';
|
||||
console.log(_ev.prop('checked'),'che')
|
||||
if(_ev.prop('checked')){
|
||||
tips = '是否关闭短信登录?'
|
||||
}else{
|
||||
tips = '<span style="color:red">开启短信登录必须【开启安全入口】,否则存在【安全风险】</span>,是否继续?'
|
||||
}
|
||||
layer.confirm(tips,{btn:['确认','取消'],icon:3,closeBtn: 2,title:'短信登录'},function(){
|
||||
that.set_config_data();
|
||||
},function(index){
|
||||
_ev.prop('checked',!_ev.prop('checked'))
|
||||
})
|
||||
})
|
||||
},
|
||||
set_config_data: function () {
|
||||
var that = this
|
||||
$.post('/config?action=set_msg_config&name=sms', {login:$('#panel_login').prop('checked')?1:0}, function (rdata) {
|
||||
if(rdata.status){
|
||||
that.refresh_data();
|
||||
}else{
|
||||
$('#panel_login').prop('checked',!$('#panel_login').prop('checked'));
|
||||
}
|
||||
layer.msg(rdata.msg,{icon:rdata.status?1:2});
|
||||
})
|
||||
},
|
||||
refresh_data: function () {
|
||||
var that = this
|
||||
$.post('/config?action=get_msg_configs', function (rdata) {
|
||||
$.each(rdata, function (index, item) {
|
||||
if (item.name == that.all_info.name) {
|
||||
$('.alarm-view .bt-w-menu p.bgw').data('data', item)
|
||||
// that.init()
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
// 人工服务 带有参数为售前客服
|
||||
function wechatKefuConsult(){
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: ['300px', '290px'],
|
||||
title: false,
|
||||
closeBtn: 2,
|
||||
shift: 0,
|
||||
content: '<div class="service_consult">\
|
||||
<div class="service_consult_title">请打开微信"扫一扫"</div>\
|
||||
<div class="contact_consult" style="margin-bottom: 5px;"><div id="contact_consult_qcode"></div><i class="wechatEnterprise"></i></div>\
|
||||
<div>【微信客服】</div>\
|
||||
<ul class="c7" style="margin-top:22px;text-align: center;">\
|
||||
<li>工作时间:9:15 - 18:00</li>\
|
||||
</ul>\
|
||||
</div>',
|
||||
success:function(){
|
||||
$('#contact_consult_qcode').qrcode({
|
||||
render: "canvas",
|
||||
width: 140,
|
||||
height: 140,
|
||||
text:'https://work.weixin.qq.com/kfid/kfc72fcbde93e26a6f3'
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
218
class/msg/sms_msg.py
Normal file
218
class/msg/sms_msg.py
Normal file
@@ -0,0 +1,218 @@
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | YakPanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: 沐落 <cjx@yakpanel.com>
|
||||
# | Author: lx
|
||||
# | 消息通道邮箱模块
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
import os, sys, public, base64, json, re
|
||||
import sys, os
|
||||
panelPath = "/www/server/panel"
|
||||
os.chdir(panelPath)
|
||||
sys.path.insert(0,panelPath + "/class/")
|
||||
import public
|
||||
|
||||
class sms_msg:
|
||||
|
||||
_APIURL = 'http://www.yakpanel.com/api/wmsg';
|
||||
__UPATH = panelPath + '/data/userInfo.json';
|
||||
conf_path = panelPath + '/data/sms_main.json'
|
||||
|
||||
#构造方法
|
||||
def __init__(self):
|
||||
self.setupPath = public.GetConfigValue('setup_path')
|
||||
pdata = {}
|
||||
data = {}
|
||||
if os.path.exists(self.__UPATH):
|
||||
try:
|
||||
self.__userInfo = json.loads(public.readFile(self.__UPATH));
|
||||
|
||||
if self.__userInfo:
|
||||
pdata['access_key'] = self.__userInfo['access_key'];
|
||||
data['secret_key'] = self.__userInfo['secret_key'];
|
||||
except :
|
||||
self.__userInfo = None
|
||||
else:
|
||||
pdata['access_key'] = 'test'
|
||||
data['secret_key'] = '123456'
|
||||
|
||||
pdata['data'] = data
|
||||
self.__PDATA = pdata
|
||||
self._APIURL = public.GetConfigValue('home').rstrip('/') + '/api/wmsg'
|
||||
self.__module_name = self.__class__.__name__.replace('_msg','')
|
||||
|
||||
|
||||
def get_version_info(self,get):
|
||||
"""
|
||||
获取版本信息
|
||||
"""
|
||||
data = {}
|
||||
data['ps'] = 'YakPanel 短信消息通道,用于接收面板消息推送'
|
||||
data['version'] = '1.1'
|
||||
data['date'] = '2022-08-02'
|
||||
data['author'] = 'YakPanel'
|
||||
data['title'] = '短信'
|
||||
data['help'] = 'http://www.yakpanel.com'
|
||||
return data
|
||||
|
||||
def get_config(self,get):
|
||||
result = {}
|
||||
data = {}
|
||||
skey = 'sms_count_{}'.format(public.get_user_info()['username'])
|
||||
try:
|
||||
from YakPanel import cache
|
||||
result = cache.get(skey)
|
||||
except:
|
||||
cache = None
|
||||
if not result:
|
||||
result = self.request('get_user_sms')
|
||||
if cache: cache.set(skey,result,3600)
|
||||
try:
|
||||
data = json.loads(public.readFile(self.conf_path))
|
||||
except :pass
|
||||
|
||||
for key in data.keys():
|
||||
result[key] = data[key]
|
||||
return result
|
||||
|
||||
|
||||
def is_strong_password(self,password):
|
||||
"""判断密码复杂度是否安全
|
||||
非弱口令标准:长度大于等于9,分别包含数字、小写。
|
||||
@return: True/False
|
||||
@author: linxiao<2020-9-19>
|
||||
"""
|
||||
if len(password) < 6:return False
|
||||
|
||||
import re
|
||||
digit_reg = "[0-9]" # 匹配数字 +1
|
||||
lower_case_letters_reg = "[a-z]" # 匹配小写字母 +1
|
||||
special_characters_reg = r"((?=[\x21-\x7e]+)[^A-Za-z0-9])" # 匹配特殊字符 +1
|
||||
|
||||
regs = [digit_reg,lower_case_letters_reg,special_characters_reg]
|
||||
grade = 0
|
||||
for reg in regs:
|
||||
if re.search(reg, password):
|
||||
grade += 1
|
||||
|
||||
if grade >= 2 or (grade == 1 and len(password) >= 9):
|
||||
return True
|
||||
return False
|
||||
|
||||
def __check_auth_path(self):
|
||||
|
||||
auth = public.readFile('data/admin_path.pl')
|
||||
if not auth: return False
|
||||
|
||||
slist = ['/','/123456','/admin123','/111111','/bt','/login','/cloudtencent','/tencentcloud','/admin','/admin888','/test']
|
||||
if auth in slist: return False
|
||||
|
||||
if not self.is_strong_password(auth.strip('/')):
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_config(self,get):
|
||||
|
||||
data = {}
|
||||
try:
|
||||
data = json.loads(public.readFile(self.conf_path))
|
||||
except :pass
|
||||
|
||||
if 'login' in get:
|
||||
is_login = int(get['login'])
|
||||
if is_login and not self.__check_auth_path(): return public.returnMsg(False, public.lang("Security entry complexity is not enough <br>1, length must not be less than 9 <br>2, English + digital combination."))
|
||||
data['login'] = is_login
|
||||
|
||||
public.writeFile(self.conf_path,json.dumps(data));
|
||||
return public.returnMsg(True, public.lang("operate successfully!"))
|
||||
|
||||
"""
|
||||
@发送短信
|
||||
@sm_type 预警类型, ssl_end|YakPanel SSL到期提醒
|
||||
@sm_args 预警参数
|
||||
"""
|
||||
def send_msg(self,sm_type = None,sm_args = None):
|
||||
|
||||
s_type = sm_type
|
||||
title = 'YakPanel 告警提醒'
|
||||
tmps = sm_type.split('|')
|
||||
if len(tmps) >= 2:
|
||||
s_type = tmps[0]
|
||||
title = tmps[1]
|
||||
|
||||
self.__PDATA['data']['sm_type'] = s_type
|
||||
self.__PDATA['data']['sm_args'] = sm_args
|
||||
result = self.request('send_msg')
|
||||
|
||||
try:
|
||||
|
||||
res = {}
|
||||
uinfo = public.get_user_info()
|
||||
u_key = '{}****{}'.format(uinfo['username'][0:3],uinfo['username'][-3:])
|
||||
|
||||
res[u_key] = 0
|
||||
if result['status']:
|
||||
res[u_key] = 1
|
||||
|
||||
skey = 'sms_count_{}'.format(public.get_user_info()['username'])
|
||||
try:
|
||||
from YakPanel import cache
|
||||
except:
|
||||
cache = None
|
||||
if not result:
|
||||
result = self.request('get_user_sms')
|
||||
if cache: cache.set(skey,result,3600)
|
||||
|
||||
public.write_push_log(self.__module_name,title,res)
|
||||
except:pass
|
||||
|
||||
return result
|
||||
|
||||
def canonical_data(self, args):
|
||||
"""规范数据内容
|
||||
|
||||
Args:
|
||||
args(dict): 消息原始参数
|
||||
|
||||
Returns:
|
||||
new args: 替换后的消息参数
|
||||
"""
|
||||
|
||||
if not type(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):
|
||||
sm_args = self.canonical_data(data['sm_args'])
|
||||
return self.send_msg(data['sm_type'],sm_args)
|
||||
|
||||
#发送请求
|
||||
def request(self,dname):
|
||||
|
||||
pdata = {}
|
||||
pdata['access_key'] = self.__PDATA['access_key']
|
||||
pdata['data'] = json.dumps(self.__PDATA['data'])
|
||||
try:
|
||||
result = public.httpPost(self._APIURL + '/' + dname,pdata)
|
||||
result = json.loads(result)
|
||||
# print("发送result:")
|
||||
# print(result)
|
||||
return result
|
||||
except Exception as e:
|
||||
# print("短信发送异常:")
|
||||
# print(e)
|
||||
return public.returnMsg(False,public.get_error_info())
|
||||
|
||||
def uninstall(self):
|
||||
if os.path.exists(self.conf_path):
|
||||
os.remove(self.conf_path)
|
||||
149
class/msg/tg.html
Normal file
149
class/msg/tg.html
Normal file
@@ -0,0 +1,149 @@
|
||||
<div class="conter_box box_tg">
|
||||
<!-- <div style="padding-bottom: 12px; margin-bottom: 18px; border-bottom: #ccc 1px dashed;">
|
||||
<div class="flex" style="align-item: center; height: 32px;">
|
||||
<span class="tname" style="width: 99px; line-height: 30px; padding-right: 20px; text-align: right;"><i class="total_tips">?</i>设为默认</span>
|
||||
<div>
|
||||
<input class="btswitch btswitch-ios" id="default_setting" type="checkbox" />
|
||||
<label style="position: relative;top: 5px;" class="btswitch-btn" for="default_setting"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="bt-form">
|
||||
<div class="line">
|
||||
<span class="tname" style="width: 100px;">ID</span>
|
||||
<div class="info-r" style="height:28px; margin-left:100px;">
|
||||
<input type="text" name="telegram_id" class="bt-input-text " style="width: 280px;" placeholder="Telegram ID">
|
||||
</div>
|
||||
</div>
|
||||
<div class="line">
|
||||
<span class="tname" style="width: 100px;">TOKEN</span>
|
||||
<div class="info-r">
|
||||
<input type="text" name="telegram_token" class="bt-input-text" style="width: 280px;" placeholder="Telegram TOKEN">
|
||||
</div>
|
||||
<button class="btn btn-success btn-sm tg_submit" style="margin: 10px 0 0 100px;">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="help-info-text c7">
|
||||
<li>ID: Your telegram user ID</li>
|
||||
<li>Token: Your telegram bot token</li>
|
||||
<li>e.g: [ 12345677:AAAAAAAAA_a0VUo2jjr__CCCCDDD ] <a class="btlink" href="https://www.yakpanel.com/forum/d/5115-how-to-add-telegram-to-panel-notifications" target="_blank" rel="noopener">Help</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<style type="text/css">
|
||||
.total_tips {
|
||||
border: 1px solid #cbcbcb;
|
||||
border-radius: 8px;
|
||||
color: #cbcbcb;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-family: arial;
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
margin-right: 5px;
|
||||
text-align: center;
|
||||
width: 14px;
|
||||
}
|
||||
</style>
|
||||
<!--tg模块-->
|
||||
<script type="text/javascript">
|
||||
var tg = {
|
||||
all_info: {},
|
||||
init: function () {
|
||||
var that = this;
|
||||
this.all_info = $('.alarm-view .bt-w-menu p.bgw').data('data'); //设置全局数据
|
||||
|
||||
this.get_data();
|
||||
|
||||
// 设置默认
|
||||
$('#default_setting').change(function () {
|
||||
var _default = $(this).prop('checked');
|
||||
var token = that.all_info.data.bot_token;
|
||||
var id = that.all_info.data.my_id;
|
||||
if (!id || !token) {
|
||||
layer.msg('Telegram is not configured', { icon: 2 });
|
||||
$(this).prop('checked', !_default);
|
||||
return
|
||||
}
|
||||
var loadTs = layer.msg('Setting Telegram module,please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=set_default_channel&channel=tg', { default: _default }, function (res) {
|
||||
layer.close(loadTs);
|
||||
layer.msg(res.msg, { icon: res.status ? 1 : 2 })
|
||||
if (res.status) that.refresh_data();
|
||||
});
|
||||
});
|
||||
|
||||
var showTips = ''
|
||||
$('.total_tips').hover(function(){
|
||||
showTips = setTimeout(function(){
|
||||
layer.tips('After setting as default, message notifications will be sent using this message channel first.', $('.total_tips'), {
|
||||
tips: [1, '#20a53a'],
|
||||
time: 0,
|
||||
success:function(layero,indexs){
|
||||
layero.css("left", $('.total_tips').offset().left - 10);
|
||||
}})
|
||||
},200)
|
||||
},function(){
|
||||
clearTimeout(showTips)
|
||||
layer.closeAll('tips');
|
||||
})
|
||||
},
|
||||
/**
|
||||
*@description 获取,保存按钮添加事件
|
||||
*/
|
||||
get_data: function () {
|
||||
var that = this;
|
||||
var data = this.all_info.data;
|
||||
|
||||
if (data) {
|
||||
var _default = data.hasOwnProperty('default') ? data.default : false
|
||||
|
||||
$('input[name=telegram_id]').val(data.my_id || '');
|
||||
$('input[name=telegram_token]').val(data.bot_token || '');
|
||||
$('#default_setting').prop('checked', _default);
|
||||
}
|
||||
// 保存按钮点击事件
|
||||
$('.tg_submit').click(function () {
|
||||
that.set_submit();
|
||||
});
|
||||
},
|
||||
/**
|
||||
*@description 保存信息
|
||||
*/
|
||||
set_submit: function () {
|
||||
var that = this;
|
||||
|
||||
var id = $('input[name=telegram_id]').val(),
|
||||
token = $('input[name=telegram_token]').val(),
|
||||
_isAll = $('#panel_alert_all').prop('checked');
|
||||
if (id == '') return layer.msg('Please enter Telegram ID!', { icon: 2 });
|
||||
if (token == '') return layer.msg('Please enter Telegram token!', { icon: 2 });
|
||||
var loadT = layer.msg('Setting Telegram module,please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=set_msg_config&name=tg', { my_id: id, bot_token: token, atall: _isAll ? 'True' : 'False' }, function (rdata) {
|
||||
layer.close(loadT);
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 })
|
||||
if (rdata.status) that.refresh_data();
|
||||
})
|
||||
},
|
||||
refresh_data: function () {
|
||||
var that = this
|
||||
$.post('/config?action=get_msg_configs', function (rdata) {
|
||||
$.each(rdata, function (key, item) {
|
||||
var $el = $('.alarm-view .bt-w-menu .men_' + key);
|
||||
if (item.data && item.data.default) {
|
||||
$el.html($el.text() + '<span class="show-default"></span>');
|
||||
} else {
|
||||
$el.find('span').remove();
|
||||
}
|
||||
$('.alarm-view .bt-w-menu .men_' + key).data('data', item);
|
||||
if (key === 'tg') {
|
||||
that.all_info = item
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
282
class/msg/tg_msg.py
Normal file
282
class/msg/tg_msg.py
Normal file
@@ -0,0 +1,282 @@
|
||||
# coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | YakPanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: jose <zhw@yakpanel.com>
|
||||
# | 消息通道电报模块
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
import sys, os, re, asyncio, 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()
|
||||
|
||||
|
||||
class tg_msg:
|
||||
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):
|
||||
try:
|
||||
red_conf_path = public.readFile(self.conf_path)
|
||||
|
||||
self.__tg_info = json.loads(red_conf_path)
|
||||
if not 'bot_token' in self.__tg_info or not 'my_id' in self.__tg_info:
|
||||
self.__tg_info = None
|
||||
except:
|
||||
self.__tg_info = None
|
||||
self.__module_name = self.__class__.__name__.replace('_msg', '')
|
||||
|
||||
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, "status": True}
|
||||
|
||||
try:
|
||||
info = public.get_push_info('Notification Configuration Reminder',
|
||||
['>Configuration status:<font color=#20a53a>successfully</font>\n\n'])
|
||||
ret = self.send_msg(info['msg'])
|
||||
except:
|
||||
ret = self.send_msg('YakPanel alarm test')
|
||||
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
|
||||
|
||||
def process_character(self, msg):
|
||||
"""
|
||||
格式化消息
|
||||
"""
|
||||
# 去掉无用的转义字符
|
||||
msg = msg.replace('\\', '')
|
||||
|
||||
# 去掉 HTML 标签
|
||||
msg = re.sub(r'<[^>]+>', '', msg)
|
||||
|
||||
# 去掉标题中的 ####
|
||||
msg = msg.replace('####', '')
|
||||
|
||||
# > 增加空格
|
||||
msg = msg.replace('>', '> ')
|
||||
|
||||
# 去掉标题两边的空格
|
||||
title = msg.split('\n')[0].strip()
|
||||
|
||||
# 去掉标题后面的换行符和空行,保留消息
|
||||
msg = msg.replace(f'{title}\n\n', '')
|
||||
|
||||
# 去掉消息前后的空格
|
||||
msg = msg.strip()
|
||||
|
||||
character = ['\\', '_', '`', '*', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '=', '.', '!']
|
||||
for c in character:
|
||||
if c in msg:
|
||||
msg = msg.replace(c, '\\' + c)
|
||||
|
||||
|
||||
# 将处理后的消息发送到Telegram
|
||||
msg = f'*{title}*\n\n{msg}'
|
||||
|
||||
return msg
|
||||
|
||||
async def send_msg_async(self, bot_token, chat_id, msg):
|
||||
"""
|
||||
tg发送信息
|
||||
@msg 消息正文
|
||||
"""
|
||||
|
||||
bot = telegram.Bot(token=bot_token)
|
||||
|
||||
msg = self.process_character(msg)
|
||||
|
||||
public.print_log(msg)
|
||||
|
||||
await bot.send_message(chat_id=chat_id, text=msg, parse_mode='MarkdownV2')
|
||||
|
||||
def send_msg(self, msg):
|
||||
"""
|
||||
tg发送信息
|
||||
@msg 消息正文
|
||||
"""
|
||||
if not self.__tg_info:
|
||||
return public.returnMsg(False, public.lang("The telegram information is incorrectly configured."))
|
||||
if isinstance(self.__tg_info['my_id'], int):
|
||||
return public.returnMsg(False, public.lang("Telegram configuration error, please reconfigure the robot."))
|
||||
msg, title = self.get_send_msg(msg)
|
||||
# public.WriteFile("/tmp/title.tg", title)
|
||||
# public.WriteFile("/tmp/msg.tg", msg)
|
||||
# send_content = msg
|
||||
# public.WriteFile("/tmp/send_content.tg", send_content)
|
||||
|
||||
# bot = telegram.Bot(self.__tg_info['bot_token'])
|
||||
bot_token = self.__tg_info['bot_token']
|
||||
chat_id = self.__tg_info['my_id']
|
||||
#text = msg
|
||||
public.print_log(msg)
|
||||
|
||||
# public.print_log("bot:{}".format(self.__tg_info['bot_token']))
|
||||
# public.print_log("my_id:{}".format(self.__tg_info['my_id']))
|
||||
|
||||
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))
|
||||
ret = {'success': 1}
|
||||
|
||||
public.write_push_log(self.__module_name, title, ret)
|
||||
|
||||
public.print_log('message sent successfully!')
|
||||
loop.close()
|
||||
|
||||
return public.returnMsg(True, public.lang("send complete, send result: True."))
|
||||
|
||||
except:
|
||||
public.print_log('Error:{}'.format(str(public.get_error_info())))
|
||||
|
||||
ret = {'success': 0}
|
||||
|
||||
public.write_push_log(self.__module_name, title, ret)
|
||||
|
||||
return public.returnMsg(False, public.lang("send complete, send result: False."))
|
||||
|
||||
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)
|
||||
|
||||
# 获取tg机器人信息
|
||||
# def get_tg_conf(self, get=None):
|
||||
# conf = self.__tg_info
|
||||
#
|
||||
# if not conf:
|
||||
# return {"setup": False, "bot_token": "", "my_id": ""}
|
||||
# try:
|
||||
# return self.__tg_info
|
||||
# except:
|
||||
# return {"setup": False, "bot_token": "", "my_id": ""}
|
||||
|
||||
# def process_character(self,content):
|
||||
# character = ['.',',','!',':','%','[',']','\/','_','-','>']
|
||||
# for c in character:
|
||||
# if c in content and '\\{}'.format(c) not in content:
|
||||
# content = content.replace(c,'\\'+c)
|
||||
# return content
|
||||
|
||||
# 使用tg机器人发送消息
|
||||
# def send_by_tg_bot(self,content,parse_mode=None):
|
||||
# "parse_mode 消息格式 html/markdown/markdownv2"
|
||||
# #content = self.process_character(content)
|
||||
# conf = self.__tg_info
|
||||
#
|
||||
# confa1 = conf['my_id']
|
||||
# public.print_log("开始检查--confa1", confa1)
|
||||
# confa2 = conf['bot_token']
|
||||
# public.print_log("开始检查--confa2", confa2)
|
||||
#
|
||||
#
|
||||
# text = send_content
|
||||
# public.WriteFile("/tmp/text.tg", text)
|
||||
#
|
||||
# bot = telegram.Bot(conf['bot_token'])
|
||||
# #result = bot.send_message(text=content, chat_id=int(conf['my_id']), parse_mode="MarkdownV2")
|
||||
# result = bot.send_message( chat_id=int(conf['my_id']), text=text, parse_mode='MarkdownV2')
|
||||
#
|
||||
# return result
|
||||
75
class/msg/weixin.html
Normal file
75
class/msg/weixin.html
Normal file
@@ -0,0 +1,75 @@
|
||||
<div class="conter_box box_weixin">
|
||||
<div class="bt-form">
|
||||
<div class="line">
|
||||
<span class="tname">Notify everyone</span>
|
||||
<div class="info-r" style="height:28px; margin-left:125px">
|
||||
<input class="btswitch btswitch-ios" id="panel_alert_all" type="checkbox" disabled="disabled" checked>
|
||||
<label style="position: relative;top: 5px;" title="Only supports notify everyone." class="btswitch-btn" for="panel_alert_all"></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="line">
|
||||
<span class="tname">WeCom URL</span>
|
||||
<div class="info-r">
|
||||
<textarea name="channel_weixin_value" class="bt-input-text mr5" type="text" placeholder="Please enter WeCom url" style="width: 300px; height:90px; line-height:20px"></textarea>
|
||||
</div>
|
||||
<button class="btn btn-success btn-sm weixin_submit" style="margin: 10px 0 0 125px;">Save</button>
|
||||
</div>
|
||||
<div class="line">
|
||||
<ul class="help-info-text c7">
|
||||
<li>Notify everyone, Immutable</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--微信模块-->
|
||||
<script type="text/javascript">
|
||||
var weixin = {
|
||||
all_info: {},
|
||||
init: function () {
|
||||
this.all_info = $('.alarm-view .bt-w-menu p.bgw').data('data'); //设置全局数据
|
||||
this.get_weixin_data();
|
||||
},
|
||||
/**
|
||||
*@description 获取微信url,保存按钮添加事件
|
||||
*/
|
||||
get_weixin_data: function () {
|
||||
var that = this;
|
||||
|
||||
if (weixin.all_info['data']) {
|
||||
$('textarea[name=channel_weixin_value]').val(weixin.all_info['data']['weixin_url']);
|
||||
}
|
||||
// 微信信息设置
|
||||
$('.weixin_submit').click(function () {
|
||||
that.set_submit_ding();
|
||||
})
|
||||
},
|
||||
/**
|
||||
*@description 设置微信url信息,保存按钮
|
||||
*/
|
||||
set_submit_ding: function () {
|
||||
var that = this;
|
||||
|
||||
var _url = $('textarea[name=channel_weixin_value]').val();
|
||||
if (_url == '') return layer.msg('Please enter WeCom url', { icon: 2 })
|
||||
var loadT = layer.msg('WeCom is being set up, please wait...', { icon: 16, time: 0, shade: [0.3, '#000'] });
|
||||
$.post('/config?action=set_msg_config&name=weixin', { url: _url, atall: 'True' }, function (rdata) {
|
||||
layer.close(loadT);
|
||||
layer.msg(rdata.msg, { icon: rdata.status ? 1 : 2 })
|
||||
if (rdata.status) that.refresh_data();
|
||||
})
|
||||
},
|
||||
refresh_data: function () {
|
||||
var that = this
|
||||
$.post('/config?action=get_msg_configs', function (rdata) {
|
||||
$.each(rdata, function (index, item) {
|
||||
if (item.name == that.all_info.name) {
|
||||
$('.alarm-view .bt-w-menu p.bgw').data('data', item)
|
||||
// that.init()
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
268
class/msg/weixin_msg.py
Normal file
268
class/msg/weixin_msg.py
Normal file
@@ -0,0 +1,268 @@
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | YakPanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: 沐落 <cjx@yakpanel.com>
|
||||
# | Author: lx
|
||||
# | 消息通道邮箱模块
|
||||
# | 常用功能
|
||||
# 字体加粗 **bold** ,[这是一个链接](http://www.yakpanel.com),代码段:`code`
|
||||
# 支持3种字体颜色 <font color="info">绿色</font> <font color="comment">灰色</font> <font color="warning">橙红色</font>
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
import os, sys, public, json, requests,re
|
||||
import sys, os,time
|
||||
panelPath = "/www/server/panel"
|
||||
os.chdir(panelPath)
|
||||
sys.path.insert(0,panelPath + "/class/")
|
||||
import public, json, requests
|
||||
from requests.packages import urllib3
|
||||
# 关闭警告
|
||||
|
||||
urllib3.disable_warnings()
|
||||
import socket
|
||||
import requests.packages.urllib3.util.connection as urllib3_cn
|
||||
class weixin_msg:
|
||||
|
||||
__module_name = None
|
||||
__default_pl = "{}/data/default_msg_channel.pl".format(panelPath)
|
||||
conf_path = 'data/weixin.json'
|
||||
__weixin_info = None
|
||||
def __init__(self):
|
||||
try:
|
||||
self.__weixin_info = json.loads(public.readFile(self.conf_path))
|
||||
if not 'weixin_url' in self.__weixin_info:
|
||||
self.__weixin_info = None
|
||||
except :
|
||||
self.__weixin_info = None
|
||||
self.__module_name = self.__class__.__name__.replace('_msg','')
|
||||
|
||||
def get_version_info(self,get):
|
||||
"""
|
||||
获取版本信息
|
||||
"""
|
||||
data = {}
|
||||
data['ps'] = 'Wecom used to receive panel message push'
|
||||
data['version'] = '1.2'
|
||||
data['date'] = '2022-08-10'
|
||||
data['author'] = 'YakPanel'
|
||||
data['title'] = 'Wecom'
|
||||
data['help'] = 'http://www.yakpanel.com'
|
||||
return data
|
||||
|
||||
def __get_default_channel(self):
|
||||
"""
|
||||
@获取默认消息通道
|
||||
"""
|
||||
try:
|
||||
if public.readFile(self.__default_pl) == self.__module_name:
|
||||
return True
|
||||
except:pass
|
||||
return False
|
||||
|
||||
def get_config(self,get):
|
||||
"""
|
||||
获取微信配置
|
||||
"""
|
||||
data = {}
|
||||
if self.__weixin_info :
|
||||
|
||||
#全局配置开关,1开启,0关闭
|
||||
if not 'state' in self.__weixin_info:
|
||||
self.__weixin_info['state'] = 1
|
||||
|
||||
data = self.__weixin_info
|
||||
|
||||
if not 'list' in data: data['list'] = {}
|
||||
|
||||
title = 'Default'
|
||||
if 'title' in data: title = data['title']
|
||||
|
||||
data['list']['default'] = {'title':title,'data':data['weixin_url'],'state':self.__weixin_info['state']}
|
||||
data['default'] = self.__get_default_channel()
|
||||
return data
|
||||
|
||||
def set_config(self,get):
|
||||
"""
|
||||
设置微信配置
|
||||
@url 微信URL
|
||||
@atall 默认@全体成员
|
||||
@key 唯一标识,default=兼容之前配置
|
||||
@title string 备注
|
||||
@user
|
||||
"""
|
||||
|
||||
if not hasattr(get, 'url'):
|
||||
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"))
|
||||
|
||||
key,status,state ='default', 1, 1
|
||||
if 'key' in get: key = get.key
|
||||
if 'status' in get: status = int(get.status)
|
||||
if 'state' in get: state = int(get.state)
|
||||
|
||||
if not self.__weixin_info: self.__weixin_info = {}
|
||||
if not 'list' in self.__weixin_info: self.__weixin_info['list'] = {}
|
||||
|
||||
#全局配置开关,1开启,0关闭
|
||||
self.__weixin_info['state'] = state
|
||||
|
||||
#增加多个机器人
|
||||
self.__weixin_info['list'][key] = {
|
||||
"data": get.url.strip(),
|
||||
"title":title,
|
||||
"status":status,
|
||||
"addtime":int(time.time())
|
||||
}
|
||||
|
||||
#兼容旧配置只有一条url的情况
|
||||
if key == 'default':
|
||||
self.__weixin_info['weixin_url'] = get.url.strip()
|
||||
self.__weixin_info['title'] = title
|
||||
|
||||
#统一格式化输出,包含主机名,ip,推送时间
|
||||
try:
|
||||
info = public.get_push_info('Message channel configuration reminder',['>configuration status: <font color=#20a53a>Success</font>\n\n'])
|
||||
ret = self.send_msg(info['msg'])
|
||||
except:
|
||||
ret = self.send_msg('YakPanel alarm test')
|
||||
|
||||
if ret['status']:
|
||||
|
||||
#默认消息通道
|
||||
if 'default' in get and get['default']:
|
||||
public.writeFile(self.__default_pl, self.__module_name)
|
||||
|
||||
if ret['success'] <= 0:
|
||||
return public.returnMsg(False, public.lang("Failed to add, please check whether the URL is correct"))
|
||||
|
||||
public.writeFile(self.conf_path, json.dumps(self.__weixin_info))
|
||||
return public.returnMsg(True, public.lang("successfully set"))
|
||||
else:
|
||||
return public.returnMsg(False, public.lang("Failed to add, please check whether the URL is correct"))
|
||||
|
||||
def get_send_msg(self,msg):
|
||||
"""
|
||||
@name 处理md格式
|
||||
"""
|
||||
try:
|
||||
title = 'YakPanel warning notification'
|
||||
if msg.find("####") >= 0:
|
||||
msg = msg.replace("\n\n","""
|
||||
""").strip()
|
||||
try:
|
||||
title = re.search(r"####(.+)", msg).groups()[0]
|
||||
except:pass
|
||||
|
||||
except:pass
|
||||
return msg,title
|
||||
|
||||
def send_msg(self,msg,to_user = 'default'):
|
||||
"""
|
||||
@name 微信发送信息
|
||||
@msg string 消息正文(正文内容,必须包含
|
||||
1、服务器名称
|
||||
2、IP地址
|
||||
3、发送时间
|
||||
)
|
||||
@to_user string 指定发送人
|
||||
"""
|
||||
if not self.__weixin_info :
|
||||
return public.returnMsg(False, public.lang("Information is not configured correctly."))
|
||||
|
||||
if 'state' in self.__weixin_info and self.__weixin_info['state'] == 0:
|
||||
return public.returnMsg(False, public.lang("Notifications have been turned off, please turn them on and try again."))
|
||||
|
||||
if msg.find('####') == -1:
|
||||
try:
|
||||
msg = public.get_push_info('Notification Configuration Reminder',['>Send Content:{}\n\n'.format(msg)])['msg']
|
||||
except:pass
|
||||
|
||||
msg,title = self.get_send_msg(msg)
|
||||
data = {
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
"content": msg
|
||||
}
|
||||
}
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
|
||||
error,success = 0,0
|
||||
conf = self.get_config(None)['list']
|
||||
|
||||
res = {}
|
||||
for to_key in to_user.split(','):
|
||||
if not to_key in conf: continue
|
||||
try:
|
||||
#x = requests.post(url = conf[to_key]['data'], data=json.dumps(data), headers=headers,verify=False,timeout=10)
|
||||
|
||||
allowed_gai_family_lib=urllib3_cn.allowed_gai_family
|
||||
def allowed_gai_family():
|
||||
family = socket.AF_INET
|
||||
return family
|
||||
urllib3_cn.allowed_gai_family = allowed_gai_family
|
||||
x = requests.post(url = conf[to_key]['data'], data = json.dumps(data),verify=False, headers=headers,timeout=10)
|
||||
urllib3_cn.allowed_gai_family=allowed_gai_family_lib
|
||||
|
||||
if x.json()["errcode"] == 0:
|
||||
success += 1
|
||||
res[conf[to_key]['title']] = 1
|
||||
else:
|
||||
error += 1
|
||||
res[conf[to_key]['title']] = 0
|
||||
except:
|
||||
error += 1
|
||||
res[conf[to_key]['title']] = 0
|
||||
try:
|
||||
public.write_push_log(self.__module_name,title,res)
|
||||
except:pass
|
||||
|
||||
ret = public.returnMsg(True,'Send completed, send successfully {}, send failed {}.'.format(success,error))
|
||||
ret['success'] = success
|
||||
ret['error'] = error
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def push_data(self,data):
|
||||
"""
|
||||
@name 统一发送接口
|
||||
@data 消息内容
|
||||
{"module":"mail","title":"提醒","msg":"提醒","to_email":"xx@qq.com","sm_type":"","sm_args":{}}
|
||||
"""
|
||||
if not 'to_user' in data:
|
||||
data['to_user'] = 'default'
|
||||
|
||||
return self.send_msg(data['msg'],data['to_user'])
|
||||
|
||||
|
||||
def _write_log(self,module,msg,res):
|
||||
"""
|
||||
@name 写日志
|
||||
"""
|
||||
user = '[ 默认 ] '
|
||||
# for key in res:
|
||||
# status = '<span style="color:#20a53a;">成功</span>'
|
||||
# if res[key] == 0: status = '<span style="color:red;">成功</span>'
|
||||
# user += '[ {}:{} ] '.format(key,status)
|
||||
|
||||
try:
|
||||
msg_obj = public.init_msg(module)
|
||||
if msg_obj: module = msg_obj.get_version_info(None)['title']
|
||||
except:pass
|
||||
|
||||
log = '[{}] sent to {}, sending content: [{}]'.format(module,user,public.xsssec(msg))
|
||||
public.WriteLog('message push',log)
|
||||
|
||||
def uninstall(self):
|
||||
if os.path.exists(self.conf_path):
|
||||
os.remove(self.conf_path)
|
||||
|
||||
|
||||
258
class/msg/wx_account.html
Normal file
258
class/msg/wx_account.html
Normal file
@@ -0,0 +1,258 @@
|
||||
<div class="conter_box wx_account_box">
|
||||
<div class="bt-form">
|
||||
<div class="form-item">
|
||||
<div class="form-label">绑定微信账号</div>
|
||||
<div class="form-content">
|
||||
<div class="bind_wechat hide">
|
||||
<div class="userinfo"></div>
|
||||
</div>
|
||||
<div class="nobind_wechat">
|
||||
<span class="red">未绑定</span>
|
||||
</div>
|
||||
<button class="btn btn-xs btn-success btn-bind-wechat">立即绑定</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-label">绑定微信公众号</div>
|
||||
<div class="form-content">
|
||||
<div class="bind_account hide">
|
||||
<span style="color: #20a53a;">已绑定</span>
|
||||
</div>
|
||||
<div class="nobind_account">
|
||||
<span class="red">未绑定</span>
|
||||
<button class="btn btn-xs btn-success btn-bind-account">立即绑定</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-item hide">
|
||||
<div class="form-label">今日剩余发送次数</div>
|
||||
<div class="form-content">
|
||||
<span class="account_remaining">0</span>
|
||||
<button class="btn btn-xs btn-success btn-send-test">发送测试消息</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="help-info-text c7">
|
||||
<li>当前为体验版,限制每个宝塔账号发送频率100条/天</li>
|
||||
</ul>
|
||||
</div>
|
||||
<style>
|
||||
.wx_account_box .bt-form {
|
||||
padding-top: 15px;
|
||||
}
|
||||
.wx_account_box .form-item {
|
||||
display: flex;
|
||||
}
|
||||
.wx_account_box .form-item + .form-item {
|
||||
margin-top: 15px;
|
||||
}
|
||||
.wx_account_box .form-label,
|
||||
.wx_account_box .form-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 32px;
|
||||
}
|
||||
.wx_account_box .form-label {
|
||||
justify-content: flex-end;
|
||||
width: 140px;
|
||||
padding-right: 20px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.wx_account_box .form-content {
|
||||
flex: 1;
|
||||
}
|
||||
.wx_account_box .userinfo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.wx_account_box .userinfo img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.wx_account_box .form-item .btn + .btn {
|
||||
margin-left: 12px;
|
||||
}
|
||||
.bind_wechat_box .qrcode {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: 0 auto;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
.bind_wechat_box .qrcode img {
|
||||
width: 148px;
|
||||
height: 148px;
|
||||
}
|
||||
.nobind_account {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.btn-send-test,
|
||||
.btn-bind-wechat,
|
||||
.btn-bind-account {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
.help-info-text {
|
||||
position: absolute;
|
||||
left: 20px;
|
||||
bottom: 50px;
|
||||
}
|
||||
</style>
|
||||
<script src="/static/js/jquery.qrcode.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
var wx_account = {
|
||||
config: {},
|
||||
init: function () {
|
||||
var that = this;
|
||||
|
||||
this.get_config();
|
||||
|
||||
// 发送测试信息
|
||||
$('.btn-send-test').click(function () {
|
||||
var laod = bt.load('正在发送测试信息,请稍候...');
|
||||
$.post(
|
||||
'/config?action=get_msg_fun',
|
||||
{
|
||||
module_name: 'wx_account',
|
||||
fun_name: 'push_data',
|
||||
msg: '发送测试信息',
|
||||
},
|
||||
function (res) {
|
||||
laod.close();
|
||||
bt.msg(res);
|
||||
if (res.status) {
|
||||
var num = $('.account_remaining').text();
|
||||
if (!isNaN(num)) {
|
||||
num -= 1;
|
||||
$('.account_remaining').text(num);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
// 绑定微信公众号
|
||||
$('.btn-bind-account').click(function () {
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: '280px',
|
||||
title: '绑定微信公众号',
|
||||
closeBtn: 2,
|
||||
shadeClose: false,
|
||||
content: '\
|
||||
<div class="bind_wechat_box pd20">\
|
||||
<div class="text-center">微信扫码</div>\
|
||||
<div class="mt10">\
|
||||
<div class="qrcode">\
|
||||
<img src="https://www.yakpanel.com/Public/img/bt_wx.jpg" />\
|
||||
</div>\
|
||||
</div>\
|
||||
</div>\
|
||||
',
|
||||
cancel: function () {
|
||||
that.get_config();
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// 更换绑定账号
|
||||
$('.btn-bind-wechat').click(function () {
|
||||
that.show_bind_account();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description 显示更换微信账号
|
||||
*/
|
||||
show_bind_account: function () {
|
||||
var that = this;
|
||||
layer.open({
|
||||
type: 1,
|
||||
area: '280px',
|
||||
title: '绑定微信账号',
|
||||
closeBtn: 2,
|
||||
shadeClose: false,
|
||||
content: '\
|
||||
<div class="bind_wechat_box pd20">\
|
||||
<div class="text-center">微信扫码</div>\
|
||||
<div class="mt10">\
|
||||
<div class="qrcode" id="wechat-qrcode"></div>\
|
||||
</div>\
|
||||
</div>\
|
||||
',
|
||||
success: function () {
|
||||
$.post('/config?action=get_msg_fun', {
|
||||
module_name: 'wx_account',
|
||||
fun_name: 'get_auth_url',
|
||||
}, function (rdata) {
|
||||
var url = rdata.msg.res;
|
||||
$('#wechat-qrcode').qrcode({
|
||||
render: 'canvas',
|
||||
width: 135,
|
||||
height: 135,
|
||||
text: url,
|
||||
correctLevel: 1
|
||||
});
|
||||
});
|
||||
},
|
||||
cancel: function () {
|
||||
that.get_config();
|
||||
}
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @description 获取配置
|
||||
*/
|
||||
get_config: function () {
|
||||
var that = this;
|
||||
var loadT = bt.load('正在获取配置,请稍候...');
|
||||
$.post(
|
||||
'/config?action=get_msg_fun',
|
||||
{
|
||||
module_name: 'wx_account',
|
||||
fun_name: 'get_web_info',
|
||||
},
|
||||
function (rdata) {
|
||||
loadT.close();
|
||||
|
||||
if (rdata.status === false) {
|
||||
bt.msg(rdata);
|
||||
}
|
||||
|
||||
var data = rdata && rdata.msg && rdata.msg.res ? rdata.msg.res : {};
|
||||
|
||||
// 绑定微信账号
|
||||
if (data.is_bound === 1) {
|
||||
$('.userinfo').html('<img src="' + data.head_img + '" /><div>' + data.nickname + '</div>');
|
||||
$('.btn-bind-wechat').text('更换微信账号');
|
||||
$('.bind_wechat').removeClass('hide');
|
||||
$('.nobind_wechat').addClass('hide');
|
||||
} else {
|
||||
$('.btn-bind-wechat').text('立即绑定');
|
||||
$('.bind_wechat').addClass('hide');
|
||||
$('.nobind_wechat').removeClass('hide');
|
||||
}
|
||||
// 判断是否绑定公众号
|
||||
if (data.is_subscribe === 1) {
|
||||
$('.bind_account').removeClass('hide');
|
||||
$('.nobind_account').addClass('hide');
|
||||
} else {
|
||||
$('.bind_account').addClass('hide');
|
||||
$('.nobind_account').removeClass('hide');
|
||||
}
|
||||
// 判断是否存在发送消息
|
||||
if (data.remaining === undefined) {
|
||||
$('.account_remaining').parents('.form-item').addClass('hide');
|
||||
} else {
|
||||
$('.account_remaining').parents('.form-item').removeClass('hide');
|
||||
$('.account_remaining').text(data.remaining);
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
</script>
|
||||
290
class/msg/wx_account_msg.py
Normal file
290
class/msg/wx_account_msg.py
Normal file
@@ -0,0 +1,290 @@
|
||||
#coding: utf-8
|
||||
# +-------------------------------------------------------------------
|
||||
# | YakPanel
|
||||
# +-------------------------------------------------------------------
|
||||
# | Copyright (c) 2015-2020 YakPanel(www.yakpanel.com) All rights reserved.
|
||||
# +-------------------------------------------------------------------
|
||||
# | Author: 沐落 <cjx@yakpanel.com>
|
||||
# | Author: lx
|
||||
# | 消息通道邮箱模块
|
||||
# +-------------------------------------------------------------------
|
||||
|
||||
import os, sys
|
||||
import time,base64
|
||||
|
||||
panelPath = "/www/server/panel"
|
||||
os.chdir(panelPath)
|
||||
sys.path.insert(0,panelPath + "/class/")
|
||||
import public, json, requests
|
||||
from requests.packages import urllib3
|
||||
# 关闭警告
|
||||
urllib3.disable_warnings()
|
||||
import socket
|
||||
import requests.packages.urllib3.util.connection as urllib3_cn
|
||||
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_default_channel(self):
|
||||
"""
|
||||
@获取默认消息通道
|
||||
"""
|
||||
try:
|
||||
if public.readFile(self.__default_pl) == self.__module_name:
|
||||
return True
|
||||
except:pass
|
||||
return False
|
||||
|
||||
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, public.lang("The setup was successful"))
|
||||
|
||||
def get_web_info(self,get):
|
||||
if public.is_self_hosted():
|
||||
return public.returnMsg(False, public.lang("WeChat official account cloud features are not available in self-hosted mode."))
|
||||
if self.user_info is None: return public.returnMsg(False, public.lang("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": 'B' * 32,
|
||||
"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":"The link to the cloud failed, please check the network,请检查网络"}))
|
||||
return public.returnMsg(False, public.lang("The link to the cloud failed, please check the network"))
|
||||
|
||||
def get_web_info2(self):
|
||||
if public.is_self_hosted():
|
||||
return public.returnMsg(False, public.lang("WeChat official account cloud features are not available in self-hosted mode."))
|
||||
if self.user_info is None: return public.returnMsg(False, public.lang("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": 'B' * 32,
|
||||
"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":"The link to the cloud failed, please check the network"}))
|
||||
return public.returnMsg(False, public.lang("The link to the cloud failed, please check the network"))
|
||||
|
||||
def get_auth_url(self,get):
|
||||
if public.is_self_hosted():
|
||||
return public.returnMsg(False, public.lang("WeChat official account cloud features are not available in self-hosted mode."))
|
||||
if self.user_info is None: return public.returnMsg(False, public.lang("The user binding information was not obtained"))
|
||||
url = "https://wafapi2.yakpanel.com/api/v2/user/wx_web/get_auth_url"
|
||||
data = {
|
||||
"uid": self.user_info["uid"],
|
||||
"access_key": 'B' * 32,
|
||||
"serverid":self.user_info["server_id"]
|
||||
}
|
||||
try:
|
||||
datas = json.loads(public.httpPost(url,data))
|
||||
if datas["success"]:
|
||||
return public.returnMsg(True, datas)
|
||||
else:
|
||||
return public.returnMsg(False, datas)
|
||||
except:
|
||||
return public.returnMsg(False, public.lang("The link to the cloud failed, please check the network"))
|
||||
|
||||
|
||||
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 = '\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, public.lang("No user information was obtained"))
|
||||
if public.is_self_hosted():
|
||||
return public.returnMsg(False, public.lang("WeChat official account cloud features are not available in self-hosted mode."))
|
||||
|
||||
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))
|
||||
# public.print_log(json.dumps(x))
|
||||
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,'Send complete.Send success :{}, send failure :{}',success,error)
|
||||
result['success'] = success
|
||||
result['error'] = error
|
||||
return result
|
||||
except:
|
||||
print(public.get_error_info())
|
||||
return public.returnMsg(False, public.lang("WeChat message delivery failed. --> {}", public.get_error_info()))
|
||||
|
||||
def push_data(self,data):
|
||||
return self.send_msg(data['msg'])
|
||||
|
||||
def uninstall(self):
|
||||
if os.path.exists(self.conf_path):
|
||||
os.remove(self.conf_path)
|
||||
Reference in New Issue
Block a user