From 5055045d22890e6ed395a755446501b44e1c9132 Mon Sep 17 00:00:00 2001 From: LinCheng Wu Date: Sat, 14 Oct 2023 20:56:32 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0chronocat=E6=97=A0=E5=A4=B4?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E7=9A=84QQNT=E6=8E=A8=E9=80=81=20=20?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E6=97=A0=E6=B3=95=E4=BD=BFgo-cqhttp=E7=9A=84?= =?UTF-8?q?=E6=8E=A5=E6=9B=BF=20(#2141)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 增加chronocat无头模式的QQNT推送 * Chronocat发送时没有配置群号或个人消息号发送出错 * 增加系统通知、去除注释 * 增加系统通知 --------- Co-authored-by: child --- back/data/notify.ts | 9 ++++ back/services/notify.ts | 73 ++++++++++++++++++++++++++++--- sample/config.sample.sh | 9 ++++ sample/notify.js | 95 +++++++++++++++++++++++++++++++++++++++++ sample/notify.py | 54 +++++++++++++++++++++++ src/utils/config.ts | 81 ++++++++++++++++++++++++++--------- 6 files changed, 295 insertions(+), 26 deletions(-) diff --git a/back/data/notify.ts b/back/data/notify.ts index 10ee855849c..4def48888a2 100644 --- a/back/data/notify.ts +++ b/back/data/notify.ts @@ -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 {} + diff --git a/back/services/notify.ts b/back/services/notify.ts index c4ea67f07b8..55827b41bfe 100644 --- a/back/services/notify.ts +++ b/back/services/notify.ts @@ -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, diff --git a/sample/config.sample.sh b/sample/config.sample.sh index 5521136329c..fa955e63e9f 100644 --- a/sample/config.sample.sh +++ b/sample/config.sample.sh @@ -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 变量名= 声明即可 diff --git a/sample/notify.js b/sample/notify.js index 19a8ff5e43b..82662745ec4 100644 --- a/sample/notify.js +++ b/sample/notify.js @@ -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, diff --git a/sample/notify.py b/sample/notify.py index 1d43b609ad6..8b5c748ed07 100644 --- a/sample/notify.py +++ b/sample/notify.py @@ -102,6 +102,9 @@ def print(text, *args, **kw): '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: """ diff --git a/src/utils/config.ts b/src/utils/config.ts index a72820931be..7a8fb03f22f 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -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_msg,群:http://127.0.0.1/send_group_msg', + tip: intl.get( + '推送到个人QQ: http://127.0.0.1/send_private_msg,群:http://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', },