Compare commits

..
12 Commits
Author SHA1 Message Date
whyour 4af9b22442 更新版本 v2.14.9 2022-10-29 18:50:16 +08:00
whyour 8a392170cf webhook通知支持嵌套body 2022-10-29 18:43:16 +08:00
whyour 6385e053ae 增加飞书机器人通知 2022-10-29 16:41:37 +08:00
Leo_chenandGitHub 2789119882 添加智能微秘书通知 (#1680) 2022-10-29 15:45:16 +08:00
whyour 3e0553c3e8 修改登陆逻辑 2022-10-29 10:05:54 +08:00
whyour 5e16ace1e7 修改检查更新日志 2022-10-27 10:34:55 +08:00
whyour 0ca23cc4ca 修改环境变量默认排序 2022-10-27 00:02:50 +08:00
whyour 4949457001 修复ql命令日志打印 2022-10-26 23:50:07 +08:00
whyour 8ae13fe823 增加 -max 指定环境变量 2022-10-26 14:27:14 +08:00
whyour fdf672092a 修改获取version文件超时时间 2022-10-25 23:52:30 +08:00
whyour 48dd6ea826 修复表格滚动高度 2022-10-25 23:12:38 +08:00
whyour 68bb4f15e2 修复shell获取磁盘使用量 2022-10-24 22:45:06 +08:00
19 changed files with 509 additions and 272 deletions
+6 -1
View File
@@ -419,7 +419,12 @@ export function parseBody(
return;
}
parsed[key] = val;
try {
const jsonValue = JSON.parse(val);
parsed[key] = jsonValue;
} catch (error) {
parsed[key] = val;
}
});
switch (contentType) {
+19 -2
View File
@@ -11,9 +11,11 @@ export enum NotificationMode {
'dingtalkBot' = 'dingtalkBot',
'weWorkBot' = 'weWorkBot',
'weWorkApp' = 'weWorkApp',
'aibotk' = 'aibotk',
'iGot' = 'iGot',
'pushPlus' = 'pushPlus',
'email' = 'email',
'feishu' = 'feishu',
'webhook' = 'webhook',
}
@@ -75,6 +77,12 @@ export class WeWorkAppNotification extends NotificationBaseInfo {
public weWorkAppKey = '';
}
export class AibotkNotification extends NotificationBaseInfo {
public aibotkKey: string = '';
public aibotkType: 'room' | 'contact' = 'room';
public aibotkName: string = '';
}
export class IGotNotification extends NotificationBaseInfo {
public iGotPushKey = '';
}
@@ -95,7 +103,14 @@ export class WebhookNotification extends NotificationBaseInfo {
public webhookBody: string = '';
public webhookUrl: string = '';
public webhookMethod: 'GET' | 'POST' | 'PUT' = 'GET';
public webhookContentType: 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded' = 'application/json';
public webhookContentType:
| 'application/json'
| 'multipart/form-data'
| 'application/x-www-form-urlencoded' = 'application/json';
}
export class LarkNotification extends NotificationBaseInfo {
public larkKey = '';
}
export interface NotificationInfo
@@ -109,7 +124,9 @@ export interface NotificationInfo
DingtalkBotNotification,
WeWorkBotNotification,
WeWorkAppNotification,
AibotkNotification,
IGotNotification,
PushPlusNotification,
EmailNotification,
WebhookNotification {}
WebhookNotification,
LarkNotification {}
+1 -1
View File
@@ -115,7 +115,7 @@ export default class EnvService {
const result = await this.find(condition, [
['status', 'ASC'],
['position', 'DESC'],
['createdAt', 'DESC'],
['createdAt', 'ASC'],
]);
return result as any;
} catch (error) {
+101 -41
View File
@@ -24,18 +24,23 @@ export default class NotificationService {
['dingtalkBot', this.dingtalkBot],
['weWorkBot', this.weWorkBot],
['weWorkApp', this.weWorkApp],
['aibotk', this.aibotk],
['iGot', this.iGot],
['pushPlus', this.pushPlus],
['email', this.email],
['webhook', this.webhook],
['lark', this.lark],
]);
private timeout = 10000;
private title = '';
private content = '';
private params!: Omit<NotificationInfo, 'type'>;
private gotOption = {
timeout: 30000,
retry: 1,
};
constructor(@Inject('logger') private logger: winston.Logger) { }
constructor(@Inject('logger') private logger: winston.Logger) {}
public async notify(
title: string,
@@ -76,8 +81,7 @@ export default class NotificationService {
const { gotifyUrl, gotifyToken, gotifyPriority } = this.params;
const res: any = await got
.post(`${gotifyUrl}/message?token=${gotifyToken}`, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
body: `title=${encodeURIComponent(
this.title,
)}&message=${encodeURIComponent(
@@ -95,8 +99,7 @@ export default class NotificationService {
const { goCqHttpBotQq, goCqHttpBotToken, goCqHttpBotUrl } = this.params;
const res: any = await got
.post(`${goCqHttpBotUrl}?${goCqHttpBotQq}`, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
json: { message: `${this.title}\n${this.content}` },
headers: { Authorization: 'Bearer ' + goCqHttpBotToken },
})
@@ -111,8 +114,7 @@ export default class NotificationService {
: `https://sc.ftqq.com/${serverChanKey}.send`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
body: `title=${this.title}&desp=${this.content}`,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
})
@@ -125,8 +127,7 @@ export default class NotificationService {
const url = `https://api2.pushdeer.com/message/push`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
body: `pushkey=${pushDeerKey}&text=${encodeURIComponent(
this.title,
)}&desp=${encodeURIComponent(this.content)}&type=markdown`,
@@ -143,8 +144,7 @@ export default class NotificationService {
const url = `${chatUrl}${chatToken}`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
body: `payload={"text":"${this.title}\n${this.content}"}`,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
})
@@ -164,8 +164,7 @@ export default class NotificationService {
)}?icon=${barkIcon}?sound=${barkSound}&group=${barkGroup}`;
const res: any = await got
.get(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
})
.json();
@@ -182,8 +181,9 @@ export default class NotificationService {
telegramBotUserId,
} = this.params;
const authStr = telegramBotProxyAuth ? `${telegramBotProxyAuth}@` : '';
const url = `https://${telegramBotApiHost ? telegramBotApiHost : 'api.telegram.org'
}/bot${telegramBotToken}/sendMessage`;
const url = `https://${
telegramBotApiHost ? telegramBotApiHost : 'api.telegram.org'
}/bot${telegramBotToken}/sendMessage`;
let agent;
if (telegramBotProxyHost && telegramBotProxyPort) {
const options: any = {
@@ -202,8 +202,7 @@ export default class NotificationService {
}
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
body: `chat_id=${telegramBotUserId}&text=${this.title}\n\n${this.content}&disable_web_page_preview=true`,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
agent,
@@ -225,8 +224,7 @@ export default class NotificationService {
const url = `https://oapi.dingtalk.com/robot/send?access_token=${dingtalkBotToken}${secretParam}`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
json: {
msgtype: 'text',
text: {
@@ -243,8 +241,7 @@ export default class NotificationService {
const url = `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${weWorkBotKey}`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
json: {
msgtype: 'text',
text: {
@@ -263,8 +260,7 @@ export default class NotificationService {
const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken`;
const tokenRes: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
json: {
corpid,
corpsecret,
@@ -314,8 +310,7 @@ export default class NotificationService {
.post(
`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${tokenRes.access_token}`,
{
timeout: this.timeout,
retry: 0,
...this.gotOption,
json: {
touser,
agentid,
@@ -329,13 +324,53 @@ export default class NotificationService {
return res.errcode === 0;
}
private async aibotk() {
const { aibotkKey, aibotkType, aibotkName } = this.params;
let url = '';
let json = {};
switch (aibotkType) {
case 'room':
url = 'https://api-bot.aibotk.com/openapi/v1/chat/room';
json = {
apiKey: `${aibotkKey}`,
roomName: `${aibotkName}`,
message: {
type: 1,
content: `【青龙快讯】\n\n${this.title}\n${this.content}`,
},
};
break;
case 'contact':
url = 'https://api-bot.aibotk.com/openapi/v1/chat/contact';
json = {
apiKey: `${aibotkKey}`,
name: `${aibotkName}`,
message: {
type: 1,
content: `【青龙快讯】\n\n${this.title}\n${this.content}`,
},
};
break;
}
const res: any = await got
.post(url, {
...this.gotOption,
json: {
...json,
},
})
.json();
return res.code === 0;
}
private async iGot() {
const { iGotPushKey } = this.params;
const url = `https://push.hellyw.com/${iGotPushKey.toLowerCase()}`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
body: `title=${this.title}&content=${this.content}`,
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
})
@@ -349,8 +384,7 @@ export default class NotificationService {
const url = `https://www.pushplus.plus/send`;
const res: any = await got
.post(url, {
timeout: this.timeout,
retry: 0,
...this.gotOption,
json: {
token: `${pushPlusToken}`,
title: `${this.title}`,
@@ -363,6 +397,21 @@ export default class NotificationService {
return res.code === 200;
}
private async lark() {
const { larkKey } = this.params;
const res: any = await got
.post(`https://open.feishu.cn/open-apis/bot/v2/hook/${larkKey}`, {
...this.gotOption,
json: {
msg_type: 'text',
content: { text: `${this.title}\n\n${this.content}` },
},
headers: { 'Content-Type': 'application/json' },
})
.json();
return res.StatusCode === 0;
}
private async email() {
const { emailPass, emailService, emailUser } = this.params;
const transporter = nodemailer.createTransport({
@@ -386,11 +435,19 @@ export default class NotificationService {
}
private async webhook() {
const { webhookUrl, webhookBody, webhookHeaders, webhookMethod, webhookContentType } =
this.params;
const {
webhookUrl,
webhookBody,
webhookHeaders,
webhookMethod,
webhookContentType,
} = this.params;
const { formatBody, formatUrl } = this.formatNotifyContent(webhookUrl, webhookBody);
if (!formatUrl && !formatBody) {
const { formatBody, formatUrl } = this.formatNotifyContent(
webhookUrl,
webhookBody,
);
if (!formatUrl && !formatBody) {
return false;
}
const headers = parseHeaders(webhookHeaders);
@@ -399,11 +456,10 @@ export default class NotificationService {
const options = {
method: webhookMethod,
headers,
timeout: this.timeout,
retry: 0,
...this.gotOption,
allowGetBody: true,
...bodyParam
}
...bodyParam,
};
const res = await got(formatUrl, options);
return String(res.statusCode).startsWith('20');
}
@@ -427,8 +483,12 @@ export default class NotificationService {
}
return {
formatUrl: url.replaceAll('$title', encodeURIComponent(this.title)).replaceAll('$content', encodeURIComponent(this.content)),
formatBody: body.replaceAll('$title', this.title).replaceAll('$content', this.content),
}
formatUrl: url
.replaceAll('$title', encodeURIComponent(this.title))
.replaceAll('$content', encodeURIComponent(this.content)),
formatBody: body
.replaceAll('$title', this.title)
.replaceAll('$content', this.content),
};
}
}
+1 -6
View File
@@ -89,8 +89,7 @@ export default class SystemService {
let lastLog = '';
try {
const result = await got.get(config.lastVersionFile, {
timeout: 6000,
retry: 0,
timeout: 30000,
});
const lastVersionFileContent = result.body;
lastVersion = lastVersionFileContent.match(versionRegx)![1];
@@ -137,10 +136,6 @@ export default class SystemService {
public async updateSystem() {
const cp = spawn('ql -l update', { shell: '/bin/bash' });
this.sockService.sendMessage({
type: 'updateSystemVersion',
message: `开始更新系统`,
});
cp.stdout.on('data', (data) => {
this.sockService.sendMessage({
type: 'updateSystemVersion',
+23 -14
View File
@@ -24,7 +24,7 @@ export default class UserService {
@Inject('logger') private logger: winston.Logger,
private scheduleService: ScheduleService,
private sockService: SockService,
) { }
) {}
public async login(
payloads: {
@@ -67,7 +67,9 @@ export default class UserService {
const retriesTime = Math.pow(3, retries) * 1000;
if (retries > 2 && timestamp - lastlogon < retriesTime) {
const waitTime = Math.ceil((retriesTime - (timestamp - lastlogon)) / 1000);
const waitTime = Math.ceil(
(retriesTime - (timestamp - lastlogon)) / 1000,
);
return {
code: 410,
message: `失败次数过多,请${waitTime}秒后重试`,
@@ -75,18 +77,23 @@ export default class UserService {
};
}
if (
username === cUsername &&
password === cPassword &&
twoFactorActivated &&
needTwoFactor
) {
this.updateAuthInfo(content, {
isTwoFactorChecking: true,
});
return {
code: 420,
message: '',
};
}
const { ip, address } = await getNetIp(req);
if (username === cUsername && password === cPassword) {
if (twoFactorActivated && needTwoFactor) {
this.updateAuthInfo(content, {
isTwoFactorChecking: true,
});
return {
code: 420,
message: '',
};
}
const data = createRandomString(50, 100);
const expiration = twoFactorActivated ? 60 : 20;
let token = jwt.sign({ data }, config.secret as any, {
@@ -109,7 +116,8 @@ export default class UserService {
});
await this.notificationService.notify(
'登录通知',
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${req.platform
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${
req.platform
}端 登录成功,ip地址 ${ip}`,
);
await this.getLoginLog();
@@ -137,7 +145,8 @@ export default class UserService {
});
await this.notificationService.notify(
'登录通知',
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${req.platform
`你于${dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')}${address} ${
req.platform
}端 登录失败,ip地址 ${ip}`,
);
await this.getLoginLog();
+9
View File
@@ -151,4 +151,13 @@ export DEER_KEY=""
export CHAT_URL=""
export CHAT_TOKEN=""
## 13. aibotk
## 官方说明文档:http://wechat.aibotk.com/oapi/oapi?from=ql
## aibotk_key (必填)填写智能微秘书个人中心的apikey
export AIBOTK_KEY=""
## aibotk_type (必填)填写发送的目标 room 或 contact, 填其他的不生效
export AIBOTK_TYPE=""
## aibotk_name (必填)填写群名或用户昵称,和上面的type类型要对应
export AIBOTK_NAME=""
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可
+188 -153
View File
@@ -110,6 +110,20 @@ let PUSH_PLUS_USER = '';
let QQ_SKEY = '';
let QQ_MODE = '';
// =======================================智能微秘书设置区域=======================================
//官方文档:http://wechat.aibotk.com/docs/about
//AIBOTK_KEY 填写智能微秘书个人中心的apikey
//AIBOTK_TYPE:填写发送的目标 room 或 contact, 填其他的不生效
//AIBOTK_NAME: 填写群名或用户昵称,和上面的type类型要对应
let AIBOTK_KEY = '';
let AIBOTK_TYPE = '';
let AIBOTK_NAME = '';
// =======================================飞书机器人设置区域=======================================
//官方文档:https://www.feishu.cn/hc/zh-CN/articles/360024984973
//FSKEY 飞书机器人的 FSKEY
let FSKEY = '';
//==========================云端环境变量的判断与接收=========================
if (process.env.GOTIFY_URL) {
GOTIFY_URL = process.env.GOTIFY_URL;
@@ -220,6 +234,20 @@ if (process.env.PUSH_PLUS_TOKEN) {
if (process.env.PUSH_PLUS_USER) {
PUSH_PLUS_USER = process.env.PUSH_PLUS_USER;
}
if (process.env.AIBOTK_KEY) {
AIBOTK_KEY = process.env.AIBOTK_KEY;
}
if (process.env.AIBOTK_TYPE) {
AIBOTK_TYPE = process.env.AIBOTK_TYPE;
}
if (process.env.AIBOTK_NAME) {
AIBOTK_NAME = process.env.AIBOTK_NAME;
}
if (process.env.FSKEY) {
FSKEY = process.env.FSKEY;
}
//==========================云端环境变量的判断与接收=========================
/**
@@ -255,6 +283,8 @@ async function sendNotify(
gotifyNotify(text, desp), //gotify
ChatNotify(text, desp), //synolog chat
PushDeerNotify(text, desp), //PushDeer
aibotkNotify(text, desp), //智能微秘书
fsBotNotify(text, desp), //飞书机器人
]);
}
@@ -295,7 +325,7 @@ function gotifyNotify(text, desp) {
});
}
function gobotNotify(text, desp, time = 2100) {
function gobotNotify(text, desp) {
return new Promise((resolve) => {
if (GOBOT_URL) {
const options = {
@@ -306,38 +336,34 @@ function gobotNotify(text, desp, time = 2100) {
},
timeout,
};
setTimeout(() => {
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('发送go-cqhttp通知调用API失败!!\n');
console.log(err);
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('发送go-cqhttp通知调用API失败!!\n');
console.log(err);
} else {
data = JSON.parse(data);
if (data.retcode === 0) {
console.log('go-cqhttp发送通知消息成功🎉\n');
} else if (data.retcode === 100) {
console.log(`go-cqhttp发送通知消息异常: ${data.errmsg}\n`);
} else {
data = JSON.parse(data);
if (data.retcode === 0) {
console.log('go-cqhttp发送通知消息成功🎉\n');
} else if (data.retcode === 100) {
console.log(`go-cqhttp发送通知消息异常: ${data.errmsg}\n`);
} else {
console.log(
`go-cqhttp发送通知消息异常\n${JSON.stringify(data)}`,
);
}
console.log(`go-cqhttp发送通知消息异常\n${JSON.stringify(data)}`);
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
}, time);
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function serverNotify(text, desp, time = 2100) {
function serverNotify(text, desp) {
return new Promise((resolve) => {
if (SCKEY) {
//微信server酱推送通知一个\n不会换行,需要两个\n才能换行,故做此替换
@@ -352,33 +378,29 @@ function serverNotify(text, desp, time = 2100) {
},
timeout,
};
setTimeout(() => {
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('发送通知调用API失败!!\n');
console.log(err);
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('发送通知调用API失败!!\n');
console.log(err);
} else {
data = JSON.parse(data);
//server酱和Server酱·Turbo版的返回json格式不太一样
if (data.errno === 0 || data.data.errno === 0) {
console.log('server酱发送通知消息成功🎉\n');
} else if (data.errno === 1024) {
// 一分钟内发送相同的内容会触发
console.log(`server酱发送通知消息异常: ${data.errmsg}\n`);
} else {
data = JSON.parse(data);
//server酱和Server酱·Turbo版的返回json格式不太一样
if (data.errno === 0 || data.data.errno === 0) {
console.log('server酱发送通知消息成功🎉\n');
} else if (data.errno === 1024) {
// 一分钟内发送相同的内容会触发
console.log(`server酱发送通知消息异常: ${data.errmsg}\n`);
} else {
console.log(
`server酱发送通知消息异常\n${JSON.stringify(data)}`,
);
}
console.log(`server酱发送通知消息异常\n${JSON.stringify(data)}`);
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
}, time);
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
@@ -398,35 +420,29 @@ function PushDeerNotify(text, desp) {
},
timeout,
};
$.post(
options,
(err, resp, data) => {
try {
if (err) {
console.log('发送通知调用API失败!!\n');
console.log(err);
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('发送通知调用API失败!!\n');
console.log(err);
} else {
data = JSON.parse(data);
// 通过返回的result的长度来判断是否成功
if (
data.content.result.length !== undefined &&
data.content.result.length > 0
) {
console.log('PushDeer发送通知消息成功🎉\n');
} else {
data = JSON.parse(data);
// 通过返回的result的长度来判断是否成功
if (
data.content.result.length !== undefined &&
data.content.result.length > 0
) {
console.log('PushDeer发送通知消息成功🎉\n');
} else {
console.log(
`PushDeer发送通知消息异常\n${JSON.stringify(data)}`,
);
}
console.log(`PushDeer发送通知消息异常\n${JSON.stringify(data)}`);
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
},
time,
);
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
@@ -470,83 +486,6 @@ function ChatNotify(text, desp) {
});
}
function CoolPush(text, desp) {
return new Promise((resolve) => {
if (QQ_SKEY) {
let options = {
url: `https://push.xuthus.cc/${QQ_MODE}/${QQ_SKEY}`,
headers: {
'Content-Type': 'application/json',
},
};
// 已知敏感词
text = text.replace(/京豆/g, '豆豆');
desp = desp.replace(/京豆/g, '');
desp = desp.replace(/🐶/g, '');
desp = desp.replace(/红包/g, 'H包');
switch (QQ_MODE) {
case 'email':
options.json = {
t: text,
c: desp,
};
break;
default:
options.body = `${text}\n\n${desp}`;
}
let pushMode = function (t) {
switch (t) {
case 'send':
return '个人';
case 'group':
return 'QQ群';
case 'wx':
return '微信';
case 'ww':
return '企业微信';
case 'email':
return '邮件';
default:
return '未知方式';
}
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log(`发送${pushMode(QQ_MODE)}通知调用API失败!!\n`);
console.log(err);
} else {
data = JSON.parse(data);
if (data.code === 200) {
console.log(`酷推发送${pushMode(QQ_MODE)}通知消息成功🎉\n`);
} else if (data.code === 400) {
console.log(
`QQ酷推(Cool Push)发送${pushMode(QQ_MODE)}推送失败:${
data.msg
}\n`,
);
} else if (data.code === 503) {
console.log(`QQ酷推出错,${data.message}${data.data}\n`);
} else {
console.log(`酷推推送异常: ${JSON.stringify(data)}`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function BarkNotify(text, desp, params = {}) {
return new Promise((resolve) => {
if (BARK_PUSH) {
@@ -649,7 +588,7 @@ function ddBotNotify(text, desp) {
json: {
msgtype: 'text',
text: {
content: ` ${text}\n\n${desp}`,
content: `${text}\n\n${desp}`,
},
},
headers: {
@@ -716,7 +655,7 @@ function qywxBotNotify(text, desp) {
json: {
msgtype: 'text',
text: {
content: ` ${text}\n\n${desp}`,
content: `${text}\n\n${desp}`,
},
},
headers: {
@@ -983,6 +922,102 @@ function pushPlusNotify(text, desp) {
});
}
function aibotkNotify(text, desp) {
return new Promise((resolve) => {
if (AIBOTK_KEY && AIBOTK_TYPE && AIBOTK_NAME) {
let json = {};
let url = '';
switch (AIBOTK_TYPE) {
case 'room':
url = 'https://api-bot.aibotk.com/openapi/v1/chat/room';
json = {
apiKey: `${AIBOTK_KEY}`,
roomName: `${AIBOTK_NAME}`,
message: {
type: 1,
content: `【青龙快讯】\n\n${text}\n${desp}`,
},
};
break;
case 'contact':
url = 'https://api-bot.aibotk.com/openapi/v1/chat/contact';
json = {
apiKey: `${AIBOTK_KEY}`,
name: `${AIBOTK_NAME}`,
message: {
type: 1,
content: `【青龙快讯】\n\n${text}\n${desp}`,
},
};
break;
}
const options = {
url: url,
json,
headers: {
'Content-Type': 'application/json',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('智能微秘书发送通知消息失败!!\n');
console.log(err);
} else {
data = JSON.parse(data);
if (data.code === 0) {
console.log('智能微秘书发送通知消息成功🎉。\n');
} else {
console.log(`${data.error}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
}
});
}
function fsBotNotify(text, desp) {
return new Promise((resolve) => {
if (FSKEY) {
const options = {
url: `https://open.feishu.cn/open-apis/bot/v2/hook/${FSKEY}`,
json: { msg_type: 'text', content: { text: `${title}\n\n${content}` } },
headers: {
'Content-Type': 'application/json',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('发送通知调用API失败!!\n');
console.log(err);
} else {
data = JSON.parse(data);
if (data.StatusCode === 0) {
console.log('飞书发送通知消息成功🎉\n');
} else {
console.log(`${data.msg}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
module.exports = {
sendNotify,
BARK_PUSH,
+39
View File
@@ -81,6 +81,10 @@ push_config = {
'TG_PROXY_AUTH': '', # tg 代理认证参数
'TG_PROXY_HOST': '', # tg 机器人的 TG_PROXY_HOST
'TG_PROXY_PORT': '', # tg 机器人的 TG_PROXY_PORT
'AIBOTK_KEY': '', # 智能微秘书 个人中心的apikey 文档地址:http://wechat.aibotk.com/docs/about
'AIBOTK_TYPE': '', # 智能微秘书 发送目标 room 或 contact
'AIBOTK_NAME': '', # 智能微秘书 发送群名 或者好友昵称和type要对应好
}
notify_function = []
# fmt: on
@@ -521,6 +525,39 @@ def telegram_bot(title: str, content: str) -> None:
print("tg 推送失败!")
def aibotk(title: str, content: str) -> None:
"""
使用 智能微秘书 推送消息。
"""
if not push_config.get("AIBOTK_KEY") or not push_config.get("AIBOTK_TYPE") or not push_config.get("AIBOTK_NAME"):
print("智能微秘书 的 AIBOTK_KEY 或者 AIBOTK_TYPE 或者 AIBOTK_NAME 未设置!!\n取消推送")
return
print("智能微秘书 服务启动")
if push_config.get("AIBOTK_TYPE") == 'room':
url = "https://api-bot.aibotk.com/openapi/v1/chat/room"
data = {
"apiKey": push_config.get("AIBOTK_KEY"),
"roomName": push_config.get("AIBOTK_NAME"),
"message": {"type": 1, "content": f'【青龙快讯】\n\n${title}\n${content}' }
}
else:
url = "https://api-bot.aibotk.com/openapi/v1/chat/contact"
data = {
"apiKey": push_config.get("AIBOTK_KEY"),
"name": push_config.get("AIBOTK_NAME"),
"message": {"type": 1, "content": f'【青龙快讯】\n\n${title}\n${content}' }
}
body = json.dumps(data).encode(encoding="utf-8")
headers = {"Content-Type": "application/json"}
response = requests.post(url=url, data=body, headers=headers).json()
print(response)
if response["code"] == 0:
print("智能微秘书 推送成功!")
else:
print(f'智能微秘书 推送失败!{response["error"]}')
def one() -> str:
"""
获取一条一言。
@@ -561,6 +598,8 @@ if push_config.get("QYWX_KEY"):
notify_function.append(wecom_bot)
if push_config.get("TG_BOT_TOKEN") and push_config.get("TG_USER_ID"):
notify_function.append(telegram_bot)
if push_config.get("AIBOTK_KEY") and push_config.get("AIBOTK_TYPE") and push_config.get("AIBOTK_NAME"):
notify_function.append(aibotk)
def send(title: str, content: str) -> None:
+17 -10
View File
@@ -75,13 +75,13 @@ run_nohup() {
}
check_server() {
cpu_use=$(top -b -n 1 | grep CPU | grep -v -E 'grep|PID' | awk '{print $2}' | cut -f 1 -d "%")
cpu_use=$(top -b -n 1 | grep CPU | grep -v -E 'grep|PID' | awk '{print $2}' | cut -f 1 -d "%" | head -n 1)
mem_free=$(free -m | grep "Mem" | awk '{print $3}')
mem_total=$(free -m | grep "Mem" | awk '{print $2}')
mem_use=$(printf "%d%%" $((mem_free * 100 / mem_total)) | cut -f 1 -d "%")
mem_free=$(free -m | grep "Mem" | awk '{print $3}' | head -n 1)
mem_total=$(free -m | grep "Mem" | awk '{print $2}' | head -n 1)
mem_use=$(printf "%d%%" $((mem_free * 100 / mem_total)) | cut -f 1 -d "%" | head -n 1)
disk_use=$(df -P | grep /dev | grep -v -E '(tmp|boot|shm)' | awk '{print $5}' | cut -f 1 -d "%")
disk_use=$(df -P | grep /dev | grep -v -E '(tmp|boot|shm)' | awk '{print $5}' | cut -f 1 -d "%" | head -n 1)
if [[ $cpu_use -gt $cpu_warn ]] || [[ $mem_free -lt $mem_warn ]] || [[ $disk_use -gt $disk_warn ]]; then
local resource=$(top -b -n 1 | grep -v -E 'grep|Mem|idle|Load' | awk '{$2="";$3="";$4="";$5="";$7="";print $0}' | head -n 10)
@@ -97,7 +97,11 @@ handle_task_before() {
[[ $is_macos -eq 0 ]] && check_server
[[ -f $task_error_log_path ]] && cat $task_error_log_path
if [[ -s $task_error_log_path ]]; then
eval cat $task_error_log_path $cmd
eval echo -e "加载 config.sh 出错,请手动检查" $cmd
eval echo $cmd
fi
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
. $file_task_before "$@"
@@ -105,9 +109,12 @@ handle_task_before() {
handle_task_after() {
. $file_task_after "$@"
local end_time=$(date '+%Y-%m-%d %H:%M:%S')
local end_timestamp=$(date "+%s")
local etime=$(date "+$time_format")
local end_time=$(format_time "$time_format" "$etime")
local end_timestamp=$(format_timestamp "$time_format" "$etime")
local diff_time=$(($end_timestamp - $begin_timestamp))
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
echo -e "\n\n## 执行结束... $end_time 耗时 $diff_time"
echo -e "\n     "
@@ -142,7 +149,7 @@ run_concurrent() {
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
@@ -190,7 +197,7 @@ run_designated() {
local envs=$(eval echo "\$${env_param}")
local array=($(echo $envs | sed 's/&/ /g'))
local tempArr=$(echo $num_param | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local tempArr=$(echo $num_param | sed "s/-max/-${#array[@]}/g" | sed "s/max-/${#array[@]}-/g" | perl -pe "s|(\d+)(-\|~\|_)(\d+)|{\1..\3}|g")
local runArr=($(eval echo $tempArr))
runArr=($(awk -v RS=' ' '!a[$1]++' <<<${runArr[@]}))
+1 -1
View File
@@ -486,4 +486,4 @@ detect_macos
define_cmd
fix_config
import_config $1 >$task_error_log_path 2>&1
import_config $1 2>$task_error_log_path
+29 -16
View File
@@ -230,10 +230,10 @@ run_extra_shell() {
## 脚本用法
usage() {
echo -e "本脚本用法:"
echo -e "ql命令使用方法:"
echo -e "1. $cmd_update update # 更新并重启青龙"
echo -e "2. $cmd_update extra # 运行自定义脚本"
echo -e "3. $cmd_update raw <fileurl> # 更新单个脚本文件"
echo -e "3. $cmd_update raw <fileurl> # 更新单个脚本文件"
echo -e "4. $cmd_update repo <repourl> <path> <blacklist> <dependence> <branch> <extensions> # 更新单个仓库的脚本"
echo -e "5. $cmd_update rmlog <days> # 删除旧日志"
echo -e "6. $cmd_update bot # 启动tg-bot"
@@ -440,18 +440,26 @@ main() {
cmd=">> $file_path 2>&1"
[[ "$show_log" == "true" ]] && cmd=""
[[ -f $task_error_log_path ]] && eval cat $task_error_log_path $cmd
if [[ "$show_log" == "true" ]] && [[ $ID ]]; then
eval echo -e "请移除 -l 参数" $cmd
exit 1
fi
local time_format="%Y-%m-%d %H:%M:%S"
local time=$(date "+$time_format")
local begin_timestamp=$(format_timestamp "$time_format" "$time")
[[ $ID ]] && update_cron "\"$ID\"" "0" "$$" "$log_path" "$begin_timestamp"
local begin_time=$(format_time "$time_format" "$time")
eval echo -e "\#\# 开始执行... $begin_time\\\n" $cmd
if [[ -s $task_error_log_path ]]; then
eval cat $task_error_log_path $cmd
eval echo -e "加载 config.sh 出错,请手动检查" $cmd
eval echo $cmd
fi
if [[ "$show_log" == "true" ]] && [[ $ID ]]; then
eval echo -e "请移除 -l 参数" $cmd
exit 1
fi
case $p1 in
update)
eval update_qinglong "$2" $cmd
@@ -464,7 +472,7 @@ main() {
if [[ -n $p2 ]]; then
update_repo "$p2" "$p3" "$p4" "$p5" "$p6" "$p7"
else
eval echo -e "命令输入错误...\\\n"
eval echo -e "命令输入错误...\\\n" $cmd
eval usage $cmd
fi
;;
@@ -473,7 +481,7 @@ main() {
if [[ -n $p2 ]]; then
update_raw "$p2"
else
eval echo -e "命令输入错误...\\\n"
eval echo -e "命令输入错误...\\\n" $cmd
eval usage $cmd
fi
;;
@@ -488,13 +496,13 @@ main() {
;;
resetlet)
auth_value=$(cat $file_auth_user | jq '.retries =0' -c)
echo -e "重置登录错误次数成功 \n $auth_value" >>$log_path
echo "$auth_value" >$file_auth_user
echo -e "重置登录错误次数成功" $cmd
;;
resettfa)
auth_value=$(cat $file_auth_user | jq '.twoFactorActivated =false' | jq '.twoFactorActived =false' -c)
echo -e "禁用两步验证成功 \n $auth_value" >>$log_path
echo "$auth_value" >$file_auth_user
eval echo -e "禁用两步验证成功" $cmd
;;
*)
eval echo -e "命令输入错误...\\\n" $cmd
@@ -502,11 +510,16 @@ main() {
;;
esac
local etime=$(date "+$time_format")
local end_time=$(format_time "$time_format" "$etime")
local end_timestamp=$(format_timestamp "$time_format" "$etime")
local diff_time=$(($end_timestamp - $begin_timestamp))
eval echo -e "\\\n\#\# 执行结束... $end_time 耗时 $diff_time" $cmd
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
eval echo -e "\\\n          " $cmd
if [[ -f $file_path ]]; then
local end_timestamp=$(date "+%s")
local diff_time=$(($end_timestamp - $begin_timestamp))
[[ $ID ]] && update_cron "\"$ID\"" "1" "" "$log_path" "$begin_timestamp" "$diff_time"
eval echo -e "\\\n          " $cmd
cat $file_path
fi
}
+9 -4
View File
@@ -1,4 +1,4 @@
import React, { PureComponent, Fragment, useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import {
Button,
message,
@@ -388,6 +388,7 @@ const Crontab = () => {
const [cronViews, setCronViews] = useState<any[]>([]);
const [enabledCronViews, setEnabledCronViews] = useState<any[]>([]);
const [moreMenuActive, setMoreMenuActive] = useState(false);
const tableRef = useRef<any>();
const goToScriptManager = (record: any) => {
const cmd = record.command.split(' ') as string[];
@@ -874,12 +875,15 @@ const Crontab = () => {
sorter: {},
filters: {},
});
setTimeout(() => {
setTableScrollHeight(getTableScroll());
});
getCronViews();
}, []);
useEffect(() => {
if (tableRef.current) {
setTableScrollHeight(getTableScroll());
}
}, [tableRef.current]);
const panelContent = (
<>
{selectedRowIds.length > 0 && (
@@ -939,6 +943,7 @@ const Crontab = () => {
</div>
)}
<Table
ref={tableRef}
columns={columns}
pagination={{
current: pageConf.page,
+5 -3
View File
@@ -168,6 +168,7 @@ const Dependence = () => {
const [logDependence, setLogDependence] = useState<any>();
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
const [type, setType] = useState('nodejs');
const tableRef = useRef<any>();
const getDependencies = () => {
setLoading(true);
@@ -368,10 +369,10 @@ const Dependence = () => {
}, [searchText, type]);
useEffect(() => {
setTimeout(() => {
if (tableRef.current) {
setTableScrollHeight(getTableScroll({ extraHeight: 87 }));
});
}, []);
}
}, [tableRef.current]);
useEffect(() => {
if (logDependence) {
@@ -454,6 +455,7 @@ const Dependence = () => {
)}
<DndProvider backend={HTML5Backend}>
<Table
ref={tableRef}
columns={columns}
rowSelection={rowSelection}
pagination={false}
+5 -3
View File
@@ -254,6 +254,7 @@ const Env = () => {
const [searchText, setSearchText] = useState('');
const [tableScrollHeight, setTableScrollHeight] = useState<number>();
const [importLoading, setImportLoading] = useState(false);
const tableRef = useRef<any>();
const getEnvs = () => {
setLoading(true);
@@ -508,10 +509,10 @@ const Env = () => {
}, [searchText]);
useEffect(() => {
setTimeout(() => {
if (tableRef.current) {
setTableScrollHeight(getTableScroll({ extraHeight: 87 }));
});
}, []);
}
}, [tableRef.current]);
return (
<PageContainer
@@ -587,6 +588,7 @@ const Env = () => {
)}
<DndProvider backend={HTML5Backend}>
<Table
ref={tableRef}
columns={columns}
rowSelection={rowSelection}
pagination={false}
+3 -5
View File
@@ -108,10 +108,9 @@ const CheckUpdate = ({ socketMessage }: any) => {
maskClosable: false,
closable: false,
okButtonProps: { disabled: true },
title: '更新日志',
title: '更新中...',
centered: true,
content: (
<div style={{ height: '60vh', overflowY: 'auto' }}>
<pre
style={{
wordBreak: 'break-all',
@@ -122,7 +121,6 @@ const CheckUpdate = ({ socketMessage }: any) => {
>
{value}
</pre>
</div>
),
});
};
@@ -145,7 +143,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
closable: updateFailed,
okButtonProps: { disabled: !updateFailed },
content: (
<div style={{ height: '60vh', overflowY: 'auto' }}>
<>
<pre
style={{
wordBreak: 'break-all',
@@ -157,7 +155,7 @@ const CheckUpdate = ({ socketMessage }: any) => {
{newMessage}
</pre>
<div id="log-identifier" style={{ paddingBottom: 5 }}></div>
</div>
</>
),
});
+9 -4
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import {
Button,
message,
@@ -247,6 +247,7 @@ const Subscription = () => {
const [searchValue, setSearchValue] = useState('');
const [isLogModalVisible, setIsLogModalVisible] = useState(false);
const [logSubscription, setLogSubscription] = useState<any>();
const tableRef = useRef<any>();
const runSubscription = (record: any, index: number) => {
Modal.confirm({
@@ -539,11 +540,14 @@ const Subscription = () => {
useEffect(() => {
setPageSize(parseInt(localStorage.getItem('pageSize') || '20'));
setTimeout(() => {
setTableScrollHeight(getTableScroll());
});
}, []);
useEffect(() => {
if (tableRef.current) {
setTableScrollHeight(getTableScroll());
}
}, [tableRef.current]);
return (
<PageContainer
className="ql-container-wrapper subscriptiontab-wrapper"
@@ -568,6 +572,7 @@ const Subscription = () => {
}}
>
<Table
ref={tableRef}
columns={columns}
pagination={{
current: currentPage,
+36 -1
View File
@@ -88,10 +88,12 @@ export default {
{ value: 'dingtalkBot', label: '钉钉机器人' },
{ value: 'weWorkBot', label: '企业微信机器人' },
{ value: 'weWorkApp', label: '企业微信应用' },
{ value: 'aibotk', label: '智能微秘书' },
{ value: 'iGot', label: 'IGot' },
{ value: 'pushPlus', label: 'PushPlus' },
{ value: 'chat', label: '群辉chat' },
{ value: 'email', label: '邮箱' },
{ value: 'lark', label: '飞书机器人' },
{ value: 'webhook', label: '自定义通知' },
{ value: 'closed', label: '已关闭' },
],
@@ -196,6 +198,28 @@ export default {
required: true,
},
],
aibotk: [
{
label: 'aibotkKey',
tip: '密钥key,智能微秘书个人中心获取apikey,申请地址:https://wechat.aibotk.com/signup?from=ql',
required: true,
},
{
label: 'aibotkType',
tip: '发送的目标,群组或者好友',
required: true,
placeholder: '请输入要发送的目标',
items: [
{ value: 'room', label: '群聊' },
{ value: 'contact', label: '好友' },
],
},
{
label: 'aibotkName',
tip: '要发送的用户昵称或群名,如果目标是群,需要填群名,如果目标是好友,需要填好友昵称',
required: true,
},
],
iGot: [
{
label: 'iGotPushKey',
@@ -214,6 +238,13 @@ export default {
tip: '一对多推送的“群组编码”(一对多推送下面->您的群组(如无则新建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)',
},
],
lark: [
{
label: 'larkKey',
tip: '飞书群组机器人:https://www.feishu.cn/hc/zh-CN/articles/360024984973',
required: true,
},
],
email: [
{
label: 'emailService',
@@ -234,7 +265,11 @@ export default {
label: 'webhookContentType',
tip: '请求头Content-Type',
required: true,
items: [{ value: 'application/json' }, { value: 'multipart/form-data' }, { value: 'application/x-www-form-urlencoded' }],
items: [
{ value: 'application/json' },
{ value: 'multipart/form-data' },
{ value: 'application/x-www-form-urlencoded' },
],
},
{
label: 'webhookUrl',
+8 -7
View File
@@ -1,8 +1,9 @@
export const version = '2.14.8';
export const changeLogLink = 'https://t.me/jiao_long/335';
export const changeLog = `2.14.8 版本说明
1. 支持环境变量导入,导入格式参考导出文件
2. 修复创建任务,命令中的引号丢失
3. 修复调试选择脚本及语言识别
4. 修复ql和task命令日志打印
export const version = '2.14.9';
export const changeLogLink = 'https://t.me/jiao_long/336';
export const changeLog = `2.14.9 版本说明
1. 通知支持飞书和智能微秘书,感谢 https://github.com/leochen-g
2. webhook通知body支持嵌套json
3. 修复shell获取磁盘占用
4. 修复定时任务移动端滚动
5. 其他bug修复
`;