@lightcone-ai/daemon 0.15.39 → 0.15.40

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.
@@ -93,7 +93,7 @@ const IMAGE_PUBLISH_URL = 'https://creator.xiaohongshu.com/publish/publish?targe
93
93
  const VIDEO_PUBLISH_URL = 'https://creator.xiaohongshu.com/publish/publish?source=video';
94
94
 
95
95
  const CREATOR_HOME_URL = 'https://creator.xiaohongshu.com/new/home';
96
- const IMAGE_FILE_INPUT_SELECTOR = 'input.upload-input[type="file"][accept*=".jpg"], input[type="file"][accept*=".png"], input[type="file"][accept*=".webp"]';
96
+ const IMAGE_FILE_INPUT_SELECTOR = 'input.upload-input[type="file"][accept*=".jpg"], input[type="file"][accept*="image"], input[type="file"][accept*=".jpg"], input[type="file"][accept*=".png"], input[type="file"][accept*=".webp"]';
97
97
  const VIDEO_FILE_INPUT_SELECTOR = 'input.upload-input[type="file"][accept*=".mp4"], input[type="file"][accept*=".mov"], input[type="file"][accept*=".mpeg"]';
98
98
  const TITLE_SELECTOR = 'input[placeholder*="标题"], input.d-text[type="text"]';
99
99
  const CONTENT_SELECTOR = '.ProseMirror[contenteditable="true"], [contenteditable="true"][role="textbox"], [contenteditable="true"]';
@@ -409,18 +409,38 @@ export class XhsAdapter extends PublisherAdapter {
409
409
  }
410
410
 
411
411
  async _waitForUploadSettled(expectedCount, timeoutMs) {
412
+ if (expectedCount === 0) return;
412
413
  const deadline = Date.now() + timeoutMs;
414
+ let sawProgress = false;
415
+ let stableRounds = 0;
416
+
413
417
  while (Date.now() < deadline) {
414
418
  await sleep(1000);
415
419
  await this._assertNoBlockingErrors();
416
420
  const state = await this._inspectPage();
417
421
  const text = state.text ?? '';
422
+
418
423
  if (/上传失败|上传出错|格式不支持|文件过大/.test(text)) {
419
424
  throw new Error(`UPLOAD_FAILED: ${(state.errors ?? []).join(';') || '页面提示上传失败'}`);
420
425
  }
421
- if (/上传中|处理中|转码中|正在上传|解析中/.test(text)) continue;
422
- if (state.hasPublishEditor || /图片编辑|视频编辑|笔记预览|上传成功|重新上传|更换|已上传|封面/.test(text)) return;
423
- if (expectedCount === 0) return;
426
+
427
+ const inProgress = /上传中|处理中|转码中|正在上传|解析中/.test(text);
428
+ if (inProgress) { sawProgress = true; stableRounds = 0; continue; }
429
+
430
+ // Definitive upload completion signals
431
+ if (/图片编辑|视频编辑|笔记预览|上传成功|重新上传|更换图片|已上传|封面/.test(text)) return;
432
+
433
+ stableRounds++;
434
+
435
+ // If we saw upload start and then it stopped, done (give 2 stable rounds)
436
+ if (sawProgress && stableRounds >= 2) return;
437
+
438
+ // hasPublishEditor alone is not enough — the form is present from page load.
439
+ // Only treat it as "done" after 10+ stable seconds (gives time for upload progress to appear)
440
+ if (state.hasPublishEditor && stableRounds >= 10) {
441
+ console.error('[XhsAdapter] _waitForUploadSettled: hasPublishEditor stable for 10s, proceeding');
442
+ return;
443
+ }
424
444
  }
425
445
  throw new Error(`UPLOAD_TIMEOUT: 等待小红书上传完成超时`);
426
446
  }
@@ -509,16 +529,54 @@ export class XhsAdapter extends PublisherAdapter {
509
529
  async _uploadFiles(filePaths, type = 'image') {
510
530
  await humanPause(600, 1700, 'before-set-files');
511
531
  const selector = type === 'video' ? VIDEO_FILE_INPUT_SELECTOR : IMAGE_FILE_INPUT_SELECTOR;
512
- const result = await this._cdp.send('Runtime.evaluate', {
513
- expression: `document.querySelector(${JSON.stringify(selector)})`,
532
+
533
+ // Use DOM.getDocument + DOM.querySelector for a stable nodeId
534
+ let nodeId = null;
535
+ const deadline = Date.now() + 10000;
536
+ while (Date.now() < deadline) {
537
+ try {
538
+ const docResult = await this._cdp.send('DOM.getDocument', { depth: 0 });
539
+ const qResult = await this._cdp.send('DOM.querySelector', {
540
+ nodeId: docResult.root.nodeId,
541
+ selector,
542
+ });
543
+ if (qResult.nodeId) { nodeId = qResult.nodeId; break; }
544
+ } catch (e) {}
545
+ await sleep(500);
546
+ }
547
+ if (!nodeId) throw new Error(`No ${type} file input found on page`);
548
+
549
+ // Force multiple-file support and ensure input is enabled before setting files
550
+ await this._cdp.send('Runtime.evaluate', {
551
+ expression: `
552
+ (function() {
553
+ const el = document.querySelector(${JSON.stringify(selector)});
554
+ if (el) {
555
+ el.removeAttribute('disabled');
556
+ if (!el.hasAttribute('multiple')) el.setAttribute('multiple', '');
557
+ }
558
+ })()
559
+ `,
514
560
  returnByValue: false,
515
561
  });
516
- if (!result.result?.objectId) throw new Error(`No ${type} file input found on page`);
517
562
 
518
- await this._cdp.send('DOM.setFileInputFiles', {
519
- objectId: result.result.objectId,
520
- files: filePaths,
563
+ console.error(`[XhsAdapter] _uploadFiles: type=${type} files=${filePaths.length} nodeId=${nodeId}`);
564
+ await this._cdp.send('DOM.setFileInputFiles', { nodeId, files: filePaths });
565
+
566
+ // Dispatch extra change/input events in case the Vue handler needs them
567
+ await this._cdp.send('Runtime.evaluate', {
568
+ expression: `
569
+ (function() {
570
+ const el = document.querySelector(${JSON.stringify(selector)});
571
+ if (el) {
572
+ el.dispatchEvent(new Event('change', { bubbles: true }));
573
+ el.dispatchEvent(new Event('input', { bubbles: true }));
574
+ }
575
+ })()
576
+ `,
577
+ returnByValue: false,
521
578
  });
579
+
522
580
  await humanPause(1200, 2600, 'after-set-files');
523
581
  }
524
582
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightcone-ai/daemon",
3
- "version": "0.15.39",
3
+ "version": "0.15.40",
4
4
  "type": "module",
5
5
  "main": "src/index.js",
6
6
  "bin": {