@lightcone-ai/daemon 0.15.38 → 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.
|
@@ -123,6 +123,9 @@ export class BilibiliAdapter {
|
|
|
123
123
|
if (!loggedIn) throw new Error('LOGIN_EXPIRED: B站登录已过期,请重新扫码连接');
|
|
124
124
|
|
|
125
125
|
if (images.length > 0) {
|
|
126
|
+
// The file input only appears after clicking the image button in the editor toolbar
|
|
127
|
+
await this._clickImageToolbarButton();
|
|
128
|
+
await sleep(1_000);
|
|
126
129
|
await this._waitForSelector(ARTICLE_IMAGE_FILE_SELECTOR, 15_000);
|
|
127
130
|
await this._uploadFiles(images, 'image');
|
|
128
131
|
await this._waitForUploadSettled(180_000);
|
|
@@ -326,6 +329,45 @@ export class BilibiliAdapter {
|
|
|
326
329
|
await sleep(400);
|
|
327
330
|
}
|
|
328
331
|
|
|
332
|
+
async _clickImageToolbarButton() {
|
|
333
|
+
const result = await this._cdp.send('Runtime.evaluate', {
|
|
334
|
+
expression: `
|
|
335
|
+
(function() {
|
|
336
|
+
const selectors = [
|
|
337
|
+
'.ql-image',
|
|
338
|
+
'[class*="toolbar-image"]',
|
|
339
|
+
'[class*="toolbar"] [class*="image"]',
|
|
340
|
+
'[class*="toolbar"] [class*="pic"]',
|
|
341
|
+
'[aria-label="图片"]',
|
|
342
|
+
'[aria-label*="插入图片"]',
|
|
343
|
+
'[title="图片"]',
|
|
344
|
+
'[title*="插入图片"]',
|
|
345
|
+
'[class*="bf-icon-image"]',
|
|
346
|
+
'[data-action="uploadImage"]',
|
|
347
|
+
'[data-type="image"]',
|
|
348
|
+
];
|
|
349
|
+
for (const sel of selectors) {
|
|
350
|
+
const el = document.querySelector(sel);
|
|
351
|
+
if (el) { el.click(); return sel; }
|
|
352
|
+
}
|
|
353
|
+
// Heuristic: any toolbar button whose title/aria-label mentions image/图片
|
|
354
|
+
for (const el of document.querySelectorAll('[class*="toolbar"] button, [class*="toolbar"] [role="button"], [class*="toolbar"] span, [class*="editor-toolbar"] *')) {
|
|
355
|
+
const hint = [el.getAttribute('title'), el.getAttribute('aria-label'), el.className?.toString?.()].filter(Boolean).join(' ').toLowerCase();
|
|
356
|
+
if (hint.includes('image') || hint.includes('图片') || hint.includes('pic')) {
|
|
357
|
+
el.click();
|
|
358
|
+
return 'toolbar-heuristic:' + hint.slice(0, 60);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return null;
|
|
362
|
+
})()
|
|
363
|
+
`,
|
|
364
|
+
returnByValue: true,
|
|
365
|
+
});
|
|
366
|
+
const clicked = result.result?.value;
|
|
367
|
+
console.error(`[BilibiliAdapter] _clickImageToolbarButton: ${clicked ?? 'not found'}`);
|
|
368
|
+
return !!clicked;
|
|
369
|
+
}
|
|
370
|
+
|
|
329
371
|
async _clickByTextCandidates(candidates = []) {
|
|
330
372
|
for (const text of candidates) {
|
|
331
373
|
const clicked = await this._clickByText(text);
|
|
@@ -128,7 +128,19 @@ export class KuaishouAdapter {
|
|
|
128
128
|
const deadline = Date.now() + timeoutMs;
|
|
129
129
|
while (Date.now() < deadline) {
|
|
130
130
|
const result = await this._cdp.send('Runtime.evaluate', {
|
|
131
|
-
expression:
|
|
131
|
+
expression: `
|
|
132
|
+
(function() {
|
|
133
|
+
const sel = ${JSON.stringify(selector)};
|
|
134
|
+
if (document.querySelector(sel)) return true;
|
|
135
|
+
for (const f of document.querySelectorAll('iframe')) {
|
|
136
|
+
try {
|
|
137
|
+
const d = f.contentDocument || f.contentWindow?.document;
|
|
138
|
+
if (d && d.querySelector(sel)) return true;
|
|
139
|
+
} catch(e) {}
|
|
140
|
+
}
|
|
141
|
+
return false;
|
|
142
|
+
})()
|
|
143
|
+
`,
|
|
132
144
|
returnByValue: true,
|
|
133
145
|
});
|
|
134
146
|
if (result.result?.value) return;
|
|
@@ -136,12 +148,16 @@ export class KuaishouAdapter {
|
|
|
136
148
|
}
|
|
137
149
|
// Dump diagnostic info to help debug page state on timeout
|
|
138
150
|
try {
|
|
139
|
-
const [urlR, titleR, bodyR, inputR, uploadR] = await Promise.all([
|
|
151
|
+
const [urlR, titleR, bodyR, inputR, uploadR, iframeR] = await Promise.all([
|
|
140
152
|
this._cdp.send('Runtime.evaluate', { expression: 'location.href', returnByValue: true }),
|
|
141
153
|
this._cdp.send('Runtime.evaluate', { expression: 'document.title', returnByValue: true }),
|
|
142
154
|
this._cdp.send('Runtime.evaluate', { expression: 'document.body?.innerText?.slice(0,400)', returnByValue: true }),
|
|
143
155
|
this._cdp.send('Runtime.evaluate', { expression: `!!document.querySelector('input[type="file"]')`, returnByValue: true }),
|
|
144
156
|
this._cdp.send('Runtime.evaluate', { expression: `document.querySelectorAll('[class*="upload"],[class*="Upload"]').length`, returnByValue: true }),
|
|
157
|
+
this._cdp.send('Runtime.evaluate', {
|
|
158
|
+
expression: `(function(){const frames=document.querySelectorAll('iframe');let found=false;for(const f of frames){try{const d=f.contentDocument||f.contentWindow?.document;if(d&&d.querySelector('input[type="file"]')){found=true;break;}}catch(e){}}return JSON.stringify({iframes:frames.length,fileInIframe:found})})()`,
|
|
159
|
+
returnByValue: true,
|
|
160
|
+
}),
|
|
145
161
|
]);
|
|
146
162
|
console.error(`[KuaishouAdapter] selector timeout diagnostics:`);
|
|
147
163
|
console.error(` url=${urlR.result?.value}`);
|
|
@@ -149,6 +165,7 @@ export class KuaishouAdapter {
|
|
|
149
165
|
console.error(` input[type=file] present=${inputR.result?.value}`);
|
|
150
166
|
console.error(` upload-class elements=${uploadR.result?.value}`);
|
|
151
167
|
console.error(` body text: ${bodyR.result?.value}`);
|
|
168
|
+
console.error(` iframe info: ${iframeR.result?.value}`);
|
|
152
169
|
} catch (diagErr) {
|
|
153
170
|
console.error(`[KuaishouAdapter] diagnostic failed: ${diagErr.message}`);
|
|
154
171
|
}
|
|
@@ -196,12 +213,27 @@ export class KuaishouAdapter {
|
|
|
196
213
|
}
|
|
197
214
|
|
|
198
215
|
async _uploadFiles(filePaths) {
|
|
199
|
-
// Wait up to 10s for the file input
|
|
216
|
+
// Wait up to 10s for the file input; check main document and same-origin iframes
|
|
200
217
|
const deadline = Date.now() + 10000;
|
|
201
218
|
let objectId = null;
|
|
202
219
|
while (Date.now() < deadline) {
|
|
203
220
|
const result = await this._cdp.send('Runtime.evaluate', {
|
|
204
|
-
expression: `
|
|
221
|
+
expression: `
|
|
222
|
+
(function() {
|
|
223
|
+
const el = document.querySelector('input[type="file"]');
|
|
224
|
+
if (el) return el;
|
|
225
|
+
for (const f of document.querySelectorAll('iframe')) {
|
|
226
|
+
try {
|
|
227
|
+
const d = f.contentDocument || f.contentWindow?.document;
|
|
228
|
+
if (d) {
|
|
229
|
+
const found = d.querySelector('input[type="file"]');
|
|
230
|
+
if (found) return found;
|
|
231
|
+
}
|
|
232
|
+
} catch(e) {}
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
})()
|
|
236
|
+
`,
|
|
205
237
|
returnByValue: false,
|
|
206
238
|
});
|
|
207
239
|
if (result.result?.objectId) { objectId = result.result.objectId; break; }
|
|
@@ -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
|
-
|
|
422
|
-
|
|
423
|
-
if (
|
|
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
|
-
|
|
513
|
-
|
|
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
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
|