@gobing-ai/knowledge-kit 0.1.12 → 0.1.13
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 +16 -4
- package/package.json +1 -1
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/publish-presets/daily-ai-voice-migration.json +3 -3
- package/plugins/kk/publish-presets/daily-ai-voice.json +4 -4
- package/plugins/kk/schedules/kk-daily-publish3-cn-peak.cron.tmpl +2 -2
- package/plugins/kk/scripts/daily-scheduled-run.ts +91 -41
- package/plugins/kk/scripts/daily-stages.ts +52 -9
- package/plugins/kk/skills/content-safety/references/platforms/podcast-salty.md +41 -0
- package/plugins/kk/skills/content-safety/references/platforms/surfdash.md +36 -0
- package/plugins/kk/skills/content-safety/references/platforms/xhs.md +39 -0
- package/plugins/kk/workflows/kk-collect.yaml +3 -3
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +12 -12
- package/plugins/kk/workflows/kk-daily-ai-voice3.yaml +2 -2
- package/plugins/publishings/surfdash-pub/dist/index.js +52 -23
- package/plugins/publishings/surfdash-pub/src/index.ts +69 -24
- package/plugins/publishings/wechatmp-pub/dist/index.js +171 -30
- package/plugins/publishings/wechatmp-pub/src/flow.ts +287 -49
- package/plugins/publishings/weibo-pub/dist/index.js +100 -7
- package/plugins/publishings/weibo-pub/src/index.ts +111 -11
- package/plugins/publishings/xhs-pub/dist/index.js +164 -12
- package/plugins/publishings/xhs-pub/dist/profile-check.js +11 -1
- package/plugins/publishings/xhs-pub/src/flow.ts +194 -23
- package/plugins/publishings/xhs-pub/src/page.ts +26 -0
|
@@ -143,7 +143,7 @@ function normalizeText(value: string): string {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
function excerpt(text: string): string {
|
|
146
|
-
return normalizeText(text).slice(0, EXCERPT_CHARS);
|
|
146
|
+
return normalizeText(text.replace(/^[#>*`\-\s]+/gm, '')).slice(0, EXCERPT_CHARS);
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
/**
|
|
@@ -368,6 +368,10 @@ export class WeiboBrowserTransport implements PublishTransport {
|
|
|
368
368
|
const metadata = { operation: 'article', driver: this.driver, publishStatus, profileDir };
|
|
369
369
|
const title = content.title ?? 'Untitled';
|
|
370
370
|
|
|
371
|
+
await page.evaluate(ensureArticleDraftScript());
|
|
372
|
+
const editorUrl = page.url();
|
|
373
|
+
const toastsBefore = await this.readToasts(page);
|
|
374
|
+
|
|
371
375
|
const titleSelector = await resolveSelector(page, WEIBO_ARTICLE_SELECTORS.title, {
|
|
372
376
|
timeoutMs: this.stepTimeoutMs,
|
|
373
377
|
label: 'article title input',
|
|
@@ -382,12 +386,19 @@ export class WeiboBrowserTransport implements PublishTransport {
|
|
|
382
386
|
await pasteHtml(page, bodySelector, markdownToHtml(content.body), { timeoutMs: this.stepTimeoutMs });
|
|
383
387
|
|
|
384
388
|
if (coverPath !== undefined) {
|
|
389
|
+
const baseline = await this.countImages(page, bodySelector);
|
|
390
|
+
const preAttached = (await trySelectors(page, WEIBO_ARTICLE_SELECTORS.fileInput, { visible: false })).found;
|
|
391
|
+
if (!preAttached) {
|
|
392
|
+
await page.evaluate(openCoverLibraryScript());
|
|
393
|
+
}
|
|
385
394
|
const fileInputSelector = await resolveSelector(page, WEIBO_ARTICLE_SELECTORS.fileInput, {
|
|
386
395
|
timeoutMs: this.stepTimeoutMs,
|
|
387
396
|
label: 'article cover file input',
|
|
388
397
|
});
|
|
389
|
-
const baseline = await this.countImages(page, bodySelector);
|
|
390
398
|
await uploadFile(page, fileInputSelector, coverPath, { timeoutMs: this.stepTimeoutMs });
|
|
399
|
+
if (!preAttached) {
|
|
400
|
+
await page.evaluate(confirmCoverModalScript());
|
|
401
|
+
}
|
|
391
402
|
// A requested cover needs a fresh observed attachment, not a successful upload call.
|
|
392
403
|
const attached = await expectOutcome(() => this.probeCoverAttached(page, bodySelector, baseline), {
|
|
393
404
|
timeoutMs: this.stepTimeoutMs,
|
|
@@ -403,9 +414,6 @@ export class WeiboBrowserTransport implements PublishTransport {
|
|
|
403
414
|
const bodyReadback = await this.requireReadback(page, bodySelector, content.body, 'article body readback');
|
|
404
415
|
if (!bodyReadback.ok) return fail(bodyReadback.error);
|
|
405
416
|
|
|
406
|
-
const editorUrl = page.url();
|
|
407
|
-
const toastsBefore = await this.readToasts(page);
|
|
408
|
-
|
|
409
417
|
if (publishStatus === 'draft') {
|
|
410
418
|
const saveSelector = await resolveSelector(page, WEIBO_ARTICLE_SELECTORS.saveDraft, {
|
|
411
419
|
timeoutMs: this.stepTimeoutMs,
|
|
@@ -590,11 +598,15 @@ export class WeiboBrowserTransport implements PublishTransport {
|
|
|
590
598
|
return parts.join('; ');
|
|
591
599
|
}
|
|
592
600
|
|
|
593
|
-
/** A toast carrying a save/success
|
|
601
|
+
/** A toast or v5 draft-list item carrying a save/success/timestamp signal not on screen before the flow. */
|
|
594
602
|
private async probeFreshToast(page: Page, toastsBefore: readonly string[]): Promise<string | null> {
|
|
595
603
|
const before = new Set(toastsBefore);
|
|
596
604
|
const now = await this.readToasts(page);
|
|
597
|
-
|
|
605
|
+
const hit = now.find(
|
|
606
|
+
(text) => !before.has(text) && /保存|草稿|成功|\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}/.test(text),
|
|
607
|
+
);
|
|
608
|
+
if (hit === undefined) return null;
|
|
609
|
+
return /#\/draft\/\d+/.test(page.url()) ? page.url() : hit;
|
|
598
610
|
}
|
|
599
611
|
|
|
600
612
|
/** Live article evidence: the browser left the editor for a published article URL. */
|
|
@@ -751,19 +763,107 @@ function readbackScript(selector: string): string {
|
|
|
751
763
|
})()`;
|
|
752
764
|
}
|
|
753
765
|
|
|
754
|
-
/** Counts rendered images in the editor's nearest container, for the cover baseline. */
|
|
766
|
+
/** Counts rendered images in the editor's nearest container (excluding modal portals), for the cover baseline. */
|
|
755
767
|
function coverCountScript(selector: string): string {
|
|
756
768
|
return `(() => { // kk-weibo-cover
|
|
757
769
|
const anchor = document.querySelector(${JSON.stringify(selector)});
|
|
758
770
|
const scope = anchor === null ? document.body : anchor.closest('form, section, article, main') || document.body;
|
|
759
|
-
return String(scope.querySelectorAll('img[src]').length);
|
|
771
|
+
return String(Array.from(scope.querySelectorAll('img[src]')).filter((el) => !el.closest('.n-modal')).length);
|
|
772
|
+
})()`;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/** Ensures the v5 article editor has an active, blank draft pane before filling title/body. */
|
|
776
|
+
function ensureArticleDraftScript(): string {
|
|
777
|
+
return `(async () => { // kk-weibo-draft-init
|
|
778
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
779
|
+
const spin = () => document.querySelectorAll('.wb-editor-spin').length > 0;
|
|
780
|
+
const titleEl = document.querySelector('input[placeholder*="标题"], textarea[placeholder*="标题"]');
|
|
781
|
+
const titleVal = titleEl && titleEl.value ? String(titleEl.value).trim() : '';
|
|
782
|
+
const hasCover = document.querySelectorAll('.cover-preview, img.cover-img').length > 0;
|
|
783
|
+
if (spin() || titleVal.length > 0 || hasCover) {
|
|
784
|
+
for (const btn of document.querySelectorAll('button')) {
|
|
785
|
+
const t = (btn.textContent || '').trim();
|
|
786
|
+
if (t === '写文章' || t.includes('创作一篇新文章')) {
|
|
787
|
+
btn.click();
|
|
788
|
+
break;
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
await sleep(600);
|
|
792
|
+
for (let i = 0; i < 15 && spin(); i += 1) await sleep(200);
|
|
793
|
+
}
|
|
794
|
+
return true;
|
|
795
|
+
})()`;
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/** Opens the v5 cover modal (`div.cover-empty`) and switches to the `图片库` tab so `input[type="file"]` mounts. */
|
|
799
|
+
function openCoverLibraryScript(): string {
|
|
800
|
+
return `(async () => { // kk-weibo-open-cover
|
|
801
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
802
|
+
let coverEmpty = null;
|
|
803
|
+
for (let i = 0; i < 15; i += 1) {
|
|
804
|
+
coverEmpty = document.querySelector('div.cover-empty, [class*="cover-empty"]');
|
|
805
|
+
if (coverEmpty) break;
|
|
806
|
+
await sleep(200);
|
|
807
|
+
}
|
|
808
|
+
if (coverEmpty instanceof HTMLElement) coverEmpty.click();
|
|
809
|
+
for (let i = 0; i < 15 && !document.querySelector('.n-modal'); i += 1) await sleep(200);
|
|
810
|
+
for (const tab of document.querySelectorAll('.n-modal .n-tabs-tab')) {
|
|
811
|
+
if ((tab.textContent || '').trim() === '图片库' && tab instanceof HTMLElement) {
|
|
812
|
+
tab.click();
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
for (let i = 0; i < 15 && !document.querySelector('input[type="file"]'); i += 1) await sleep(200);
|
|
816
|
+
return true;
|
|
817
|
+
})()`;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/** Selects the uploaded image in the v5 `.n-modal` library, advances through crop (`下一步` → `确定`), and waits for the modal to close. */
|
|
821
|
+
function confirmCoverModalScript(): string {
|
|
822
|
+
return `(async () => { // kk-weibo-confirm-cover
|
|
823
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
824
|
+
if (!document.querySelector('.n-modal')) return true;
|
|
825
|
+
await sleep(1500);
|
|
826
|
+
let item = null;
|
|
827
|
+
for (let i = 0; i < 25; i += 1) {
|
|
828
|
+
item = document.querySelector('.n-modal .image-item, .n-modal img[src]');
|
|
829
|
+
if (item) break;
|
|
830
|
+
await sleep(200);
|
|
831
|
+
}
|
|
832
|
+
if (item instanceof HTMLElement && !item.classList.contains('is-selected')) {
|
|
833
|
+
item.click();
|
|
834
|
+
}
|
|
835
|
+
for (let i = 0; i < 15; i += 1) {
|
|
836
|
+
const nextBtn = Array.from(document.querySelectorAll('.n-modal button')).find(
|
|
837
|
+
(b) => (b.textContent || '').trim() === '下一步' && !b.disabled,
|
|
838
|
+
);
|
|
839
|
+
if (nextBtn) {
|
|
840
|
+
nextBtn.click();
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
await sleep(200);
|
|
844
|
+
}
|
|
845
|
+
await sleep(1800);
|
|
846
|
+
for (let i = 0; i < 15; i += 1) {
|
|
847
|
+
const okBtn = Array.from(document.querySelectorAll('.n-modal button')).find(
|
|
848
|
+
(b) => (b.textContent || '').trim() === '确定' && !b.disabled,
|
|
849
|
+
);
|
|
850
|
+
if (okBtn) {
|
|
851
|
+
okBtn.click();
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
854
|
+
await sleep(200);
|
|
855
|
+
}
|
|
856
|
+
for (let i = 0; i < 25 && document.querySelector('.n-modal'); i += 1) {
|
|
857
|
+
await sleep(200);
|
|
858
|
+
}
|
|
859
|
+
return true;
|
|
760
860
|
})()`;
|
|
761
861
|
}
|
|
762
862
|
|
|
763
|
-
/** Visible toast texts, used as the pre-save baseline and as the fresh-signal read. */
|
|
863
|
+
/** Visible toast texts and v5 draft sidebar items, used as the pre-save baseline and as the fresh-signal read. */
|
|
764
864
|
function toastTextsScript(): string {
|
|
765
865
|
return `(() => { // kk-weibo-toasts
|
|
766
|
-
const nodes = document.querySelectorAll('[class*="toast" i], [class*="Toast"], [class*="message" i], [role="alert"]');
|
|
866
|
+
const nodes = document.querySelectorAll('[class*="toast" i], [class*="Toast"], [class*="message" i], [role="alert"], .list-item');
|
|
767
867
|
const texts = [];
|
|
768
868
|
for (const node of nodes) {
|
|
769
869
|
const text = (node.textContent || '').trim();
|
|
@@ -21342,6 +21342,7 @@ function wrapPlaywrightPage(raw, shadow) {
|
|
|
21342
21342
|
isVisible: (options) => loc.isVisible(options),
|
|
21343
21343
|
fill: (text, options) => loc.fill(text, options),
|
|
21344
21344
|
click: (options) => loc.click(options),
|
|
21345
|
+
boundingBox: async () => (await onScreen(selector)).at(-1)?.box ?? await loc.boundingBox?.() ?? null,
|
|
21345
21346
|
type: (text, options) => loc.pressSequentially(text, options),
|
|
21346
21347
|
textContent: () => loc.textContent()
|
|
21347
21348
|
};
|
|
@@ -21360,7 +21361,16 @@ function wrapPlaywrightPage(raw, shadow) {
|
|
|
21360
21361
|
return true;
|
|
21361
21362
|
},
|
|
21362
21363
|
findShadowControl: async (labels) => shadow ? shadow.findControl(labels) : null,
|
|
21363
|
-
clickAt: (x, y) => pw.mouse.click(x, y)
|
|
21364
|
+
clickAt: (x, y) => pw.mouse.click(x, y),
|
|
21365
|
+
waitForEvent: (event, options) => {
|
|
21366
|
+
if (typeof pw.waitForEvent !== "function") {
|
|
21367
|
+
return Promise.reject(new Error(`the page seam does not expose a '${event}' event source`));
|
|
21368
|
+
}
|
|
21369
|
+
return pw.waitForEvent(event, options);
|
|
21370
|
+
},
|
|
21371
|
+
mouse: {
|
|
21372
|
+
click: (x, y) => pw.mouse.click(x, y)
|
|
21373
|
+
}
|
|
21364
21374
|
};
|
|
21365
21375
|
}
|
|
21366
21376
|
async function findSelector(page, selectors, what, timeoutMs = STEP_TIMEOUT_MS) {
|
|
@@ -21412,8 +21422,9 @@ var ARTICLE_LAYOUT = ['button:has-text("\u4E00\u952E\u6392\u7248")', "div.ailayo
|
|
|
21412
21422
|
var PODCAST_OPEN = ["text=\u4E0A\u4F20\u97F3\u9891", 'button:has-text("\u4E0A\u4F20\u97F3\u9891")'];
|
|
21413
21423
|
var PODCAST_MODAL = ["div.creator-modal-sty", ".d-modal.d-modal-centered", ".d-modal"];
|
|
21414
21424
|
var PODCAST_AUDIO_INPUT = ['input[type="file"][accept*=".mp3"]', 'input[type="file"][accept*=".wav"]'];
|
|
21415
|
-
var PODCAST_COVER_INPUT = ['input[type="file"][accept*=".jpg"]', 'input[type="file"][accept*=".png"]'];
|
|
21416
21425
|
var PODCAST_MODAL_ADVANCE = ["\u53BB\u53D1\u5E03"];
|
|
21426
|
+
var PODCAST_COVER_ZONE = '.d-modal div:has-text("\u8BBE\u7F6E\u5C01\u9762")';
|
|
21427
|
+
var PODCAST_UPLOAD_ACK = "\u4E0A\u4F20\u6210\u529F";
|
|
21417
21428
|
var DRAFT_LABELS = ["\u6682\u5B58\u79BB\u5F00", "\u5B58\u8349\u7A3F", "\u4FDD\u5B58\u8349\u7A3F"];
|
|
21418
21429
|
var PUBLISH_LABELS = ["\u53D1\u5E03"];
|
|
21419
21430
|
var PUBLISH_CONFIRM = ["\u53D1\u5E03\u6210\u529F", "\u5DF2\u53D1\u5E03"];
|
|
@@ -21589,18 +21600,67 @@ async function composeArticle(deps) {
|
|
|
21589
21600
|
async function isPodcastModalOpen(page) {
|
|
21590
21601
|
return trySelectors(page, PODCAST_MODAL).then((found) => found.found).catch(() => false);
|
|
21591
21602
|
}
|
|
21603
|
+
async function clickPodcastModalDomFallback(page) {
|
|
21604
|
+
const res = await page.evaluate(`(() => {
|
|
21605
|
+
const modals = Array.from(document.querySelectorAll('.d-modal, div.creator-modal-sty'));
|
|
21606
|
+
const buttons = Array.from(document.querySelectorAll('.d-modal button, div.creator-modal-sty button, .d-modal-mask button'));
|
|
21607
|
+
const confirmLabels = ['\u786E\u5B9A', '\u5B8C\u6210', '\u786E\u8BA4', '\u4FDD\u5B58'];
|
|
21608
|
+
const cropBtn = buttons.find((b) => {
|
|
21609
|
+
const t = (b.textContent || '').trim();
|
|
21610
|
+
return confirmLabels.includes(t) && !b.classList.contains('cancel-btn') && !b.dataset.kkClicked;
|
|
21611
|
+
}) || (modals.length > 1 ? Array.from(modals[modals.length - 1].querySelectorAll('button')).find((b) => !b.classList.contains('cancel-btn') && b.classList.contains('bg-red') && !(b.textContent || '').includes('\u53BB\u53D1\u5E03') && !b.dataset.kkClicked) : null);
|
|
21612
|
+
if (cropBtn && typeof cropBtn.click === 'function') {
|
|
21613
|
+
cropBtn.dataset.kkClicked = '1';
|
|
21614
|
+
cropBtn.click();
|
|
21615
|
+
return JSON.stringify({ modals: modals.length, clickedCrop: true, clickedAdvance: false });
|
|
21616
|
+
}
|
|
21617
|
+
const advBtns = buttons.filter((b) => (b.textContent || '').includes('\u53BB\u53D1\u5E03') && !b.hasAttribute('disabled') && b.getAttribute('aria-disabled') !== 'true');
|
|
21618
|
+
const adv = advBtns[advBtns.length - 1];
|
|
21619
|
+
let clickedAdvance = false;
|
|
21620
|
+
if (adv && !adv.dataset.kkClicked && typeof adv.click === 'function') {
|
|
21621
|
+
adv.dataset.kkClicked = '1';
|
|
21622
|
+
adv.click();
|
|
21623
|
+
clickedAdvance = true;
|
|
21624
|
+
}
|
|
21625
|
+
return JSON.stringify({
|
|
21626
|
+
modals: modals.length,
|
|
21627
|
+
clickedCrop: false,
|
|
21628
|
+
clickedAdvance,
|
|
21629
|
+
buttons: buttons.map((b) => ({ text: (b.textContent || '').trim(), cls: b.className, disabled: b.hasAttribute('disabled') })),
|
|
21630
|
+
});
|
|
21631
|
+
})()`).catch(() => "");
|
|
21632
|
+
return typeof res === "string" ? res : "";
|
|
21633
|
+
}
|
|
21592
21634
|
async function leavePodcastModal(deps) {
|
|
21593
21635
|
const closed = () => isPodcastModalOpen(deps.page).then((open) => !open);
|
|
21594
|
-
if (await
|
|
21636
|
+
if (!await isPodcastModalOpen(deps.page))
|
|
21637
|
+
return;
|
|
21638
|
+
let usedFallback = false;
|
|
21639
|
+
let lastDiag = "";
|
|
21640
|
+
try {
|
|
21641
|
+
const advance = await clickLightControl(deps.page, PODCAST_MODAL_ADVANCE, Math.min(deps.stepTimeoutMs, 2500), "podcast modal advance");
|
|
21642
|
+
if (!advance.ok) {
|
|
21643
|
+
lastDiag = await clickPodcastModalDomFallback(deps.page);
|
|
21644
|
+
if (!lastDiag.startsWith("{"))
|
|
21645
|
+
throw new Error(advance.error);
|
|
21646
|
+
usedFallback = true;
|
|
21647
|
+
}
|
|
21648
|
+
} catch {
|
|
21649
|
+
lastDiag = await clickPodcastModalDomFallback(deps.page);
|
|
21650
|
+
usedFallback = true;
|
|
21651
|
+
}
|
|
21652
|
+
if (await waitUntil(async () => {
|
|
21653
|
+
if (await closed())
|
|
21654
|
+
return true;
|
|
21655
|
+
if (usedFallback) {
|
|
21656
|
+
lastDiag = await clickPodcastModalDomFallback(deps.page);
|
|
21657
|
+
}
|
|
21658
|
+
return false;
|
|
21659
|
+
}, deps.stepTimeoutMs)) {
|
|
21595
21660
|
return;
|
|
21596
|
-
if (deps.publishStatus === "public") {
|
|
21597
|
-
const advance = await clickLightControl(deps.page, PODCAST_MODAL_ADVANCE, deps.stepTimeoutMs, "podcast modal advance");
|
|
21598
|
-
if (!advance.ok)
|
|
21599
|
-
throw new Error(advance.error);
|
|
21600
|
-
if (await waitUntil(closed, deps.stepTimeoutMs))
|
|
21601
|
-
return;
|
|
21602
21661
|
}
|
|
21603
|
-
|
|
21662
|
+
const diagSuffix = lastDiag ? ` [modal=${lastDiag}]` : "";
|
|
21663
|
+
throw new Error(`podcast modal: the \u53D1\u64AD\u5BA2 modal stayed open ${deps.stepTimeoutMs}ms after the advance click; the measured 2026-11-06 flow moves to the podcast editor page with the closed-shadow '${DRAFT_LABELS[0]}' bar, so a fast re-measure with a fresh probe is the next step, not a retry${diagSuffix}`);
|
|
21604
21664
|
}
|
|
21605
21665
|
async function composePodcast(input, deps) {
|
|
21606
21666
|
await enterMode(deps, input.operation);
|
|
@@ -21609,10 +21669,102 @@ async function composePodcast(input, deps) {
|
|
|
21609
21669
|
await findSelector(deps.page, PODCAST_MODAL, "podcast modal", deps.stepTimeoutMs);
|
|
21610
21670
|
const audioInput = await findAttachedSelector(deps.page, PODCAST_AUDIO_INPUT, "podcast audio input", deps.stepTimeoutMs);
|
|
21611
21671
|
await uploadFile(asPlaywrightPage(deps.page), audioInput, input.audio, { timeoutMs: deps.stepTimeoutMs });
|
|
21612
|
-
const
|
|
21613
|
-
|
|
21672
|
+
const ack = await waitUntil(async () => {
|
|
21673
|
+
try {
|
|
21674
|
+
return await deps.page.locator(`text=${PODCAST_UPLOAD_ACK}`).count() > 0;
|
|
21675
|
+
} catch {
|
|
21676
|
+
return false;
|
|
21677
|
+
}
|
|
21678
|
+
}, deps.stepTimeoutMs);
|
|
21679
|
+
if (!ack) {
|
|
21680
|
+
throw new Error(`podcast modal: the audio card never showed '${PODCAST_UPLOAD_ACK}' within ${deps.stepTimeoutMs}ms of the file assignment; mimic-file upload of ${input.audio.split("/").pop()} did not register`);
|
|
21681
|
+
}
|
|
21682
|
+
await uploadPodcastCover(deps, input.cover);
|
|
21683
|
+
await podcastAdvanceReady(deps);
|
|
21614
21684
|
await leavePodcastModal(deps);
|
|
21615
21685
|
}
|
|
21686
|
+
async function uploadPodcastCover(deps, cover) {
|
|
21687
|
+
const zone = deps.page.locator(PODCAST_COVER_ZONE).first();
|
|
21688
|
+
const box = await zone.boundingBox().catch(() => null);
|
|
21689
|
+
if (!box) {
|
|
21690
|
+
throw new Error(`podcast modal: the cover card ('${PODCAST_COVER_ZONE}') has no bounding box to click`);
|
|
21691
|
+
}
|
|
21692
|
+
if (!deps.page.mouse) {
|
|
21693
|
+
throw new Error("podcast modal: the page seam does not expose a raw mouse for the cover-card coordinate click");
|
|
21694
|
+
}
|
|
21695
|
+
const chooser = deps.page.waitForEvent?.("filechooser", { timeout: 8000 }).catch(() => null);
|
|
21696
|
+
await deps.page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
|
|
21697
|
+
const pending = Symbol("pending");
|
|
21698
|
+
const quick = await Promise.race([
|
|
21699
|
+
chooser,
|
|
21700
|
+
new Promise((resolve) => setTimeout(() => resolve(pending), 400))
|
|
21701
|
+
]);
|
|
21702
|
+
let diag = "";
|
|
21703
|
+
if (quick === pending) {
|
|
21704
|
+
const fallback = await deps.page.evaluate(`(() => {
|
|
21705
|
+
const modal = document.querySelector('.d-modal') || document.body;
|
|
21706
|
+
const inputs = Array.from(modal.querySelectorAll('input[type="file"]'));
|
|
21707
|
+
const coverInput = inputs.find((i) => !String(i.getAttribute('accept') || '').includes('.mp3'));
|
|
21708
|
+
const all = Array.from(modal.querySelectorAll('*')).filter((el) => (el.textContent || '').includes('\u8BBE\u7F6E\u5C01\u9762'));
|
|
21709
|
+
const leaf = all[all.length - 1];
|
|
21710
|
+
let card = leaf;
|
|
21711
|
+
while (card && card.parentElement && card.parentElement !== modal) {
|
|
21712
|
+
const pt = card.parentElement.textContent || '';
|
|
21713
|
+
if (pt.includes('\u4E0A\u4F20\u6210\u529F') || pt.includes('\u70B9\u51FB\u4E0A\u4F20') || pt.includes('\u53BB\u53D1\u5E03')) break;
|
|
21714
|
+
card = card.parentElement;
|
|
21715
|
+
}
|
|
21716
|
+
let clickedInput = false;
|
|
21717
|
+
if (coverInput && typeof coverInput.click === 'function') {
|
|
21718
|
+
coverInput.click();
|
|
21719
|
+
clickedInput = true;
|
|
21720
|
+
} else {
|
|
21721
|
+
if (card && typeof card.click === 'function') card.click();
|
|
21722
|
+
if (leaf && typeof leaf.click === 'function') leaf.click();
|
|
21723
|
+
}
|
|
21724
|
+
const rect = card ? card.getBoundingClientRect() : null;
|
|
21725
|
+
return JSON.stringify({
|
|
21726
|
+
clickedInput,
|
|
21727
|
+
x: rect ? Math.round(rect.left + rect.width / 2) : null,
|
|
21728
|
+
y: rect ? Math.round(rect.top + rect.height * 0.35) : null,
|
|
21729
|
+
inputs: inputs.map((i) => ({ accept: i.getAttribute('accept'), cls: i.className })),
|
|
21730
|
+
cardTag: card ? card.tagName + '.' + String(card.className) : null,
|
|
21731
|
+
cardRect: rect ? [Math.round(rect.left), Math.round(rect.top), Math.round(rect.width), Math.round(rect.height)] : null,
|
|
21732
|
+
});
|
|
21733
|
+
})()`).catch(() => "");
|
|
21734
|
+
if (typeof fallback === "string" && fallback.startsWith("{")) {
|
|
21735
|
+
diag = ` [modal=${fallback}]`;
|
|
21736
|
+
try {
|
|
21737
|
+
const parsed = JSON.parse(fallback);
|
|
21738
|
+
if (!parsed.clickedInput && typeof parsed.x === "number" && typeof parsed.y === "number") {
|
|
21739
|
+
await deps.page.mouse.click(parsed.x, parsed.y);
|
|
21740
|
+
}
|
|
21741
|
+
} catch {}
|
|
21742
|
+
}
|
|
21743
|
+
}
|
|
21744
|
+
const opened = quick === pending ? await chooser : quick;
|
|
21745
|
+
if (!opened) {
|
|
21746
|
+
throw new Error(`podcast modal: clicking the cover card did not open a native file chooser within 8000ms${diag}`);
|
|
21747
|
+
}
|
|
21748
|
+
await opened.setFiles(cover);
|
|
21749
|
+
}
|
|
21750
|
+
async function podcastAdvanceReady(deps) {
|
|
21751
|
+
if (deps.publishStatus !== "draft") {
|
|
21752
|
+
return;
|
|
21753
|
+
}
|
|
21754
|
+
const enabled = await waitUntil(async () => {
|
|
21755
|
+
try {
|
|
21756
|
+
if (await deps.page.locator('button:has-text("\u53BB\u53D1\u5E03")').count() === 0)
|
|
21757
|
+
return false;
|
|
21758
|
+
const disabled = await deps.page.locator('button:has-text("\u53BB\u53D1\u5E03")[disabled], button:has-text("\u53BB\u53D1\u5E03")[aria-disabled="true"]').count();
|
|
21759
|
+
return disabled === 0;
|
|
21760
|
+
} catch {
|
|
21761
|
+
return false;
|
|
21762
|
+
}
|
|
21763
|
+
}, deps.stepTimeoutMs);
|
|
21764
|
+
if (!enabled) {
|
|
21765
|
+
throw new Error(`podcast modal: '${PODCAST_MODAL_ADVANCE[0]}' stayed disabled \u2014 the cards were not both accepted; check the audio ack '${PODCAST_UPLOAD_ACK}' and retry with the cover chooser path`);
|
|
21766
|
+
}
|
|
21767
|
+
}
|
|
21616
21768
|
async function compose(input, deps) {
|
|
21617
21769
|
switch (input.operation) {
|
|
21618
21770
|
case "\u4E0A\u4F20\u56FE\u6587":
|
|
@@ -20996,6 +20996,7 @@ function wrapPlaywrightPage(raw, shadow) {
|
|
|
20996
20996
|
isVisible: (options) => loc.isVisible(options),
|
|
20997
20997
|
fill: (text, options) => loc.fill(text, options),
|
|
20998
20998
|
click: (options) => loc.click(options),
|
|
20999
|
+
boundingBox: async () => (await onScreen(selector)).at(-1)?.box ?? await loc.boundingBox?.() ?? null,
|
|
20999
21000
|
type: (text, options) => loc.pressSequentially(text, options),
|
|
21000
21001
|
textContent: () => loc.textContent()
|
|
21001
21002
|
};
|
|
@@ -21014,7 +21015,16 @@ function wrapPlaywrightPage(raw, shadow) {
|
|
|
21014
21015
|
return true;
|
|
21015
21016
|
},
|
|
21016
21017
|
findShadowControl: async (labels) => shadow ? shadow.findControl(labels) : null,
|
|
21017
|
-
clickAt: (x, y) => pw.mouse.click(x, y)
|
|
21018
|
+
clickAt: (x, y) => pw.mouse.click(x, y),
|
|
21019
|
+
waitForEvent: (event, options) => {
|
|
21020
|
+
if (typeof pw.waitForEvent !== "function") {
|
|
21021
|
+
return Promise.reject(new Error(`the page seam does not expose a '${event}' event source`));
|
|
21022
|
+
}
|
|
21023
|
+
return pw.waitForEvent(event, options);
|
|
21024
|
+
},
|
|
21025
|
+
mouse: {
|
|
21026
|
+
click: (x, y) => pw.mouse.click(x, y)
|
|
21027
|
+
}
|
|
21018
21028
|
};
|
|
21019
21029
|
}
|
|
21020
21030
|
|