qinglong/sample/notify.js
2025-01-15 23:23:54 +08:00

1473 lines
43 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const querystring = require('node:querystring');
const got = require('got');
const timeout = 15000;
const push_config = {
HITOKOTO: true, // 启用一言(随机句子)
BARK_PUSH: '', // bark IP 或设备码https://api.day.app/DxHcxxxxxRxxxxxxcm/
BARK_ARCHIVE: '', // bark 推送是否存档
BARK_GROUP: '', // bark 推送分组
BARK_SOUND: '', // bark 推送声音
BARK_ICON: '', // bark 推送图标
BARK_LEVEL: '', // bark 推送时效性
BARK_URL: '', // bark 推送跳转URL
DD_BOT_SECRET: '', // 钉钉机器人的 DD_BOT_SECRET
DD_BOT_TOKEN: '', // 钉钉机器人的 DD_BOT_TOKEN
FSKEY: '', // 飞书机器人的 FSKEY
// 推送到个人QQhttp://127.0.0.1/send_private_msg
// 群http://127.0.0.1/send_group_msg
GOBOT_URL: '', // go-cqhttp
// 推送到个人QQ 填入 user_id=个人QQ
// 群 填入 group_id=QQ群
GOBOT_QQ: '', // go-cqhttp 的推送群或用户
GOBOT_TOKEN: '', // go-cqhttp 的 access_token
GOTIFY_URL: '', // gotify地址,如https://push.example.de:8080
GOTIFY_TOKEN: '', // gotify的消息应用token
GOTIFY_PRIORITY: 0, // 推送消息优先级,默认为0
IGOT_PUSH_KEY: '', // iGot 聚合推送的 IGOT_PUSH_KEY例如https://push.hellyw.com/XXXXXXXX
PUSH_KEY: '', // server 酱的 PUSH_KEY兼容旧版与 Turbo 版
DEER_KEY: '', // PushDeer 的 PUSHDEER_KEY
DEER_URL: '', // PushDeer 的 PUSHDEER_URL
CHAT_URL: '', // synology chat url
CHAT_TOKEN: '', // synology chat token
// 官方文档https://www.pushplus.plus/
PUSH_PLUS_TOKEN: '', // pushplus 推送的用户令牌
PUSH_PLUS_USER: '', // pushplus 推送的群组编码
PUSH_PLUS_TEMPLATE: 'html', // pushplus 发送模板支持html,txt,json,markdown,cloudMonitor,jenkins,route,pay
PUSH_PLUS_CHANNEL: 'wechat', // pushplus 发送渠道支持wechat,webhook,cp,mail,sms
PUSH_PLUS_WEBHOOK: '', // pushplus webhook编码可在pushplus公众号上扩展配置出更多渠道
PUSH_PLUS_CALLBACKURL: '', // pushplus 发送结果回调地址,会把推送最终结果通知到这个地址上
PUSH_PLUS_TO: '', // pushplus 好友令牌微信公众号渠道填写好友令牌企业微信渠道填写企业微信用户id
// 微加机器人官方网站https://www.weplusbot.com/
WE_PLUS_BOT_TOKEN: '', // 微加机器人的用户令牌
WE_PLUS_BOT_RECEIVER: '', // 微加机器人的消息接收人
WE_PLUS_BOT_VERSION: 'pro', //微加机器人调用版本pro和personal为空默认使用pro(专业版)个人版填写personal
QMSG_KEY: '', // qmsg 酱的 QMSG_KEY
QMSG_TYPE: '', // qmsg 酱的 QMSG_TYPE
QYWX_ORIGIN: 'https://qyapi.weixin.qq.com', // 企业微信代理地址
/*
此处填你企业微信应用消息的值(详见文档 https://work.weixin.qq.com/api/doc/90000/90135/90236)
环境变量名 QYWX_AM依次填入 corpid,corpsecret,touser(注:多个成员ID使用|隔开),agentid,消息类型(选填,不填默认文本消息类型)
注意用,号隔开(英文输入法的逗号)例如wwcff56746d9adwers,B-791548lnzXBE6_BWfxdf3kSTMJr9vFEPKAbh6WERQ,mingcheng,1000001,2COXgjH2UIfERF2zxrtUOKgQ9XklUqMdGSWLBoW_lSDAdafat
可选推送消息类型(推荐使用图文消息mpnews):
- 文本卡片消息: 0 (数字零)
- 文本消息: 1 (数字一)
- 图文消息mpnews: 素材库图片id, 可查看此教程(http://note.youdao.com/s/HMiudGkb)或者(https://note.youdao.com/ynoteshare1/index.html?id=1a0c8aff284ad28cbd011b29b3ad0191&type=note)
*/
QYWX_AM: '', // 企业微信应用
QYWX_KEY: '', // 企业微信机器人的 webhook(详见文档 https://work.weixin.qq.com/api/doc/90000/90136/91770)例如693a91f6-7xxx-4bc4-97a0-0ec2sifa5aaa
TG_BOT_TOKEN: '', // tg 机器人的 TG_BOT_TOKEN1407203283:AAG9rt-6RDaaX0HBLZQq0laNOh898iFYaRQ
TG_USER_ID: '', // tg 机器人的 TG_USER_ID1434078534
TG_API_HOST: 'https://api.telegram.org', // tg 代理 api
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要对应好
SMTP_SERVICE: '', // 邮箱服务名称,比如 126、163、Gmail、QQ 等,支持列表 https://github.com/nodemailer/nodemailer/blob/master/lib/well-known/services.json
SMTP_EMAIL: '', // SMTP 收发件邮箱,通知将会由自己发给自己
SMTP_PASSWORD: '', // SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定
SMTP_NAME: '', // SMTP 收发件人姓名,可随意填写
PUSHME_KEY: '', // 官方文档https://push.i-i.mePushMe 酱的 PUSHME_KEY
// CHRONOCAT API https://chronocat.vercel.app/install/docker/official/
CHRONOCAT_QQ: '', // 个人: user_id=个人QQ 群则填入 group_id=QQ群 多个用英文;隔开同时支持个人和群
CHRONOCAT_TOKEN: '', // 填写在CHRONOCAT文件生成的访问密钥
CHRONOCAT_URL: '', // Red 协议连接地址 例: http://127.0.0.1:16530
WEBHOOK_URL: '', // 自定义通知 请求地址
WEBHOOK_BODY: '', // 自定义通知 请求体
WEBHOOK_HEADERS: '', // 自定义通知 请求头
WEBHOOK_METHOD: '', // 自定义通知 请求方法
WEBHOOK_CONTENT_TYPE: '', // 自定义通知 content-type
NTFY_URL: '', // ntfy地址,如https://ntfy.sh,默认为https://ntfy.sh
NTFY_TOPIC: '', // ntfy的消息应用topic
NTFY_PRIORITY: '3', // 推送消息优先级,默认为3
// 官方文档: https://wxpusher.zjiecode.com/docs/
// 管理后台: https://wxpusher.zjiecode.com/admin/
WXPUSHER_APP_TOKEN: '', // wxpusher 的 appToken
WXPUSHER_TOPIC_IDS: '', // wxpusher 的 主题ID多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行
WXPUSHER_UIDS: '', // wxpusher 的 用户ID多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行
};
for (const key in push_config) {
const v = process.env[key];
if (v) {
push_config[key] = v;
}
}
const $ = {
post: (params, callback) => {
const { url, ...others } = params;
got.post(url, others).then(
(res) => {
let body = res.body;
try {
body = JSON.parse(body);
} catch (error) {}
callback(null, res, body);
},
(err) => {
callback(err?.response?.body || err);
},
);
},
get: (params, callback) => {
const { url, ...others } = params;
got.get(url, others).then(
(res) => {
let body = res.body;
try {
body = JSON.parse(body);
} catch (error) {}
callback(null, res, body);
},
(err) => {
callback(err?.response?.body || err);
},
);
},
logErr: console.log,
};
async function one() {
const url = 'https://v1.hitokoto.cn/';
const res = await got.get(url);
const body = JSON.parse(res.body);
return `${body.hitokoto} ----${body.from}`;
}
function gotifyNotify(text, desp) {
return new Promise((resolve) => {
const { GOTIFY_URL, GOTIFY_TOKEN, GOTIFY_PRIORITY } = push_config;
if (GOTIFY_URL && GOTIFY_TOKEN) {
const options = {
url: `${GOTIFY_URL}/message?token=${GOTIFY_TOKEN}`,
body: `title=${encodeURIComponent(text)}&message=${encodeURIComponent(
desp,
)}&priority=${GOTIFY_PRIORITY}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('Gotify 发送通知调用API失败😞\n', err);
} else {
if (data.id) {
console.log('Gotify 发送通知消息成功🎉\n');
} else {
console.log(`Gotify 发送通知调用API失败😞 ${data.message}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve();
}
});
} else {
resolve();
}
});
}
function gobotNotify(text, desp) {
return new Promise((resolve) => {
const { GOBOT_URL, GOBOT_TOKEN, GOBOT_QQ } = push_config;
if (GOBOT_URL) {
const options = {
url: `${GOBOT_URL}?access_token=${GOBOT_TOKEN}&${GOBOT_QQ}`,
json: { message: `${text}\n${desp}` },
headers: {
'Content-Type': 'application/json',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('Go-cqhttp 通知调用API失败😞\n', err);
} else {
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 发送通知消息异常 ${JSON.stringify(data)}`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function serverNotify(text, desp) {
return new Promise((resolve) => {
const { PUSH_KEY } = push_config;
if (PUSH_KEY) {
// 微信server酱推送通知一个\n不会换行需要两个\n才能换行故做此替换
desp = desp.replace(/[\n\r]/g, '\n\n');
const matchResult = PUSH_KEY.match(/^sctp(\d+)t/i);
const options = {
url:
matchResult && matchResult[1]
? `https://${matchResult[1]}.push.ft07.com/send/${PUSH_KEY}.send`
: `https://sctapi.ftqq.com/${PUSH_KEY}.send`,
body: `text=${encodeURIComponent(text)}&desp=${encodeURIComponent(
desp,
)}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('Server 酱发送通知调用API失败😞\n', err);
} else {
// 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 酱发送通知消息异常 ${JSON.stringify(data)}`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function pushDeerNotify(text, desp) {
return new Promise((resolve) => {
const { DEER_KEY, DEER_URL } = push_config;
if (DEER_KEY) {
// PushDeer 建议对消息内容进行 urlencode
desp = encodeURI(desp);
const options = {
url: DEER_URL || `https://api2.pushdeer.com/message/push`,
body: `pushkey=${DEER_KEY}&text=${text}&desp=${desp}&type=markdown`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('PushDeer 通知调用API失败😞\n', err);
} else {
// 通过返回的result的长度来判断是否成功
if (
data.content.result.length !== undefined &&
data.content.result.length > 0
) {
console.log('PushDeer 发送通知消息成功🎉\n');
} else {
console.log(
`PushDeer 发送通知消息异常😞 ${JSON.stringify(data)}`,
);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function chatNotify(text, desp) {
return new Promise((resolve) => {
const { CHAT_URL, CHAT_TOKEN } = push_config;
if (CHAT_URL && CHAT_TOKEN) {
// 对消息内容进行 urlencode
desp = encodeURI(desp);
const options = {
url: `${CHAT_URL}${CHAT_TOKEN}`,
body: `payload={"text":"${text}\n${desp}"}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('Chat 发送通知调用API失败😞\n', err);
} else {
if (data.success) {
console.log('Chat 发送通知消息成功🎉\n');
} else {
console.log(`Chat 发送通知消息异常 ${JSON.stringify(data)}`);
}
}
} catch (e) {
$.logErr(e);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function barkNotify(text, desp, params = {}) {
return new Promise((resolve) => {
let {
BARK_PUSH,
BARK_ICON,
BARK_SOUND,
BARK_GROUP,
BARK_LEVEL,
BARK_ARCHIVE,
BARK_URL,
} = push_config;
if (BARK_PUSH) {
// 兼容BARK本地用户只填写设备码的情况
if (!BARK_PUSH.startsWith('http')) {
BARK_PUSH = `https://api.day.app/${BARK_PUSH}`;
}
const options = {
url: `${BARK_PUSH}`,
json: {
title: text,
body: desp,
icon: BARK_ICON,
sound: BARK_SOUND,
group: BARK_GROUP,
isArchive: BARK_ARCHIVE,
level: BARK_LEVEL,
url: BARK_URL,
...params,
},
headers: {
'Content-Type': 'application/json',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('Bark APP 发送通知调用API失败😞\n', err);
} else {
if (data.code === 200) {
console.log('Bark APP 发送通知消息成功🎉\n');
} else {
console.log(`Bark APP 发送通知消息异常 ${data.message}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve();
}
});
} else {
resolve();
}
});
}
function tgBotNotify(text, desp) {
return new Promise((resolve) => {
const {
TG_BOT_TOKEN,
TG_USER_ID,
TG_PROXY_HOST,
TG_PROXY_PORT,
TG_API_HOST,
TG_PROXY_AUTH,
} = push_config;
if (TG_BOT_TOKEN && TG_USER_ID) {
let options = {
url: `${TG_API_HOST}/bot${TG_BOT_TOKEN}/sendMessage`,
json: {
chat_id: `${TG_USER_ID}`,
text: `${text}\n\n${desp}`,
disable_web_page_preview: true,
},
headers: {
'Content-Type': 'application/json',
},
timeout,
};
if (TG_PROXY_HOST && TG_PROXY_PORT) {
const { HttpProxyAgent, HttpsProxyAgent } = require('hpagent');
const _options = {
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 256,
maxFreeSockets: 256,
proxy: `http://${TG_PROXY_AUTH}${TG_PROXY_HOST}:${TG_PROXY_PORT}`,
};
const httpAgent = new HttpProxyAgent(_options);
const httpsAgent = new HttpsProxyAgent(_options);
const agent = {
http: httpAgent,
https: httpsAgent,
};
options.agent = agent;
}
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('Telegram 发送通知消息失败😞\n', err);
} else {
if (data.ok) {
console.log('Telegram 发送通知消息成功🎉。\n');
} else if (data.error_code === 400) {
console.log(
'请主动给bot发送一条消息并检查接收用户ID是否正确。\n',
);
} else if (data.error_code === 401) {
console.log('Telegram bot token 填写错误。\n');
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function ddBotNotify(text, desp) {
return new Promise((resolve) => {
const { DD_BOT_TOKEN, DD_BOT_SECRET } = push_config;
const options = {
url: `https://oapi.dingtalk.com/robot/send?access_token=${DD_BOT_TOKEN}`,
json: {
msgtype: 'text',
text: {
content: `${text}\n\n${desp}`,
},
},
headers: {
'Content-Type': 'application/json',
},
timeout,
};
if (DD_BOT_TOKEN && DD_BOT_SECRET) {
const crypto = require('crypto');
const dateNow = Date.now();
const hmac = crypto.createHmac('sha256', DD_BOT_SECRET);
hmac.update(`${dateNow}\n${DD_BOT_SECRET}`);
const result = encodeURIComponent(hmac.digest('base64'));
options.url = `${options.url}&timestamp=${dateNow}&sign=${result}`;
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('钉钉发送通知消息失败😞\n', err);
} else {
if (data.errcode === 0) {
console.log('钉钉发送通知消息成功🎉\n');
} else {
console.log(`钉钉发送通知消息异常 ${data.errmsg}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else if (DD_BOT_TOKEN) {
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('钉钉发送通知消息失败😞\n', err);
} else {
if (data.errcode === 0) {
console.log('钉钉发送通知消息成功🎉\n');
} else {
console.log(`钉钉发送通知消息异常 ${data.errmsg}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function qywxBotNotify(text, desp) {
return new Promise((resolve) => {
const { QYWX_ORIGIN, QYWX_KEY } = push_config;
const options = {
url: `${QYWX_ORIGIN}/cgi-bin/webhook/send?key=${QYWX_KEY}`,
json: {
msgtype: 'text',
text: {
content: `${text}\n\n${desp}`,
},
},
headers: {
'Content-Type': 'application/json',
},
timeout,
};
if (QYWX_KEY) {
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('企业微信发送通知消息失败😞\n', err);
} else {
if (data.errcode === 0) {
console.log('企业微信发送通知消息成功🎉。\n');
} else {
console.log(`企业微信发送通知消息异常 ${data.errmsg}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function ChangeUserId(desp) {
const { QYWX_AM } = push_config;
const QYWX_AM_AY = QYWX_AM.split(',');
if (QYWX_AM_AY[2]) {
const userIdTmp = QYWX_AM_AY[2].split('|');
let userId = '';
for (let i = 0; i < userIdTmp.length; i++) {
const count = '账号' + (i + 1);
const count2 = '签到号 ' + (i + 1);
if (desp.match(count2)) {
userId = userIdTmp[i];
}
}
if (!userId) userId = QYWX_AM_AY[2];
return userId;
} else {
return '@all';
}
}
async function qywxamNotify(text, desp) {
const MAX_LENGTH = 900;
if (desp.length > MAX_LENGTH) {
let d = desp.substr(0, MAX_LENGTH) + '\n==More==';
await do_qywxamNotify(text, d);
await qywxamNotify(text, desp.substr(MAX_LENGTH));
} else {
return await do_qywxamNotify(text, desp);
}
}
function do_qywxamNotify(text, desp) {
return new Promise((resolve) => {
const { QYWX_AM, QYWX_ORIGIN } = push_config;
if (QYWX_AM) {
const QYWX_AM_AY = QYWX_AM.split(',');
const options_accesstoken = {
url: `${QYWX_ORIGIN}/cgi-bin/gettoken`,
json: {
corpid: `${QYWX_AM_AY[0]}`,
corpsecret: `${QYWX_AM_AY[1]}`,
},
headers: {
'Content-Type': 'application/json',
},
timeout,
};
$.post(options_accesstoken, (err, resp, json) => {
let html = desp.replace(/\n/g, '<br/>');
let accesstoken = json.access_token;
let options;
switch (QYWX_AM_AY[4]) {
case '0':
options = {
msgtype: 'textcard',
textcard: {
title: `${text}`,
description: `${desp}`,
url: 'https://github.com/whyour/qinglong',
btntxt: '更多',
},
};
break;
case '1':
options = {
msgtype: 'text',
text: {
content: `${text}\n\n${desp}`,
},
};
break;
default:
options = {
msgtype: 'mpnews',
mpnews: {
articles: [
{
title: `${text}`,
thumb_media_id: `${QYWX_AM_AY[4]}`,
author: `智能助手`,
content_source_url: ``,
content: `${html}`,
digest: `${desp}`,
},
],
},
};
}
if (!QYWX_AM_AY[4]) {
// 如不提供第四个参数,则默认进行文本消息类型推送
options = {
msgtype: 'text',
text: {
content: `${text}\n\n${desp}`,
},
};
}
options = {
url: `${QYWX_ORIGIN}/cgi-bin/message/send?access_token=${accesstoken}`,
json: {
touser: `${ChangeUserId(desp)}`,
agentid: `${QYWX_AM_AY[3]}`,
safe: '0',
...options,
},
headers: {
'Content-Type': 'application/json',
},
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log(
'成员ID:' +
ChangeUserId(desp) +
'企业微信应用消息发送通知消息失败😞\n',
err,
);
} else {
if (data.errcode === 0) {
console.log(
'成员ID:' +
ChangeUserId(desp) +
'企业微信应用消息发送通知消息成功🎉。\n',
);
} else {
console.log(
`企业微信应用消息发送通知消息异常 ${data.errmsg}\n`,
);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
});
} else {
resolve();
}
});
}
function iGotNotify(text, desp, params = {}) {
return new Promise((resolve) => {
const { IGOT_PUSH_KEY } = push_config;
if (IGOT_PUSH_KEY) {
// 校验传入的IGOT_PUSH_KEY是否有效
const IGOT_PUSH_KEY_REGX = new RegExp('^[a-zA-Z0-9]{24}$');
if (!IGOT_PUSH_KEY_REGX.test(IGOT_PUSH_KEY)) {
console.log('您所提供的 IGOT_PUSH_KEY 无效\n');
resolve();
return;
}
const options = {
url: `https://push.hellyw.com/${IGOT_PUSH_KEY.toLowerCase()}`,
body: `title=${text}&content=${desp}&${querystring.stringify(params)}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('IGot 发送通知调用API失败😞\n', err);
} else {
if (data.ret === 0) {
console.log('IGot 发送通知消息成功🎉\n');
} else {
console.log(`IGot 发送通知消息异常 ${data.errMsg}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function pushPlusNotify(text, desp) {
return new Promise((resolve) => {
const {
PUSH_PLUS_TOKEN,
PUSH_PLUS_USER,
PUSH_PLUS_TEMPLATE,
PUSH_PLUS_CHANNEL,
PUSH_PLUS_WEBHOOK,
PUSH_PLUS_CALLBACKURL,
PUSH_PLUS_TO,
} = push_config;
if (PUSH_PLUS_TOKEN) {
desp = desp.replace(/[\n\r]/g, '<br>'); // 默认为html, 不支持plaintext
const body = {
token: `${PUSH_PLUS_TOKEN}`,
title: `${text}`,
content: `${desp}`,
topic: `${PUSH_PLUS_USER}`,
template: `${PUSH_PLUS_TEMPLATE}`,
channel: `${PUSH_PLUS_CHANNEL}`,
webhook: `${PUSH_PLUS_WEBHOOK}`,
callbackUrl: `${PUSH_PLUS_CALLBACKURL}`,
to: `${PUSH_PLUS_TO}`,
};
const options = {
url: `https://www.pushplus.plus/send`,
body: JSON.stringify(body),
headers: {
'Content-Type': ' application/json',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log(
`pushplus 发送${
PUSH_PLUS_USER ? '一对多' : '一对一'
}通知消息失败😞\n`,
err,
);
} else {
if (data.code === 200) {
console.log(
`pushplus 发送${
PUSH_PLUS_USER ? '一对多' : '一对一'
}通知请求成功🎉,可根据流水号查询推送结果:${
data.data
}\n注意请求成功并不代表推送成功如未收到消息请到pushplus官网使用流水号查询推送最终结果`,
);
} else {
console.log(
`pushplus 发送${
PUSH_PLUS_USER ? '一对多' : '一对一'
}通知消息异常 ${data.msg}\n`,
);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function wePlusBotNotify(text, desp) {
return new Promise((resolve) => {
const { WE_PLUS_BOT_TOKEN, WE_PLUS_BOT_RECEIVER, WE_PLUS_BOT_VERSION } =
push_config;
if (WE_PLUS_BOT_TOKEN) {
let template = 'txt';
if (desp.length > 800) {
desp = desp.replace(/[\n\r]/g, '<br>');
template = 'html';
}
const body = {
token: `${WE_PLUS_BOT_TOKEN}`,
title: `${text}`,
content: `${desp}`,
template: `${template}`,
receiver: `${WE_PLUS_BOT_RECEIVER}`,
version: `${WE_PLUS_BOT_VERSION}`,
};
const options = {
url: `https://www.weplusbot.com/send`,
body: JSON.stringify(body),
headers: {
'Content-Type': ' application/json',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log(`微加机器人发送通知消息失败😞\n`, err);
} else {
if (data.code === 200) {
console.log(`微加机器人发送通知消息完成🎉\n`);
} else {
console.log(`微加机器人发送通知消息异常 ${data.msg}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function aibotkNotify(text, desp) {
return new Promise((resolve) => {
const { AIBOTK_KEY, AIBOTK_TYPE, AIBOTK_NAME } = push_config;
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', err);
} else {
if (data.code === 0) {
console.log('智能微秘书发送通知消息成功🎉。\n');
} else {
console.log(`智能微秘书发送通知消息异常 ${data.error}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function fsBotNotify(text, desp) {
return new Promise((resolve) => {
const { FSKEY } = push_config;
if (FSKEY) {
const options = {
url: `https://open.feishu.cn/open-apis/bot/v2/hook/${FSKEY}`,
json: { msg_type: 'text', content: { text: `${text}\n\n${desp}` } },
headers: {
'Content-Type': 'application/json',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('飞书发送通知调用API失败😞\n', err);
} else {
if (data.StatusCode === 0 || data.code === 0) {
console.log('飞书发送通知消息成功🎉\n');
} else {
console.log(`飞书发送通知消息异常 ${data.msg}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
async function smtpNotify(text, desp) {
const { SMTP_EMAIL, SMTP_PASSWORD, SMTP_SERVICE, SMTP_NAME } = push_config;
if (![SMTP_EMAIL, SMTP_PASSWORD].every(Boolean) || !SMTP_SERVICE) {
return;
}
try {
const nodemailer = require('nodemailer');
const transporter = nodemailer.createTransport({
service: SMTP_SERVICE,
auth: {
user: SMTP_EMAIL,
pass: SMTP_PASSWORD,
},
});
const addr = SMTP_NAME ? `"${SMTP_NAME}" <${SMTP_EMAIL}>` : SMTP_EMAIL;
const info = await transporter.sendMail({
from: addr,
to: addr,
subject: text,
html: `${desp.replace(/\n/g, '<br/>')}`,
});
transporter.close();
if (info.messageId) {
console.log('SMTP 发送通知消息成功🎉\n');
return true;
}
console.log('SMTP 发送通知消息失败😞\n');
} catch (e) {
console.log('SMTP 发送通知消息出现异常😞\n', e);
}
}
function pushMeNotify(text, desp, params = {}) {
return new Promise((resolve) => {
const { PUSHME_KEY, PUSHME_URL } = push_config;
if (PUSHME_KEY) {
const options = {
url: PUSHME_URL || 'https://push.i-i.me',
json: { push_key: PUSHME_KEY, title: text, content: desp, ...params },
headers: {
'Content-Type': 'application/json',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('PushMe 发送通知调用API失败😞\n', err);
} else {
if (data === 'success') {
console.log('PushMe 发送通知消息成功🎉\n');
} else {
console.log(`PushMe 发送通知消息异常 ${data}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function chronocatNotify(title, desp) {
return new Promise((resolve) => {
const { CHRONOCAT_TOKEN, CHRONOCAT_QQ, CHRONOCAT_URL } = push_config;
if (!CHRONOCAT_TOKEN || !CHRONOCAT_QQ || !CHRONOCAT_URL) {
resolve();
return;
}
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', err);
} else {
if (chat_type === 1) {
console.log(`Chronocat 个人消息 ${ids}推送成功🎉`);
} else {
console.log(`Chronocat 群消息 ${ids}推送成功🎉`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
}
}
});
}
function qmsgNotify(text, desp) {
return new Promise((resolve) => {
const { QMSG_KEY, QMSG_TYPE } = push_config;
if (QMSG_KEY && QMSG_TYPE) {
const options = {
url: `https://qmsg.zendee.cn/${QMSG_TYPE}/${QMSG_KEY}`,
body: `msg=${text}\n\n${desp.replace('----', '-')}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('Qmsg 发送通知调用API失败😞\n', err);
} else {
if (data.code === 0) {
console.log('Qmsg 发送通知消息成功🎉\n');
} else {
console.log(`Qmsg 发送通知消息异常 ${data}\n`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function webhookNotify(text, desp) {
return new Promise((resolve) => {
const {
WEBHOOK_URL,
WEBHOOK_BODY,
WEBHOOK_HEADERS,
WEBHOOK_CONTENT_TYPE,
WEBHOOK_METHOD,
} = push_config;
if (
!WEBHOOK_METHOD ||
!WEBHOOK_URL ||
(!WEBHOOK_URL.includes('$title') && !WEBHOOK_BODY.includes('$title'))
) {
resolve();
return;
}
const headers = parseHeaders(WEBHOOK_HEADERS);
const body = parseBody(WEBHOOK_BODY, WEBHOOK_CONTENT_TYPE, (v) =>
v
?.replaceAll('$title', text?.replaceAll('\n', '\\n'))
?.replaceAll('$content', desp?.replaceAll('\n', '\\n')),
);
const bodyParam = formatBodyFun(WEBHOOK_CONTENT_TYPE, body);
const options = {
method: WEBHOOK_METHOD,
headers,
allowGetBody: true,
...bodyParam,
timeout,
retry: 1,
};
const formatUrl = WEBHOOK_URL.replaceAll(
'$title',
encodeURIComponent(text),
).replaceAll('$content', encodeURIComponent(desp));
got(formatUrl, options).then((resp) => {
try {
if (resp.statusCode !== 200) {
console.log(`自定义发送通知消息失败😞 ${resp.body}\n`);
} else {
console.log(`自定义发送通知消息成功🎉 ${resp.body}\n`);
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(resp.body);
}
});
});
}
function ntfyNotify(text, desp) {
function encodeRFC2047(text) {
const encodedBase64 = Buffer.from(text).toString('base64');
return `=?utf-8?B?${encodedBase64}?=`;
}
return new Promise((resolve) => {
const { NTFY_URL, NTFY_TOPIC, NTFY_PRIORITY } = push_config;
if (NTFY_TOPIC) {
const options = {
url: `${NTFY_URL || 'https://ntfy.sh'}/${NTFY_TOPIC}`,
body: `${desp}`,
headers: {
Title: `${encodeRFC2047(text)}`,
Priority: NTFY_PRIORITY || '3',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('Ntfy 通知调用API失败😞\n', err);
} else {
if (data.id) {
console.log('Ntfy 发送通知消息成功🎉\n');
} else {
console.log(`Ntfy 发送通知消息异常 ${JSON.stringify(data)}`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function wxPusherNotify(text, desp) {
return new Promise((resolve) => {
const { WXPUSHER_APP_TOKEN, WXPUSHER_TOPIC_IDS, WXPUSHER_UIDS } =
push_config;
if (WXPUSHER_APP_TOKEN) {
// 处理topic_ids将分号分隔的字符串转为数组
const topicIds = WXPUSHER_TOPIC_IDS
? WXPUSHER_TOPIC_IDS.split(';')
.map((id) => id.trim())
.filter((id) => id)
.map((id) => parseInt(id))
: [];
// 处理uids将分号分隔的字符串转为数组
const uids = WXPUSHER_UIDS
? WXPUSHER_UIDS.split(';')
.map((uid) => uid.trim())
.filter((uid) => uid)
: [];
// topic_ids uids 至少有一个
if (!topicIds.length && !uids.length) {
console.log(
'wxpusher 服务的 WXPUSHER_TOPIC_IDS 和 WXPUSHER_UIDS 至少设置一个!!',
);
return resolve();
}
const body = {
appToken: WXPUSHER_APP_TOKEN,
content: `<h1>${text}</h1><br/><div style='white-space: pre-wrap;'>${desp}</div>`,
summary: text,
contentType: 2,
topicIds: topicIds,
uids: uids,
verifyPayType: 0,
};
const options = {
url: 'https://wxpusher.zjiecode.com/api/send/message',
body: JSON.stringify(body),
headers: {
'Content-Type': 'application/json',
},
timeout,
};
$.post(options, (err, resp, data) => {
try {
if (err) {
console.log('wxpusher发送通知消息失败\n', err);
} else {
if (data.code === 1000) {
console.log('wxpusher发送通知消息完成');
} else {
console.log(`wxpusher发送通知消息异常${data.msg}`);
}
}
} catch (e) {
$.logErr(e, resp);
} finally {
resolve(data);
}
});
} else {
resolve();
}
});
}
function parseString(input, valueFormatFn) {
const regex = /(\w+):\s*((?:(?!\n\w+:).)*)/g;
const matches = {};
let match;
while ((match = regex.exec(input)) !== null) {
const [, key, value] = match;
const _key = key.trim();
if (!_key || matches[_key]) {
continue;
}
let _value = value.trim();
try {
_value = valueFormatFn ? valueFormatFn(_value) : _value;
const jsonValue = JSON.parse(_value);
matches[_key] = jsonValue;
} catch (error) {
matches[_key] = _value;
}
}
return matches;
}
function parseHeaders(headers) {
if (!headers) return {};
const parsed = {};
let key;
let val;
let i;
headers &&
headers.split('\n').forEach(function parser(line) {
i = line.indexOf(':');
key = line.substring(0, i).trim().toLowerCase();
val = line.substring(i + 1).trim();
if (!key) {
return;
}
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
});
return parsed;
}
function parseBody(body, contentType, valueFormatFn) {
if (contentType === 'text/plain' || !body) {
return valueFormatFn && body ? valueFormatFn(body) : body;
}
const parsed = parseString(body, valueFormatFn);
switch (contentType) {
case 'multipart/form-data':
return Object.keys(parsed).reduce((p, c) => {
p.append(c, parsed[c]);
return p;
}, new FormData());
case 'application/x-www-form-urlencoded':
return Object.keys(parsed).reduce((p, c) => {
return p ? `${p}&${c}=${parsed[c]}` : `${c}=${parsed[c]}`;
});
}
return parsed;
}
function formatBodyFun(contentType, body) {
if (!body) return {};
switch (contentType) {
case 'application/json':
return { json: body };
case 'multipart/form-data':
return { form: body };
case 'application/x-www-form-urlencoded':
case 'text/plain':
return { body };
}
return {};
}
/**
* sendNotify 推送通知功能
* @param text 通知头
* @param desp 通知体
* @param params 某些推送通知方式点击弹窗可跳转, 例:{ url: 'https://abc.com' }
* @returns {Promise<unknown>}
*/
async function sendNotify(text, desp, params = {}) {
// 根据标题跳过一些消息推送环境变量SKIP_PUSH_TITLE 用回车分隔
let skipTitle = process.env.SKIP_PUSH_TITLE;
if (skipTitle) {
if (skipTitle.split('\n').includes(text)) {
console.info(text + '在 SKIP_PUSH_TITLE 环境变量内,跳过推送');
return;
}
}
if (push_config.HITOKOTO !== 'false') {
desp += '\n\n' + (await one());
}
await Promise.all([
serverNotify(text, desp), // 微信server酱
pushPlusNotify(text, desp), // pushplus
wePlusBotNotify(text, desp), // 微加机器人
barkNotify(text, desp, params), // iOS Bark APP
tgBotNotify(text, desp), // telegram 机器人
ddBotNotify(text, desp), // 钉钉机器人
qywxBotNotify(text, desp), // 企业微信机器人
qywxamNotify(text, desp), // 企业微信应用消息推送
iGotNotify(text, desp, params), // iGot
gobotNotify(text, desp), // go-cqhttp
gotifyNotify(text, desp), // gotify
chatNotify(text, desp), // synolog chat
pushDeerNotify(text, desp), // PushDeer
aibotkNotify(text, desp), // 智能微秘书
fsBotNotify(text, desp), // 飞书机器人
smtpNotify(text, desp), // SMTP 邮件
pushMeNotify(text, desp, params), // PushMe
chronocatNotify(text, desp), // Chronocat
webhookNotify(text, desp), // 自定义通知
qmsgNotify(text, desp), // 自定义通知
ntfyNotify(text, desp), // Ntfy
wxPusherNotify(text, desp), // wxpusher
]);
}
module.exports = {
sendNotify,
};