增加chronocat无头模式的QQNT推送 用于无法使go-cqhttp的接替 (#2141)

* 增加chronocat无头模式的QQNT推送

* Chronocat发送时没有配置群号或个人消息号发送出错

* 增加系统通知、去除注释

* 增加系统通知

---------

Co-authored-by: child <wulincheng@javalc.com>
This commit is contained in:
LinCheng Wu 2023-10-14 20:56:32 +08:00 committed by GitHub
parent 9f7beb934d
commit 5055045d22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 295 additions and 26 deletions

View File

@ -18,6 +18,7 @@ export enum NotificationMode {
'pushMe' = 'pushMe',
'feishu' = 'feishu',
'webhook' = 'webhook',
'chronocat' = 'Chronocat',
}
abstract class NotificationBaseInfo {
@ -108,6 +109,12 @@ export class PushMeNotification extends NotificationBaseInfo {
public pushMeKey: string = '';
}
export class ChronocatNotification extends NotificationBaseInfo {
public chronocatURL: string = '';
public chronocatQQ: string = '';
public chronocatToekn: string = '';
}
export class WebhookNotification extends NotificationBaseInfo {
public webhookHeaders: string = '';
public webhookBody: string = '';
@ -140,4 +147,6 @@ export interface NotificationInfo
EmailNotification,
PushMeNotification,
WebhookNotification,
ChronocatNotification,
LarkNotification {}

View File

@ -1,12 +1,12 @@
import { NotificationInfo } from '../data/notify';
import { Service, Inject } from 'typedi';
import winston from 'winston';
import UserService from './user';
import got from 'got';
import nodemailer from 'nodemailer';
import crypto from 'crypto';
import got from 'got';
import { HttpProxyAgent, HttpsProxyAgent } from 'hpagent';
import nodemailer from 'nodemailer';
import { Inject, Service } from 'typedi';
import winston from 'winston';
import { parseBody, parseHeaders } from '../config/util';
import { NotificationInfo } from '../data/notify';
import UserService from './user';
@Service()
export default class NotificationService {
@ -31,6 +31,7 @@ export default class NotificationService {
['pushMe', this.pushMe],
['webhook', this.webhook],
['lark', this.lark],
['chronocat', this.chronocat],
]);
private title = '';
@ -195,7 +196,8 @@ export default class NotificationService {
}
private async bark() {
let { barkPush, barkIcon, barkSound, barkGroup, barkLevel, barkUrl } = this.params;
let { barkPush, barkIcon, barkSound, barkGroup, barkLevel, barkUrl } =
this.params;
if (!barkPush.startsWith('http')) {
barkPush = `https://api.day.app/${barkPush}`;
}
@ -588,6 +590,63 @@ export default class NotificationService {
}
}
private async chronocat() {
const { chronocatURL, chronocatQQ, chronocatToekn } = this.params;
try {
const user_ids = chronocatQQ
.match(/user_id=(\d+)/g)
?.map((match: any) => match.split('=')[1]);
const group_ids = chronocatQQ
.match(/group_id=(\d+)/g)
?.map((match: any) => match.split('=')[1]);
const url = `${chronocatURL}/api/message/send`;
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${chronocatToekn}`,
};
for (const [chat_type, ids] of [
[1, user_ids],
[2, group_ids],
]) {
if (!ids) {
continue;
}
let _ids: any = ids;
for (const chat_id of _ids) {
const data = {
peer: {
chatType: chat_type,
peerUin: chat_id,
},
elements: [
{
elementType: 1,
textElement: {
content: `${this.title}\n\n${this.content}`,
},
},
],
};
const res: any = await got.post(url, {
...this.gotOption,
json: data,
headers,
});
if (res.body === 'success') {
return true;
} else {
throw new Error(res.body);
}
}
}
return false;
} catch (error: any) {
throw new Error(error.response ? error.response.body : error);
}
}
private async webhook() {
const {
webhookUrl,

View File

@ -173,4 +173,13 @@ export SMTP_NAME=""
## PUSHME_KEY (必填)填写PushMe APP上获取的push_key
export PUSHME_KEY=""
## 13. CHRONOCAT
## CHRONOCAT_URL 推送 http://127.0.0.1:16530
## CHRONOCAT_TOKEN 填写在CHRONOCAT文件生成的访问密钥
## CHRONOCAT_QQ 个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如user_id=xxx;group_id=xxxx;group_id=xxxxx
## CHRONOCAT相关API https://chronocat.vercel.app/install/docker/official/
export CHRONOCAT_URL=""
export CHRONOCAT_QQ="" #
export CHRONOCAT_TOKEN=""
## 其他需要的变量,脚本中需要的变量使用 export 变量名= 声明即可

View File

@ -150,6 +150,15 @@ let SMTP_NAME = '';
//此处填你的PushMe KEY.
let PUSHME_KEY = '';
// =======================================CHRONOCAT通知设置区域===========================================
// CHRONOCAT_URL Red协议连接地址 例: http://127.0.0.1:16530
// CHRONOCAT_TOKEN 填写在CHRONOCAT文件生成的访问密钥
// CHRONOCAT_QQ 个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群
// CHRONOCAT相关API https://chronocat.vercel.app/install/docker/official/
let CHRONOCAT_URL = ''; // CHRONOCAT Red协议连接地址
let CHRONOCAT_TOKEN = ''; //CHRONOCAT 生成的访问密钥
let CHRONOCAT_QQ = ''; // 个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如user_id=xxx;group_id=xxxx;group_id=xxxxx
//==========================云端环境变量的判断与接收=========================
if (process.env.GOTIFY_URL) {
GOTIFY_URL = process.env.GOTIFY_URL;
@ -306,6 +315,16 @@ if (process.env.SMTP_NAME) {
if (process.env.PUSHME_KEY) {
PUSHME_KEY = process.env.PUSHME_KEY;
}
if (process.env.CHRONOCAT_URL) {
CHRONOCAT_URL = process.env.CHRONOCAT_URL;
}
if (process.env.CHRONOCAT_QQ) {
CHRONOCAT_QQ = process.env.CHRONOCAT_QQ;
}
if (process.env.CHRONOCAT_TOKEN) {
CHRONOCAT_TOKEN = process.env.CHRONOCAT_TOKEN;
}
//==========================云端环境变量的判断与接收=========================
/**
@ -355,6 +374,7 @@ async function sendNotify(
fsBotNotify(text, desp), //飞书机器人
smtpNotify(text, desp), //SMTP 邮件
PushMeNotify(text, desp, params), //PushMe
ChronocatNotify(text, desp), // Chronocat
]);
}
@ -1171,6 +1191,81 @@ function PushMeNotify(text, desp, params = {}) {
});
}
function ChronocatNotify(title, desp) {
return new Promise((resolve) => {
if (!CHRONOCAT_TOKEN || !CHRONOCAT_QQ || !CHRONOCAT_URL) {
console.log(
'CHRONOCAT 服务的 CHRONOCAT_URL 或 CHRONOCAT_QQ 未设置!!\n取消推送',
);
return;
}
console.log('CHRONOCAT 服务启动');
const user_ids = CHRONOCAT_QQ.match(/user_id=(\d+)/g)?.map(
(match) => match.split('=')[1],
);
const group_ids = CHRONOCAT_QQ.match(/group_id=(\d+)/g)?.map(
(match) => match.split('=')[1],
);
const url = `${CHRONOCAT_URL}/api/message/send`;
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${CHRONOCAT_TOKEN}`,
};
for (const [chat_type, ids] of [
[1, user_ids],
[2, group_ids],
]) {
if (!ids) {
continue;
}
for (const chat_id of ids) {
const data = {
peer: {
chatType: chat_type,
peerUin: chat_id,
},
elements: [
{
elementType: 1,
textElement: {
content: `${title}\n\n${desp}`,
},
},
],
};
const options = {
url: url,
json: data,
headers,
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('Chronocat发送QQ通知消息失败\n');
console.log(err);
} else {
data = JSON.parse(data);
if (chat_type === 1) {
console.log(`QQ个人消息:${ids}推送成功!`);
} else {
console.log(`QQ群消息:${ids}推送成功!`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
}
}
});
}
module.exports = {
sendNotify,
BARK_PUSH,

View File

@ -102,6 +102,9 @@ push_config = {
'SMTP_NAME': '', # SMTP 收发件人姓名,可随意填写
'PUSHME_KEY': '', # PushMe 酱的 PUSHME_KEY
'CHRONOCAT_QQ': '', # qq号
'CHRONOCAT_TOKEN': '', # CHRONOCAT 的token
'CHRONOCAT_URL': '' # CHRONOCAT的url地址
}
notify_function = []
# fmt: on
@ -664,6 +667,57 @@ def pushme(title: str, content: str) -> None:
else:
print(f"PushMe 推送失败!{response.status_code} {response.text}")
def chronocat(title: str, content: str) -> None:
"""
使用 CHRONOCAT 推送消息
"""
if not push_config.get("CHRONOCAT_URL") or not push_config.get("CHRONOCAT_QQ") or not push_config.get(
"CHRONOCAT_TOKEN"):
print("CHRONOCAT 服务的 CHRONOCAT_URL 或 CHRONOCAT_QQ 未设置!!\n取消推送")
return
print("CHRONOCAT 服务启动")
user_ids = re.findall(r"user_id=(\d+)", push_config.get("CHRONOCAT_QQ"))
group_ids = re.findall(r"group_id=(\d+)", push_config.get("CHRONOCAT_QQ"))
url = f'{push_config.get("CHRONOCAT_URL")}/api/message/send'
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {push_config.get("CHRONOCAT_TOKEN")}'
}
for chat_type, ids in [(1, user_ids), (2, group_ids)]:
if not ids:
continue
for chat_id in ids:
data = {
"peer": {
"chatType": chat_type,
"peerUin": chat_id
},
"elements": [
{
"elementType": 1,
"textElement": {
"content": f'{title}\n\n{content}'
}
}
]
}
response = requests.post(url, headers=headers, data=json.dumps(data))
if response.status_code == 200:
if chat_type == 1:
print(f'QQ个人消息:{ids}推送成功!')
else:
print(f'QQ群消息:{ids}推送成功!')
else:
if chat_type == 1:
print(f'QQ个人消息:{ids}推送失败!')
else:
print(f'QQ群消息:{ids}推送失败!')
def one() -> str:
"""

View File

@ -98,6 +98,7 @@ export default {
{ value: 'email', label: intl.get('邮箱') },
{ value: 'lark', label: intl.get('飞书机器人') },
{ value: 'pushMe', label: 'PushMe' },
{ value: 'chronocat', label: 'Chronocat' },
{ value: 'webhook', label: intl.get('自定义通知') },
{ value: 'closed', label: intl.get('已关闭') },
],
@ -126,14 +127,16 @@ export default {
goCqHttpBot: [
{
label: 'goCqHttpBotUrl',
tip: intl.get('推送到个人QQ: http://127.0.0.1/send_private_msghttp://127.0.0.1/send_group_msg',
tip: intl.get(
'推送到个人QQ: http://127.0.0.1/send_private_msghttp://127.0.0.1/send_group_msg',
),
required: true,
},
{ label: 'goCqHttpBotToken', tip: intl.get('访问密钥'), required: true },
{
label: 'goCqHttpBotQq',
tip: intl.get('如果GOBOT_URL设置 /send_private_msg 则需要填入 user_id=个人QQ 相反如果是 /send_group_msg 则需要填入 group_id=QQ群',
tip: intl.get(
'如果GOBOT_URL设置 /send_private_msg 则需要填入 user_id=个人QQ 相反如果是 /send_group_msg 则需要填入 group_id=QQ群',
),
required: true,
},
@ -153,14 +156,16 @@ export default {
},
{
label: 'pushDeerUrl',
tip: intl.get('PushDeer的自架API endpoint默认是 https://api2.pushdeer.com/message/push',
tip: intl.get(
'PushDeer的自架API endpoint默认是 https://api2.pushdeer.com/message/push',
),
},
],
bark: [
{
label: 'barkPush',
tip: intl.get('Bark的信息IP/设备码例如https://api.day.app/XXXXXXXX',
tip: intl.get(
'Bark的信息IP/设备码例如https://api.day.app/XXXXXXXX',
),
required: true,
},
@ -188,7 +193,8 @@ export default {
telegramBot: [
{
label: 'telegramBotToken',
tip: intl.get('telegram机器人的token例如1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw',
tip: intl.get(
'telegram机器人的token例如1077xxx4424:AAFjv0FcqxxxxxxgEMGfi22B4yh15R5uw',
),
required: true,
},
@ -201,7 +207,8 @@ export default {
{ label: 'telegramBotProxyPort', tip: intl.get('代理端口') },
{
label: 'telegramBotProxyAuth',
tip: intl.get('telegram代理配置认证参数用户名与密码用英文冒号连接 user:password',
tip: intl.get(
'telegram代理配置认证参数用户名与密码用英文冒号连接 user:password',
),
},
{
@ -212,20 +219,23 @@ export default {
dingtalkBot: [
{
label: 'dingtalkBotToken',
tip: intl.get('钉钉机器人webhook token例如5a544165465465645d0f31dca676e7bd07415asdasd',
tip: intl.get(
'钉钉机器人webhook token例如5a544165465465645d0f31dca676e7bd07415asdasd',
),
required: true,
},
{
label: 'dingtalkBotSecret',
tip: intl.get('密钥机器人安全设置页面加签一栏下面显示的SEC开头的字符串',
tip: intl.get(
'密钥机器人安全设置页面加签一栏下面显示的SEC开头的字符串',
),
},
],
weWorkBot: [
{
label: 'weWorkBotKey',
tip: intl.get('企业微信机器人的webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770)例如693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa',
tip: intl.get(
'企业微信机器人的webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770)例如693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa',
),
required: true,
},
@ -237,7 +247,8 @@ export default {
weWorkApp: [
{
label: 'weWorkAppKey',
tip: intl.get('corpid、corpsecret、touser(注:多个成员ID使用|隔开)、agentid、消息类型(选填,不填默认文本消息类型) 注意用,号隔开(英文输入法的逗号)例如wwcfrs,B-76WERQ,qinglong,1000001,2COat',
tip: intl.get(
'corpid、corpsecret、touser(注:多个成员ID使用|隔开)、agentid、消息类型(选填,不填默认文本消息类型) 注意用,号隔开(英文输入法的逗号)例如wwcfrs,B-76WERQ,qinglong,1000001,2COat',
),
required: true,
},
@ -249,7 +260,8 @@ export default {
aibotk: [
{
label: 'aibotkKey',
tip: intl.get('密钥key智能微秘书个人中心获取apikey申请地址https://wechat.aibotk.com/signup?from=ql',
tip: intl.get(
'密钥key智能微秘书个人中心获取apikey申请地址https://wechat.aibotk.com/signup?from=ql',
),
required: true,
},
@ -265,7 +277,8 @@ export default {
},
{
label: 'aibotkName',
tip: intl.get('要发送的用户昵称或群名,如果目标是群,需要填群名,如果目标是好友,需要填好友昵称',
tip: intl.get(
'要发送的用户昵称或群名,如果目标是群,需要填群名,如果目标是好友,需要填好友昵称',
),
required: true,
},
@ -273,7 +286,8 @@ export default {
iGot: [
{
label: 'iGotPushKey',
tip: intl.get('iGot的信息推送key例如https://push.hellyw.com/XXXXXXXX',
tip: intl.get(
'iGot的信息推送key例如https://push.hellyw.com/XXXXXXXX',
),
required: true,
},
@ -281,20 +295,23 @@ export default {
pushPlus: [
{
label: 'pushPlusToken',
tip: intl.get('微信扫码登录后一对一推送或一对多推送下面的token(您的Token)不提供PUSH_PLUS_USER则默认为一对一推送参考 https://www.pushplus.plus/',
tip: intl.get(
'微信扫码登录后一对一推送或一对多推送下面的token(您的Token)不提供PUSH_PLUS_USER则默认为一对一推送参考 https://www.pushplus.plus/',
),
required: true,
},
{
label: 'pushPlusUser',
tip: intl.get('一对多推送的“群组编码”(一对多推送下面->您的群组(如无则创建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)',
tip: intl.get(
'一对多推送的“群组编码”(一对多推送下面->您的群组(如无则创建)->群组编码,如果您是创建群组人。也需点击“查看二维码”扫描绑定,否则不能接受群组消息推送)',
),
},
],
lark: [
{
label: 'larkKey',
tip: intl.get('飞书群组机器人https://www.feishu.cn/hc/zh-CN/articles/360024984973',
tip: intl.get(
'飞书群组机器人https://www.feishu.cn/hc/zh-CN/articles/360024984973',
),
required: true,
},
@ -302,7 +319,8 @@ export default {
email: [
{
label: 'emailService',
tip: intl.get('邮箱服务名称比如126、163、Gmail、QQ等支持列表https://nodemailer.com/smtp/well-known/',
tip: intl.get(
'邮箱服务名称比如126、163、Gmail、QQ等支持列表https://nodemailer.com/smtp/well-known/',
),
required: true,
},
@ -316,6 +334,29 @@ export default {
required: true,
},
],
chronocat: [
{
label: 'chronocatURL',
tip: intl.get(
'Chronocat Red 服务的连接地址 https://chronocat.vercel.app/install/docker/official/',
),
required: true,
},
{
label: 'chronocatQQ',
tip: intl.get(
'个人:user_id=个人QQ 群则填入group_id=QQ群 多个用英文;隔开同时支持个人和群 如user_id=xxx;group_id=xxxx;group_id=xxxxx',
),
required: true,
},
{
label: 'chronocatToken',
tip: intl.get(
'docker安装在持久化config目录下的chronocat.yml文件可找到',
),
required: true,
},
],
webhook: [
{
label: 'webhookMethod',
@ -335,7 +376,8 @@ export default {
},
{
label: 'webhookUrl',
tip: intl.get('请求链接以http或者https开头。url或者body中必须包含$title$content可选对应api内容的位置',
tip: intl.get(
'请求链接以http或者https开头。url或者body中必须包含$title$content可选对应api内容的位置',
),
required: true,
placeholder: 'https://xxx.cn/api?content=$title\n',
@ -347,7 +389,8 @@ export default {
},
{
label: 'webhookBody',
tip: intl.get('请求体格式key1: value1多个换行分割。url或者body中必须包含$title$content可选对应api内容的位置',
tip: intl.get(
'请求体格式key1: value1多个换行分割。url或者body中必须包含$title$content可选对应api内容的位置',
),
placeholder: 'key1: $title\nkey2: $content',
},