feishu-bot-notify
Version:
基于 tsdown 打包的飞书机器人通知工具
173 lines (169 loc) • 4.49 kB
JavaScript
import { FetchError, ofetch } from "ofetch";
//#region src/card.ts
const DEFAULT_TITLE = "组件更新啦!!!";
const DEFAULT_BUTTON_TEXT = "去看看";
/**
* 构造飞书交互式卡片
*/
function buildComponentUpdateCard(options) {
const { componentName, version, changes, buttonUrl, title, buttonText } = options;
const markdownUpdates = formatChanges(changes);
return {
schema: "2.0",
config: {
update_multi: true,
style: { text_size: { normal_v2: {
default: "normal",
pc: "normal",
mobile: "heading"
} } }
},
header: {
title: {
tag: "plain_text",
content: title ?? DEFAULT_TITLE
},
template: "blue",
icon: {
tag: "standard_icon",
token: "speaker_filled"
},
padding: "12px 12px 12px 12px"
},
body: {
direction: "vertical",
padding: "12px 12px 12px 12px",
elements: [
{
tag: "hr",
margin: "0px 0px 0px 0px"
},
buildMetaColumn(componentName, version),
{
tag: "div",
text: {
tag: "lark_md",
content: `**📝更新说明:**\n${markdownUpdates}`,
text_size: "normal_v2",
text_align: "left",
text_color: "default",
lines: 5
},
margin: "0px 0px 0px 0px"
},
{
tag: "hr",
margin: "0px 0px 0px 0px"
},
{
tag: "button",
text: {
tag: "plain_text",
content: buttonText ?? DEFAULT_BUTTON_TEXT
},
type: "primary_filled",
width: "default",
size: "medium",
behaviors: [{
type: "open_url",
default_url: buttonUrl
}],
margin: "0px 0px 0px 0px",
element_id: "go_component_url"
}
]
}
};
}
function buildMetaColumn(componentName, version) {
return {
tag: "column_set",
columns: [{
tag: "column",
width: "weighted",
elements: [{
tag: "markdown",
content: `**📦组件名称:**${componentName}`,
text_align: "left",
text_size: "normal_v2"
}],
vertical_align: "top",
weight: 1
}, {
tag: "column",
width: "weighted",
elements: [{
tag: "markdown",
content: `**💥版本:** ${version}`,
text_align: "left",
text_size: "normal_v2"
}],
vertical_align: "top",
weight: 1
}]
};
}
function formatChanges(changes) {
if (!Array.isArray(changes)) return changes;
if (!changes.length) return "- 暂无更新内容";
return changes.map((item) => item.startsWith("-") ? item : `- ${item}`).join("\n");
}
//#endregion
//#region src/sender.ts
const client = ofetch.create({
headers: { "Content-Type": "application/json" },
retry: 0
});
/**
* 负责向飞书 webhook 推送交互式卡片,默认使用 ofetch 并保留详细错误信息
*/
async function sendCardToWebhook(options) {
const { webhookUrl, card, timeoutMs } = options;
const payload = {
msg_type: "interactive",
card
};
try {
const { status, _data } = await client.raw(webhookUrl, {
method: "POST",
body: payload,
timeout: timeoutMs
});
if (status < 200 || status >= 300) throw new Error(`飞书接口返回 HTTP ${status}`);
if (!_data || typeof _data.code !== "number") throw new Error("飞书接口返回内容为空或结构异常");
if (_data.code !== 0) throw new Error(`飞书接口响应异常:${JSON.stringify(_data)}`);
return _data;
} catch (error) {
if (error instanceof FetchError) {
var _error$cause;
if (error.status === 408 || ((_error$cause = error.cause) === null || _error$cause === void 0 ? void 0 : _error$cause.name) === "AbortError") throw new Error("请求飞书接口超时");
if (typeof error.status === "number" && error.status >= 400) {
const bodyText = typeof error.data === "string" ? error.data : JSON.stringify(error.data ?? {});
throw new Error(`飞书接口返回 HTTP ${error.status}:${bodyText}`);
}
}
throw error instanceof Error ? error : /* @__PURE__ */ new Error("请求飞书接口失败");
}
}
//#endregion
//#region src/index.ts
/**
* 基于组件更新信息构建卡片并推送至飞书机器人
*/
async function sendComponentUpdateNotification(options) {
const { webhookUrl, timeoutMs,...cardOptions } = options;
const card = buildComponentUpdateCard(cardOptions);
return sendCardToWebhook({
webhookUrl,
card,
timeoutMs
});
}
/**
* 仅构建飞书交互式卡片,便于在外部服务中复用
*/
function createComponentUpdateCard(options) {
return buildComponentUpdateCard(options);
}
//#endregion
export { createComponentUpdateCard, sendComponentUpdateNotification };