@lightcone-ai/daemon 0.9.70 → 0.9.72
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.
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Douyin (抖音) publisher adapter.
|
|
3
3
|
* Uses 抖音创作服务平台: https://creator.douyin.com
|
|
4
4
|
*/
|
|
5
|
+
import { formatTextWithTags } from '../text.js';
|
|
5
6
|
|
|
6
7
|
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
7
8
|
|
|
@@ -51,7 +52,7 @@ export class DouyinAdapter {
|
|
|
51
52
|
await sleep(3000);
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
const fullText =
|
|
55
|
+
const fullText = formatTextWithTags(text, tags);
|
|
55
56
|
await this._fillField('[class*="content"] [contenteditable], [placeholder*="添加作品描述"]', fullText);
|
|
56
57
|
|
|
57
58
|
if (title) {
|
|
@@ -78,7 +79,7 @@ export class DouyinAdapter {
|
|
|
78
79
|
await this._uploadFiles([video], 'video');
|
|
79
80
|
await this._waitForText('上传成功', 120000);
|
|
80
81
|
|
|
81
|
-
const fullText =
|
|
82
|
+
const fullText = formatTextWithTags(text, tags);
|
|
82
83
|
await this._fillField('[placeholder*="添加作品描述"], [class*="content"] [contenteditable]', fullText);
|
|
83
84
|
|
|
84
85
|
if (title) {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Kuaishou (快手) publisher adapter.
|
|
3
3
|
* Uses 快手创作者平台: https://cp.kuaishou.com
|
|
4
4
|
*/
|
|
5
|
+
import { formatTextWithTags } from '../text.js';
|
|
5
6
|
|
|
6
7
|
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
7
8
|
|
|
@@ -55,7 +56,7 @@ export class KuaishouAdapter {
|
|
|
55
56
|
await this._fillField('[placeholder*="标题"]', title);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
const fullText =
|
|
59
|
+
const fullText = formatTextWithTags(text, tags);
|
|
59
60
|
await this._fillField('[placeholder*="描述"], [contenteditable]', fullText);
|
|
60
61
|
|
|
61
62
|
await sleep(1000);
|
|
@@ -82,7 +83,7 @@ export class KuaishouAdapter {
|
|
|
82
83
|
await this._fillField('[placeholder*="标题"]', title);
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
const fullText =
|
|
86
|
+
const fullText = formatTextWithTags(text, tags);
|
|
86
87
|
await this._fillField('[placeholder*="描述"], [contenteditable]', fullText);
|
|
87
88
|
|
|
88
89
|
await sleep(1000);
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
* XHS (小红书) publisher adapter.
|
|
3
3
|
* Uses deterministic CDP operations — no AI vision required.
|
|
4
4
|
*/
|
|
5
|
+
import { formatTextWithTags } from '../text.js';
|
|
5
6
|
|
|
6
7
|
function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
|
|
7
8
|
function randomInt(min, max) { return Math.floor(min + Math.random() * (max - min + 1)); }
|
|
8
9
|
|
|
9
10
|
const PACE_SCALE = Math.max(0.2, Number(process.env.PUBLISHER_PACE_SCALE ?? '1') || 1);
|
|
11
|
+
const DRY_RUN = process.env.XHS_PUBLISH_DRY_RUN !== '0';
|
|
10
12
|
async function humanPause(minMs, maxMs, label = '') {
|
|
11
13
|
const ms = Math.round(randomInt(minMs, maxMs) * PACE_SCALE);
|
|
12
14
|
if (label) console.error(`[XhsAdapter] pause ${label}: ${ms}ms`);
|
|
@@ -96,15 +98,19 @@ export class XhsAdapter {
|
|
|
96
98
|
await humanPause(1200, 2800, 'after-title');
|
|
97
99
|
}
|
|
98
100
|
|
|
99
|
-
// Fill content text
|
|
100
|
-
const fullText =
|
|
101
|
+
// Fill content text and only append tags that are not already present.
|
|
102
|
+
const fullText = formatTextWithTags(text, tags);
|
|
101
103
|
await this._fillField(CONTENT_SELECTOR, fullText);
|
|
102
104
|
|
|
103
105
|
await humanPause(4500, 9000, 'before-publish-check');
|
|
104
106
|
await this._assertNoBlockingErrors();
|
|
105
107
|
await this._assertPublishButtonReady();
|
|
106
108
|
|
|
107
|
-
|
|
109
|
+
if (DRY_RUN) {
|
|
110
|
+
console.error('[XhsAdapter] XHS_PUBLISH_DRY_RUN enabled; skipping final publish click.');
|
|
111
|
+
return { success: true, dry_run: true, post_url: null };
|
|
112
|
+
}
|
|
113
|
+
|
|
108
114
|
await humanPause(1200, 3000, 'before-publish-click');
|
|
109
115
|
const clicked = await this._clickByText('发布');
|
|
110
116
|
if (!clicked) throw new Error('PUBLISH_FAILED: 找不到发布按钮,页面结构可能已变化');
|
|
@@ -137,13 +143,18 @@ export class XhsAdapter {
|
|
|
137
143
|
await humanPause(1200, 2800, 'after-title');
|
|
138
144
|
}
|
|
139
145
|
|
|
140
|
-
const fullText =
|
|
146
|
+
const fullText = formatTextWithTags(text, tags);
|
|
141
147
|
await this._fillField(CONTENT_SELECTOR, fullText);
|
|
142
148
|
|
|
143
149
|
await humanPause(4500, 9000, 'before-publish-check');
|
|
144
150
|
await this._assertNoBlockingErrors();
|
|
145
151
|
await this._assertPublishButtonReady();
|
|
146
152
|
|
|
153
|
+
if (DRY_RUN) {
|
|
154
|
+
console.error('[XhsAdapter] XHS_PUBLISH_DRY_RUN enabled; skipping final publish click.');
|
|
155
|
+
return { success: true, dry_run: true, post_url: null };
|
|
156
|
+
}
|
|
157
|
+
|
|
147
158
|
await humanPause(1200, 3000, 'before-publish-click');
|
|
148
159
|
const clicked = await this._clickByText('发布');
|
|
149
160
|
if (!clicked) throw new Error('PUBLISH_FAILED: 找不到发布按钮,页面结构可能已变化');
|
|
@@ -205,6 +205,11 @@ images/video 字段填写本地绝对路径(在 agent workspace 目录下)
|
|
|
205
205
|
});
|
|
206
206
|
|
|
207
207
|
await completeApproval(approval_action_id, true, result, null);
|
|
208
|
+
if (result?.dry_run) {
|
|
209
|
+
return {
|
|
210
|
+
content: [{ type: 'text', text: `✓ ${label}发布流程已完成到发布前一步,已跳过最终“发布”点击。` }],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
208
213
|
const postUrl = result.post_url ? `\n发布链接: ${result.post_url}` : '';
|
|
209
214
|
return {
|
|
210
215
|
content: [{ type: 'text', text: `✓ 已成功发布到${label}。${postUrl}` }],
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
function escapeRegExp(value) {
|
|
2
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function normalizeTags(tags = []) {
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
const normalized = [];
|
|
8
|
+
for (const raw of tags) {
|
|
9
|
+
const tag = String(raw ?? '').trim().replace(/^[##]+/, '').trim();
|
|
10
|
+
if (!tag || seen.has(tag)) continue;
|
|
11
|
+
seen.add(tag);
|
|
12
|
+
normalized.push(tag);
|
|
13
|
+
}
|
|
14
|
+
return normalized;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function formatTextWithTags(text, tags = []) {
|
|
18
|
+
const base = String(text ?? '').trimEnd();
|
|
19
|
+
const missingTags = normalizeTags(tags).filter(tag => {
|
|
20
|
+
const pattern = new RegExp(`(^|[\\s,,。!?!?::;;、])(?:#|#)${escapeRegExp(tag)}(?=$|[\\s,,。!?!?::;;、])`);
|
|
21
|
+
return !pattern.test(base);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
if (missingTags.length === 0) return base;
|
|
25
|
+
const suffix = missingTags.map(tag => `#${tag}`).join(' ');
|
|
26
|
+
return base ? `${base}\n${suffix}` : suffix;
|
|
27
|
+
}
|