UNPKG

push-all-in-one

Version:

Push All In One!支持 Server酱(以及 Server 酱³)、自定义邮件、钉钉机器人、企业微信机器人、企业微信应用、pushplus、iGot 、Qmsg、息知、PushDeer、Discord、OneBot、Telegram 等多种推送方式

2,119 lines (2,098 loc) 62.4 kB
// src/push/custom-email.ts import debug from "debug"; import nodemailer from "nodemailer"; // src/utils/helper.ts var colors; if (globalThis.process && typeof globalThis.process.on === "function") { import("@colors/colors").then((value) => { colors = value.default; }).catch(console.error); } function warn(text) { if (colors) { text = colors.yellow(text); } console.warn(text); } function error(text) { if (colors) { text = colors.red(text); } console.error(text); } var logger = { warn, error }; var isHttpURL = (url) => /^(https?:\/\/)/.test(url); var isSocksUrl = (url) => /^(socks5?:\/\/)/.test(url); function isEmpty(value) { return value === null || value === void 0 || value === ""; } function uniq(arr) { return Array.from(new Set(arr)); } // src/utils/validate.ts function validate(config, schema) { Object.keys(schema).forEach((key) => { const item = schema[key]; const value = config[key]; if (!item.required && isEmpty(value)) { return; } if (item.required && isEmpty(value)) { throw new Error(`"${key}" 字段是必须的!`); } if (item.type === "select") { const { options } = item; if (!options.map((e) => e.value).includes(value)) { throw new Error(`"${key}" 字段必须是以下选项之一:${options.map((e) => e.value).join(",")}`); } return; } if (item.type === "string") { if (typeof value !== "string") { throw new Error(`"${key}" 字段必须是字符串!`); } return; } if (item.type === "number") { if (typeof value !== "number") { throw new Error(`"${key}" 字段必须是数字!`); } return; } if (item.type === "boolean") { if (typeof value !== "boolean") { throw new Error(`"${key}" 字段必须是布尔值!`); } return; } if (item.type === "array") { if (!Array.isArray(value)) { throw new Error(`"${key}" 字段必须是数组!`); } return; } if (item.type === "object") { if (typeof value !== "object") { throw new Error(`"${key}" 字段必须是对象!`); } return; } throw new Error(`"${key}" 字段类型不支持!`); }); } // src/push/custom-email.ts var Debugger = debug("push:custom-email"); var customEmailConfigSchema = { EMAIL_TYPE: { type: "select", title: "邮件类型", description: "邮件类型", required: true, default: "text", options: [ { label: "文本", value: "text" }, { label: "HTML", value: "html" } ] }, EMAIL_TO_ADDRESS: { type: "string", title: "收件邮箱", description: "收件邮箱", required: true, default: "" }, EMAIL_AUTH_USER: { type: "string", title: "发件邮箱", description: "发件邮箱", required: true, default: "" }, EMAIL_AUTH_PASS: { type: "string", title: "发件授权码(或密码)", description: "发件授权码(或密码)", required: true, default: "" }, EMAIL_HOST: { type: "string", title: "发件域名", description: "发件域名", required: true, default: "" }, EMAIL_PORT: { type: "number", title: "发件端口", description: "发件端口", required: true, default: 465 } }; var customEmailOptionSchema = { to: { type: "string", title: "收件邮箱", description: "收件邮箱", required: false, default: "" }, from: { type: "string", title: "发件邮箱", description: "发件邮箱", required: false, default: "" }, subject: { type: "string", title: "邮件主题", description: "邮件主题", required: false, default: "" }, text: { type: "string", title: "邮件内容", description: "邮件内容", required: false, default: "" }, html: { type: "string", title: "邮件内容", description: "邮件内容", required: false, default: "" } }; var _CustomEmail = class _CustomEmail { constructor(config) { this.config = config; Debugger("CustomEmailConfig: %o", config); validate(config, _CustomEmail.configSchema); const { EMAIL_AUTH_USER, EMAIL_AUTH_PASS, EMAIL_HOST, EMAIL_PORT } = this.config; this.transporter = nodemailer.createTransport({ host: EMAIL_HOST, port: Number(EMAIL_PORT), auth: { user: EMAIL_AUTH_USER, pass: EMAIL_AUTH_PASS } }); } /** * 释放资源(需要支持 Symbol.dispose) * * @author CaoMeiYouRen * @date 2024-11-08 */ [Symbol.dispose]() { if (this.transporter) { this.transporter.close(); } } /** * * @author CaoMeiYouRen * @date 2024-11-08 * @param title 消息的标题 * @param [desp] 消息的内容,支持 html * @param [option] 额外选项 */ async send(title, desp, option) { var _a; Debugger('title: "%s", desp: "%s", option: %o', title, desp, option); const { EMAIL_TYPE, EMAIL_TO_ADDRESS, EMAIL_AUTH_USER } = this.config; if (!await this.transporter.verify()) { throw new Error("自定义邮件的发件配置无效"); } const { to: _to, ...args } = option || {}; const from = EMAIL_AUTH_USER; const to = _to || EMAIL_TO_ADDRESS; const type = EMAIL_TYPE; const response = await this.transporter.sendMail({ from, to, subject: title, [type]: desp, ...args }); if (typeof Symbol.dispose === "undefined") { this.transporter.close(); } Debugger("CustomEmail Response: %o", response); if ((_a = response.response) == null ? void 0 : _a.includes("250 OK")) { return { status: 200, statusText: "OK", data: response, headers: {} }; } return { status: 500, statusText: "Internal Server Error", data: response, headers: {} }; } }; // 命名空间 _CustomEmail.namespace = "自定义邮件"; _CustomEmail.configSchema = customEmailConfigSchema; _CustomEmail.optionSchema = customEmailOptionSchema; var CustomEmail = _CustomEmail; // src/push/dingtalk.ts import debug3 from "debug"; // src/utils/ajax.ts import axios from "axios"; import debug2 from "debug"; import { HttpsProxyAgent } from "https-proxy-agent"; import { SocksProxyAgent } from "socks-proxy-agent"; var Debugger2 = debug2("push:ajax"); async function ajax(config) { try { Debugger2("ajax config: %O", config); const { url, query = {}, method = "GET", baseURL = "", proxyUrl } = config; const headers = config.headers || {}; let { data = {} } = config; if (headers["Content-Type"] === "application/x-www-form-urlencoded" && typeof data === "object") { data = new URLSearchParams(data).toString(); } let httpAgent = null; Debugger2("NO_PROXY: %s", process.env.NO_PROXY); if (process.env.NO_PROXY !== "true") { Debugger2("HTTP_PROXY: %s", process.env.HTTP_PROXY); Debugger2("HTTPS_PROXY: %s", process.env.HTTPS_PROXY); Debugger2("SOCKS_PROXY: %s", process.env.SOCKS_PROXY); if (isHttpURL(proxyUrl)) { httpAgent = new HttpsProxyAgent(proxyUrl); } else if (isSocksUrl(proxyUrl)) { httpAgent = new SocksProxyAgent(proxyUrl); } else if (process.env.HTTPS_PROXY) { httpAgent = new HttpsProxyAgent(process.env.HTTPS_PROXY); } else if (process.env.HTTP_PROXY) { httpAgent = new HttpsProxyAgent(process.env.HTTP_PROXY); } else if (process.env.SOCKS_PROXY) { httpAgent = new SocksProxyAgent(process.env.SOCKS_PROXY); } } const response = await axios(url, { baseURL, method, headers, params: query, data, timeout: 6e4, httpAgent, httpsAgent: httpAgent, proxy: false }); Debugger2("response data: %O", response.data); return response; } catch (error2) { if (error2 == null ? void 0 : error2.response) { logger.error(error2.response); return error2.response; } throw error2; } } // src/utils/crypto.ts import crypto from "crypto"; function generateSignature(timestamp, suiteTicket, suiteSecret) { const stringToSign = `${timestamp} ${suiteTicket}`; const hmac = crypto.createHmac("sha256", suiteSecret); hmac.update(stringToSign, "utf8"); const signature = hmac.digest("base64"); return signature; } function base64Encode(str) { return Buffer.from(str).toString("base64"); } function rfc2047Encode(str) { return `=?utf-8?B?${base64Encode(str)}?=`; } // src/push/dingtalk.ts var Debugger3 = debug3("push:dingtalk"); var dingtalkConfigSchema = { DINGTALK_ACCESS_TOKEN: { type: "string", title: "钉钉机器人 access_token", description: "钉钉机器人 access_token", required: true, default: "" }, DINGTALK_SECRET: { type: "string", title: "加签安全秘钥(HmacSHA256)", required: false, default: "" } }; var dingtalkOptionSchema = { msgtype: { type: "select", title: "消息类型", description: "消息类型", required: false, default: "text", options: [ { label: "文本", value: "text" }, { label: "Markdown", value: "markdown" }, { label: "链接", value: "link" }, { label: "按钮", value: "actionCard" }, { label: "FeedCard", value: "feedCard" } ] }, text: { type: "object", title: "文本", description: "文本", required: false, default: {} }, markdown: { type: "object", title: "Markdown", description: "Markdown", required: false, default: {} }, link: { type: "object", title: "链接", description: "链接", required: false, default: {} }, actionCard: { type: "object", title: "动作卡片", description: "动作卡片", required: false, default: {} }, feedCard: { type: "object", title: "订阅卡片", description: "订阅卡片", required: false, default: {} } }; var _Dingtalk = class _Dingtalk { /** * 参考文档 [钉钉开放平台 - 自定义机器人接入](https://developers.dingtalk.com/document/app/custom-robot-access) * @author CaoMeiYouRen * @date 2024-11-08 * @param config */ constructor(config) { this.webhook = "https://oapi.dingtalk.com/robot/send"; const { DINGTALK_ACCESS_TOKEN, DINGTALK_SECRET } = config; this.ACCESS_TOKEN = DINGTALK_ACCESS_TOKEN; this.SECRET = DINGTALK_SECRET; Debugger3("DINGTALK_ACCESS_TOKEN: %s , DINGTALK_SECRET: %s", this.ACCESS_TOKEN, this.SECRET); validate(config, _Dingtalk.configSchema); if (!this.SECRET) { warn("未提供 DINGTALK_SECRET !"); } } getSign(timeStamp) { let signStr = ""; if (this.SECRET) { signStr = generateSignature(timeStamp, this.SECRET, this.SECRET); Debugger3("Sign string is %s, result is %s", `${timeStamp} ${this.SECRET}`, signStr); } return signStr; } async push(data) { const timestamp = Date.now(); const sign = this.getSign(timestamp); const result = await ajax({ url: this.webhook, method: "POST", headers: { "Content-Type": "application/json" }, query: { timestamp, sign, access_token: this.ACCESS_TOKEN }, data }); Debugger3("Result is %s, %s。", result.data.errcode, result.data.errmsg); if (result.data.errcode === 31e4) { console.error("Send Failed:", result.data); Debugger3("Please check safe config : %O", result.data); } return result; } /** * * * @author CaoMeiYouRen * @date 2024-11-08 * @param title 消息的标题 * @param [desp] 消息的内容,支持 Markdown * @returns */ async send(title, desp, option) { var _a, _b, _c, _d, _e, _f, _g; Debugger3('title: "%s", desp: "%s", option: %O', title, desp, option); switch (option.msgtype) { case "text": return this.push({ msgtype: "text", text: { content: `${title}${desp ? ` ${desp}` : ""}` }, ...option }); case "markdown": return this.push({ msgtype: "markdown", markdown: { title, text: `# ${title}${desp ? ` ${desp}` : ""}` }, ...option }); case "link": return this.push({ msgtype: "link", link: { title, text: desp || "", picUrl: ((_a = option == null ? void 0 : option.link) == null ? void 0 : _a.picUrl) || "", messageUrl: ((_b = option.link) == null ? void 0 : _b.messageUrl) || "" }, ...option }); case "actionCard": return this.push({ msgtype: "actionCard", actionCard: { title, text: desp || "", btnOrientation: ((_c = option == null ? void 0 : option.actionCard) == null ? void 0 : _c.btnOrientation) || "0", btns: (_d = option == null ? void 0 : option.actionCard) == null ? void 0 : _d.btns, singleTitle: (_e = option == null ? void 0 : option.actionCard) == null ? void 0 : _e.singleTitle, singleURL: (_f = option == null ? void 0 : option.actionCard) == null ? void 0 : _f.singleURL }, ...option }); case "feedCard": return this.push({ msgtype: "feedCard", feedCard: { links: ((_g = option == null ? void 0 : option.feedCard) == null ? void 0 : _g.links) || [] }, ...option }); default: throw new Error("msgtype is required!"); } } }; _Dingtalk.namespace = "钉钉"; _Dingtalk.configSchema = dingtalkConfigSchema; _Dingtalk.optionSchema = dingtalkOptionSchema; var Dingtalk = _Dingtalk; // src/push/discord.ts import debug4 from "debug"; var Debugger4 = debug4("push:discord"); var discordConfigSchema = { DISCORD_WEBHOOK: { type: "string", title: "Webhook Url", description: "Webhook Url 可在服务器设置 -> 整合 -> Webhook -> 创建 Webhook 中获取", required: true }, PROXY_URL: { type: "string", title: "代理地址", description: "代理地址", required: false } }; var discordOptionSchema = { username: { type: "string", title: "机器人显示的名称", description: "机器人显示的名称", required: false }, avatar_url: { type: "string", title: "机器人头像的 Url", description: "机器人头像的 Url", required: false } }; var _Discord = class _Discord { /** * 创建 Discord 实例 * @author CaoMeiYouRen * @date 2024-11-08 * @param config 配置 */ constructor(config) { const { DISCORD_WEBHOOK, PROXY_URL } = config; Debugger4("DISCORD_WEBHOOK: %s, PROXY_URL: %s", DISCORD_WEBHOOK, PROXY_URL); this.DISCORD_WEBHOOK = DISCORD_WEBHOOK; if (PROXY_URL) { this.proxyUrl = PROXY_URL; } validate(config, _Discord.configSchema); } /** * 发送消息 * * @author CaoMeiYouRen * @date 2024-11-08 * @param title 消息的标题 * @param [desp] 消息的描述。最多 2000 个字符 * @param [option] 额外选项 */ async send(title, desp, option) { Debugger4('title: "%s", desp: "%s", option: %o', title, desp, option); const { username, avatar_url, ...args } = option || {}; const proxyUrl = this.proxyUrl; const content = `${title}${desp ? ` ${desp}` : ""}`; return ajax({ url: this.DISCORD_WEBHOOK, method: "POST", proxyUrl, data: { username, content, avatar_url, ...args } }); } }; _Discord.namespace = "Discord"; _Discord.configSchema = discordConfigSchema; _Discord.optionSchema = discordOptionSchema; var Discord = _Discord; // src/push/feishu.ts import debug5 from "debug"; var Debugger5 = debug5("push:feishu"); var feishuConfigSchema = { FEISHU_APP_ID: { type: "string", title: "飞书应用 ID", description: "飞书应用 ID", required: true, default: "" }, FEISHU_APP_SECRET: { type: "string", title: "飞书应用密钥", description: "飞书应用密钥", required: true, default: "" } }; var feishuOptionSchema = { receive_id_type: { type: "select", title: "用户 ID 类型", description: "用户 ID 类型", required: true, options: [ { label: "open_id", value: "open_id" }, { label: "union_id", value: "union_id" }, { label: "user_id", value: "user_id" }, { label: "email", value: "email" }, { label: "chat_id", value: "chat_id" } ] }, receive_id: { type: "string", title: "消息接收者的 ID", description: "消息接收者的 ID,ID 类型与查询参数 receive_id_type 的取值一致。", required: true }, msg_type: { type: "select", title: "消息类型", description: "消息类型", required: true, options: [ { label: "文本", value: "text" }, { label: "富文本", value: "post" }, { label: "图片", value: "image" }, { label: "文件", value: "file" }, { label: "语音", value: "audio" }, { label: "视频", value: "media" }, { label: "表情包", value: "sticker" }, { label: "卡片", value: "interactive" }, { label: "分享群名片", value: "share_chat" }, { label: "分享个人名片", value: "share_user" }, { label: "系统消息", value: "system" } ] }, content: { type: "string", title: "消息内容", description: "消息内容,JSON 结构序列化后的字符串。该参数的取值与 msg_type 对应,例如 msg_type 取值为 text,则该参数需要传入文本类型的内容。", required: false }, uuid: { type: "string", title: "自定义设置的唯一字符串序列", description: "自定义设置的唯一字符串序列,用于在发送消息时请求去重。持有相同 uuid 的请求,在 1 小时内至多成功发送一条消息。", required: false } }; var _Feishu = class _Feishu { constructor(config) { this.config = config; validate(config, _Feishu.configSchema); } async getAccessToken() { const { FEISHU_APP_ID, FEISHU_APP_SECRET } = this.config; const url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal"; const data = { app_id: FEISHU_APP_ID, app_secret: FEISHU_APP_SECRET }; const result = await ajax({ url, method: "POST", headers: { "Content-Type": "application/json; charset=utf-8" }, data }); const { code, msg, tenant_access_token, expire } = result.data; if (code !== 0) { throw new Error(msg || "获取 tenant_access_token 失败!"); } this.expiresTime = Date.now() + expire * 1e3; Debugger5("获取 tenant_access_token 成功: %s", tenant_access_token); return tenant_access_token; } async send(title, desp, option) { Debugger5('title: "%s", desp: "%s", option: %O', title, desp, option); if (!this.accessToken || Date.now() >= this.expiresTime) { this.accessToken = await this.getAccessToken(); } const { receive_id_type = "open_id", receive_id, msg_type = "text", content, uuid } = option; const data = { receive_id, msg_type, content, uuid }; if (!data.content) { switch (msg_type) { case "text": data.content = JSON.stringify({ text: `${title}${desp ? ` ${desp}` : ""}` }); break; case "post": data.content = JSON.stringify({ post: { zh_cn: { title, content: [ [ { tag: "text", text: desp } ] ] } } }); break; default: throw new Error("msg_type is required!"); } } const result = await ajax({ url: "https://open.feishu.cn/open-apis/im/v1/messages", method: "POST", headers: { "Content-Type": "application/json; charset=utf-8", Authorization: `Bearer ${this.accessToken}` }, data, query: { receive_id_type: receive_id_type || "open_id" } }); return result; } }; _Feishu.namespace = "飞书"; _Feishu.configSchema = feishuConfigSchema; _Feishu.optionSchema = feishuOptionSchema; var Feishu = _Feishu; // src/push/i-got.ts import debug6 from "debug"; var Debugger6 = debug6("push:i-got"); var iGotConfigSchema = { I_GOT_KEY: { type: "string", title: "iGot 推送key", description: "iGot 推送key", required: true, default: "" } }; var iGotOptionSchema = { url: { type: "string", title: "链接", description: "链接; 点开消息后会主动跳转至此地址", required: false, default: "" }, automaticallyCopy: { type: "number", title: "是否自动复制", description: "是否自动复制; 为1自动复制", required: false, default: 0 }, urgent: { type: "number", title: "紧急消息", description: "紧急消息,为1表示紧急。此消息将置顶在小程序内, 同时会在推送的消息内做一定的特殊标识", required: false, default: 0 }, copy: { type: "string", title: "需要自动复制的文本内容", description: "需要自动复制的文本内容", required: false, default: "" }, topic: { type: "string", title: "主题", description: "主题; 订阅链接下有效;对推送内容分类,用户可选择性订阅", required: false, default: "" } }; var _IGot = class _IGot { /** * @author CaoMeiYouRen * @date 2024-11-08 * @param config 微信搜索小程序“iGot”获取推送key */ constructor(config) { const { I_GOT_KEY } = config; this.I_GOT_KEY = I_GOT_KEY; Debugger6('set I_GOT_KEY: "%s"', I_GOT_KEY); validate(config, _IGot.configSchema); } /** * * * @author CaoMeiYouRen * @date 2024-11-08 * @param title 消息标题 * @param [desp] 消息正文 * @param [option] 额外选项 * @returns */ send(title, desp, option) { Debugger6('title: "%s", desp: "%s", option: "%o"', title, desp, option); return ajax({ url: `https://push.hellyw.com/${this.I_GOT_KEY}`, method: "POST", headers: { "Content-Type": "application/json" }, data: { title, content: desp || title, automaticallyCopy: 0, // 关闭自动复制 ...option } }); } }; _IGot.namespace = "iGot"; _IGot.configSchema = iGotConfigSchema; _IGot.optionSchema = iGotOptionSchema; var IGot = _IGot; // src/push/ntfy.ts import debug7 from "debug"; var Debugger7 = debug7("push:ntfy"); var ntfyConfigSchema = { NTFY_URL: { type: "string", title: "推送地址", description: "推送地址", required: true, default: "" }, NTFY_TOPIC: { type: "string", title: "主题", description: "主题", required: true, default: "" }, NTFY_AUTH: { type: "string", title: "认证参数", description: '支持 Basic Auth、Bearer Token。\nBasic Auth 示例:"Basic dGVzdDpwYXNz"\nBearer Token 示例:"Bearer tk_..."', required: false, default: "" } }; var ntfyOptionSchema = { title: { type: "string", title: "标题", description: "标题", required: false, default: "" }, body: { type: "string", title: "消息正文", description: "消息正文", required: false, default: "" }, priority: { type: "number", title: "消息优先级", description: "消息优先级(1-5,1最低,5最高)", required: false, default: 3 }, tags: { type: "string", title: "标签列表", description: "标签列表(逗号分隔),支持Emoji短代码", required: false, default: "" }, markdown: { type: "boolean", title: "启用Markdown格式", description: "启用Markdown格式(设为`true`或`yes`)", required: false, default: false }, delay: { type: "string", title: "延迟发送时间", description: "延迟发送时间(支持时间戳、自然语言如`tomorrow 10am`)", required: false, default: "" }, click: { type: "string", title: "点击通知时打开的URL", description: "点击通知时打开的URL", required: false, default: "" }, attach: { type: "string", title: "附加文件的URL", description: "附加文件的URL", required: false, default: "" }, filename: { type: "string", title: "附件的显示文件名", description: "附件的显示文件名", required: false, default: "" }, icon: { type: "string", title: "通知图标的URL", description: "通知图标的URL(仅支持JPEG/PNG)", required: false, default: "" }, actions: { type: "string", title: "定义通知的操作按钮", description: "定义通知的操作按钮(JSON或简写格式)", required: false, default: "" }, cache: { type: "boolean", title: "禁止服务器缓存消息", description: "设为`no`禁止服务器缓存消息", required: false, default: false }, firebase: { type: "boolean", title: "禁止转发到Firebase", description: "设为`no`禁止转发到Firebase(仅影响Android推送)", required: false, default: false }, unifiedPush: { type: "boolean", title: "启用UnifiedPush模式", description: "设为`1`启用UnifiedPush模式(用于Matrix网关)", required: false, default: false }, email: { type: "string", title: "邮箱", description: "将通知转发到指定邮箱", required: false, default: "" }, call: { type: "string", title: "发送语音呼叫", description: "发送语音呼叫(需验证手机号,仅限认证用户)", required: false, default: "" }, contentType: { type: "string", title: "编码格式", description: "设为`text/markdown`启用Markdown", required: false, default: "" }, file: { type: "object", title: "附件", description: "直接上传文件作为附件(需设置`X-Filename`)", required: false } }; var _Ntfy = class _Ntfy { constructor(config) { const { NTFY_URL, NTFY_AUTH, NTFY_TOPIC } = config; this.NTFY_URL = NTFY_URL; this.NTFY_TOPIC = NTFY_TOPIC; this.NTFY_AUTH = NTFY_AUTH; Debugger7('set NTFY_URL: "%s", NTFY_TOPIC: "%s", NTFY_AUTH: "%s"', NTFY_URL, NTFY_TOPIC, NTFY_AUTH); validate(config, _Ntfy.configSchema); } async send(title, desp, option) { Debugger7('option: "%o"', option); const { message, body, priority, tags, markdown, delay, click, attach, filename, icon, actions, cache, firebase, unifiedPush, email, call, contentType, file } = option || {}; const headers = {}; if (this.NTFY_AUTH) { headers["Authorization"] = this.NTFY_AUTH; } if (contentType) { headers["Content-Type"] = contentType; } const xTitle = title || option.title; if (xTitle) { headers["X-Title"] = rfc2047Encode(xTitle); } if (message) { headers["X-Message"] = rfc2047Encode(message); } if (priority) { headers["X-Priority"] = priority.toString(); } if (tags) { headers["X-Tags"] = tags; } if (markdown) { headers["X-Markdown"] = markdown.toString(); } if (delay) { headers["X-Delay"] = delay; } if (click) { headers["X-Click"] = click; } if (attach) { headers["X-Attach"] = attach; } if (filename) { headers["X-Filename"] = filename; } if (icon) { headers["X-Icon"] = icon; } if (actions) { headers["X-Actions"] = actions; } if (cache) { headers["X-Cache"] = cache ? "yes" : "no"; } if (firebase) { headers["X-Firebase"] = firebase ? "yes" : "no"; } if (unifiedPush) { headers["X-UnifiedPush"] = unifiedPush ? "1" : "0"; } if (email) { headers["X-Email"] = email; } if (call) { headers["X-Call"] = call; } if (file) { headers["X-Filename"] = file.name; headers["Content-Type"] = "application/octet-stream"; headers["Content-Length"] = file.size; headers["Content-Disposition"] = `attachment; filename="${file.name}"`; } Debugger7('headers: "%o"', headers); const data = desp || body || message; Debugger7('data: "%s"', data); const url = new URL(this.NTFY_TOPIC, this.NTFY_URL).toString(); const response = await ajax({ url, method: "POST", headers, data }); return response; } }; _Ntfy.namespace = "ntfy"; _Ntfy.configSchema = ntfyConfigSchema; _Ntfy.optionSchema = ntfyOptionSchema; var Ntfy = _Ntfy; // src/push/one-bot.ts import debug8 from "debug"; var Debugger8 = debug8("push:one-bot"); var oneBotConfigSchema = { ONE_BOT_BASE_URL: { type: "string", title: "OneBot HTTP 基础路径", description: "OneBot HTTP 基础路径", required: true }, ONE_BOT_ACCESS_TOKEN: { type: "string", title: "OneBot AccessToken", description: "出于安全原因,请务必设置 AccessToken", required: false } }; var oneBotOptionSchema = { message_type: { type: "select", title: "消息类型", description: "消息类型,private 或 group,默认为 private", required: true, default: "private", options: [ { label: "私聊", value: "private" }, { label: "群聊", value: "group" } ] }, user_id: { type: "number", title: " QQ 号", description: "对方 QQ 号。仅私聊有效。", required: false }, group_id: { type: "number", title: "群号", description: "群号。仅群聊有效。", required: false }, auto_escape: { type: "boolean", title: "消息内容是否作为纯文本发送(即不解析 CQ 码),只在 message 字段是字符串时有效", description: "消息内容是否作为纯文本发送(即不解析 CQ 码),只在 message 字段是字符串时有效", required: false } }; var _OneBot = class _OneBot { /** * 创建 OneBot 实例 * @author CaoMeiYouRen * @date 2024-11-08 * @param config OneBot 配置 */ constructor(config) { const { ONE_BOT_BASE_URL, ONE_BOT_ACCESS_TOKEN } = config; this.ONE_BOT_BASE_URL = ONE_BOT_BASE_URL; this.ONE_BOT_ACCESS_TOKEN = ONE_BOT_ACCESS_TOKEN; Debugger8('set ONE_BOT_BASE_URL: "%s", ONE_BOT_ACCESS_TOKEN: "%s"', ONE_BOT_BASE_URL, ONE_BOT_ACCESS_TOKEN); validate(config, _OneBot.configSchema); if (!this.ONE_BOT_ACCESS_TOKEN) { warn("未提供 ONE_BOT_ACCESS_TOKEN !出于安全原因,请务必设置 AccessToken!"); } } /** * * * @author CaoMeiYouRen * @date 2024-11-08 * @param title 消息标题 * @param desp 消息正文 * @param option 额外推送选项 */ async send(title, desp, option) { Debugger8('title: "%s", desp: "%s", option: "%o"', title, desp, option); validate(option, _OneBot.optionSchema); if (option.message_type === "private" && !option.user_id) { throw new Error("OneBot 私聊消息类型必须提供 user_id"); } if (option.message_type === "group" && !option.group_id) { throw new Error("OneBot 群聊消息类型必须提供 group_id"); } const { message_type = "private", ...args } = option || {}; const message = `${title}${desp ? ` ${desp}` : ""}`; return ajax({ baseURL: this.ONE_BOT_BASE_URL, url: "/send_msg", method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.ONE_BOT_ACCESS_TOKEN}` }, data: { auto_escape: false, message_type, message, ...args } }); } }; _OneBot.namespace = "OneBot"; _OneBot.configSchema = oneBotConfigSchema; _OneBot.optionSchema = oneBotOptionSchema; /** * OneBot 协议版本号 * * @author CaoMeiYouRen * @date 2023-10-22 * @static */ _OneBot.version = 11; var OneBot = _OneBot; // src/push/push-deer.ts import debug9 from "debug"; var Debugger9 = debug9("push:push-deer"); var pushDeerConfigSchema = { PUSH_DEER_PUSH_KEY: { type: "string", title: "pushkey", description: "请参考 https://github.com/easychen/pushdeer 获取", required: true }, PUSH_DEER_ENDPOINT: { type: "string", title: "使用自架版时的服务器端地址", description: "例如 http://127.0..1:8800。默认为 https://api2.pushdeer.com", required: false, default: "https://api2.pushdeer.com" } }; var pushDeerOptionSchema = { type: { type: "select", title: "格式", description: "文本=text,markdown,图片=image,默认为markdown。type 为 image 时,text 中为要发送图片的URL", required: false, default: "markdown", options: [ { label: "文本", value: "text" }, { label: "Markdown", value: "markdown" }, { label: "图片", value: "image" } ] } }; var _PushDeer = class _PushDeer { /** * 创建 PushDeer 实例 * @author CaoMeiYouRen * @date 2024-11-08 * @param config 配置 */ constructor(config) { const { PUSH_DEER_PUSH_KEY, PUSH_DEER_ENDPOINT } = config; this.PUSH_DEER_PUSH_KEY = PUSH_DEER_PUSH_KEY; this.PUSH_DEER_ENDPOINT = PUSH_DEER_ENDPOINT || "https://api2.pushdeer.com"; Debugger9('set PUSH_DEER_PUSH_KEY: "%s", PUSH_DEER_ENDPOINT: "%s"', PUSH_DEER_PUSH_KEY, PUSH_DEER_ENDPOINT); validate(config, _PushDeer.configSchema); } /** * @author CaoMeiYouRen * @date 2024-11-08 * @param text 推送消息内容 * @param [desp=''] 消息内容第二部分 * @param [option={}] 额外推送选项 */ async send(title, desp = "", option) { Debugger9('title: "%s", desp: "%s", option: "%o"', title, desp, option); const { type = "markdown" } = option || {}; return ajax({ baseURL: this.PUSH_DEER_ENDPOINT, url: "/message/push", method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, data: { text: title, desp, pushkey: this.PUSH_DEER_PUSH_KEY, type } }); } }; _PushDeer.namespace = "PushDeer"; _PushDeer.configSchema = pushDeerConfigSchema; _PushDeer.optionSchema = pushDeerOptionSchema; var PushDeer = _PushDeer; // src/push/push-plus.ts import debug10 from "debug"; var Debugger10 = debug10("push:push-plus"); var pushPlusConfigSchema = { PUSH_PLUS_TOKEN: { type: "string", title: "PushPlus Token", description: "请前往 https://www.pushplus.plus/ 领取", required: true } }; var pushPlusOptionSchema = { template: { type: "select", title: "模板类型", description: "html,txt,json,markdown,cloudMonitor,jenkins,route", required: false, default: "html", options: [ { label: "HTML", value: "html" }, { label: "文本", value: "txt" }, { label: "JSON", value: "json" }, { label: "Markdown", value: "markdown" }, { label: "阿里云监控", value: "cloudMonitor" }, { label: "Jenkins", value: "jenkins" }, { label: "路由器", value: "route" } ] }, channel: { type: "select", title: "渠道类型", description: "wechat,webhook,cp,sms,mail", required: false, default: "wechat", options: [ { label: "微信", value: "wechat" }, { label: "Webhook", value: "webhook" }, { label: "企业微信", value: "cp" }, { label: "邮件", value: "mail" }, { label: "短信", value: "sms" } ] }, topic: { type: "string", title: "群组编码", description: "不填仅发送给自己;channel为webhook时无效", required: false, default: "" }, webhook: { type: "string", title: "webhook编码", description: "仅在channel使用webhook渠道和CP渠道时需要填写", required: false, default: "" }, callbackUrl: { type: "string", title: "发送结果回调地址", description: "发送结果回调地址", required: false, default: "" }, timestamp: { type: "number", title: "毫秒时间戳", description: "格式如:1632993318000。服务器时间戳大于此时间戳,则消息不会发送", required: false // default: 0, } }; var _PushPlus = class _PushPlus { /** * * @author CaoMeiYouRen * @date 2024-11-08 * @param config 请前往 https://www.pushplus.plus 领取 */ constructor(config) { const { PUSH_PLUS_TOKEN } = config; this.PUSH_PLUS_TOKEN = PUSH_PLUS_TOKEN; Debugger10('set PUSH_PLUS_TOKEN: "%s"', PUSH_PLUS_TOKEN); validate(config, _PushPlus.configSchema); } /** * 发送消息 * * @author CaoMeiYouRen * @date 2024-11-08 * @param title 消息标题 * @param [desp=''] 消息内容 * @param [option] 额外推送选项 */ send(title, desp = "", option) { Debugger10('title: "%s", desp: "%s", option: "%o"', title, desp, option); const { template = "html", channel = "wechat", ...args } = option || {}; const content = desp || title; return ajax({ url: "http://www.pushplus.plus/send", method: "POST", headers: { "Content-Type": "application/json" }, data: { token: this.PUSH_PLUS_TOKEN, title, content: content || title, template, channel, ...args } }); } }; _PushPlus.namespace = "PushPlus"; _PushPlus.configSchema = pushPlusConfigSchema; _PushPlus.optionSchema = pushPlusOptionSchema; var PushPlus = _PushPlus; // src/push/qmsg.ts import debug11 from "debug"; var Debugger11 = debug11("push:qmsg"); var qmsgConfigSchema = { QMSG_KEY: { type: "string", title: "推送的 key", description: "在 [Qmsg 酱管理台](https://qmsg.zendee.cn/user) 查看", required: true } }; var qmsgOptionSchema = { type: { type: "select", title: "消息类型", description: "send 表示发送消息给指定的QQ号,group 表示发送消息给指定的QQ群。默认为 send", required: true, default: "send", options: [ { label: "私聊", value: "send" }, { label: "群聊", value: "group" } ] }, qq: { type: "string", title: "指定要接收消息的QQ号或者QQ群", description: "多个以英文逗号分割,例如:12345,12346", required: true }, bot: { type: "string", title: "机器人的QQ号", description: "指定使用哪个机器人来发送消息,不指定则会自动随机选择一个在线的机器人发送消息。该参数仅私有云有效", required: false } }; var _Qmsg = class _Qmsg { constructor(config) { const { QMSG_KEY } = config; this.QMSG_KEY = QMSG_KEY; Debugger11('set QMSG_KEY: "%s"', QMSG_KEY); validate(config, _Qmsg.configSchema); } /** * * 发送消息 * @author CaoMeiYouRen * @date 2024-11-08 * @param title 消息标题 * @param [desp] 消息描述 * @param [option] QmsgOption 选项 */ async send(title, desp, option) { Debugger11('title: "%s", desp: "%s", option: "%o"', title, desp, option); validate(option, _Qmsg.optionSchema); const { qq, type = "send", bot } = option || {}; const msg = `${title}${desp ? ` ${desp}` : ""}`; return ajax({ url: `https://qmsg.zendee.cn/${type}/${this.QMSG_KEY}`, headers: { "Content-Type": "application/x-www-form-urlencoded" }, method: "POST", data: { msg, qq, bot } }); } }; _Qmsg.namespace = "Qmsg酱"; _Qmsg.configSchema = qmsgConfigSchema; _Qmsg.optionSchema = qmsgOptionSchema; var Qmsg = _Qmsg; // src/push/server-chan-turbo.ts import debug12 from "debug"; var Debugger12 = debug12("push:server-chan-turbo"); var serverChanTurboConfigSchema = { SERVER_CHAN_TURBO_SENDKEY: { type: "string", title: "SCTKEY", description: "Server酱 Turbo 的 SCTKEY。请前往 https://sct.ftqq.com/sendkey 领取", required: true } }; var serverChanTurboOptionSchema = { short: { type: "string", title: "消息卡片内容", description: "选填。最大长度 64。如果不指定,将自动从 desp 中截取生成。", required: false }, noip: { type: "boolean", title: "是否隐藏调用 IP", description: "选填。如果不指定,则显示;为 1/true 则隐藏。", required: false }, channel: { type: "string", title: "消息通道", description: '选填。动态指定本次推送使用的消息通道,支持最多两个通道,多个通道值用竖线 "|" 隔开。', required: false }, openid: { type: "string", title: "消息抄送的 openid", description: '选填。只支持测试号和企业微信应用消息通道。多个 openid 用 "," 隔开。企业微信应用消息通道的 openid 参数,内容为接收人在企业微信中的 UID,多个人请 "|" 隔开。', required: false } }; var _ServerChanTurbo = class _ServerChanTurbo { /** * * @author CaoMeiYouRen * @date 2024-11-08 * @param config 请前往 https://sct.ftqq.com/sendkey 领取 */ constructor(config) { const { SERVER_CHAN_TURBO_SENDKEY } = config; this.SCTKEY = SERVER_CHAN_TURBO_SENDKEY; Debugger12('set SCTKEY: "%s"', this.SCTKEY); validate(config, _ServerChanTurbo.configSchema); } /** * 发送消息 * * @author CaoMeiYouRen * @date 2024-11-08 * @param title 消息的标题 * @param [desp=''] 消息的内容,支持 Markdown * @param [option={}] 额外发送选项 */ async send(title, desp = "", option = {}) { Debugger12('title: "%s", desp: "%s", option: %O', title, desp, option); if (option.noip === 1 || option.noip === true) { option.noip = "1"; } const data = { text: title, desp, ...option }; return ajax({ url: `https://sctapi.ftqq.com/${this.SCTKEY}.send`, method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, data }); } }; _ServerChanTurbo.namespace = "Server酱·Turbo"; _ServerChanTurbo.configSchema = serverChanTurboConfigSchema; _ServerChanTurbo.optionSchema = serverChanTurboOptionSchema; var ServerChanTurbo = _ServerChanTurbo; // src/push/server-chan-v3.ts import debug13 from "debug"; var Debugger13 = debug13("push:server-chan-v3"); var serverChanV3ConfigSchema = { SERVER_CHAN_V3_SENDKEY: { type: "string", title: "SENDKEY", description: "请前往 https://sc3.ft07.com/sendkey 领取", required: true } }; var serverChanV3OptionSchema = { tags: { type: "array", title: "标签列表", description: "多个标签用数组格式", required: false }, short: { type: "string", title: "推送消息的简短描述", description: "用于指定消息卡片的内容部分,尤其是在推送markdown的时候", required: false } }; var _ServerChanV3 = class _ServerChanV3 { /** * 创建 ServerChanV3 实例 * @author CaoMeiYouRen * @date 2024-11-08 * @param config 请前往 https://sc3.ft07.com/sendkey 领取 */ constructor(config) { this.uid = ""; var _a; const { SERVER_CHAN_V3_SENDKEY } = config; const sendkey = SERVER_CHAN_V3_SENDKEY; this.sendkey = sendkey; Debugger13('set sendkey: "%s"', sendkey); validate(config, _ServerChanV3.configSchema); this.uid = (_a = this.sendkey.match(/^sctp(\d+)t/)) == null ? void 0 : _a[1]; if (!this.uid) { throw new Error("SERVER_CHAN_V3_SENDKEY 不合法!"); } } /** * 发送消息 * * @author CaoMeiYouRen * @date 2024-11-08 * @param title 消息的标题 * @param [desp=''] 消息的内容,支持 Markdown * @param [option={}] 额外发送选项 */ async send(title, desp = "", option = {}) { Debugger13('title: "%s", desp: "%s", option: %O', title, desp, option); if (Array.isArray(option.tags)) { option.tags = option.tags.join("|"); } const data = { text: title, desp, ...option }; return ajax({ url: `https://${this.uid}.push.ft07.com/send/${this.sendkey}.send`, method: "POST", headers: { "Content-Type": "application/json" }, data }); } }; _ServerChanV3.namespace = "Server酱³"; _ServerChanV3.configSchema = serverChanV3ConfigSchema; _ServerChanV3.optionSchema = serverChanV3OptionSchema; var ServerChanV3 = _ServerChanV3; // src/push/telegram.ts import debug14 from "debug"; var Debugger14 = debug14("push:telegram"); var telegramConfigSchema = { TELEGRAM_BOT_TOKEN: { type: "string", title: "机器人令牌", description: "您可以从 https://t.me/BotFather 获取 Token。", required: true }, TELEGRAM_CHAT_ID: { type: "number", title: "支持对话/群组/频道的 Chat ID", description: "您可以转发消息到 https://t.me/JsonDumpBot 获取 Chat ID", required: true }, PROXY_URL: { type: "string", title: "代理地址", description: "代理地址", required: false } }; var telegramOptionSchema = { disable_notification: { type: "boolean", title: "静默发送", description: "静默地发送消息。消息发布后用户会收到无声通知。", required: false }, protect_content: { type: "boolean", title: "阻止转发/保存", description: "如果启用,Telegram 中的机器人消息将受到保护,不会被转发和保存。", required: false }, message_thread_id: { type: "string", title: "话题 ID", description: "可选的唯一标识符,用以向该标识符对应的话题发送消息,仅限启用了话题功能的超级群组可用", required: false } }; var _Telegram = class _Telegram { constructor(config) { Debugger14("config: %O", config); Object.assign(this, config); validate(config, _Telegram.configSchema); if (config.PROXY_URL) { this.proxyUrl = config.PROXY_URL; } } /** * 发送消息 * * @author CaoMeiYouRen * @date 2024-11-09 * @param title 消息标题 * @param [desp] 消息正文,和 title 相加后不超过 4096 个字符 * @param [option] 其他参数 */ async send(title, desp, option) { const url = `https://api.telegram.org/bot${this.TELEGRAM_BOT_TOKEN}/sendMessage`; Debugger14('title: "%s", desp: "%s", option: %O', title, desp, option); const text = `${title}${desp ? ` ${desp}` : ""}`; return ajax({ url, method: "POST", proxyUrl: this.proxyUrl, data: { chat_id: this.TELEGRAM_CHAT_ID, text } }); } }; _Telegram.namespace = "Telegram"; _Telegram.configSchema = telegramConfigSchema; _Telegram.optionSchema = telegramOptionSchema; var Telegram = _Telegram; // src/push/wechat-app.ts import debug15 from "debug"; var Debugger15 = debug15("push:wechat-app"); var wechatAppConfigSchema = { WECHAT_APP_CORPID: { type: "string", title: "企业ID", description: "企业ID,获取方式参考:[术语说明-corpid](https://work.weixin.qq.com/api/doc/90000/90135/91039#14953/corpid)", required: true, default: "" }, WECHAT_APP_SECRET: { type: "string", title: "应用的凭证密钥", description: "应用的凭证密钥,获取方式参考:[术语说明-secret](https://work.weixin.qq.com/api/doc/90000/90135/91039#14953/secret)", required: true, default: "" }, WECHAT_APP_AGENTID: { type: "number", title: "企业应用的id", description: "企业应用的id。企业内部开发,可在应用的设置页面查看", required: true, default: 0 } }; var wechatAppOptionSchema = { msgtype: { type: "select", title: "消息类型", description: "消息类型", required: true, options: [ { label: "文本", value: "text" }, { label: "Markdown", value: "markdown" }, { label: "语音", value: "voice" }, { label: "文件", value: "file" }, { label: "图片", value: "image" }, { label: "视频", value: "video" }, { label: "图文", value: "news" }, { label: "小程序通知", value: "miniprogram_notice" }, { label: "模板卡片", value: "template_card" } ] }, safe: { type: "select", title: "是否是保密消息", description: "表示是否是保密消息,0表示可对外分享,1表示不能", required: false, options: [ { label: "否", value: 0 }, { label: "是", value: 1 } ] }, enable_id_trans: { type: "select", title: "是否开启id转译", description: "表示是否开启id转译,0表示否,1表示是,默认0。", required: false, options: [ { label: "否", value: 0 }, { label: "是", value: 1 } ] }, enable_duplicate_check: { type: "select", title: "是否开启重复消息检查", description: "表示是否开启重复消息检查,0表示否,1表示是,默认", required: false, options: [ { label: "否", value: 0 }, { label: "是", value: 1 } ] }, duplicate_check_interval: { type: "number", title: "重复消息检查的时间间隔", description: "表示是否重复消息检查的时间间隔,默认1800s,最大不超过4小时", required: false }, touser: { type: "string", title: "指定接收消息的成员", description: "指定接收消息的成员,成员ID列表(多个接收者用‘|’分隔,最多支持1000个)。", required: false }, toparty: { type: "string", title: "指定接收消息的部门", description: "指定接收消息的部门,部门ID列表,多个接收者用‘|’分隔,最多支持100个。", required: false }, totag: { type: "string", title: "指定接收消息的标签", description: "指定接收消息的标签,标签ID列表,多个接收者用‘|’分隔,最多支持100个。", required: false } }; var _WechatApp = class _WechatApp { constructor(config) { Debugger15("config: %O", config); Object.assign(this, config); validate(config, _WechatApp.configSchema); } async getAccessToken() { const { data } = await ajax({ url: "https://qyapi.weixin.qq.com/cgi-bin/gettoken", query: { corpid: this.WECHAT_APP_CORPID, corpsecret: this.WECHAT_APP_SECRET } }); if ((data == null ? void 0 : data.errcode) !== 0) { throw new Error((data == null ? void 0 : data.errmsg) || "获取 access_token 失败!"); } const { access_token, expires_in = 7200 } = data; Debugger15("获取 access_token 成功: %s", access_token); this.extendexpiresTime(expires_in); return access_token; } /** * 延长过期时间 * * @author CaoMeiYouRen * @date 2021-03-03 * @private * @param expiresIn 延长的秒数 */ extendexpiresTime(expiresIn) { this.expiresTime = Date.now() + expiresIn * 1e3; } /** * 发送消息 * * @author CaoMeiYouRen * @date 2024-11-08 * @param title 消息标题 * @param [desp] 消息内容,最长不超过2048个字节,超过将截断(支持id转译) * @param [option] 额外推送选项 */ async send(title, desp, option) { Debugger15('title: "%s", desp: "%s", option: %O', title, desp, option); if (!this.ACCESS_TOKEN || Date.now() >= this.expiresTime) { this.ACCESS_TOKEN = await this.getAccessToken(); } const { msgtype = "text", touser: _touser, ...args } = option || {}; if (!_touser) { warn('未指定 touser,将使用 "@all" 向全体成员推送'); } const sep = msgtype === "markdown" ? "\n\n" : "\n"; const content = `${title}${desp ? `${sep}${desp}` : ""}`; const touser = _touser || "@all"; return ajax({ url: "https://qyapi.weixin.qq.com/cgi-bin/message/send", method: "POST", headers: { "Content-Type": "application/json" }, query: { access_token: this.ACCESS_TOKEN }, data: { touser, msgtype, agentid: this.WECHAT_APP_AGENTID, [msgtype]: { content }, ...args } }); } }; _WechatApp.namespace = "企业微信应用"; _WechatApp.configSchema = wechatAppConfigSchema; _WechatApp.optionSchema = wechatAppOptionSchema; var WechatApp = _WechatApp; // src/push/wechat-robot.ts import debug16 from "debug"; var Debugger16 = debug16("push:wechat-robot"); var wechatRobotConfigSchema = { WECHAT_ROBOT_KEY: { type: "string", title: "企业微信机器人的key", description: "企业微信机器人的key", required: true } }; var wechatRobotOptionSchema = { msgtype: { type: "select", title: "消息类型", description: "消息类型", options: [ { label: "文本", value: "text" }, { label: "Markdown", value: "markdown" }, { label: "图片", value: "image" }, { label: "图文", value: "news" }, { label: "文件", value: "file" }, { label: "语音", value: "voice" }, { label: "模板卡片", value: "template_card