Initial YakPanel commit

This commit is contained in:
Niranjan
2026-04-07 02:04:22 +05:30
commit 2826d3e7f3
5359 changed files with 1390724 additions and 0 deletions

Binary file not shown.

Binary file not shown.

143
class/msg/dingding.html Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 moduleplease 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 moduleplease 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
View 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
View 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
View 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
View 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
View 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)