dingtalk-pingo 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -13
- package/dist/pingo.js +948 -48
- package/package.json +1 -1
- package/skills/pingo/SKILL.md +45 -10
- package/skills/pingo-follow/SKILL.md +19 -0
package/dist/pingo.js
CHANGED
|
@@ -7421,10 +7421,22 @@ var FORMAT_REJECTION_CODES = /* @__PURE__ */ new Set([
|
|
|
7421
7421
|
var PingoError = class extends Error {
|
|
7422
7422
|
category;
|
|
7423
7423
|
code;
|
|
7424
|
-
|
|
7424
|
+
/** 为 true 时,发送管线可以降级到下一种消息格式重试(目前仅用于卡片被钉钉明确拒绝)。 */
|
|
7425
|
+
degradable;
|
|
7426
|
+
constructor(category, message, code, options) {
|
|
7425
7427
|
super(message);
|
|
7426
7428
|
this.category = category;
|
|
7427
7429
|
this.code = code;
|
|
7430
|
+
this.degradable = options?.degradable === true;
|
|
7431
|
+
}
|
|
7432
|
+
};
|
|
7433
|
+
var PartialSendError = class extends PingoError {
|
|
7434
|
+
sent;
|
|
7435
|
+
failed;
|
|
7436
|
+
constructor(cause, sent, failed) {
|
|
7437
|
+
super(cause.category, cause.message, cause.code);
|
|
7438
|
+
this.sent = sent;
|
|
7439
|
+
this.failed = failed;
|
|
7428
7440
|
}
|
|
7429
7441
|
};
|
|
7430
7442
|
function classifyDingTalkError(httpStatus, code) {
|
|
@@ -7443,6 +7455,7 @@ function isFormatRejection(code) {
|
|
|
7443
7455
|
var CRED_KEYS = ["clientId", "clientSecret", "robotCode", "userId"];
|
|
7444
7456
|
var MESSAGE_TYPES = ["success", "warning", "error", "info"];
|
|
7445
7457
|
var FORMATS = ["text", "markdown"];
|
|
7458
|
+
var SEND_FORMATS = ["card", "markdown", "text"];
|
|
7446
7459
|
function fail(message) {
|
|
7447
7460
|
throw new PingoError("config", message);
|
|
7448
7461
|
}
|
|
@@ -7509,15 +7522,36 @@ function validateConfig(raw, opts) {
|
|
|
7509
7522
|
const device = { id, name: str(deviceRaw.name) ?? id };
|
|
7510
7523
|
const icon = str(deviceRaw.icon);
|
|
7511
7524
|
if (icon !== void 0) device.icon = icon;
|
|
7525
|
+
const avatar = str(deviceRaw.avatar);
|
|
7526
|
+
if (avatar !== void 0) {
|
|
7527
|
+
if (!/^https?:\/\//i.test(avatar)) {
|
|
7528
|
+
fail(`\u914D\u7F6E\u9879 device.avatar \u5FC5\u987B\u662F http(s) \u56FE\u7247\u5730\u5740,\u5F53\u524D\u503C:${avatar}\u3002`);
|
|
7529
|
+
}
|
|
7530
|
+
device.avatar = avatar;
|
|
7531
|
+
}
|
|
7532
|
+
const cardRaw = isRecord(raw.card) ? raw.card : {};
|
|
7533
|
+
const card = {};
|
|
7534
|
+
const templateId = str(cardRaw.templateId);
|
|
7535
|
+
if (templateId !== void 0) card.templateId = templateId;
|
|
7536
|
+
const preferredFormat = pickEnum(
|
|
7537
|
+
msgRaw.preferredFormat,
|
|
7538
|
+
SEND_FORMATS,
|
|
7539
|
+
card.templateId !== void 0 ? "card" : "markdown",
|
|
7540
|
+
"message.preferredFormat"
|
|
7541
|
+
);
|
|
7542
|
+
if (preferredFormat === "card" && card.templateId === void 0) {
|
|
7543
|
+
fail("message.preferredFormat \u4E3A card \u65F6\u5FC5\u987B\u914D\u7F6E card.templateId(\u9489\u9489\u5361\u7247\u6A21\u677F ID)\u3002");
|
|
7544
|
+
}
|
|
7512
7545
|
return {
|
|
7513
7546
|
device,
|
|
7514
7547
|
dingtalk,
|
|
7515
7548
|
message: {
|
|
7516
7549
|
defaultType: pickEnum(msgRaw.defaultType, MESSAGE_TYPES, "info", "message.defaultType"),
|
|
7517
|
-
preferredFormat
|
|
7550
|
+
preferredFormat,
|
|
7518
7551
|
fallbackFormat: pickEnum(msgRaw.fallbackFormat, FORMATS, "text", "message.fallbackFormat"),
|
|
7519
7552
|
timeoutMs
|
|
7520
7553
|
},
|
|
7554
|
+
card,
|
|
7521
7555
|
templates
|
|
7522
7556
|
};
|
|
7523
7557
|
}
|
|
@@ -7616,10 +7650,14 @@ function loadConfig(opts) {
|
|
|
7616
7650
|
const deviceId = nonEmpty(opts.env.PINGO_DEVICE_ID) ?? nonEmpty(deviceYaml.id) ?? opts.hostname;
|
|
7617
7651
|
const deviceName = nonEmpty(opts.env.PINGO_DEVICE_NAME) ?? nonEmpty(deviceYaml.name) ?? opts.hostname;
|
|
7618
7652
|
const deviceIcon = nonEmpty(opts.env.PINGO_DEVICE_ICON) ?? nonEmpty(deviceYaml.icon);
|
|
7653
|
+
const deviceAvatar = nonEmpty(opts.env.PINGO_DEVICE_AVATAR) ?? nonEmpty(deviceYaml.avatar);
|
|
7654
|
+
const cardYaml = isRecord2(yamlRaw.card) ? yamlRaw.card : {};
|
|
7655
|
+
const templateId = nonEmpty(opts.env.PINGO_CARD_TEMPLATE_ID) ?? nonEmpty(cardYaml.templateId);
|
|
7619
7656
|
const merged = {
|
|
7620
|
-
device: { id: deviceId, name: deviceName, icon: deviceIcon },
|
|
7657
|
+
device: { id: deviceId, name: deviceName, icon: deviceIcon, avatar: deviceAvatar },
|
|
7621
7658
|
dingtalk: creds,
|
|
7622
7659
|
message: yamlRaw.message,
|
|
7660
|
+
card: { templateId },
|
|
7623
7661
|
templates: yamlRaw.templates
|
|
7624
7662
|
};
|
|
7625
7663
|
const config = validateConfig(merged, { requireCredentials: opts.requireCredentials });
|
|
@@ -7680,6 +7718,12 @@ var TYPE_ICONS = {
|
|
|
7680
7718
|
error: "\u274C",
|
|
7681
7719
|
info: "\u2139\uFE0F"
|
|
7682
7720
|
};
|
|
7721
|
+
var TYPE_LABELS = {
|
|
7722
|
+
success: "\u5DF2\u5B8C\u6210",
|
|
7723
|
+
warning: "\u9700\u8981\u5173\u6CE8",
|
|
7724
|
+
error: "\u4EFB\u52A1\u5F02\u5E38",
|
|
7725
|
+
info: "\u6D88\u606F\u62A5\u544A"
|
|
7726
|
+
};
|
|
7683
7727
|
var ACTION_TEXT = "\u26A0\uFE0F \u9700\u8981\u4EBA\u5DE5\u5904\u7406";
|
|
7684
7728
|
function nonBlank(value) {
|
|
7685
7729
|
if (value === void 0) return void 0;
|
|
@@ -7729,7 +7773,14 @@ function buildMessage(input, ctx) {
|
|
|
7729
7773
|
let bodyTemplate = override.body ?? defaults.body;
|
|
7730
7774
|
if (!isMarkdown) bodyTemplate = bodyTemplate.replaceAll("**", "");
|
|
7731
7775
|
const title = collapseSpaces(renderTemplate(titleTemplate, titleVars));
|
|
7732
|
-
|
|
7776
|
+
let body = renderTemplate(bodyTemplate, bodyVars);
|
|
7777
|
+
const links = input.links ?? [];
|
|
7778
|
+
if (links.length > 0 && body !== "") {
|
|
7779
|
+
const linkLines = isMarkdown ? [links.map((l) => `[${escapeMarkdown(l.text)}](${l.url})`).join(" \xB7 ")] : links.map((l) => `${l.text}:${l.url}`);
|
|
7780
|
+
body = `${body}
|
|
7781
|
+
|
|
7782
|
+
${linkLines.join("\n")}`;
|
|
7783
|
+
}
|
|
7733
7784
|
if (title === "" || body === "") {
|
|
7734
7785
|
throw new PingoError("content", "\u6E32\u67D3\u540E\u7684\u6807\u9898\u6216\u6B63\u6587\u4E3A\u7A7A,\u8BF7\u68C0\u67E5\u6D88\u606F\u5185\u5BB9\u4E0E\u6A21\u677F\u3002");
|
|
7735
7786
|
}
|
|
@@ -7755,6 +7806,395 @@ ${body}`,
|
|
|
7755
7806
|
};
|
|
7756
7807
|
}
|
|
7757
7808
|
|
|
7809
|
+
// src/messages/card.ts
|
|
7810
|
+
import { randomUUID } from "node:crypto";
|
|
7811
|
+
function nonBlank2(value) {
|
|
7812
|
+
if (value === void 0) return void 0;
|
|
7813
|
+
const t = value.trim();
|
|
7814
|
+
return t === "" ? void 0 : t;
|
|
7815
|
+
}
|
|
7816
|
+
function senderName(device, agent, project) {
|
|
7817
|
+
return [device.name, nonBlank2(agent), nonBlank2(project)].filter((s) => s !== void 0).join(" \xB7 ");
|
|
7818
|
+
}
|
|
7819
|
+
function titleOf(type, title) {
|
|
7820
|
+
return `${TYPE_ICONS[type]} ${nonBlank2(title) ?? TYPE_LABELS[type]}`;
|
|
7821
|
+
}
|
|
7822
|
+
function btnsOf(links) {
|
|
7823
|
+
return JSON.stringify(
|
|
7824
|
+
links.map((l) => ({
|
|
7825
|
+
text: l.text,
|
|
7826
|
+
color: "blue",
|
|
7827
|
+
status: "normal",
|
|
7828
|
+
event: { type: "openLink", params: { url: l.url } }
|
|
7829
|
+
}))
|
|
7830
|
+
);
|
|
7831
|
+
}
|
|
7832
|
+
var CHART_TYPES = ["lineChart", "histogram", "pieChart"];
|
|
7833
|
+
function parseChart(raw) {
|
|
7834
|
+
let value;
|
|
7835
|
+
try {
|
|
7836
|
+
value = JSON.parse(raw);
|
|
7837
|
+
} catch (err) {
|
|
7838
|
+
throw new PingoError("usage", `--chart \u6587\u4EF6\u4E0D\u662F\u5408\u6CD5 JSON:${err.message}`);
|
|
7839
|
+
}
|
|
7840
|
+
const chart = value;
|
|
7841
|
+
if (chart === null || typeof chart !== "object" || Array.isArray(chart)) {
|
|
7842
|
+
throw new PingoError("usage", '--chart \u5FC5\u987B\u662F JSON \u5BF9\u8C61:{"type":"lineChart","data":[...],"config":{}}\u3002');
|
|
7843
|
+
}
|
|
7844
|
+
if (typeof chart.type !== "string" || !CHART_TYPES.includes(chart.type)) {
|
|
7845
|
+
throw new PingoError("usage", `--chart \u7684 type \u5FC5\u987B\u662F ${CHART_TYPES.join(" / ")} \u4E4B\u4E00\u3002`);
|
|
7846
|
+
}
|
|
7847
|
+
if (!Array.isArray(chart.data) || chart.data.length === 0) {
|
|
7848
|
+
throw new PingoError("usage", "--chart \u7684 data \u5FC5\u987B\u662F\u975E\u7A7A\u6570\u7EC4\u3002");
|
|
7849
|
+
}
|
|
7850
|
+
return JSON.stringify({ ...chart, config: chart.config ?? {} });
|
|
7851
|
+
}
|
|
7852
|
+
function contentOf(p, mediaIds) {
|
|
7853
|
+
const body = p.markdown !== void 0 && p.markdown.trim() !== "" ? p.markdown : nonBlank2(p.message);
|
|
7854
|
+
if (body === void 0) {
|
|
7855
|
+
throw new PingoError("content", "\u6D88\u606F\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A:\u8BF7\u63D0\u4F9B --message \u6216 --markdown\u3002");
|
|
7856
|
+
}
|
|
7857
|
+
const parts = [];
|
|
7858
|
+
if (p.actionRequired) parts.push(ACTION_TEXT);
|
|
7859
|
+
const task = nonBlank2(p.task);
|
|
7860
|
+
if (task !== void 0) parts.push(`\u4EFB\u52A1:${task}`);
|
|
7861
|
+
parts.push(body);
|
|
7862
|
+
if (mediaIds.length > 0) parts.push(mediaIds.map((id) => ``).join("\n"));
|
|
7863
|
+
const content = parts.join("\n\n");
|
|
7864
|
+
const bytes = Buffer.byteLength(content, "utf8");
|
|
7865
|
+
if (bytes > MAX_MSGPARAM_BYTES) {
|
|
7866
|
+
throw new PingoError(
|
|
7867
|
+
"content",
|
|
7868
|
+
`\u6D88\u606F\u8FC7\u957F:${bytes} \u5B57\u8282,\u8D85\u8FC7\u4E0A\u9650 ${MAX_MSGPARAM_BYTES} \u5B57\u8282,\u8BF7\u7CBE\u7B80\u5185\u5BB9\u540E\u91CD\u8BD5\u3002`
|
|
7869
|
+
);
|
|
7870
|
+
}
|
|
7871
|
+
return content;
|
|
7872
|
+
}
|
|
7873
|
+
function renderCard(input, ctx, mediaIds) {
|
|
7874
|
+
const content = contentOf(input, mediaIds);
|
|
7875
|
+
const cardParamMap = {
|
|
7876
|
+
senderName: senderName(ctx.device, input.agent, input.project),
|
|
7877
|
+
title: titleOf(input.type, input.title),
|
|
7878
|
+
content,
|
|
7879
|
+
btns: btnsOf(input.links ?? [])
|
|
7880
|
+
};
|
|
7881
|
+
if (ctx.device.avatar !== void 0) cardParamMap.avatar = ctx.device.avatar;
|
|
7882
|
+
const date = nonBlank2(input.date);
|
|
7883
|
+
if (date !== void 0) cardParamMap.date = date;
|
|
7884
|
+
if (input.chart !== void 0) cardParamMap.chart = input.chart;
|
|
7885
|
+
const preview = [cardParamMap.senderName, cardParamMap.title, "", content].join("\n");
|
|
7886
|
+
return { cardParamMap, preview, bytes: Buffer.byteLength(content, "utf8") };
|
|
7887
|
+
}
|
|
7888
|
+
function renderCardUpdate(input, ctx, mediaIds) {
|
|
7889
|
+
const map = {};
|
|
7890
|
+
if (input.type !== void 0 || input.title !== void 0) {
|
|
7891
|
+
map.title = titleOf(input.type ?? input.defaultType, input.title);
|
|
7892
|
+
}
|
|
7893
|
+
if (input.message !== void 0 || input.markdown !== void 0 || input.actionRequired === true || mediaIds.length > 0) {
|
|
7894
|
+
map.content = contentOf(input, mediaIds);
|
|
7895
|
+
}
|
|
7896
|
+
if (input.agent !== void 0 || input.project !== void 0) {
|
|
7897
|
+
map.senderName = senderName(ctx.device, input.agent, input.project);
|
|
7898
|
+
}
|
|
7899
|
+
if (input.links !== void 0) {
|
|
7900
|
+
map.btns = btnsOf(input.links);
|
|
7901
|
+
}
|
|
7902
|
+
const date = nonBlank2(input.date);
|
|
7903
|
+
if (date !== void 0) map.date = date;
|
|
7904
|
+
if (input.chart !== void 0) map.chart = input.chart;
|
|
7905
|
+
if (Object.keys(map).length === 0) {
|
|
7906
|
+
throw new PingoError(
|
|
7907
|
+
"usage",
|
|
7908
|
+
"pingo update \u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u8981\u66F4\u65B0\u7684\u5B57\u6BB5(--type\u3001--title\u3001--message\u3001--markdown\u3001--image\u3001--link\u3001--agent\u3001--project\u3001--date\u3001--chart)\u3002"
|
|
7909
|
+
);
|
|
7910
|
+
}
|
|
7911
|
+
return map;
|
|
7912
|
+
}
|
|
7913
|
+
var CARD_ID = /^pingo-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
7914
|
+
function newCardId() {
|
|
7915
|
+
return `pingo-${randomUUID()}`;
|
|
7916
|
+
}
|
|
7917
|
+
function isCardId(value) {
|
|
7918
|
+
return CARD_ID.test(value);
|
|
7919
|
+
}
|
|
7920
|
+
|
|
7921
|
+
// src/messages/links.ts
|
|
7922
|
+
var MAX_LINKS = 3;
|
|
7923
|
+
function parseLinks(values) {
|
|
7924
|
+
if (values.length > MAX_LINKS) {
|
|
7925
|
+
throw new PingoError("usage", `--link \u6700\u591A ${MAX_LINKS} \u4E2A,\u5F53\u524D ${values.length} \u4E2A\u3002`);
|
|
7926
|
+
}
|
|
7927
|
+
return values.map((raw) => {
|
|
7928
|
+
const idx = raw.indexOf("=");
|
|
7929
|
+
const text = idx < 0 ? "" : raw.slice(0, idx).trim();
|
|
7930
|
+
const url = idx < 0 ? "" : raw.slice(idx + 1).trim();
|
|
7931
|
+
if (idx < 0 || text === "" || url === "") {
|
|
7932
|
+
throw new PingoError("usage", `--link \u683C\u5F0F\u5E94\u4E3A "\u6587\u5B57=URL":${raw}`);
|
|
7933
|
+
}
|
|
7934
|
+
if (!/^https?:\/\//i.test(url)) {
|
|
7935
|
+
throw new PingoError("usage", `--link \u53EA\u652F\u6301 http(s) \u5730\u5740:${url}`);
|
|
7936
|
+
}
|
|
7937
|
+
return { text, url };
|
|
7938
|
+
});
|
|
7939
|
+
}
|
|
7940
|
+
|
|
7941
|
+
// src/media/cover.ts
|
|
7942
|
+
import { spawn } from "node:child_process";
|
|
7943
|
+
import { deflateSync } from "node:zlib";
|
|
7944
|
+
var FFMPEG_TIMEOUT_MS = 1e4;
|
|
7945
|
+
var ffmpegFrameGrabber = (videoPath, atSec) => new Promise((resolve2) => {
|
|
7946
|
+
let settled = false;
|
|
7947
|
+
const done = (v) => {
|
|
7948
|
+
if (!settled) {
|
|
7949
|
+
settled = true;
|
|
7950
|
+
resolve2(v);
|
|
7951
|
+
}
|
|
7952
|
+
};
|
|
7953
|
+
let child;
|
|
7954
|
+
try {
|
|
7955
|
+
child = spawn(
|
|
7956
|
+
"ffmpeg",
|
|
7957
|
+
["-loglevel", "error", "-ss", String(atSec), "-i", videoPath, "-frames:v", "1", "-f", "image2", "-vcodec", "mjpeg", "pipe:1"],
|
|
7958
|
+
{ stdio: ["ignore", "pipe", "ignore"] }
|
|
7959
|
+
);
|
|
7960
|
+
} catch {
|
|
7961
|
+
done(void 0);
|
|
7962
|
+
return;
|
|
7963
|
+
}
|
|
7964
|
+
const chunks = [];
|
|
7965
|
+
const timer = setTimeout(() => {
|
|
7966
|
+
child.kill("SIGKILL");
|
|
7967
|
+
done(void 0);
|
|
7968
|
+
}, FFMPEG_TIMEOUT_MS);
|
|
7969
|
+
child.stdout?.on("data", (c) => chunks.push(c));
|
|
7970
|
+
child.on("error", () => {
|
|
7971
|
+
clearTimeout(timer);
|
|
7972
|
+
done(void 0);
|
|
7973
|
+
});
|
|
7974
|
+
child.on("close", (code) => {
|
|
7975
|
+
clearTimeout(timer);
|
|
7976
|
+
done(code === 0 && chunks.length > 0 ? Buffer.concat(chunks) : void 0);
|
|
7977
|
+
});
|
|
7978
|
+
});
|
|
7979
|
+
var CRC_TABLE = (() => {
|
|
7980
|
+
const t = new Uint32Array(256);
|
|
7981
|
+
for (let n = 0; n < 256; n++) {
|
|
7982
|
+
let c = n;
|
|
7983
|
+
for (let k = 0; k < 8; k++) c = c & 1 ? c >>> 1 ^ 3988292384 : c >>> 1;
|
|
7984
|
+
t[n] = c >>> 0;
|
|
7985
|
+
}
|
|
7986
|
+
return t;
|
|
7987
|
+
})();
|
|
7988
|
+
function crc32(buf) {
|
|
7989
|
+
let c = 4294967295;
|
|
7990
|
+
for (const byte of buf) c = CRC_TABLE[(c ^ byte) & 255] ^ c >>> 8;
|
|
7991
|
+
return (c ^ 4294967295) >>> 0;
|
|
7992
|
+
}
|
|
7993
|
+
function chunk(type, data) {
|
|
7994
|
+
const len = Buffer.alloc(4);
|
|
7995
|
+
len.writeUInt32BE(data.length);
|
|
7996
|
+
const typed = Buffer.concat([Buffer.from(type, "ascii"), data]);
|
|
7997
|
+
const crc = Buffer.alloc(4);
|
|
7998
|
+
crc.writeUInt32BE(crc32(typed));
|
|
7999
|
+
return Buffer.concat([len, typed, crc]);
|
|
8000
|
+
}
|
|
8001
|
+
function hash(seed) {
|
|
8002
|
+
let h = 2166136261;
|
|
8003
|
+
for (const ch of Buffer.from(seed, "utf8")) {
|
|
8004
|
+
h ^= ch;
|
|
8005
|
+
h = Math.imul(h, 16777619) >>> 0;
|
|
8006
|
+
}
|
|
8007
|
+
return h;
|
|
8008
|
+
}
|
|
8009
|
+
function hslToRgb(h, s, l) {
|
|
8010
|
+
const a = s * Math.min(l, 1 - l);
|
|
8011
|
+
const f = (n) => {
|
|
8012
|
+
const k = (n + h / 30) % 12;
|
|
8013
|
+
return Math.round(255 * (l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1))));
|
|
8014
|
+
};
|
|
8015
|
+
return [f(0), f(8), f(4)];
|
|
8016
|
+
}
|
|
8017
|
+
var COVER_W = 320;
|
|
8018
|
+
var COVER_H = 180;
|
|
8019
|
+
function generateGradientCover(seed) {
|
|
8020
|
+
const hue = hash(seed) % 360;
|
|
8021
|
+
const from = hslToRgb(hue, 0.65, 0.45);
|
|
8022
|
+
const to = hslToRgb((hue + 40) % 360, 0.7, 0.62);
|
|
8023
|
+
const rows = [];
|
|
8024
|
+
for (let y = 0; y < COVER_H; y++) {
|
|
8025
|
+
const row = Buffer.alloc(1 + COVER_W * 3);
|
|
8026
|
+
for (let x = 0; x < COVER_W; x++) {
|
|
8027
|
+
const t = (x / (COVER_W - 1) + y / (COVER_H - 1)) / 2;
|
|
8028
|
+
for (let i = 0; i < 3; i++) row[1 + x * 3 + i] = Math.round(from[i] + (to[i] - from[i]) * t);
|
|
8029
|
+
}
|
|
8030
|
+
rows.push(row);
|
|
8031
|
+
}
|
|
8032
|
+
const ihdr = Buffer.alloc(13);
|
|
8033
|
+
ihdr.writeUInt32BE(COVER_W, 0);
|
|
8034
|
+
ihdr.writeUInt32BE(COVER_H, 4);
|
|
8035
|
+
ihdr[8] = 8;
|
|
8036
|
+
ihdr[9] = 2;
|
|
8037
|
+
return Buffer.concat([
|
|
8038
|
+
Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]),
|
|
8039
|
+
chunk("IHDR", ihdr),
|
|
8040
|
+
chunk("IDAT", deflateSync(Buffer.concat(rows))),
|
|
8041
|
+
chunk("IEND", Buffer.alloc(0))
|
|
8042
|
+
]);
|
|
8043
|
+
}
|
|
8044
|
+
function isJpeg(b) {
|
|
8045
|
+
return b !== void 0 && b.length >= 3 && b[0] === 255 && b[1] === 216 && b[2] === 255;
|
|
8046
|
+
}
|
|
8047
|
+
async function resolveCover(p) {
|
|
8048
|
+
if (p.cover !== void 0) return { file: p.cover, source: "cover" };
|
|
8049
|
+
let frame;
|
|
8050
|
+
try {
|
|
8051
|
+
frame = await p.grab(p.video.path, p.video.durationSec > 1 ? 1 : 0);
|
|
8052
|
+
} catch {
|
|
8053
|
+
frame = void 0;
|
|
8054
|
+
}
|
|
8055
|
+
if (isJpeg(frame)) {
|
|
8056
|
+
return { file: { path: "cover.jpg", name: "cover.jpg", mime: "image/jpeg", bytes: frame }, source: "ffmpeg" };
|
|
8057
|
+
}
|
|
8058
|
+
return {
|
|
8059
|
+
file: { path: "cover.png", name: "cover.png", mime: "image/png", bytes: generateGradientCover(p.seed) },
|
|
8060
|
+
source: "generated"
|
|
8061
|
+
};
|
|
8062
|
+
}
|
|
8063
|
+
|
|
8064
|
+
// src/media/image.ts
|
|
8065
|
+
import { basename, extname } from "node:path";
|
|
8066
|
+
var MAX_IMAGES = 5;
|
|
8067
|
+
var MAX_IMAGE_BYTES = 10 * 1024 * 1024;
|
|
8068
|
+
var BY_EXT = {
|
|
8069
|
+
".png": "image/png",
|
|
8070
|
+
".jpg": "image/jpeg",
|
|
8071
|
+
".jpeg": "image/jpeg",
|
|
8072
|
+
".gif": "image/gif",
|
|
8073
|
+
".bmp": "image/bmp"
|
|
8074
|
+
};
|
|
8075
|
+
var MAGIC = {
|
|
8076
|
+
"image/png": [137, 80, 78, 71],
|
|
8077
|
+
"image/jpeg": [255, 216, 255],
|
|
8078
|
+
"image/gif": [71, 73, 70, 56],
|
|
8079
|
+
"image/bmp": [66, 77]
|
|
8080
|
+
};
|
|
8081
|
+
function matchesMagic(bytes, mime) {
|
|
8082
|
+
const sig = MAGIC[mime];
|
|
8083
|
+
return bytes.length >= sig.length && sig.every((b, i) => bytes[i] === b);
|
|
8084
|
+
}
|
|
8085
|
+
function mib(n) {
|
|
8086
|
+
return `${(n / 1024 / 1024).toFixed(1)} MiB`;
|
|
8087
|
+
}
|
|
8088
|
+
function loadImages(paths, readBytes, maxBytes = MAX_IMAGE_BYTES) {
|
|
8089
|
+
if (paths.length > MAX_IMAGES) {
|
|
8090
|
+
throw new PingoError("usage", `--image \u6700\u591A ${MAX_IMAGES} \u5F20,\u5F53\u524D ${paths.length} \u5F20\u3002`);
|
|
8091
|
+
}
|
|
8092
|
+
return paths.map((path) => {
|
|
8093
|
+
const mime = BY_EXT[extname(path).toLowerCase()];
|
|
8094
|
+
let bytes;
|
|
8095
|
+
try {
|
|
8096
|
+
bytes = readBytes(path);
|
|
8097
|
+
} catch (err) {
|
|
8098
|
+
throw new PingoError("usage", `\u65E0\u6CD5\u8BFB\u53D6\u56FE\u7247 ${path}:${err.message}`);
|
|
8099
|
+
}
|
|
8100
|
+
if (mime === void 0) {
|
|
8101
|
+
throw new PingoError("content", `\u4E0D\u652F\u6301\u7684\u56FE\u7247\u683C\u5F0F:${path}(\u652F\u6301 png\u3001jpg\u3001gif\u3001bmp)\u3002`);
|
|
8102
|
+
}
|
|
8103
|
+
if (bytes.length === 0) {
|
|
8104
|
+
throw new PingoError("content", `\u56FE\u7247\u6587\u4EF6\u4E3A\u7A7A:${path}`);
|
|
8105
|
+
}
|
|
8106
|
+
if (!matchesMagic(bytes, mime)) {
|
|
8107
|
+
throw new PingoError("content", `\u56FE\u7247\u5185\u5BB9\u4E0E\u6269\u5C55\u540D\u4E0D\u4E00\u81F4:${path}(\u6587\u4EF6\u5185\u5BB9\u4E0D\u662F ${mime})\u3002`);
|
|
8108
|
+
}
|
|
8109
|
+
if (bytes.length > maxBytes) {
|
|
8110
|
+
throw new PingoError(
|
|
8111
|
+
"content",
|
|
8112
|
+
`\u56FE\u7247\u8FC7\u5927:${path} \u4E3A ${mib(bytes.length)}(${bytes.length} \u5B57\u8282),\u4E0A\u9650 ${maxBytes} \u5B57\u8282(${mib(maxBytes)})\u3002`
|
|
8113
|
+
);
|
|
8114
|
+
}
|
|
8115
|
+
return { path, name: basename(path), mime, bytes };
|
|
8116
|
+
});
|
|
8117
|
+
}
|
|
8118
|
+
|
|
8119
|
+
// src/media/video.ts
|
|
8120
|
+
import { basename as basename2, extname as extname2 } from "node:path";
|
|
8121
|
+
var MAX_VIDEO_BYTES = 19 * 1024 * 1024;
|
|
8122
|
+
function bad(message) {
|
|
8123
|
+
throw new PingoError("content", `\u65E0\u6CD5\u8BFB\u53D6 mp4 \u65F6\u957F:${message}`);
|
|
8124
|
+
}
|
|
8125
|
+
function* boxes(buf, start, end) {
|
|
8126
|
+
let off = start;
|
|
8127
|
+
while (off + 8 <= end) {
|
|
8128
|
+
let size = buf.readUInt32BE(off);
|
|
8129
|
+
const type = buf.toString("latin1", off + 4, off + 8);
|
|
8130
|
+
let header = 8;
|
|
8131
|
+
if (size === 1) {
|
|
8132
|
+
if (off + 16 > end) bad("\u6587\u4EF6\u88AB\u622A\u65AD");
|
|
8133
|
+
const large = buf.readBigUInt64BE(off + 8);
|
|
8134
|
+
if (large > BigInt(Number.MAX_SAFE_INTEGER)) bad("box \u5C3A\u5BF8\u8FC7\u5927");
|
|
8135
|
+
size = Number(large);
|
|
8136
|
+
header = 16;
|
|
8137
|
+
} else if (size === 0) {
|
|
8138
|
+
size = end - off;
|
|
8139
|
+
}
|
|
8140
|
+
if (size < header || off + size > end) bad("\u6587\u4EF6\u88AB\u622A\u65AD\u6216\u683C\u5F0F\u9519\u8BEF");
|
|
8141
|
+
yield { type, start: off, contentStart: off + header, end: off + size };
|
|
8142
|
+
off += size;
|
|
8143
|
+
}
|
|
8144
|
+
}
|
|
8145
|
+
function parseMp4Duration(buf) {
|
|
8146
|
+
for (const top of boxes(buf, 0, buf.length)) {
|
|
8147
|
+
if (top.type !== "moov") continue;
|
|
8148
|
+
for (const child of boxes(buf, top.contentStart, top.end)) {
|
|
8149
|
+
if (child.type !== "mvhd") continue;
|
|
8150
|
+
const c = child.contentStart;
|
|
8151
|
+
const version = buf[c];
|
|
8152
|
+
let timescale;
|
|
8153
|
+
let duration;
|
|
8154
|
+
if (version === 1) {
|
|
8155
|
+
if (c + 32 > child.end) bad("mvhd \u88AB\u622A\u65AD");
|
|
8156
|
+
timescale = buf.readUInt32BE(c + 20);
|
|
8157
|
+
duration = Number(buf.readBigUInt64BE(c + 24));
|
|
8158
|
+
} else {
|
|
8159
|
+
if (c + 20 > child.end) bad("mvhd \u88AB\u622A\u65AD");
|
|
8160
|
+
timescale = buf.readUInt32BE(c + 12);
|
|
8161
|
+
duration = buf.readUInt32BE(c + 16);
|
|
8162
|
+
}
|
|
8163
|
+
if (timescale === 0) bad("mvhd \u7684 timescale \u4E3A 0");
|
|
8164
|
+
return Math.max(1, Math.ceil(duration / timescale));
|
|
8165
|
+
}
|
|
8166
|
+
bad("moov \u4E2D\u6CA1\u6709 mvhd");
|
|
8167
|
+
}
|
|
8168
|
+
return bad("\u6587\u4EF6\u4E2D\u6CA1\u6709 moov(\u4E0D\u662F\u5B8C\u6574\u7684 mp4)");
|
|
8169
|
+
}
|
|
8170
|
+
function mib2(n) {
|
|
8171
|
+
return `${(n / 1024 / 1024).toFixed(1)} MiB`;
|
|
8172
|
+
}
|
|
8173
|
+
function loadVideo(path, readBytes) {
|
|
8174
|
+
let bytes;
|
|
8175
|
+
try {
|
|
8176
|
+
bytes = readBytes(path);
|
|
8177
|
+
} catch (err) {
|
|
8178
|
+
throw new PingoError("usage", `\u65E0\u6CD5\u8BFB\u53D6\u89C6\u9891 ${path}:${err.message}`);
|
|
8179
|
+
}
|
|
8180
|
+
if (extname2(path).toLowerCase() !== ".mp4") {
|
|
8181
|
+
throw new PingoError("content", `\u53EA\u652F\u6301 mp4 \u89C6\u9891:${path}`);
|
|
8182
|
+
}
|
|
8183
|
+
if (bytes.length === 0) {
|
|
8184
|
+
throw new PingoError("content", `\u89C6\u9891\u6587\u4EF6\u4E3A\u7A7A:${path}`);
|
|
8185
|
+
}
|
|
8186
|
+
if (bytes.length < 8 || bytes.toString("latin1", 4, 8) !== "ftyp") {
|
|
8187
|
+
throw new PingoError("content", `\u6587\u4EF6\u5185\u5BB9\u4E0D\u662F mp4:${path}`);
|
|
8188
|
+
}
|
|
8189
|
+
if (bytes.length > MAX_VIDEO_BYTES) {
|
|
8190
|
+
throw new PingoError(
|
|
8191
|
+
"content",
|
|
8192
|
+
`\u89C6\u9891\u8FC7\u5927:${path} \u4E3A ${mib2(bytes.length)}(${bytes.length} \u5B57\u8282),\u4E0A\u9650 ${MAX_VIDEO_BYTES} \u5B57\u8282(19 MiB)\u3002`
|
|
8193
|
+
);
|
|
8194
|
+
}
|
|
8195
|
+
return { path, name: basename2(path), bytes, durationSec: parseMp4Duration(bytes) };
|
|
8196
|
+
}
|
|
8197
|
+
|
|
7758
8198
|
// src/utils/redact.ts
|
|
7759
8199
|
function maskSecret(value) {
|
|
7760
8200
|
if (value.length <= 4) return "***";
|
|
@@ -7779,8 +8219,15 @@ function redact(text, secrets) {
|
|
|
7779
8219
|
// src/providers/dingtalk.ts
|
|
7780
8220
|
var TOKEN_URL = "https://api.dingtalk.com/v1.0/oauth2/accessToken";
|
|
7781
8221
|
var SEND_URL = "https://api.dingtalk.com/v1.0/robot/oToMessages/batchSend";
|
|
8222
|
+
var CARD_DELIVER_URL = "https://api.dingtalk.com/v1.0/card/instances/createAndDeliver";
|
|
8223
|
+
var CARD_UPDATE_URL = "https://api.dingtalk.com/v1.0/card/instances";
|
|
8224
|
+
var UPLOAD_URL = "https://oapi.dingtalk.com/media/upload";
|
|
7782
8225
|
var TOKEN_REFRESH_MARGIN_SEC = 300;
|
|
7783
8226
|
var RETRY_DELAYS_MS = [500, 1e3];
|
|
8227
|
+
function uploadTimeoutMs(bytes, baseTimeoutMs) {
|
|
8228
|
+
const estimate = 3e4 + Math.ceil(bytes / (128 * 1024)) * 1e3;
|
|
8229
|
+
return Math.max(baseTimeoutMs, estimate);
|
|
8230
|
+
}
|
|
7784
8231
|
function defaultSleep(ms) {
|
|
7785
8232
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
7786
8233
|
}
|
|
@@ -7790,11 +8237,11 @@ function createDingTalkClient(opts) {
|
|
|
7790
8237
|
const sleep = opts.sleep ?? defaultSleep;
|
|
7791
8238
|
let cached;
|
|
7792
8239
|
const scrub = (text) => redact(text, [opts.creds.clientSecret, cached?.token ?? ""]);
|
|
7793
|
-
async function request(url, init, onFetchError) {
|
|
8240
|
+
async function request(url, init, onFetchError, timeoutMs = opts.timeoutMs) {
|
|
7794
8241
|
let res;
|
|
7795
8242
|
let text;
|
|
7796
8243
|
try {
|
|
7797
|
-
res = await fetchImpl(url, { ...init, signal: AbortSignal.timeout(
|
|
8244
|
+
res = await fetchImpl(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
|
|
7798
8245
|
text = await res.text();
|
|
7799
8246
|
} catch (err) {
|
|
7800
8247
|
throw new PingoError(onFetchError, `\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25:${scrub(err.message)}`);
|
|
@@ -7892,7 +8339,247 @@ function createDingTalkClient(opts) {
|
|
|
7892
8339
|
const token = await getToken();
|
|
7893
8340
|
return withRetry(() => sendOnce(token, msg));
|
|
7894
8341
|
}
|
|
7895
|
-
|
|
8342
|
+
function failCard(res) {
|
|
8343
|
+
const code = typeof res.json?.code === "string" ? res.json.code : void 0;
|
|
8344
|
+
const message = typeof res.json?.message === "string" ? res.json.message : `HTTP ${res.status}`;
|
|
8345
|
+
const label = scrub(code ? `${code}: ${message}` : message);
|
|
8346
|
+
const classified = classifyDingTalkError(res.status, code);
|
|
8347
|
+
if (classified === "auth") {
|
|
8348
|
+
throw new PingoError("auth", `\u9489\u9489\u5361\u7247\u63A5\u53E3\u9274\u6743\u5931\u8D25(HTTP ${res.status}):${label}`, code);
|
|
8349
|
+
}
|
|
8350
|
+
if (res.status === 429 || res.status >= 500) {
|
|
8351
|
+
throw new PingoError("transient", `\u9489\u9489\u5361\u7247\u63A5\u53E3\u6682\u65F6\u4E0D\u53EF\u7528(HTTP ${res.status}):${label}`, code);
|
|
8352
|
+
}
|
|
8353
|
+
throw new PingoError("config", `\u9489\u9489\u62D2\u7EDD\u4E86\u5361\u7247(HTTP ${res.status}):${label}`, code, { degradable: true });
|
|
8354
|
+
}
|
|
8355
|
+
async function deliverCardOnce(token, p) {
|
|
8356
|
+
const res = await request(
|
|
8357
|
+
CARD_DELIVER_URL,
|
|
8358
|
+
{
|
|
8359
|
+
method: "POST",
|
|
8360
|
+
headers: { "Content-Type": "application/json", "x-acs-dingtalk-access-token": token },
|
|
8361
|
+
body: JSON.stringify({
|
|
8362
|
+
cardTemplateId: p.templateId,
|
|
8363
|
+
outTrackId: p.outTrackId,
|
|
8364
|
+
cardData: { cardParamMap: p.cardParamMap },
|
|
8365
|
+
callbackType: "STREAM",
|
|
8366
|
+
openSpaceId: `dtv1.card//IM_ROBOT.${opts.creds.userId}`,
|
|
8367
|
+
imRobotOpenSpaceModel: { supportForward: true },
|
|
8368
|
+
imRobotOpenDeliverModel: { spaceType: "IM_ROBOT", robotCode: opts.creds.robotCode },
|
|
8369
|
+
userIdType: 1
|
|
8370
|
+
})
|
|
8371
|
+
},
|
|
8372
|
+
"network_unknown"
|
|
8373
|
+
);
|
|
8374
|
+
if (res.status !== 200) failCard(res);
|
|
8375
|
+
if (res.json === void 0) {
|
|
8376
|
+
throw new PingoError("network_unknown", "\u9489\u9489\u5361\u7247\u54CD\u5E94\u4E3A\u7A7A\u6216\u4E0D\u662F JSON,\u65E0\u6CD5\u786E\u8BA4\u5361\u7247\u662F\u5426\u5DF2\u53D1\u51FA\u3002");
|
|
8377
|
+
}
|
|
8378
|
+
const result = res.json.result;
|
|
8379
|
+
const results = result?.deliverResults ?? [];
|
|
8380
|
+
if (results.length === 0) {
|
|
8381
|
+
throw new PingoError("network_unknown", "\u9489\u9489\u5361\u7247\u54CD\u5E94\u4E2D\u6CA1\u6709\u6295\u9012\u7ED3\u679C,\u65E0\u6CD5\u786E\u8BA4\u5361\u7247\u662F\u5426\u5DF2\u53D1\u51FA\u3002");
|
|
8382
|
+
}
|
|
8383
|
+
const failedItem = results.find((r) => r.success !== true);
|
|
8384
|
+
if (failedItem !== void 0) {
|
|
8385
|
+
throw new PingoError(
|
|
8386
|
+
"config",
|
|
8387
|
+
`\u9489\u9489\u5361\u7247\u6295\u9012\u5931\u8D25:${scrub(failedItem.errorMsg || "\u672A\u77E5\u539F\u56E0")}`,
|
|
8388
|
+
void 0,
|
|
8389
|
+
{ degradable: true }
|
|
8390
|
+
);
|
|
8391
|
+
}
|
|
8392
|
+
}
|
|
8393
|
+
async function deliverCard(p) {
|
|
8394
|
+
const token = await getToken();
|
|
8395
|
+
let hadTransient = false;
|
|
8396
|
+
return withRetry(async () => {
|
|
8397
|
+
try {
|
|
8398
|
+
return await deliverCardOnce(token, p);
|
|
8399
|
+
} catch (err) {
|
|
8400
|
+
if (err instanceof PingoError && err.category === "transient") {
|
|
8401
|
+
hadTransient = true;
|
|
8402
|
+
} else if (hadTransient && err instanceof PingoError && err.degradable) {
|
|
8403
|
+
throw new PingoError(
|
|
8404
|
+
"network_unknown",
|
|
8405
|
+
`\u9489\u9489\u5361\u7247\u63A5\u53E3\u5728\u91CD\u8BD5\u540E\u62D2\u7EDD\u4E86\u8BF7\u6C42,\u4E4B\u524D\u7684\u8BF7\u6C42\u53EF\u80FD\u5DF2\u6295\u653E\u6210\u529F:${err.message}`,
|
|
8406
|
+
err.code
|
|
8407
|
+
);
|
|
8408
|
+
}
|
|
8409
|
+
throw err;
|
|
8410
|
+
}
|
|
8411
|
+
});
|
|
8412
|
+
}
|
|
8413
|
+
async function updateCardOnce(token, p) {
|
|
8414
|
+
const res = await request(
|
|
8415
|
+
CARD_UPDATE_URL,
|
|
8416
|
+
{
|
|
8417
|
+
method: "PUT",
|
|
8418
|
+
headers: { "Content-Type": "application/json", "x-acs-dingtalk-access-token": token },
|
|
8419
|
+
body: JSON.stringify({
|
|
8420
|
+
outTrackId: p.outTrackId,
|
|
8421
|
+
cardData: { cardParamMap: p.cardParamMap },
|
|
8422
|
+
cardUpdateOptions: { updateCardDataByKey: true },
|
|
8423
|
+
userIdType: 1
|
|
8424
|
+
})
|
|
8425
|
+
},
|
|
8426
|
+
"network_unknown"
|
|
8427
|
+
);
|
|
8428
|
+
if (res.status !== 200) {
|
|
8429
|
+
try {
|
|
8430
|
+
failCard(res);
|
|
8431
|
+
} catch (err) {
|
|
8432
|
+
if (err instanceof PingoError && err.degradable) throw new PingoError(err.category, err.message, err.code);
|
|
8433
|
+
throw err;
|
|
8434
|
+
}
|
|
8435
|
+
}
|
|
8436
|
+
if (res.json === void 0) {
|
|
8437
|
+
throw new PingoError("network_unknown", "\u9489\u9489\u5361\u7247\u66F4\u65B0\u54CD\u5E94\u4E3A\u7A7A\u6216\u4E0D\u662F JSON,\u65E0\u6CD5\u786E\u8BA4\u662F\u5426\u5DF2\u66F4\u65B0\u3002");
|
|
8438
|
+
}
|
|
8439
|
+
if (res.json.result !== true) {
|
|
8440
|
+
throw new PingoError("config", "\u9489\u9489\u5361\u7247\u66F4\u65B0\u5931\u8D25:cardId \u53EF\u80FD\u65E0\u6548\u6216\u5361\u7247\u5DF2\u4E0D\u53EF\u66F4\u65B0\u3002");
|
|
8441
|
+
}
|
|
8442
|
+
}
|
|
8443
|
+
async function updateCard(p) {
|
|
8444
|
+
const token = await getToken();
|
|
8445
|
+
return withRetry(() => updateCardOnce(token, p));
|
|
8446
|
+
}
|
|
8447
|
+
async function uploadOnce(token, file, type) {
|
|
8448
|
+
const form = new FormData();
|
|
8449
|
+
form.append("media", new Blob([new Uint8Array(file.bytes)], { type: file.mime }), file.name);
|
|
8450
|
+
const url = `${UPLOAD_URL}?access_token=${encodeURIComponent(token)}&type=${type}`;
|
|
8451
|
+
const res = await request(url, { method: "POST", body: form }, "transient", uploadTimeoutMs(file.bytes.length, opts.timeoutMs));
|
|
8452
|
+
if (res.status === 413) {
|
|
8453
|
+
throw new PingoError("content", `\u6587\u4EF6 ${file.name} \u8D85\u8FC7\u9489\u9489\u4E0A\u4F20\u4E0A\u9650(HTTP 413),\u8BF7\u538B\u7F29\u540E\u91CD\u8BD5\u3002`);
|
|
8454
|
+
}
|
|
8455
|
+
if (res.status === 429 || res.status >= 500) {
|
|
8456
|
+
throw new PingoError("transient", `\u9489\u9489\u5A92\u4F53\u4E0A\u4F20\u6682\u65F6\u4E0D\u53EF\u7528(HTTP ${res.status})\u3002`);
|
|
8457
|
+
}
|
|
8458
|
+
const errcode = res.json?.errcode;
|
|
8459
|
+
const mediaId = res.json?.media_id;
|
|
8460
|
+
if (res.status !== 200 || errcode !== 0 || typeof mediaId !== "string" || mediaId === "") {
|
|
8461
|
+
const errmsg = typeof res.json?.errmsg === "string" ? res.json.errmsg : "";
|
|
8462
|
+
throw new PingoError(
|
|
8463
|
+
"content",
|
|
8464
|
+
`\u6587\u4EF6 ${file.name} \u4E0A\u4F20\u5931\u8D25:HTTP ${res.status},errcode=${String(errcode)} ${scrub(errmsg)}`.trim()
|
|
8465
|
+
);
|
|
8466
|
+
}
|
|
8467
|
+
return mediaId;
|
|
8468
|
+
}
|
|
8469
|
+
async function uploadMedia(file, type) {
|
|
8470
|
+
const token = await getToken();
|
|
8471
|
+
return withRetry(() => uploadOnce(token, file, type));
|
|
8472
|
+
}
|
|
8473
|
+
async function uploadImage(file) {
|
|
8474
|
+
return uploadMedia(file, "image");
|
|
8475
|
+
}
|
|
8476
|
+
return { getToken, send, deliverCard, updateCard, uploadMedia, uploadImage };
|
|
8477
|
+
}
|
|
8478
|
+
|
|
8479
|
+
// src/send/fallback.ts
|
|
8480
|
+
function decideFallback(from, err, opts) {
|
|
8481
|
+
if (!(err instanceof PingoError)) return void 0;
|
|
8482
|
+
if (from === "card") return err.degradable ? "markdown" : void 0;
|
|
8483
|
+
if (from === "markdown") {
|
|
8484
|
+
if (isFormatRejection(err.code) && opts.fallbackFormat === "text" && !opts.markdownFromFile) {
|
|
8485
|
+
return "text";
|
|
8486
|
+
}
|
|
8487
|
+
return void 0;
|
|
8488
|
+
}
|
|
8489
|
+
return void 0;
|
|
8490
|
+
}
|
|
8491
|
+
|
|
8492
|
+
// src/send/pipeline.ts
|
|
8493
|
+
function toPingoError(err) {
|
|
8494
|
+
return err instanceof PingoError ? err : new PingoError("transient", String(err?.message ?? err));
|
|
8495
|
+
}
|
|
8496
|
+
async function runPipeline(p, client, newId = newCardId) {
|
|
8497
|
+
const mediaIds = [];
|
|
8498
|
+
for (const image of p.images) {
|
|
8499
|
+
mediaIds.push(await client.uploadMedia(image, "image"));
|
|
8500
|
+
}
|
|
8501
|
+
let videoMsgParam;
|
|
8502
|
+
if (p.video !== void 0) {
|
|
8503
|
+
const picMediaId = await client.uploadMedia(p.video.cover, "image");
|
|
8504
|
+
const videoMediaId = await client.uploadMedia(
|
|
8505
|
+
{ bytes: p.video.file.bytes, name: p.video.file.name, mime: "video/mp4" },
|
|
8506
|
+
"video"
|
|
8507
|
+
);
|
|
8508
|
+
videoMsgParam = JSON.stringify({
|
|
8509
|
+
duration: String(p.video.file.durationSec),
|
|
8510
|
+
videoMediaId,
|
|
8511
|
+
videoType: "mp4",
|
|
8512
|
+
picMediaId
|
|
8513
|
+
});
|
|
8514
|
+
}
|
|
8515
|
+
async function sendVideo(sent) {
|
|
8516
|
+
if (videoMsgParam === void 0) return;
|
|
8517
|
+
try {
|
|
8518
|
+
await client.send({ msgKey: "sampleVideo", msgParam: videoMsgParam });
|
|
8519
|
+
sent.push("video");
|
|
8520
|
+
} catch (err) {
|
|
8521
|
+
throw new PartialSendError(toPingoError(err), sent, ["video"]);
|
|
8522
|
+
}
|
|
8523
|
+
}
|
|
8524
|
+
async function attemptCard() {
|
|
8525
|
+
if (p.templateId === void 0) {
|
|
8526
|
+
throw new PingoError("config", "\u4F7F\u7528\u5361\u7247\u683C\u5F0F\u9700\u8981\u914D\u7F6E card.templateId\u3002");
|
|
8527
|
+
}
|
|
8528
|
+
const { cardParamMap } = renderCard(p.input, { device: p.device }, mediaIds);
|
|
8529
|
+
const cardId = newId();
|
|
8530
|
+
await client.deliverCard({ templateId: p.templateId, outTrackId: cardId, cardParamMap });
|
|
8531
|
+
const sent = ["card"];
|
|
8532
|
+
await sendVideo(sent);
|
|
8533
|
+
return { cardId, sent };
|
|
8534
|
+
}
|
|
8535
|
+
async function attemptPlain(format2) {
|
|
8536
|
+
const built = buildMessage(p.input, { device: p.device, templates: p.templates, format: format2 });
|
|
8537
|
+
const result = await client.send({ msgKey: built.msgKey, msgParam: built.msgParam });
|
|
8538
|
+
const sent = ["text-message"];
|
|
8539
|
+
for (let i = 0; i < mediaIds.length; i++) {
|
|
8540
|
+
try {
|
|
8541
|
+
await client.send({ msgKey: "sampleImageMsg", msgParam: JSON.stringify({ photoURL: mediaIds[i] }) });
|
|
8542
|
+
sent.push(`image-${i + 1}`);
|
|
8543
|
+
} catch (err) {
|
|
8544
|
+
const failed = mediaIds.slice(i).map((_, j) => `image-${i + j + 1}`);
|
|
8545
|
+
if (videoMsgParam !== void 0) failed.push("video");
|
|
8546
|
+
throw new PartialSendError(toPingoError(err), sent, failed);
|
|
8547
|
+
}
|
|
8548
|
+
}
|
|
8549
|
+
await sendVideo(sent);
|
|
8550
|
+
return { processQueryKey: result.processQueryKey, sent };
|
|
8551
|
+
}
|
|
8552
|
+
let format = p.format;
|
|
8553
|
+
let degradedFrom;
|
|
8554
|
+
let degradeReason;
|
|
8555
|
+
for (; ; ) {
|
|
8556
|
+
try {
|
|
8557
|
+
const attempt = format === "card" ? await attemptCard() : await attemptPlain(format);
|
|
8558
|
+
return {
|
|
8559
|
+
format,
|
|
8560
|
+
degraded: degradedFrom !== void 0,
|
|
8561
|
+
degradedFrom,
|
|
8562
|
+
degradeReason,
|
|
8563
|
+
cardId: attempt.cardId,
|
|
8564
|
+
processQueryKey: attempt.processQueryKey,
|
|
8565
|
+
images: mediaIds.length,
|
|
8566
|
+
video: videoMsgParam !== void 0,
|
|
8567
|
+
sent: attempt.sent
|
|
8568
|
+
};
|
|
8569
|
+
} catch (err) {
|
|
8570
|
+
if (err instanceof PartialSendError) throw err;
|
|
8571
|
+
const next = decideFallback(format, err, {
|
|
8572
|
+
fallbackFormat: p.fallbackFormat,
|
|
8573
|
+
markdownFromFile: p.markdownFromFile
|
|
8574
|
+
});
|
|
8575
|
+
if (next === void 0) throw err;
|
|
8576
|
+
if (degradedFrom === void 0) {
|
|
8577
|
+
degradedFrom = format;
|
|
8578
|
+
degradeReason = err.message;
|
|
8579
|
+
}
|
|
8580
|
+
format = next;
|
|
8581
|
+
}
|
|
8582
|
+
}
|
|
7896
8583
|
}
|
|
7897
8584
|
|
|
7898
8585
|
// src/skills/install.ts
|
|
@@ -8069,7 +8756,15 @@ function uninstallSkills(opts) {
|
|
|
8069
8756
|
|
|
8070
8757
|
// src/cli.ts
|
|
8071
8758
|
var MESSAGE_TYPES2 = ["success", "warning", "error", "info"];
|
|
8072
|
-
var
|
|
8759
|
+
var SEND_FORMATS2 = ["card", "markdown", "text"];
|
|
8760
|
+
var FORMAT_LABELS = { card: "\u5361\u7247", markdown: "Markdown", text: "\u6587\u672C" };
|
|
8761
|
+
function sentLabel(item) {
|
|
8762
|
+
if (item === "card") return "\u5361\u7247";
|
|
8763
|
+
if (item === "text-message") return "\u6587\u5B57\u6D88\u606F";
|
|
8764
|
+
if (item === "video") return "\u89C6\u9891";
|
|
8765
|
+
const m = /^image-(\d+)$/.exec(item);
|
|
8766
|
+
return m ? `\u56FE\u7247 ${m[1]}` : item;
|
|
8767
|
+
}
|
|
8073
8768
|
var USAGE = `pingo \u2014 \u628A\u6D88\u606F\u53D1\u5230\u624B\u673A\u9489\u9489
|
|
8074
8769
|
|
|
8075
8770
|
\u7528\u6CD5:
|
|
@@ -8080,14 +8775,25 @@ send \u9009\u9879:
|
|
|
8080
8775
|
--title <\u6587\u672C> \u6807\u9898\u884C(\u663E\u793A\u5728\u6B63\u6587\u5F00\u5934)
|
|
8081
8776
|
--message <\u6587\u672C> \u6D88\u606F\u6B63\u6587
|
|
8082
8777
|
--markdown <\u6587\u4EF6> \u4ECE Markdown \u6587\u4EF6\u8BFB\u53D6\u6B63\u6587(\u53EA\u80FD\u4EE5 markdown \u683C\u5F0F\u53D1\u9001)
|
|
8083
|
-
--format <
|
|
8778
|
+
--format <card|markdown|text> \u53D1\u9001\u683C\u5F0F(\u9ED8\u8BA4\u53D6\u914D\u7F6E message.preferredFormat;\u914D\u7F6E\u4E86 card.templateId \u65F6\u9ED8\u8BA4 card)
|
|
8084
8779
|
--agent <\u540D\u79F0> --project <\u540D\u79F0> --task <\u540D\u79F0>
|
|
8085
8780
|
--action-required \u6807\u8BB0\u9700\u8981\u4EBA\u5DE5\u5904\u7406
|
|
8781
|
+
--image <\u6587\u4EF6> \u9644\u5E26\u56FE\u7247(png/jpg/gif/bmp,\u53EF\u91CD\u590D,\u6700\u591A 5 \u5F20,\u5355\u5F20 \u2264 10 MiB)
|
|
8782
|
+
--video <\u6587\u4EF6.mp4> \u9644\u5E26\u89C6\u9891(\u2264 19 MiB,\u5728\u4E3B\u6D88\u606F\u4E4B\u540E\u5355\u72EC\u53D1\u9001)
|
|
8783
|
+
--cover <\u56FE\u7247> \u89C6\u9891\u5C01\u9762(\u4E0D\u4F20\u5219\u7528 ffmpeg \u622A\u5E27,\u5931\u8D25\u5219\u81EA\u52A8\u751F\u6210)
|
|
8784
|
+
--date <\u6587\u672C> \u5361\u7247 date \u53D8\u91CF(\u65F6\u95F4,\u4EC5\u5361\u7247)
|
|
8785
|
+
--chart <\u6587\u4EF6.json> \u5361\u7247 chart \u53D8\u91CF(\u56FE\u8868 JSON,type \u53EF\u9009 lineChart|histogram|pieChart,\u5982 {"type":"lineChart","data":[{"x":"N0","type":"line","y":48}],"config":{}},\u4EC5\u5361\u7247)
|
|
8786
|
+
--link "\u6587\u5B57=URL" \u94FE\u63A5\u6309\u94AE(\u53EF\u91CD\u590D,\u6700\u591A 3 \u4E2A,\u4EC5 http(s))
|
|
8086
8787
|
--config <\u8DEF\u5F84> \u6307\u5B9A\u914D\u7F6E\u6587\u4EF6
|
|
8087
8788
|
--dry-run \u53EA\u9884\u89C8, \u4E0D\u53D1\u9001(\u4E0D\u9700\u8981\u51ED\u636E)
|
|
8088
8789
|
--json \u8F93\u51FA\u673A\u5668\u53EF\u8BFB JSON
|
|
8089
8790
|
--help \u663E\u793A\u672C\u5E2E\u52A9
|
|
8090
8791
|
|
|
8792
|
+
pingo update <cardId> [--type ...] [--title ...] [--message ... | --markdown <\u6587\u4EF6>] [--image ...] [--link ...]
|
|
8793
|
+
[--action-required] [--agent ...] [--project ...] [--date ...] [--chart <\u6587\u4EF6>] [--json]
|
|
8794
|
+
\u539F\u5730\u66F4\u65B0 pingo send --json \u8FD4\u56DE\u7684\u5361\u7247\u3002\u53EA\u66F4\u65B0\u4F20\u5165\u7684\u5B57\u6BB5:--type/--title \u91CD\u65B0\u751F\u6210\u6807\u9898;
|
|
8795
|
+
--message/--markdown/--image/--action-required \u6574\u4F53\u66FF\u6362\u6B63\u6587;--agent/--project \u66F4\u65B0\u53D1\u9001\u8005\u884C\u3002
|
|
8796
|
+
|
|
8091
8797
|
pingo skills install|uninstall [--tool kiro,claude,codex] [--yes]
|
|
8092
8798
|
\u5B89\u88C5/\u5378\u8F7D pingo\u3001pingo-follow \u4E24\u4E2A Skill \u5230\u6240\u9009 AI \u5DE5\u5177(Kiro / Claude Code / Codex)\u3002
|
|
8093
8799
|
--tool \u672A\u6307\u5B9A\u4E14\u5728\u4EA4\u4E92\u7EC8\u7AEF\u4E2D\u8FD0\u884C\u65F6\u4F1A\u8BE2\u95EE; \u975E\u4EA4\u4E92\u73AF\u5883\u5FC5\u987B\u663E\u5F0F\u6307\u5B9A\u3002
|
|
@@ -8106,6 +8812,8 @@ function defaultDeps() {
|
|
|
8106
8812
|
stderr: (s) => process.stderr.write(`${s}
|
|
8107
8813
|
`),
|
|
8108
8814
|
readFile: (p) => readFileSync2(p, "utf8"),
|
|
8815
|
+
readBytes: (p) => readFileSync2(p),
|
|
8816
|
+
grabFrame: ffmpegFrameGrabber,
|
|
8109
8817
|
// src/cli.ts 与打包后的 dist/pingo.js 都位于包根目录下一层, 所以都能这样反推出包目录。
|
|
8110
8818
|
packageDir: fileURLToPath(new URL("..", import.meta.url)),
|
|
8111
8819
|
isInteractive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
|
|
@@ -8122,18 +8830,24 @@ function defaultDeps() {
|
|
|
8122
8830
|
function reportError(r, error) {
|
|
8123
8831
|
const pingo = error instanceof PingoError ? error : new PingoError("usage", `\u672A\u9884\u671F\u7684\u9519\u8BEF:${error?.message ?? String(error)}`);
|
|
8124
8832
|
const message = redact(pingo.message, r.secrets);
|
|
8833
|
+
const partial = pingo instanceof PartialSendError ? pingo : void 0;
|
|
8125
8834
|
if (r.json) {
|
|
8126
8835
|
r.out(
|
|
8127
8836
|
JSON.stringify({
|
|
8128
8837
|
ok: false,
|
|
8129
8838
|
category: pingo.category,
|
|
8130
8839
|
code: pingo.code ?? null,
|
|
8131
|
-
message
|
|
8840
|
+
message,
|
|
8841
|
+
...partial ? { sent: partial.sent, failed: partial.failed } : {}
|
|
8132
8842
|
})
|
|
8133
8843
|
);
|
|
8134
8844
|
} else {
|
|
8135
8845
|
r.err(`\u2717 ${HINTS[pingo.category]}`);
|
|
8136
8846
|
r.err(` \u539F\u56E0:${message}`);
|
|
8847
|
+
if (partial) {
|
|
8848
|
+
r.err(` \u5DF2\u53D1\u51FA:${partial.sent.map(sentLabel).join("\u3001") || "\u65E0"}`);
|
|
8849
|
+
r.err(` \u672A\u53D1\u51FA:${partial.failed.map(sentLabel).join("\u3001")}(\u5DF2\u53D1\u51FA\u7684\u4E0D\u4F1A\u91CD\u8BD5\u6216\u64A4\u56DE)`);
|
|
8850
|
+
}
|
|
8137
8851
|
}
|
|
8138
8852
|
return EXIT_CODES[pingo.category];
|
|
8139
8853
|
}
|
|
@@ -8142,11 +8856,49 @@ function pickEnum2(value, allowed, flag) {
|
|
|
8142
8856
|
if (allowed.includes(value)) return value;
|
|
8143
8857
|
throw new PingoError("usage", `${flag} \u7684\u503C\u4E0D\u5408\u6CD5:${value}(\u53EF\u9009:${allowed.join(", ")})\u3002`);
|
|
8144
8858
|
}
|
|
8859
|
+
function nonBlank3(value) {
|
|
8860
|
+
return value === void 0 || value.trim() === "" ? void 0 : value;
|
|
8861
|
+
}
|
|
8862
|
+
function loadChart(path, read) {
|
|
8863
|
+
if (path === void 0) return void 0;
|
|
8864
|
+
let raw;
|
|
8865
|
+
try {
|
|
8866
|
+
raw = read(path);
|
|
8867
|
+
} catch (err) {
|
|
8868
|
+
throw new PingoError("usage", `\u65E0\u6CD5\u8BFB\u53D6 --chart \u6587\u4EF6 ${path}:${err.message}`);
|
|
8869
|
+
}
|
|
8870
|
+
return parseChart(raw);
|
|
8871
|
+
}
|
|
8872
|
+
function mibText(n) {
|
|
8873
|
+
return `${(n / 1024 / 1024).toFixed(1)} MiB`;
|
|
8874
|
+
}
|
|
8145
8875
|
async function runSend(args, d) {
|
|
8146
8876
|
const r = { secrets: [], json: args.json, out: d.stdout, err: d.stderr };
|
|
8147
8877
|
try {
|
|
8148
8878
|
const type = pickEnum2(args.type, MESSAGE_TYPES2, "--type");
|
|
8149
|
-
const formatFlag = pickEnum2(args.format,
|
|
8879
|
+
const formatFlag = pickEnum2(args.format, SEND_FORMATS2, "--format");
|
|
8880
|
+
if (args.message !== void 0 && args.markdownPath !== void 0) {
|
|
8881
|
+
throw new PingoError("usage", "--message \u4E0E --markdown \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528\u3002");
|
|
8882
|
+
}
|
|
8883
|
+
if (args.cover !== void 0 && args.video === void 0) {
|
|
8884
|
+
throw new PingoError("usage", "--cover \u9700\u8981\u4E0E --video \u4E00\u8D77\u4F7F\u7528\u3002");
|
|
8885
|
+
}
|
|
8886
|
+
const links = parseLinks(args.links);
|
|
8887
|
+
const chart = loadChart(args.chart, d.readFile);
|
|
8888
|
+
const images = loadImages(args.images, d.readBytes);
|
|
8889
|
+
const video = args.video !== void 0 ? loadVideo(args.video, d.readBytes) : void 0;
|
|
8890
|
+
const coverFile = args.cover !== void 0 ? loadImages([args.cover], d.readBytes)[0] : void 0;
|
|
8891
|
+
let markdown;
|
|
8892
|
+
if (args.markdownPath !== void 0) {
|
|
8893
|
+
try {
|
|
8894
|
+
markdown = d.readFile(args.markdownPath);
|
|
8895
|
+
} catch (err) {
|
|
8896
|
+
throw new PingoError("usage", `\u65E0\u6CD5\u8BFB\u53D6 --markdown \u6587\u4EF6 ${args.markdownPath}:${err.message}`);
|
|
8897
|
+
}
|
|
8898
|
+
}
|
|
8899
|
+
if (nonBlank3(args.message) === void 0 && nonBlank3(markdown) === void 0) {
|
|
8900
|
+
throw new PingoError("content", "\u6D88\u606F\u5185\u5BB9\u4E0D\u80FD\u4E3A\u7A7A:\u8BF7\u63D0\u4F9B --message \u6216 --markdown\u3002");
|
|
8901
|
+
}
|
|
8150
8902
|
const loaded = loadConfig({
|
|
8151
8903
|
configPath: args.config,
|
|
8152
8904
|
env: d.env,
|
|
@@ -8159,16 +8911,13 @@ async function runSend(args, d) {
|
|
|
8159
8911
|
const config = loaded.config;
|
|
8160
8912
|
r.secrets = [config.dingtalk.clientSecret];
|
|
8161
8913
|
if (!args.json) for (const w of loaded.warnings) d.stderr(`! ${w}`);
|
|
8162
|
-
|
|
8163
|
-
|
|
8914
|
+
const preferred = config.message.preferredFormat;
|
|
8915
|
+
const format = formatFlag ?? (markdown !== void 0 && preferred === "text" ? "markdown" : preferred);
|
|
8916
|
+
if (format === "card" && config.card.templateId === void 0) {
|
|
8917
|
+
throw new PingoError("config", "\u4F7F\u7528\u5361\u7247\u683C\u5F0F\u9700\u8981\u914D\u7F6E card.templateId(\u9489\u9489\u5361\u7247\u6A21\u677F ID)\u3002");
|
|
8164
8918
|
}
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
try {
|
|
8168
|
-
markdown = d.readFile(args.markdownPath);
|
|
8169
|
-
} catch (err) {
|
|
8170
|
-
throw new PingoError("usage", `\u65E0\u6CD5\u8BFB\u53D6 --markdown \u6587\u4EF6 ${args.markdownPath}:${err.message}`);
|
|
8171
|
-
}
|
|
8919
|
+
if (format !== "card" && (args.date !== void 0 || chart !== void 0)) {
|
|
8920
|
+
throw new PingoError("usage", "--date / --chart \u53EA\u80FD\u7528\u4E8E\u5361\u7247\u683C\u5F0F(--format card)\u3002");
|
|
8172
8921
|
}
|
|
8173
8922
|
const input = {
|
|
8174
8923
|
type: type ?? config.message.defaultType,
|
|
@@ -8178,21 +8927,51 @@ async function runSend(args, d) {
|
|
|
8178
8927
|
agent: args.agent,
|
|
8179
8928
|
project: args.project,
|
|
8180
8929
|
task: args.task,
|
|
8181
|
-
actionRequired: args.actionRequired
|
|
8930
|
+
actionRequired: args.actionRequired,
|
|
8931
|
+
links,
|
|
8932
|
+
date: args.date,
|
|
8933
|
+
chart
|
|
8182
8934
|
};
|
|
8183
|
-
const
|
|
8184
|
-
const
|
|
8185
|
-
|
|
8935
|
+
const placeholders = images.map((_, i) => `<image-${i + 1}>`);
|
|
8936
|
+
const preview = format === "card" ? renderCard(input, { device: config.device }, placeholders) : (() => {
|
|
8937
|
+
const built = buildMessage(input, { device: config.device, templates: config.templates, format });
|
|
8938
|
+
return { preview: built.preview, bytes: built.bytes };
|
|
8939
|
+
})();
|
|
8186
8940
|
if (args.dryRun) {
|
|
8941
|
+
const coverSource = video === void 0 ? void 0 : coverFile !== void 0 ? "cover" : "ffmpeg(\u5F85\u622A\u53D6,\u5931\u8D25\u5219\u751F\u6210)";
|
|
8187
8942
|
if (args.json) {
|
|
8188
|
-
d.stdout(
|
|
8943
|
+
d.stdout(
|
|
8944
|
+
JSON.stringify({
|
|
8945
|
+
ok: true,
|
|
8946
|
+
dryRun: true,
|
|
8947
|
+
format,
|
|
8948
|
+
bytes: preview.bytes,
|
|
8949
|
+
preview: preview.preview,
|
|
8950
|
+
images: images.length,
|
|
8951
|
+
video: video !== void 0,
|
|
8952
|
+
...video ? { videoDurationSec: video.durationSec, coverSource } : {}
|
|
8953
|
+
})
|
|
8954
|
+
);
|
|
8189
8955
|
} else {
|
|
8190
|
-
d.stdout(`[dry-run] \u683C\u5F0F:${
|
|
8956
|
+
d.stdout(`[dry-run] \u683C\u5F0F:${format} | \u5927\u5C0F:${preview.bytes} \u5B57\u8282 | \u56FE\u7247:${images.length} \u5F20 | \u672A\u53D1\u9001`);
|
|
8191
8957
|
d.stdout("");
|
|
8192
|
-
d.stdout(
|
|
8958
|
+
d.stdout(preview.preview);
|
|
8959
|
+
if (format === "card" && nonBlank3(args.date) !== void 0) d.stdout(`
|
|
8960
|
+
date:${args.date}`);
|
|
8961
|
+
if (format === "card" && chart !== void 0) d.stdout(`
|
|
8962
|
+
chart:${chart}`);
|
|
8963
|
+
if (format === "card" && links.length > 0) {
|
|
8964
|
+
d.stdout("");
|
|
8965
|
+
d.stdout(`\u6309\u94AE:${links.map((l) => `${l.text} \u2192 ${l.url}`).join(" | ")}`);
|
|
8966
|
+
}
|
|
8967
|
+
images.forEach((img, i) => d.stdout(`\u56FE\u7247 ${i + 1}:${img.name}(${img.bytes.length} \u5B57\u8282,${img.mime})`));
|
|
8968
|
+
if (video) {
|
|
8969
|
+
d.stdout(`\u89C6\u9891:${video.name}(${mibText(video.bytes.length)},${video.durationSec} \u79D2,\u5C01\u9762:${coverSource})`);
|
|
8970
|
+
}
|
|
8193
8971
|
}
|
|
8194
8972
|
return 0;
|
|
8195
8973
|
}
|
|
8974
|
+
const cover = video ? await resolveCover({ cover: coverFile, video, seed: config.device.id, grab: d.grabFrame }) : void 0;
|
|
8196
8975
|
const client = createDingTalkClient({
|
|
8197
8976
|
creds: config.dingtalk,
|
|
8198
8977
|
timeoutMs: config.message.timeoutMs,
|
|
@@ -8200,26 +8979,128 @@ async function runSend(args, d) {
|
|
|
8200
8979
|
sleep: d.sleep,
|
|
8201
8980
|
now: d.now
|
|
8202
8981
|
});
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
|
|
8209
|
-
|
|
8210
|
-
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8982
|
+
const result = await runPipeline(
|
|
8983
|
+
{
|
|
8984
|
+
input,
|
|
8985
|
+
images,
|
|
8986
|
+
format,
|
|
8987
|
+
device: config.device,
|
|
8988
|
+
templates: config.templates,
|
|
8989
|
+
templateId: config.card.templateId,
|
|
8990
|
+
fallbackFormat: config.message.fallbackFormat,
|
|
8991
|
+
markdownFromFile: markdown !== void 0,
|
|
8992
|
+
video: video && cover ? { file: video, cover: cover.file } : void 0
|
|
8993
|
+
},
|
|
8994
|
+
client,
|
|
8995
|
+
d.newCardId
|
|
8996
|
+
);
|
|
8216
8997
|
if (args.json) {
|
|
8217
8998
|
d.stdout(
|
|
8218
|
-
JSON.stringify({
|
|
8999
|
+
JSON.stringify({
|
|
9000
|
+
ok: true,
|
|
9001
|
+
format: result.format,
|
|
9002
|
+
degraded: result.degraded,
|
|
9003
|
+
degradedFrom: result.degradedFrom,
|
|
9004
|
+
cardId: result.cardId,
|
|
9005
|
+
processQueryKey: result.processQueryKey,
|
|
9006
|
+
images: result.images,
|
|
9007
|
+
video: result.video
|
|
9008
|
+
})
|
|
8219
9009
|
);
|
|
8220
9010
|
} else {
|
|
8221
|
-
|
|
8222
|
-
if (
|
|
9011
|
+
const parts = [`\u2713 \u9489\u9489\u5DF2\u63A5\u53D7\u8BF7\u6C42(${FORMAT_LABELS[result.format]})`];
|
|
9012
|
+
if (result.cardId) parts.push(`cardId: ${result.cardId}`);
|
|
9013
|
+
else if (result.processQueryKey) parts.push(`processQueryKey: ${result.processQueryKey.slice(0, 8)}\u2026`);
|
|
9014
|
+
if (result.images > 0) parts.push(`\u56FE\u7247 ${result.images} \u5F20`);
|
|
9015
|
+
if (result.video) parts.push(`\u89C6\u9891 1 \u6761${cover && cover.source !== "cover" ? `(\u5C01\u9762:${cover.source})` : ""}`);
|
|
9016
|
+
d.stdout(parts.join(","));
|
|
9017
|
+
if (result.degradedFrom) {
|
|
9018
|
+
d.stdout(
|
|
9019
|
+
`! ${FORMAT_LABELS[result.degradedFrom]} \u53D1\u9001\u5931\u8D25,\u5DF2\u964D\u7EA7\u4E3A${FORMAT_LABELS[result.format]}:${redact(result.degradeReason ?? "", r.secrets)}`
|
|
9020
|
+
);
|
|
9021
|
+
}
|
|
9022
|
+
}
|
|
9023
|
+
return 0;
|
|
9024
|
+
} catch (error) {
|
|
9025
|
+
return reportError(r, error);
|
|
9026
|
+
}
|
|
9027
|
+
}
|
|
9028
|
+
async function runUpdate(args, d) {
|
|
9029
|
+
const r = { secrets: [], json: args.json, out: d.stdout, err: d.stderr };
|
|
9030
|
+
try {
|
|
9031
|
+
const cardId = args.positionals[1];
|
|
9032
|
+
if (cardId === void 0) {
|
|
9033
|
+
throw new PingoError("usage", "\u7528\u6CD5:pingo update <cardId> [--type ...] [--title ...] [--message ...] ...");
|
|
9034
|
+
}
|
|
9035
|
+
if (!isCardId(cardId)) {
|
|
9036
|
+
throw new PingoError("usage", `cardId \u683C\u5F0F\u4E0D\u6B63\u786E:${cardId}(\u5E94\u4E3A pingo send --json \u8FD4\u56DE\u7684 cardId)\u3002`);
|
|
9037
|
+
}
|
|
9038
|
+
if (args.format !== void 0) throw new PingoError("usage", "pingo update \u53EA\u7528\u4E8E\u5361\u7247,\u4E0D\u652F\u6301 --format\u3002");
|
|
9039
|
+
if (args.video !== void 0 || args.cover !== void 0) {
|
|
9040
|
+
throw new PingoError("usage", "pingo update \u4E0D\u652F\u6301 --video / --cover,\u8BF7\u7528 pingo send \u53D1\u9001\u89C6\u9891\u3002");
|
|
9041
|
+
}
|
|
9042
|
+
if (args.dryRun) throw new PingoError("usage", "pingo update \u4E0D\u652F\u6301 --dry-run\u3002");
|
|
9043
|
+
if (args.message !== void 0 && args.markdownPath !== void 0) {
|
|
9044
|
+
throw new PingoError("usage", "--message \u4E0E --markdown \u4E0D\u80FD\u540C\u65F6\u4F7F\u7528\u3002");
|
|
9045
|
+
}
|
|
9046
|
+
const type = pickEnum2(args.type, MESSAGE_TYPES2, "--type");
|
|
9047
|
+
const links = args.links.length > 0 ? parseLinks(args.links) : void 0;
|
|
9048
|
+
const chart = loadChart(args.chart, d.readFile);
|
|
9049
|
+
const images = loadImages(args.images, d.readBytes);
|
|
9050
|
+
let markdown;
|
|
9051
|
+
if (args.markdownPath !== void 0) {
|
|
9052
|
+
try {
|
|
9053
|
+
markdown = d.readFile(args.markdownPath);
|
|
9054
|
+
} catch (err) {
|
|
9055
|
+
throw new PingoError("usage", `\u65E0\u6CD5\u8BFB\u53D6 --markdown \u6587\u4EF6 ${args.markdownPath}:${err.message}`);
|
|
9056
|
+
}
|
|
9057
|
+
}
|
|
9058
|
+
const fields = {
|
|
9059
|
+
type,
|
|
9060
|
+
title: args.title,
|
|
9061
|
+
message: args.message,
|
|
9062
|
+
markdown,
|
|
9063
|
+
actionRequired: args.actionRequired ? true : void 0,
|
|
9064
|
+
links,
|
|
9065
|
+
agent: args.agent,
|
|
9066
|
+
project: args.project,
|
|
9067
|
+
task: args.task,
|
|
9068
|
+
date: args.date,
|
|
9069
|
+
chart
|
|
9070
|
+
};
|
|
9071
|
+
renderCardUpdate({ ...fields, defaultType: "info" }, { device: { id: "", name: "" } }, images.map(() => "<image>"));
|
|
9072
|
+
const loaded = loadConfig({
|
|
9073
|
+
configPath: args.config,
|
|
9074
|
+
env: d.env,
|
|
9075
|
+
cwd: d.cwd,
|
|
9076
|
+
homeDir: d.homeDir,
|
|
9077
|
+
platform: d.platform,
|
|
9078
|
+
hostname: d.hostname,
|
|
9079
|
+
requireCredentials: true
|
|
9080
|
+
});
|
|
9081
|
+
const config = loaded.config;
|
|
9082
|
+
r.secrets = [config.dingtalk.clientSecret];
|
|
9083
|
+
if (!args.json) for (const w of loaded.warnings) d.stderr(`! ${w}`);
|
|
9084
|
+
const client = createDingTalkClient({
|
|
9085
|
+
creds: config.dingtalk,
|
|
9086
|
+
timeoutMs: config.message.timeoutMs,
|
|
9087
|
+
fetchImpl: d.fetchImpl,
|
|
9088
|
+
sleep: d.sleep,
|
|
9089
|
+
now: d.now
|
|
9090
|
+
});
|
|
9091
|
+
const mediaIds = [];
|
|
9092
|
+
for (const image of images) mediaIds.push(await client.uploadMedia(image, "image"));
|
|
9093
|
+
const cardParamMap = renderCardUpdate(
|
|
9094
|
+
{ ...fields, defaultType: config.message.defaultType },
|
|
9095
|
+
{ device: config.device },
|
|
9096
|
+
mediaIds
|
|
9097
|
+
);
|
|
9098
|
+
await client.updateCard({ outTrackId: cardId, cardParamMap });
|
|
9099
|
+
const updated = Object.keys(cardParamMap);
|
|
9100
|
+
if (args.json) {
|
|
9101
|
+
d.stdout(JSON.stringify({ ok: true, cardId, updated }));
|
|
9102
|
+
} else {
|
|
9103
|
+
d.stdout(`\u2713 \u5361\u7247\u5DF2\u66F4\u65B0(${cardId}):${updated.join("\u3001")}`);
|
|
8223
9104
|
}
|
|
8224
9105
|
return 0;
|
|
8225
9106
|
} catch (error) {
|
|
@@ -8236,7 +9117,13 @@ var STRING_OPTIONS = /* @__PURE__ */ new Set([
|
|
|
8236
9117
|
"project",
|
|
8237
9118
|
"task",
|
|
8238
9119
|
"config",
|
|
8239
|
-
"tool"
|
|
9120
|
+
"tool",
|
|
9121
|
+
"image",
|
|
9122
|
+
"link",
|
|
9123
|
+
"video",
|
|
9124
|
+
"cover",
|
|
9125
|
+
"date",
|
|
9126
|
+
"chart"
|
|
8240
9127
|
]);
|
|
8241
9128
|
var KNOWN_OPTIONS = /* @__PURE__ */ new Set([
|
|
8242
9129
|
...STRING_OPTIONS,
|
|
@@ -8323,9 +9210,9 @@ function printResults(d, results) {
|
|
|
8323
9210
|
let hasProblem = false;
|
|
8324
9211
|
for (const r of results) {
|
|
8325
9212
|
const label = r.status === "created" ? "\u5DF2\u521B\u5EFA" : r.status === "skipped" ? "\u5DF2\u5B58\u5728, \u8DF3\u8FC7" : r.status === "replaced" ? "\u5DF2\u4FEE\u590D\u8FC7\u671F\u94FE\u63A5" : r.status === "removed" ? "\u5DF2\u5220\u9664" : r.status === "failed" ? `\u5931\u8D25:${r.detail ?? "\u672A\u77E5\u539F\u56E0"}` : "\u51B2\u7A81, \u8DF3\u8FC7(\u8BE5\u8DEF\u5F84\u4E0D\u662F\u672C\u5305\u521B\u5EFA\u7684\u94FE\u63A5)";
|
|
8326
|
-
const
|
|
8327
|
-
d.stdout(`${
|
|
8328
|
-
if (
|
|
9213
|
+
const bad2 = r.status === "conflict" || r.status === "failed";
|
|
9214
|
+
d.stdout(`${bad2 ? "\u2717" : "\u2713"} [${TOOL_LABELS[r.tool]}] ${r.name}:${label} (${r.path})`);
|
|
9215
|
+
if (bad2) hasProblem = true;
|
|
8329
9216
|
}
|
|
8330
9217
|
return hasProblem;
|
|
8331
9218
|
}
|
|
@@ -8556,10 +9443,22 @@ function parseSendArgs(argv) {
|
|
|
8556
9443
|
json: { type: "boolean" },
|
|
8557
9444
|
help: { type: "boolean" },
|
|
8558
9445
|
tool: { type: "string" },
|
|
8559
|
-
yes: { type: "boolean" }
|
|
9446
|
+
yes: { type: "boolean" },
|
|
9447
|
+
image: { type: "string", multiple: true },
|
|
9448
|
+
link: { type: "string", multiple: true },
|
|
9449
|
+
video: { type: "string" },
|
|
9450
|
+
cover: { type: "string" },
|
|
9451
|
+
date: { type: "string" },
|
|
9452
|
+
chart: { type: "string" }
|
|
8560
9453
|
}
|
|
8561
9454
|
});
|
|
8562
9455
|
return {
|
|
9456
|
+
images: values.image ?? [],
|
|
9457
|
+
links: values.link ?? [],
|
|
9458
|
+
video: values.video,
|
|
9459
|
+
cover: values.cover,
|
|
9460
|
+
date: values.date,
|
|
9461
|
+
chart: values.chart,
|
|
8563
9462
|
positionals,
|
|
8564
9463
|
help: values.help === true,
|
|
8565
9464
|
type: values.type,
|
|
@@ -8592,6 +9491,7 @@ async function runCli(argv, overrides = {}) {
|
|
|
8592
9491
|
return 0;
|
|
8593
9492
|
}
|
|
8594
9493
|
if (command === "send") return runSend(args, d);
|
|
9494
|
+
if (command === "update") return runUpdate(args, d);
|
|
8595
9495
|
if (command === "config") return runConfig(argv.slice(1), d);
|
|
8596
9496
|
if (command === "doctor") return runDoctor(argv.slice(1), d);
|
|
8597
9497
|
if (command === "skills") return runSkills(argv.slice(1), d);
|