@gobing-ai/knowledge-kit 0.1.9 → 0.1.11
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/.claude-plugin/marketplace.json +1 -1
- package/dist/index.js +132 -25
- package/package.json +1 -1
- package/plugins/generations/content-gen/dist/index.js +4 -0
- package/plugins/generations/core-facts-gen/dist/index.js +4 -0
- package/plugins/generations/daily-article-gen/dist/index.js +4 -0
- package/plugins/generations/dailynews-gen/dist/index.js +4 -0
- package/plugins/generations/episode-plan-gen/dist/index.js +102 -1
- package/plugins/generations/episode-plan-gen/src/index.ts +2 -4
- package/plugins/generations/gemini-tts/dist/index.js +4 -0
- package/plugins/generations/image-gen/dist/index.js +4 -0
- package/plugins/generations/news-report-gen/dist/index.js +4 -0
- package/plugins/generations/voice-gen/dist/index.js +4 -0
- package/plugins/generations/voxcpm-tts/dist/index.js +4 -0
- package/plugins/ingestions/aihot-ingest/dist/index.js +4 -0
- package/plugins/ingestions/horizon-ingest/dist/index.js +4 -0
- package/plugins/ingestions/last30days-ingest/dist/index.js +11 -3
- package/plugins/ingestions/last30days-ingest/src/index.ts +14 -3
- package/plugins/ingestions/web-search/dist/index.js +4 -0
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/scripts/daily-scheduled-run.ts +298 -0
- package/plugins/kk/scripts/daily-stages.ts +1083 -49
- package/plugins/kk/scripts/kk-workflow-stages.ts +76 -10
- package/plugins/kk/scripts/safety-artifact-check.ts +235 -0
- package/plugins/kk/scripts/sentences.ts +134 -0
- package/plugins/kk/scripts/series-names.ts +124 -0
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +194 -113
- package/plugins/kk/workflows/kk-daily-safety.yaml +160 -36
- package/plugins/publishings/bilibili-pub/dist/index.js +6 -1
- package/plugins/publishings/bilibili-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/cnblogs-pub/dist/index.js +6 -1
- package/plugins/publishings/cnblogs-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/csdn-pub/dist/index.js +6 -1
- package/plugins/publishings/csdn-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/emdash-pub/dist/index.js +4 -0
- package/plugins/publishings/infoq-pub/dist/index.js +6 -1
- package/plugins/publishings/infoq-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/jike-pub/dist/index.js +6 -1
- package/plugins/publishings/jike-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/juejin-pub/dist/index.js +6 -1
- package/plugins/publishings/juejin-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/medium-pub/dist/index.js +4 -0
- package/plugins/publishings/podcast-pub/dist/index.js +151 -16
- package/plugins/publishings/podcast-pub/src/index.ts +62 -5
- package/plugins/publishings/podcast-pub/src/seo.ts +2 -4
- package/plugins/publishings/podcast-pub/src/show-notes.ts +3 -8
- package/plugins/publishings/qiita-pub/dist/index.js +4 -0
- package/plugins/publishings/shipinhao-pub/dist/index.js +6 -1
- package/plugins/publishings/shipinhao-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/sspai-pub/dist/index.js +6 -1
- package/plugins/publishings/sspai-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/substack-pub/dist/index.js +6 -1
- package/plugins/publishings/substack-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/surfdash-pub/dist/index.js +691 -63
- package/plugins/publishings/surfdash-pub/src/digest-check.ts +284 -0
- package/plugins/publishings/surfdash-pub/src/index.ts +656 -84
- package/plugins/publishings/surfdash-pub/src/patch.ts +44 -7
- package/plugins/publishings/threads-pub/dist/index.js +4 -0
- package/plugins/publishings/wechatmp-pub/dist/index.js +937 -98
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +64 -3
- package/plugins/publishings/wechatmp-pub/src/flow.ts +1125 -85
- package/plugins/publishings/wechatmp-pub/src/index.ts +112 -30
- package/plugins/publishings/wechatmp-pub/src/page.ts +105 -0
- package/plugins/publishings/weibo-pub/dist/index.js +6 -1
- package/plugins/publishings/weibo-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/x-pub/dist/index.js +6 -1
- package/plugins/publishings/x-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/xhs-pub/dist/index.js +6 -1
- package/plugins/publishings/xhs-pub/dist/profile-check.js +6 -1
- package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +6 -1
- package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/zenn-pub/dist/index.js +4 -0
|
@@ -12254,6 +12254,10 @@ init_config();
|
|
|
12254
12254
|
|
|
12255
12255
|
// ../../packages/kk-core/src/render-md.ts
|
|
12256
12256
|
if (false) {}
|
|
12257
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
12258
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
12259
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
12260
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
12257
12261
|
// ../../packages/publish-harness/src/index.ts
|
|
12258
12262
|
class FakeTransportCore {
|
|
12259
12263
|
callLogs = [];
|
|
@@ -11984,6 +11984,7 @@ function findProjectRoot(startDir) {
|
|
|
11984
11984
|
var init_file_system_node = () => {};
|
|
11985
11985
|
|
|
11986
11986
|
// ../../plugins/publishings/podcast-pub/src/index.ts
|
|
11987
|
+
import { createHash } from "crypto";
|
|
11987
11988
|
import { readFileSync as readFileSync3 } from "fs";
|
|
11988
11989
|
import { readFile } from "fs/promises";
|
|
11989
11990
|
import { basename, dirname as dirname2, extname as extname2 } from "path";
|
|
@@ -12312,6 +12313,107 @@ async function writeJsonFile(path, value, fs = createNodeFileSystem()) {
|
|
|
12312
12313
|
}
|
|
12313
12314
|
// ../../packages/kk-core/src/render-md.ts
|
|
12314
12315
|
if (false) {}
|
|
12316
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
12317
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
12318
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
12319
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
12320
|
+
var OPEN_PAIRS = {
|
|
12321
|
+
"\u300C": "\u300D",
|
|
12322
|
+
"\u300E": "\u300F",
|
|
12323
|
+
"\u201C": "\u201D",
|
|
12324
|
+
"\uFF08": "\uFF09",
|
|
12325
|
+
"(": ")"
|
|
12326
|
+
};
|
|
12327
|
+
var CLOSE_PAIRS = {
|
|
12328
|
+
"\u300D": "\u300C",
|
|
12329
|
+
"\u300F": "\u300E",
|
|
12330
|
+
"\u201D": "\u201C",
|
|
12331
|
+
"\uFF09": "\uFF08",
|
|
12332
|
+
")": "("
|
|
12333
|
+
};
|
|
12334
|
+
function splitSentences(text, options = {}) {
|
|
12335
|
+
if (!text || typeof text !== "string")
|
|
12336
|
+
return [];
|
|
12337
|
+
const results = [];
|
|
12338
|
+
let sentenceStart = 0;
|
|
12339
|
+
let stack = [];
|
|
12340
|
+
let i = 0;
|
|
12341
|
+
const isTerminator = (c) => CJK_TERMINATORS.has(c) || options.cjkOnly !== true && LATIN_TERMINATORS.has(c);
|
|
12342
|
+
while (i < text.length) {
|
|
12343
|
+
const ch = text[i];
|
|
12344
|
+
if (isTerminator(ch)) {
|
|
12345
|
+
let termEnd = i;
|
|
12346
|
+
let hasCjkTerm = false;
|
|
12347
|
+
let hasLatinTerm = false;
|
|
12348
|
+
while (termEnd < text.length) {
|
|
12349
|
+
const termChar = text[termEnd];
|
|
12350
|
+
if (!termChar || !isTerminator(termChar)) {
|
|
12351
|
+
break;
|
|
12352
|
+
}
|
|
12353
|
+
if (CJK_TERMINATORS.has(termChar))
|
|
12354
|
+
hasCjkTerm = true;
|
|
12355
|
+
if (LATIN_TERMINATORS.has(termChar))
|
|
12356
|
+
hasLatinTerm = true;
|
|
12357
|
+
termEnd++;
|
|
12358
|
+
}
|
|
12359
|
+
let closerEnd = termEnd;
|
|
12360
|
+
const tempStack = [...stack];
|
|
12361
|
+
while (closerEnd < text.length) {
|
|
12362
|
+
const closer = text[closerEnd];
|
|
12363
|
+
if (!closer || !ATTACHED_CLOSERS.has(closer)) {
|
|
12364
|
+
break;
|
|
12365
|
+
}
|
|
12366
|
+
const top = tempStack[tempStack.length - 1];
|
|
12367
|
+
if (top !== undefined && top === closer) {
|
|
12368
|
+
tempStack.pop();
|
|
12369
|
+
} else if (CLOSE_PAIRS[closer]) {
|
|
12370
|
+
const idx = tempStack.lastIndexOf(closer);
|
|
12371
|
+
if (idx >= 0)
|
|
12372
|
+
tempStack.splice(idx, 1);
|
|
12373
|
+
}
|
|
12374
|
+
closerEnd++;
|
|
12375
|
+
}
|
|
12376
|
+
let isBoundary = false;
|
|
12377
|
+
if (tempStack.length === 0) {
|
|
12378
|
+
if (hasCjkTerm) {
|
|
12379
|
+
isBoundary = true;
|
|
12380
|
+
} else if (hasLatinTerm) {
|
|
12381
|
+
const nextChar = text[closerEnd];
|
|
12382
|
+
if (closerEnd === text.length || nextChar !== undefined && /\s/.test(nextChar)) {
|
|
12383
|
+
isBoundary = true;
|
|
12384
|
+
}
|
|
12385
|
+
}
|
|
12386
|
+
}
|
|
12387
|
+
if (isBoundary) {
|
|
12388
|
+
const sentence = text.slice(sentenceStart, closerEnd).trim();
|
|
12389
|
+
if (sentence.length > 0) {
|
|
12390
|
+
results.push(sentence);
|
|
12391
|
+
}
|
|
12392
|
+
sentenceStart = closerEnd;
|
|
12393
|
+
stack = tempStack;
|
|
12394
|
+
i = closerEnd;
|
|
12395
|
+
continue;
|
|
12396
|
+
}
|
|
12397
|
+
}
|
|
12398
|
+
const openMatch = OPEN_PAIRS[ch];
|
|
12399
|
+
const closeMatch = CLOSE_PAIRS[ch];
|
|
12400
|
+
if (openMatch) {
|
|
12401
|
+
stack.push(openMatch);
|
|
12402
|
+
} else if (closeMatch) {
|
|
12403
|
+
const idx = stack.lastIndexOf(ch);
|
|
12404
|
+
if (idx >= 0)
|
|
12405
|
+
stack.splice(idx, 1);
|
|
12406
|
+
}
|
|
12407
|
+
i++;
|
|
12408
|
+
}
|
|
12409
|
+
if (sentenceStart < text.length) {
|
|
12410
|
+
const tail = text.slice(sentenceStart).trim();
|
|
12411
|
+
if (tail.length > 0) {
|
|
12412
|
+
results.push(tail);
|
|
12413
|
+
}
|
|
12414
|
+
}
|
|
12415
|
+
return results;
|
|
12416
|
+
}
|
|
12315
12417
|
// ../../packages/publish-harness/src/index.ts
|
|
12316
12418
|
class FakeTransportCore {
|
|
12317
12419
|
callLogs = [];
|
|
@@ -12514,8 +12616,8 @@ class MicrofeedClient {
|
|
|
12514
12616
|
}
|
|
12515
12617
|
|
|
12516
12618
|
// ../../plugins/publishings/podcast-pub/src/seo.ts
|
|
12517
|
-
init_dist();
|
|
12518
12619
|
import { readFileSync as readFileSync2 } from "fs";
|
|
12620
|
+
init_dist();
|
|
12519
12621
|
function codePointLength(value) {
|
|
12520
12622
|
return Array.from(value).length;
|
|
12521
12623
|
}
|
|
@@ -12869,11 +12971,7 @@ function clauseFromTitle(title) {
|
|
|
12869
12971
|
return cut.replace(/\s+/g, " ").trim();
|
|
12870
12972
|
}
|
|
12871
12973
|
function firstSentence(text) {
|
|
12872
|
-
|
|
12873
|
-
if (trimmed.length === 0)
|
|
12874
|
-
return "";
|
|
12875
|
-
const parts = trimmed.split(/(?<=[\u3002\uFF01\uFF1F.!?])/);
|
|
12876
|
-
return (parts[0] ?? trimmed).trim();
|
|
12974
|
+
return splitSentences(text.replace(/\s+/g, " ").trim())[0] ?? "";
|
|
12877
12975
|
}
|
|
12878
12976
|
function planItemsFromMetadata(metadata) {
|
|
12879
12977
|
const raw = podcastOf(metadata).planItems;
|
|
@@ -13869,7 +13967,7 @@ function itemBrief(d) {
|
|
|
13869
13967
|
const meta = d.metadata ?? {};
|
|
13870
13968
|
if (typeof meta.brief === "string" && meta.brief.trim().length > 0)
|
|
13871
13969
|
return meta.brief.trim();
|
|
13872
|
-
return (d.body ?? "").trim()
|
|
13970
|
+
return splitSentences((d.body ?? "").trim(), { cjkOnly: true }).slice(0, 2).join("");
|
|
13873
13971
|
}
|
|
13874
13972
|
function renderShowNotesMarkdown(docs, opts, warnings) {
|
|
13875
13973
|
const slugify = (t) => {
|
|
@@ -13937,7 +14035,7 @@ function renderShowNotesMarkdown(docs, opts, warnings) {
|
|
|
13937
14035
|
} else {
|
|
13938
14036
|
docs.forEach((d, i) => {
|
|
13939
14037
|
const body = (d.body ?? "").trim();
|
|
13940
|
-
const sentences = body
|
|
14038
|
+
const sentences = splitSentences(body, { cjkOnly: true }).slice(0, 2).join("");
|
|
13941
14039
|
lines.push(`## ${i + 1}. ${d.title ?? `\u6761\u76EE ${i + 1}`}`, "", sentences, "", `[\u6765\u6E90\uFF1A${sourceLabel(d)}](${d.sourceUri ?? ""})`, "");
|
|
13942
14040
|
});
|
|
13943
14041
|
lines.push(`\u8BE6\u60C5\u89C1\u672C\u671F\u5B8C\u6574\u6587\u7AE0\uFF1A[ ${opts.title} ](${articleUrl})`, "");
|
|
@@ -14237,26 +14335,53 @@ function shapedEpisode(episode, fields) {
|
|
|
14237
14335
|
link: fields.link
|
|
14238
14336
|
};
|
|
14239
14337
|
}
|
|
14240
|
-
|
|
14338
|
+
function canonicalJson(value) {
|
|
14339
|
+
if (value === null || value === undefined)
|
|
14340
|
+
return "null";
|
|
14341
|
+
if (typeof value === "string" || typeof value === "boolean")
|
|
14342
|
+
return JSON.stringify(value);
|
|
14343
|
+
if (typeof value === "number")
|
|
14344
|
+
return Number.isFinite(value) ? JSON.stringify(value) : "null";
|
|
14345
|
+
if (Array.isArray(value))
|
|
14346
|
+
return `[${value.map((entry) => canonicalJson(entry)).join(",")}]`;
|
|
14347
|
+
if (typeof value === "object") {
|
|
14348
|
+
const entries = Object.entries(value).filter(([, entry]) => entry !== undefined).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0);
|
|
14349
|
+
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
|
|
14350
|
+
}
|
|
14351
|
+
return JSON.stringify(String(value));
|
|
14352
|
+
}
|
|
14353
|
+
function deterministicUuid(name) {
|
|
14354
|
+
const namespace = Buffer.from("6ba7b8119dad11d180b400c04fd430c8", "hex");
|
|
14355
|
+
const digest = createHash("sha1").update(namespace).update(name, "utf8").digest();
|
|
14356
|
+
digest[6] = (digest[6] ?? 0) & 15 | 80;
|
|
14357
|
+
digest[8] = (digest[8] ?? 0) & 63 | 128;
|
|
14358
|
+
const hex = digest.subarray(0, 16).toString("hex");
|
|
14359
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
14360
|
+
}
|
|
14361
|
+
function createItemIdempotencyKey(body, mediaIdentity) {
|
|
14362
|
+
return deterministicUuid(canonicalJson({ mediaIdentity, payload: body }));
|
|
14363
|
+
}
|
|
14364
|
+
async function createEpisodeItem(client, body, slug, warnings, mediaIdentity) {
|
|
14365
|
+
const create = (input) => client.createItem(input, createItemIdempotencyKey(input, mediaIdentity));
|
|
14241
14366
|
const withSlug = (itemSlug) => {
|
|
14242
14367
|
const micro = body._microfeed ?? {};
|
|
14243
14368
|
return { ...body, _microfeed: { ...micro, slug: itemSlug } };
|
|
14244
14369
|
};
|
|
14245
14370
|
if (!slug)
|
|
14246
|
-
return
|
|
14371
|
+
return create(body);
|
|
14247
14372
|
try {
|
|
14248
|
-
return await
|
|
14373
|
+
return await create(withSlug(slug));
|
|
14249
14374
|
} catch (error) {
|
|
14250
14375
|
if (!(error instanceof MicrofeedRequestError) || error.status !== 409)
|
|
14251
14376
|
throw error;
|
|
14252
14377
|
const retry = `${slug}-2`;
|
|
14253
14378
|
try {
|
|
14254
|
-
return await
|
|
14379
|
+
return await create(withSlug(retry));
|
|
14255
14380
|
} catch (retryError) {
|
|
14256
14381
|
if (!(retryError instanceof MicrofeedRequestError) || retryError.status !== 409)
|
|
14257
14382
|
throw retryError;
|
|
14258
14383
|
warn(`slug "${slug}" conflicted (HTTP 409); publishing without a custom slug`, warnings);
|
|
14259
|
-
return
|
|
14384
|
+
return create(body);
|
|
14260
14385
|
}
|
|
14261
14386
|
}
|
|
14262
14387
|
}
|
|
@@ -14361,6 +14486,14 @@ async function publishEpisode(client, payload, options) {
|
|
|
14361
14486
|
}
|
|
14362
14487
|
const seo = mergeSocial(fields.seo, social);
|
|
14363
14488
|
episode = shapedEpisode(episode, fields);
|
|
14489
|
+
let localAudioBytes;
|
|
14490
|
+
let mediaIdentity;
|
|
14491
|
+
if (episode.audio.kind === "url") {
|
|
14492
|
+
mediaIdentity = `audio-url:${episode.audio.url}\x00${episode.audio.mimeType}`;
|
|
14493
|
+
} else {
|
|
14494
|
+
localAudioBytes = new Uint8Array(await readFile(episode.audio.path));
|
|
14495
|
+
mediaIdentity = `audio-file-sha256:${createHash("sha256").update(localAudioBytes).digest("hex")}\x00${episode.audio.mimeType}`;
|
|
14496
|
+
}
|
|
14364
14497
|
if (episode.audio.kind === "url") {
|
|
14365
14498
|
const itemId = await createEpisodeItem(client, {
|
|
14366
14499
|
title: episode.title,
|
|
@@ -14368,7 +14501,7 @@ async function publishEpisode(client, payload, options) {
|
|
|
14368
14501
|
status: episode.status,
|
|
14369
14502
|
attachments: [audioAttachment(episode.audio, episode.audio.url, undefined, episode.durationSeconds)],
|
|
14370
14503
|
...episodeExtras(episode, seo)
|
|
14371
|
-
}, fields.slug, fields.warnings);
|
|
14504
|
+
}, fields.slug, fields.warnings, mediaIdentity);
|
|
14372
14505
|
let hostedItem;
|
|
14373
14506
|
try {
|
|
14374
14507
|
hostedItem = (await client.getItem(itemId)).items?.[0];
|
|
@@ -14388,8 +14521,10 @@ async function publishEpisode(client, payload, options) {
|
|
|
14388
14521
|
title: episode.title,
|
|
14389
14522
|
content_html: episode.contentHtml,
|
|
14390
14523
|
status: "unpublished"
|
|
14391
|
-
}, fields.slug, fields.warnings);
|
|
14392
|
-
const bytes =
|
|
14524
|
+
}, fields.slug, fields.warnings, mediaIdentity);
|
|
14525
|
+
const bytes = localAudioBytes;
|
|
14526
|
+
if (!bytes)
|
|
14527
|
+
return fail2("local audio identity was not prepared before item creation");
|
|
14393
14528
|
const prepared = await client.prepareUpload({
|
|
14394
14529
|
category: "audio",
|
|
14395
14530
|
full_local_file_path: episode.audio.path,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { readFileSync } from 'node:fs';
|
|
2
3
|
import { readFile } from 'node:fs/promises';
|
|
3
4
|
import { basename, dirname, extname } from 'node:path';
|
|
@@ -306,29 +307,72 @@ function shapedEpisode(episode: EpisodeMapping, fields: PreparedEpisodeFields):
|
|
|
306
307
|
};
|
|
307
308
|
}
|
|
308
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Canonical JSON for the create request fingerprint. The API's idempotency window is only 24h,
|
|
312
|
+
* so the key must be stable across a process restart while remaining different for a changed body.
|
|
313
|
+
* Object-key ordering is normalized because callers build the same logical payload in more than
|
|
314
|
+
* one branch (hosted audio and the draft half of local-audio publishing).
|
|
315
|
+
*/
|
|
316
|
+
function canonicalJson(value: unknown): string {
|
|
317
|
+
if (value === null || value === undefined) return 'null';
|
|
318
|
+
if (typeof value === 'string' || typeof value === 'boolean') return JSON.stringify(value);
|
|
319
|
+
if (typeof value === 'number') return Number.isFinite(value) ? JSON.stringify(value) : 'null';
|
|
320
|
+
if (Array.isArray(value)) return `[${value.map((entry) => canonicalJson(entry)).join(',')}]`;
|
|
321
|
+
if (typeof value === 'object') {
|
|
322
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
323
|
+
.filter(([, entry]) => entry !== undefined)
|
|
324
|
+
.sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0));
|
|
325
|
+
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(',')}}`;
|
|
326
|
+
}
|
|
327
|
+
return JSON.stringify(String(value));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** RFC 4122 UUIDv5-shaped key; microfeed validates Idempotency-Key as a UUID. */
|
|
331
|
+
function deterministicUuid(name: string): string {
|
|
332
|
+
// The URL namespace is only a stable namespace marker; the payload remains the name input.
|
|
333
|
+
const namespace = Buffer.from('6ba7b8119dad11d180b400c04fd430c8', 'hex');
|
|
334
|
+
const digest = createHash('sha1').update(namespace).update(name, 'utf8').digest();
|
|
335
|
+
digest[6] = ((digest[6] ?? 0) & 0x0f) | 0x50;
|
|
336
|
+
digest[8] = ((digest[8] ?? 0) & 0x3f) | 0x80;
|
|
337
|
+
const hex = digest.subarray(0, 16).toString('hex');
|
|
338
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Derives the caller-supplied create key from the exact request body and its primary media.
|
|
343
|
+
* Local audio uses content identity rather than its temporary path, so a resumed run can replay
|
|
344
|
+
* the same draft even when the workspace path changes.
|
|
345
|
+
*/
|
|
346
|
+
function createItemIdempotencyKey(body: MicrofeedItemInput, mediaIdentity: string): string {
|
|
347
|
+
return deterministicUuid(canonicalJson({ mediaIdentity, payload: body }));
|
|
348
|
+
}
|
|
349
|
+
|
|
309
350
|
/** First createItem carries the slug. A 409 retries once as `{slug}-2`, then publishes with no slug. */
|
|
310
351
|
async function createEpisodeItem(
|
|
311
352
|
client: MicrofeedClient,
|
|
312
353
|
body: MicrofeedItemInput,
|
|
313
354
|
slug: string | undefined,
|
|
314
355
|
warnings: string[],
|
|
356
|
+
mediaIdentity: string,
|
|
315
357
|
): Promise<string> {
|
|
358
|
+
const create = (input: MicrofeedItemInput): Promise<string> =>
|
|
359
|
+
client.createItem(input, createItemIdempotencyKey(input, mediaIdentity));
|
|
316
360
|
const withSlug = (itemSlug: string): MicrofeedItemInput => {
|
|
317
361
|
const micro = (body._microfeed as Record<string, unknown> | undefined) ?? {};
|
|
318
362
|
return { ...body, _microfeed: { ...micro, slug: itemSlug } };
|
|
319
363
|
};
|
|
320
|
-
if (!slug) return
|
|
364
|
+
if (!slug) return create(body);
|
|
321
365
|
try {
|
|
322
|
-
return await
|
|
366
|
+
return await create(withSlug(slug));
|
|
323
367
|
} catch (error: unknown) {
|
|
324
368
|
if (!(error instanceof MicrofeedRequestError) || error.status !== 409) throw error;
|
|
325
369
|
const retry = `${slug}-2`;
|
|
326
370
|
try {
|
|
327
|
-
return await
|
|
371
|
+
return await create(withSlug(retry));
|
|
328
372
|
} catch (retryError: unknown) {
|
|
329
373
|
if (!(retryError instanceof MicrofeedRequestError) || retryError.status !== 409) throw retryError;
|
|
330
374
|
warn(`slug "${slug}" conflicted (HTTP 409); publishing without a custom slug`, warnings);
|
|
331
|
-
return
|
|
375
|
+
return create(body);
|
|
332
376
|
}
|
|
333
377
|
}
|
|
334
378
|
}
|
|
@@ -464,6 +508,15 @@ async function publishEpisode(
|
|
|
464
508
|
const seo = mergeSocial(fields.seo, social);
|
|
465
509
|
episode = shapedEpisode(episode, fields);
|
|
466
510
|
|
|
511
|
+
let localAudioBytes: Uint8Array | undefined;
|
|
512
|
+
let mediaIdentity: string;
|
|
513
|
+
if (episode.audio.kind === 'url') {
|
|
514
|
+
mediaIdentity = `audio-url:${episode.audio.url}\0${episode.audio.mimeType}`;
|
|
515
|
+
} else {
|
|
516
|
+
localAudioBytes = new Uint8Array(await readFile(episode.audio.path));
|
|
517
|
+
mediaIdentity = `audio-file-sha256:${createHash('sha256').update(localAudioBytes).digest('hex')}\0${episode.audio.mimeType}`;
|
|
518
|
+
}
|
|
519
|
+
|
|
467
520
|
if (episode.audio.kind === 'url') {
|
|
468
521
|
const itemId = await createEpisodeItem(
|
|
469
522
|
client,
|
|
@@ -476,6 +529,7 @@ async function publishEpisode(
|
|
|
476
529
|
},
|
|
477
530
|
fields.slug,
|
|
478
531
|
fields.warnings,
|
|
532
|
+
mediaIdentity,
|
|
479
533
|
);
|
|
480
534
|
// Best-effort canonical-URL enrichment: the episode is already published, so a failed
|
|
481
535
|
// follow-up GET (or an unparsable response) falls back to publicItemUrl's own chain.
|
|
@@ -506,10 +560,13 @@ async function publishEpisode(
|
|
|
506
560
|
},
|
|
507
561
|
fields.slug,
|
|
508
562
|
fields.warnings,
|
|
563
|
+
mediaIdentity,
|
|
509
564
|
);
|
|
510
565
|
|
|
511
566
|
// Phase 2-3: presign against the draft, then PUT the raw bytes.
|
|
512
|
-
|
|
567
|
+
// The bytes were read before create so their digest can make a resumed create idempotent.
|
|
568
|
+
const bytes = localAudioBytes;
|
|
569
|
+
if (!bytes) return fail('local audio identity was not prepared before item creation');
|
|
513
570
|
const prepared = await client.prepareUpload({
|
|
514
571
|
category: 'audio',
|
|
515
572
|
full_local_file_path: episode.audio.path,
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { readFileSync } from 'node:fs';
|
|
15
|
+
import { splitSentences } from '@gobing-ai/kk-core';
|
|
15
16
|
import { echoError } from '@gobing-ai/ts-utils';
|
|
16
17
|
|
|
17
18
|
/** Code-point length. Microfeed counts the same way for slugs and SEO limits. */
|
|
@@ -506,10 +507,7 @@ export function clauseFromTitle(title: string): string {
|
|
|
506
507
|
}
|
|
507
508
|
|
|
508
509
|
export function firstSentence(text: string): string {
|
|
509
|
-
|
|
510
|
-
if (trimmed.length === 0) return '';
|
|
511
|
-
const parts = trimmed.split(/(?<=[。!?.!?])/);
|
|
512
|
-
return (parts[0] ?? trimmed).trim();
|
|
510
|
+
return splitSentences(text.replace(/\s+/g, ' ').trim())[0] ?? '';
|
|
513
511
|
}
|
|
514
512
|
|
|
515
513
|
export function planItemsFromMetadata(metadata: Record<string, unknown> | undefined): PlanItem[] {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* input is escaped first, and only the tags this module emits reach the API.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import type
|
|
14
|
+
import { type Doc, splitSentences } from '@gobing-ai/kk-core';
|
|
15
15
|
import {
|
|
16
16
|
type PlanItem,
|
|
17
17
|
type PodcastSeoConfig,
|
|
@@ -311,9 +311,7 @@ export interface ShowNotesRenderOptions {
|
|
|
311
311
|
function itemBrief(d: Doc): string {
|
|
312
312
|
const meta = d.metadata ?? {};
|
|
313
313
|
if (typeof meta.brief === 'string' && meta.brief.trim().length > 0) return meta.brief.trim();
|
|
314
|
-
return (d.body ?? '')
|
|
315
|
-
.trim()
|
|
316
|
-
.split(/(?<=[。!?])\s*/)
|
|
314
|
+
return splitSentences((d.body ?? '').trim(), { cjkOnly: true })
|
|
317
315
|
.slice(0, 2)
|
|
318
316
|
.join('');
|
|
319
317
|
}
|
|
@@ -407,10 +405,7 @@ export function renderShowNotesMarkdown(docs: Doc[], opts: ShowNotesRenderOption
|
|
|
407
405
|
} else {
|
|
408
406
|
docs.forEach((d, i) => {
|
|
409
407
|
const body = (d.body ?? '').trim();
|
|
410
|
-
const sentences = body
|
|
411
|
-
.split(/(?<=[。!?])\s*/)
|
|
412
|
-
.slice(0, 2)
|
|
413
|
-
.join('');
|
|
408
|
+
const sentences = splitSentences(body, { cjkOnly: true }).slice(0, 2).join('');
|
|
414
409
|
lines.push(
|
|
415
410
|
`## ${i + 1}. ${d.title ?? `条目 ${i + 1}`}`,
|
|
416
411
|
'',
|
|
@@ -12254,6 +12254,10 @@ init_config();
|
|
|
12254
12254
|
|
|
12255
12255
|
// ../../packages/kk-core/src/render-md.ts
|
|
12256
12256
|
if (false) {}
|
|
12257
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
12258
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
12259
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
12260
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
12257
12261
|
// ../../packages/publish-harness/src/index.ts
|
|
12258
12262
|
class FakeTransportCore {
|
|
12259
12263
|
callLogs = [];
|
|
@@ -20163,6 +20163,10 @@ init_runtime_node_bun();
|
|
|
20163
20163
|
|
|
20164
20164
|
// ../../packages/kk-core/src/render-md.ts
|
|
20165
20165
|
if (false) {}
|
|
20166
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
20167
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
20168
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
20169
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
20166
20170
|
// ../../packages/publish-harness/src/index.ts
|
|
20167
20171
|
import { homedir } from "os";
|
|
20168
20172
|
import { join, resolve } from "path";
|
|
@@ -20436,9 +20440,11 @@ import { isAbsolute, join as join4, relative, resolve as resolve4, sep } from "p
|
|
|
20436
20440
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
20437
20441
|
import {
|
|
20438
20442
|
chmodSync,
|
|
20443
|
+
closeSync,
|
|
20439
20444
|
copyFileSync,
|
|
20440
20445
|
lstatSync,
|
|
20441
20446
|
mkdirSync as mkdirSync2,
|
|
20447
|
+
openSync,
|
|
20442
20448
|
readdirSync as readdirSync2,
|
|
20443
20449
|
realpathSync as realpathSync2,
|
|
20444
20450
|
rmdirSync
|
|
@@ -20683,7 +20689,6 @@ class BotError extends Error {
|
|
|
20683
20689
|
this.code = code;
|
|
20684
20690
|
}
|
|
20685
20691
|
}
|
|
20686
|
-
|
|
20687
20692
|
// ../../packages/publish-harness/src/profile-dir.ts
|
|
20688
20693
|
import { mkdir as mkdir2, stat } from "fs/promises";
|
|
20689
20694
|
async function ensureProfileDir2(profileDir, headless) {
|
|
@@ -19847,9 +19847,11 @@ import { isAbsolute, join as join3, relative, resolve as resolve3, sep } from "p
|
|
|
19847
19847
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
19848
19848
|
import {
|
|
19849
19849
|
chmodSync,
|
|
19850
|
+
closeSync,
|
|
19850
19851
|
copyFileSync,
|
|
19851
19852
|
lstatSync,
|
|
19852
19853
|
mkdirSync as mkdirSync2,
|
|
19854
|
+
openSync,
|
|
19853
19855
|
readdirSync as readdirSync2,
|
|
19854
19856
|
realpathSync as realpathSync2,
|
|
19855
19857
|
rmdirSync
|
|
@@ -20101,7 +20103,6 @@ class BotError extends Error {
|
|
|
20101
20103
|
this.code = code;
|
|
20102
20104
|
}
|
|
20103
20105
|
}
|
|
20104
|
-
|
|
20105
20106
|
// ../../packages/publish-harness/src/index.ts
|
|
20106
20107
|
import { homedir as homedir4 } from "os";
|
|
20107
20108
|
import { join as join4, resolve as resolve4 } from "path";
|
|
@@ -20163,6 +20163,10 @@ init_runtime_node_bun();
|
|
|
20163
20163
|
|
|
20164
20164
|
// ../../packages/kk-core/src/render-md.ts
|
|
20165
20165
|
if (false) {}
|
|
20166
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
20167
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
20168
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
20169
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
20166
20170
|
// ../../packages/publish-harness/src/index.ts
|
|
20167
20171
|
import { homedir } from "os";
|
|
20168
20172
|
import { join, resolve } from "path";
|
|
@@ -20460,9 +20464,11 @@ import { isAbsolute, join as join4, relative, resolve as resolve4, sep } from "p
|
|
|
20460
20464
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
20461
20465
|
import {
|
|
20462
20466
|
chmodSync,
|
|
20467
|
+
closeSync,
|
|
20463
20468
|
copyFileSync,
|
|
20464
20469
|
lstatSync,
|
|
20465
20470
|
mkdirSync as mkdirSync2,
|
|
20471
|
+
openSync,
|
|
20466
20472
|
readdirSync as readdirSync2,
|
|
20467
20473
|
realpathSync as realpathSync2,
|
|
20468
20474
|
rmdirSync
|
|
@@ -20707,7 +20713,6 @@ class BotError extends Error {
|
|
|
20707
20713
|
this.code = code;
|
|
20708
20714
|
}
|
|
20709
20715
|
}
|
|
20710
|
-
|
|
20711
20716
|
// ../../packages/publish-harness/src/profile-dir.ts
|
|
20712
20717
|
import { mkdir as mkdir2, stat } from "fs/promises";
|
|
20713
20718
|
async function ensureProfileDir2(profileDir, headless) {
|
|
@@ -19847,9 +19847,11 @@ import { isAbsolute, join as join3, relative, resolve as resolve3, sep } from "p
|
|
|
19847
19847
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
19848
19848
|
import {
|
|
19849
19849
|
chmodSync,
|
|
19850
|
+
closeSync,
|
|
19850
19851
|
copyFileSync,
|
|
19851
19852
|
lstatSync,
|
|
19852
19853
|
mkdirSync as mkdirSync2,
|
|
19854
|
+
openSync,
|
|
19853
19855
|
readdirSync as readdirSync2,
|
|
19854
19856
|
realpathSync as realpathSync2,
|
|
19855
19857
|
rmdirSync
|
|
@@ -20101,7 +20103,6 @@ class BotError extends Error {
|
|
|
20101
20103
|
this.code = code;
|
|
20102
20104
|
}
|
|
20103
20105
|
}
|
|
20104
|
-
|
|
20105
20106
|
// ../../packages/publish-harness/src/index.ts
|
|
20106
20107
|
import { homedir as homedir4 } from "os";
|
|
20107
20108
|
import { join as join4, resolve as resolve4 } from "path";
|
|
@@ -20163,6 +20163,10 @@ init_runtime_node_bun();
|
|
|
20163
20163
|
|
|
20164
20164
|
// ../../packages/kk-core/src/render-md.ts
|
|
20165
20165
|
if (false) {}
|
|
20166
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
20167
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
20168
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
20169
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
20166
20170
|
// ../../packages/publish-harness/src/index.ts
|
|
20167
20171
|
import { homedir } from "os";
|
|
20168
20172
|
import { join, resolve } from "path";
|
|
@@ -20454,9 +20458,11 @@ import { isAbsolute, join as join4, relative, resolve as resolve4, sep } from "p
|
|
|
20454
20458
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
20455
20459
|
import {
|
|
20456
20460
|
chmodSync,
|
|
20461
|
+
closeSync,
|
|
20457
20462
|
copyFileSync,
|
|
20458
20463
|
lstatSync,
|
|
20459
20464
|
mkdirSync as mkdirSync2,
|
|
20465
|
+
openSync,
|
|
20460
20466
|
readdirSync as readdirSync2,
|
|
20461
20467
|
realpathSync as realpathSync2,
|
|
20462
20468
|
rmdirSync
|
|
@@ -20701,7 +20707,6 @@ class BotError extends Error {
|
|
|
20701
20707
|
this.code = code;
|
|
20702
20708
|
}
|
|
20703
20709
|
}
|
|
20704
|
-
|
|
20705
20710
|
// ../../packages/publish-harness/src/profile-dir.ts
|
|
20706
20711
|
import { mkdir as mkdir2, stat } from "fs/promises";
|
|
20707
20712
|
async function ensureProfileDir2(profileDir, headless) {
|
|
@@ -20119,9 +20119,11 @@ import { isAbsolute, join as join3, relative, resolve as resolve3, sep } from "p
|
|
|
20119
20119
|
// ../../packages/publish-harness/src/bot-fs.ts
|
|
20120
20120
|
import {
|
|
20121
20121
|
chmodSync,
|
|
20122
|
+
closeSync,
|
|
20122
20123
|
copyFileSync,
|
|
20123
20124
|
lstatSync,
|
|
20124
20125
|
mkdirSync as mkdirSync2,
|
|
20126
|
+
openSync,
|
|
20125
20127
|
readdirSync as readdirSync2,
|
|
20126
20128
|
realpathSync as realpathSync2,
|
|
20127
20129
|
rmdirSync
|
|
@@ -20373,7 +20375,6 @@ class BotError extends Error {
|
|
|
20373
20375
|
this.code = code;
|
|
20374
20376
|
}
|
|
20375
20377
|
}
|
|
20376
|
-
|
|
20377
20378
|
// ../../packages/publish-harness/src/index.ts
|
|
20378
20379
|
import { homedir as homedir4 } from "os";
|
|
20379
20380
|
import { join as join4, resolve as resolve4 } from "path";
|
|
@@ -20972,6 +20973,10 @@ var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx)
|
|
|
20972
20973
|
});
|
|
20973
20974
|
// ../../packages/kk-core/src/render-md.ts
|
|
20974
20975
|
if (false) {}
|
|
20976
|
+
// ../../packages/kk-core/src/sentences.ts
|
|
20977
|
+
var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
|
|
20978
|
+
var LATIN_TERMINATORS = new Set([".", "!", "?"]);
|
|
20979
|
+
var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
|
|
20975
20980
|
// ../../plugins/publishings/substack-pub/src/index.ts
|
|
20976
20981
|
var CHANNEL = "substack";
|
|
20977
20982
|
var LOGIN_URL_RE = /(\/sign-in|substack\.com\/account\/login)/;
|