push-all-in-one
Version:
Push All In One!支持 Server酱(以及 Server 酱³)、自定义邮件、钉钉机器人、企业微信机器人、企业微信应用、pushplus、iGot 、Qmsg、息知、PushDeer、Discord、OneBot、Telegram 等多种推送方式
2,284 lines (2,263 loc) • 62.7 kB
JavaScript
import { createRequire } from "node:module";
import debug from "debug";
import nodemailer from "nodemailer";
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";
import { SocksProxyAgent } from "socks-proxy-agent";
import crypto from "node:crypto";
//#region rolldown:runtime
var __require = /* @__PURE__ */ createRequire(import.meta.url);
//#endregion
//#region src/utils/helper.ts
let colors;
if (globalThis.process && typeof globalThis.process.on === "function") try {
colors = __require("@colors/colors");
} catch {
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);
}
const logger = {
warn,
error
};
/**
* 检测是否为 http/https 开头的 url
* @param url
* @returns
*/
const isHttpURL = (url) => /^(https?:\/\/)/.test(url);
/**
* 检测是否为 socks/socks5 开头的 url
* @param url
* @returns
*/
const isSocksUrl = (url) => /^(socks5?:\/\/)/.test(url);
/**
* 判断是否为 null 或 undefined 或 空字符串
* @param value
* @returns
*/
function isEmpty(value) {
return value === null || value === void 0 || value === "";
}
/**
* 数组去重
*
* @author CaoMeiYouRen
* @date 2025-03-05
* @export
* @template T
* @param arr
*/
function uniq(arr) {
return Array.from(new Set(arr));
}
//#endregion
//#region src/utils/validate.ts
/**
* 验证配置是否符合 Schema 规则,如果不符合则抛出错误
*
* @author CaoMeiYouRen
* @date 2024-11-17
* @export
* @template T
* @param config
* @param schema
*/
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}" 字段类型不支持!`);
});
}
//#endregion
//#region src/push/custom-email.ts
const Debugger$17 = debug("push:custom-email");
const 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
}
};
const 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: ""
}
};
/**
* 自定义邮件。官方文档: https://github.com/nodemailer/nodemailer
*
* @author CaoMeiYouRen
* @date 2023-03-12
* @export
* @class CustomEmail
*/
var CustomEmail = class CustomEmail {
static namespace = "自定义邮件";
static configSchema = customEmailConfigSchema;
static optionSchema = customEmailOptionSchema;
config;
transporter;
constructor(config) {
this.config = config;
Debugger$17("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) {
Debugger$17("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$17("CustomEmail Response: %o", response);
if (response.response?.includes("250 OK")) return {
status: 200,
statusText: "OK",
data: response,
headers: {}
};
return {
status: 500,
statusText: "Internal Server Error",
data: response,
headers: {}
};
}
};
//#endregion
//#region src/utils/ajax.ts
const Debugger$16 = debug("push:ajax");
/**
* axios 接口封装
*
* @author CaoMeiYouRen
* @date 2021-02-27
* @export
* @param config
* @returns
*/
async function ajax(config) {
try {
Debugger$16("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;
Debugger$16("NO_PROXY: %s", process.env.NO_PROXY);
if (process.env.NO_PROXY !== "true") {
Debugger$16("HTTP_PROXY: %s", process.env.HTTP_PROXY);
Debugger$16("HTTPS_PROXY: %s", process.env.HTTPS_PROXY);
Debugger$16("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
});
Debugger$16("response data: %O", response.data);
return response;
} catch (error$1) {
if (error$1?.response) {
logger.error(error$1.response);
return error$1.response;
}
throw error$1;
}
}
//#endregion
//#region src/utils/crypto.ts
/**
* 生成钉钉签名
*
* @author CaoMeiYouRen
* @date 2024-10-30
* @export
* @param timestamp
* @param suiteTicket
* @param suiteSecret
*/
function generateSignature(timestamp, suiteTicket, suiteSecret) {
const stringToSign = `${timestamp}\n${suiteTicket}`;
const hmac = crypto.createHmac("sha256", suiteSecret);
hmac.update(stringToSign, "utf8");
return hmac.digest("base64");
}
function base64Encode(str) {
return Buffer.from(str).toString("base64");
}
function rfc2047Encode(str) {
return `=?utf-8?B?${base64Encode(str)}?=`;
}
//#endregion
//#region src/push/dingtalk.ts
const Debugger$15 = debug("push:dingtalk");
const dingtalkConfigSchema = {
DINGTALK_ACCESS_TOKEN: {
type: "string",
title: "钉钉机器人 access_token",
description: "钉钉机器人 access_token",
required: true,
default: ""
},
DINGTALK_SECRET: {
type: "string",
title: "加签安全秘钥(HmacSHA256)",
required: false,
default: ""
}
};
const 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: {}
}
};
/**
* 钉钉机器人推送
* 在 [dingtalk-robot-sdk](https://github.com/ineo6/dingtalk-robot-sdk) 的基础上重构了一下,用法几乎完全一致。
* @author CaoMeiYouRen
* @date 2021-02-27
* @export
* @class Dingtalk
*/
var Dingtalk = class Dingtalk {
static namespace = "钉钉";
static configSchema = dingtalkConfigSchema;
static optionSchema = dingtalkOptionSchema;
ACCESS_TOKEN;
/**
* 加签安全秘钥(HmacSHA256)
*
* @private
*/
SECRET;
webhook = "https://oapi.dingtalk.com/robot/send";
/**
* 参考文档 [钉钉开放平台 - 自定义机器人接入](https://developers.dingtalk.com/document/app/custom-robot-access)
* @author CaoMeiYouRen
* @date 2024-11-08
* @param config
*/
constructor(config) {
const { DINGTALK_ACCESS_TOKEN, DINGTALK_SECRET } = config;
this.ACCESS_TOKEN = DINGTALK_ACCESS_TOKEN;
this.SECRET = DINGTALK_SECRET;
Debugger$15("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);
Debugger$15("Sign string is %s, result is %s", `${timeStamp}\n${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
});
Debugger$15("Result is %s, %s。", result.data.errcode, result.data.errmsg);
if (result.data.errcode === 31e4) {
console.error("Send Failed:", result.data);
Debugger$15("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) {
Debugger$15("title: \"%s\", desp: \"%s\", option: %O", title, desp, option);
switch (option.msgtype) {
case "text": return this.push({
msgtype: "text",
text: { content: `${title}${desp ? `\n${desp}` : ""}` },
...option
});
case "markdown": return this.push({
msgtype: "markdown",
markdown: {
title,
text: `# ${title}${desp ? `\n\n${desp}` : ""}`
},
...option
});
case "link": return this.push({
msgtype: "link",
link: {
title,
text: desp || "",
picUrl: option?.link?.picUrl || "",
messageUrl: option.link?.messageUrl || ""
},
...option
});
case "actionCard": return this.push({
msgtype: "actionCard",
actionCard: {
title,
text: desp || "",
btnOrientation: option?.actionCard?.btnOrientation || "0",
btns: (option?.actionCard)?.btns,
singleTitle: (option?.actionCard)?.singleTitle,
singleURL: (option?.actionCard)?.singleURL
},
...option
});
case "feedCard": return this.push({
msgtype: "feedCard",
feedCard: { links: option?.feedCard?.links || [] },
...option
});
default: throw new Error("msgtype is required!");
}
}
};
//#endregion
//#region src/push/discord.ts
const Debugger$14 = debug("push:discord");
const discordConfigSchema = {
DISCORD_WEBHOOK: {
type: "string",
title: "Webhook Url",
description: "Webhook Url 可在服务器设置 -> 整合 -> Webhook -> 创建 Webhook 中获取",
required: true
},
PROXY_URL: {
type: "string",
title: "代理地址",
description: "代理地址",
required: false
}
};
const discordOptionSchema = {
username: {
type: "string",
title: "机器人显示的名称",
description: "机器人显示的名称",
required: false
},
avatar_url: {
type: "string",
title: "机器人头像的 Url",
description: "机器人头像的 Url",
required: false
}
};
/**
* Discord Webhook 推送
*
* @author CaoMeiYouRen
* @date 2023-09-17
* @export
* @class Discord
*/
var Discord = class Discord {
static namespace = "Discord";
static configSchema = discordConfigSchema;
static optionSchema = discordOptionSchema;
/**
* Webhook Url 可在服务器设置 -> 整合 -> Webhook -> 创建 Webhook 中获取
*
* @author CaoMeiYouRen
* @date 2023-09-17
* @private
*/
DISCORD_WEBHOOK;
proxyUrl;
/**
* 创建 Discord 实例
* @author CaoMeiYouRen
* @date 2024-11-08
* @param config 配置
*/
constructor(config) {
const { DISCORD_WEBHOOK, PROXY_URL } = config;
Debugger$14("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) {
Debugger$14("title: \"%s\", desp: \"%s\", option: %o", title, desp, option);
const { username, avatar_url, ...args } = option || {};
const proxyUrl = this.proxyUrl;
const content = `${title}${desp ? `\n${desp}` : ""}`;
return ajax({
url: this.DISCORD_WEBHOOK,
method: "POST",
proxyUrl,
data: {
username,
content,
avatar_url,
...args
}
});
}
};
//#endregion
//#region src/push/feishu.ts
const Debugger$13 = debug("push:feishu");
const feishuConfigSchema = {
FEISHU_APP_ID: {
type: "string",
title: "飞书应用 ID",
description: "飞书应用 ID",
required: true,
default: ""
},
FEISHU_APP_SECRET: {
type: "string",
title: "飞书应用密钥",
description: "飞书应用密钥",
required: true,
default: ""
}
};
const 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
}
};
/**
* 飞书。官方文档:https://open.feishu.cn/document/home/index
*
* @author CaoMeiYouRen
* @date 2025-02-10
* @export
* @class Feishu
*/
var Feishu = class Feishu {
static namespace = "飞书";
static configSchema = feishuConfigSchema;
static optionSchema = feishuOptionSchema;
config;
/**
* accessToken 的过期时间(时间戳)
*/
expiresTime;
accessToken;
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 { code, msg, tenant_access_token, expire } = (await ajax({
url,
method: "POST",
headers: { "Content-Type": "application/json; charset=utf-8" },
data
})).data;
if (code !== 0) throw new Error(msg || "获取 tenant_access_token 失败!");
this.expiresTime = Date.now() + expire * 1e3;
Debugger$13("获取 tenant_access_token 成功: %s", tenant_access_token);
return tenant_access_token;
}
async send(title, desp, option) {
Debugger$13("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 ? `\n${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!");
}
return 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" }
});
}
};
//#endregion
//#region src/push/i-got.ts
const Debugger$12 = debug("push:i-got");
const iGotConfigSchema = { I_GOT_KEY: {
type: "string",
title: "iGot 推送key",
description: "iGot 推送key",
required: true,
default: ""
} };
const 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: ""
}
};
/**
* iGot 推送,官方文档:http://hellyw.com
*
* @author CaoMeiYouRen
* @date 2021-03-03
* @export
* @class IGot
*/
var IGot = class IGot {
static namespace = "iGot";
static configSchema = iGotConfigSchema;
static optionSchema = iGotOptionSchema;
/**
* 微信搜索小程序“iGot”获取推送key
*
* @private
*/
I_GOT_KEY;
/**
* @author CaoMeiYouRen
* @date 2024-11-08
* @param config 微信搜索小程序“iGot”获取推送key
*/
constructor(config) {
const { I_GOT_KEY } = config;
this.I_GOT_KEY = I_GOT_KEY;
Debugger$12("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) {
Debugger$12("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
}
});
}
};
//#endregion
//#region src/push/ntfy.ts
const Debugger$11 = debug("push:ntfy");
const 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: ""
}
};
const 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
}
};
/**
* ntfy推送。
* 官方文档:https://ntfy.sh/docs/publish/
*
* @author CaoMeiYouRen
* @date 2025-02-11
* @export
* @class Ntfy
*/
var Ntfy = class Ntfy {
static namespace = "ntfy";
static configSchema = ntfyConfigSchema;
static optionSchema = ntfyOptionSchema;
/**
* 推送地址
*/
NTFY_URL;
/**
* 认证参数。
* 支持 Basic Auth、Bearer Token。
* Basic Auth 示例:"Basic dGVzdDpwYXNz"
* Bearer Token 示例:"Bearer tk_..."
*/
NTFY_AUTH;
/**
* 主题
* 用于区分不同的推送目标。
* 主题本质上是一个密码,所以请选择不容易猜到的东西。
* 例如:`my-topic`
*/
NTFY_TOPIC;
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;
Debugger$11("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) {
Debugger$11("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}"`;
}
Debugger$11("headers: \"%o\"", headers);
const data = desp || body || message;
Debugger$11("data: \"%s\"", data);
return await ajax({
url: new URL(this.NTFY_TOPIC, this.NTFY_URL).toString(),
method: "POST",
headers,
data
});
}
};
//#endregion
//#region src/push/one-bot.ts
const Debugger$10 = debug("push:one-bot");
const 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
}
};
const 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
}
};
/**
* OneBot。官方文档:https://github.com/botuniverse/onebot-11
* 本项目实现的版本为 OneBot 11
* @author CaoMeiYouRen
* @date 2023-10-22
* @export
* @class OneBot
*/
var OneBot = class OneBot {
static namespace = "OneBot";
static configSchema = oneBotConfigSchema;
static optionSchema = oneBotOptionSchema;
/**
* OneBot 协议版本号
*
* @author CaoMeiYouRen
* @date 2023-10-22
* @static
*/
static version = 11;
/**
* OneBot HTTP 基础路径
*
* @author CaoMeiYouRen
* @date 2023-10-22
* @private
* @example http://127.0.0.1
*/
ONE_BOT_BASE_URL;
/**
* OneBot AccessToken
* 出于安全原因,请务必设置 AccessToken
* @author CaoMeiYouRen
* @date 2023-10-22
* @private
*/
ONE_BOT_ACCESS_TOKEN;
/**
* 创建 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;
Debugger$10("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) {
Debugger$10("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 ? `\n${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
}
});
}
};
//#endregion
//#region src/push/push-deer.ts
const Debugger$9 = debug("push:push-deer");
const 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"
}
};
const 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"
}
]
} };
/**
* PushDeer 推送。 官方文档 https://github.com/easychen/pushdeer
*
* @author CaoMeiYouRen
* @date 2022-02-28
* @export
* @class PushDeer
*/
var PushDeer = class PushDeer {
static namespace = "PushDeer";
static configSchema = pushDeerConfigSchema;
static optionSchema = pushDeerOptionSchema;
/**
* pushkey,请参考 https://github.com/easychen/pushdeer 获取
*
* @author CaoMeiYouRen
* @date 2022-02-28
* @private
*/
PUSH_DEER_PUSH_KEY;
/**
* 使用自架版时的服务器端地址。例如 http://127.0.0.1:8800
*
* @author CaoMeiYouRen
* @date 2022-02-28
* @private
*/
PUSH_DEER_ENDPOINT;
/**
* 创建 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";
Debugger$9("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) {
Debugger$9("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
}
});
}
};
//#endregion
//#region src/push/push-plus.ts
const Debugger$8 = debug("push:push-plus");
const pushPlusConfigSchema = { PUSH_PLUS_TOKEN: {
type: "string",
title: "PushPlus Token",
description: "请前往 https://www.pushplus.plus/ 领取",
required: true
} };
const 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
}
};
/**
* pushplus 推送加开放平台,仅支持一对一推送。官方文档:https://www.pushplus.plus/doc/
*
* @author CaoMeiYouRen
* @date 2021-03-03
* @export
* @class PushPlus
*/
var PushPlus = class PushPlus {
static namespace = "PushPlus";
static configSchema = pushPlusConfigSchema;
static optionSchema = pushPlusOptionSchema;
/**
* 请前往 https://www.pushplus.plus 领取
*
* @private
*/
PUSH_PLUS_TOKEN;
/**
*
* @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;
Debugger$8("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) {
Debugger$8("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
}
});
}
};
//#endregion
//#region src/push/qmsg.ts
const Debugger$7 = debug("push:qmsg");
const qmsgConfigSchema = { QMSG_KEY: {
type: "string",
title: "推送的 key",
description: "在 [Qmsg 酱管理台](https://qmsg.zendee.cn/user) 查看",
required: true
} };
const 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
}
};
/**
* Qmsg酱。使用说明见 [Qmsg酱](https://qmsg.zendee.cn/docs)
*
* @author CaoMeiYouRen
* @date 2022-02-17
* @export
* @class Qmsg
*/
var Qmsg = class Qmsg {
static namespace = "Qmsg酱";
static configSchema = qmsgConfigSchema;
static optionSchema = qmsgOptionSchema;
QMSG_KEY;
constructor(config) {
const { QMSG_KEY } = config;
this.QMSG_KEY = QMSG_KEY;
Debugger$7("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) {
Debugger$7("title: \"%s\", desp: \"%s\", option: \"%o\"", title, desp, option);
validate(option, Qmsg.optionSchema);
const { qq, type = "send", bot } = option || {};
const msg = `${title}${desp ? `\n${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
}
});
}
};
//#endregion
//#region src/push/server-chan-turbo.ts
const Debugger$6 = debug("push:server-chan-turbo");
const serverChanTurboConfigSchema = { SERVER_CHAN_TURBO_SENDKEY: {
type: "string",
title: "SCTKEY",
description: "Server酱 Turbo 的 SCTKEY。请前往 https://sct.ftqq.com/sendkey 领取",
required: true
} };
const 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
}
};
/**
* Server 酱·Turbo
* 文档 https://sct.ftqq.com/
*
* @author CaoMeiYouRen
* @date 2021-02-27
* @export
* @class ServerChanTurbo
*/
var ServerChanTurbo = class ServerChanTurbo {
static namespace = "Server酱·Turbo";
static configSchema = serverChanTurboConfigSchema;
static optionSchema = serverChanTurboOptionSchema;
/**
*
* @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;
Debugger$6("set SCTKEY: \"%s\"", this.SCTKEY);
validate(config, ServerChanTurbo.configSchema);
}
/**
*
*
* @private 请前往 https://sct.ftqq.com/sendkey 领取
*/
SCTKEY;
/**
* 发送消息
*
* @author CaoMeiYouRen
* @date 2024-11-08
* @param title 消息的标题
* @param [desp=''] 消息的内容,支持 Markdown
* @param [option={}] 额外发送选项
*/
async send(title, desp = "", option = {}) {
Debugger$6("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
});
}
};
//#endregion
//#region src/push/server-chan-v3.ts
const Debugger$5 = debug("push:server-chan-v3");
const serverChanV3ConfigSchema = { SERVER_CHAN_V3_SENDKEY: {
type: "string",
title: "SENDKEY",
description: "请前往 https://sc3.ft07.com/sendkey 领取",
required: true
} };
const serverChanV3OptionSchema = {
tags: {
type: "array",
title: "标签列表",
description: "多个标签用数组格式",
required: false
},
short: {
type: "string",
title: "推送消息的简短描述",
description: "用于指定消息卡片的内容部分,尤其是在推送markdown的时候",
required: false
}
};
/**
* Server酱³
* 文档:https://sc3.ft07.com/doc
* @author CaoMeiYouRen
* @date 2024-10-04
* @export
* @class ServerChanV3
*/
var ServerChanV3 = class ServerChanV3 {
static namespace = "Server酱³";
static configSchema = serverChanV3ConfigSchema;
static optionSchema = serverChanV3OptionSchema;
/**
* 请前往 https://sc3.ft07.com/sendkey 领取
*
* @author CaoMeiYouRen
* @date 2024-10-04
* @private
*/
sendkey;
uid = "";
/**
* 创建 ServerChanV3 实例
* @author CaoMeiYouRen
* @date 2024-11-08
* @param config 请前往 https://sc3.ft07.com/sendkey 领取
*/
constructor(config) {
const { SERVER_CHAN_V3_SENDKEY } = config;
const sendkey = SERVER_CHAN_V3_SENDKEY;
this.sendkey = sendkey;
Debugger$5("set sendkey: \"%s\"", sendkey);
validate(config, ServerChanV3.configSchema);
this.uid = this.sendkey.match(/^sctp(\d+)t/)?.[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 = {}) {
Debugger$5("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
});
}
};
//#endregion
//#region src/push/telegram.ts
const Debugger$4 = debug("push:telegram");
const 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
}
};
const 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
},
parse_mode: {
type: "select",
title: "消息格式",
description: "消息格式,支持 MarkdownV2、HTML 等格式。Markdown字段已弃用,请改为使用MarkdownV2。",
required: false,
options: [{
label: "MarkdownV2",
value: "MarkdownV2"
}, {
label: "HTML",
value: "HTML"
}]
}
};
/**
* Telegram Bot 推送。
* 官方文档:https://core.telegram.org/bots/api#making-requests
*
* @author CaoMeiYouRen
* @date 2023-09-16
* @export
* @class Telegram
*/
var Telegram = class Telegram {
static namespace = "Telegram";
static configSchema = telegramConfigSchema;
static optionSchema = telegramOptionSchema;
/**
* 机器人令牌
* 您可以从 https://t.me/BotFather 获取 Token。
* @author CaoMeiYouRen
* @date 2023-10-22
* @private
*/
TELEGRAM_BOT_TOKEN;
/**
* 支持对话/群组/频道的 Chat ID
* 您可以转发消息到 https://t.me/JsonDumpBot 获取 Chat ID
* @author CaoMeiYouRen
* @date 2023-10-22
* @private
*/
TELEGRAM_CHAT_ID;
proxyUrl;
constructor(config) {
Debugger$4("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`;
Debugger$4("title: \"%s\", desp: \"%s\", option: %O", title, desp, option);
const text = `${title}${desp ? `\n${desp}` : ""}`;
return ajax({
url,
method: "POST",
proxyUrl: this.proxyUrl,
data: {
chat_id: this.TELEGRAM_CHAT_ID,
text
}
});
}
};
//#endregion
//#region src/push/wechat-app.ts
const Debugger$3 = debug("push:wechat-app");
const 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
}
};
const 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
}
};
/**
* 企业微信应用推送,文档:https://developer.work.weixin.qq.com/document/path/90664
*
* @author CaoMeiYouRen
* @date 2021-02-28
* @export
* @class WechatApp
*/
var WechatApp = class WechatApp {
static namespace = "企业微信应用";
static configSchema = wechatAppConfigSchema;
static optionSchema = wechatAppOptionSchema;
ACCESS_TOKEN;
WECHAT_APP_CORPID;
WECHAT_APP_SECRET;
WECHAT_APP_AGENTID;
/**
* ACCESS_TOKEN 的过期时间(时间戳)
*
* @private
*/
expiresTime;
constructor(config) {
Debugger$3("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?.errcode !== 0) throw new Error(data?.errmsg || "获取 access_token 失败!");
const { access_token, expires_in = 7200 } = data;
Debugger$3("获取 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) {
Debugger$3("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 content = `${title}${desp ? `${msgtype === "markdown" ? "\n\n" : "\n"}${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
}
});
}
};
//#endregion
//#region src/push/wechat-robot.ts
const Debugger$2 = debug("push:wechat-robot");
const wechatRobotConfigSchema = { WECHAT_ROBOT_KEY: {
type: "string",
title: "企业微信机器人的key",
description: "企业微信机器人的key",
required: true
} };
const 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"
}
],
required: false,
default: "text"
} };
/**
* 企业微信群机器人。文档: [如何使用群机器人](https://developer.work.weixin.qq.com/document/path/91770)
*
* @author CaoMeiYouRen
* @date 2021-02-28
* @export
* @class WechatRobot
*/
var WechatRobot = class WechatRobot {
static namespace = "企业微信群机器人";
static configSchema = wechatRobotConfigSchema;
static optionSchema = wechatRobotOptionSchema;
WECHAT_ROBOT_KEY;
constructor(config) {
const { WECHAT_ROBOT_KEY } = config;
this.WECHAT_ROBOT_KEY = WECHAT_ROBOT_KEY;
Debugger$2("set WECHAT_ROBOT_KEY: \"%s\"", WECHAT_ROBOT_KEY);
validate(config, WechatRobot.configSchema);
}
/**
*
*
* @author CaoMeiYouRen
* @date 2024-11-08
* @param title 消息标题
* @param [desp] 消息内容。text内容,最长不超过2048个字节;markdown内容,最长不超过4096个字节;必须是utf8编码
* @param [option] 额外推送选项
*/
async send(title, desp, option) {
Debugger$2("title: \"%s\", desp: \"%s\", option: %O", title, desp, option);
const { msgtype = "text", ...args } = option || {};
const content = `${title}${desp ? `${msgtype === "markdown" ? "\n\n" : "\n"}${desp}` : ""}`;
return ajax({
url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send",
headers: { "Content-Type": "application/json" },
method: "POST",
query: { key: this.WECHAT_ROBOT_KEY },
data: {
msgtype,
[msgtype]: { content },
...args
}
});
}
};
//#endregion
//#region src/push/xi-zhi.ts
const Debugger$1 = debug("push:xi-zhi");
const