@harness.farm/social-cli 0.1.0
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/adapters/bilibili.yaml +271 -0
- package/adapters/douyin.yaml +176 -0
- package/adapters/x.yaml +116 -0
- package/adapters/xhs.yaml +178 -0
- package/adapters/xiaohongshu.yaml +120 -0
- package/package.json +49 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
platform: bilibili
|
|
2
|
+
login_url: https://www.bilibili.com
|
|
3
|
+
login_check:
|
|
4
|
+
cookie: SESSDATA
|
|
5
|
+
|
|
6
|
+
commands:
|
|
7
|
+
|
|
8
|
+
# ── 搜索 ──────────────────────────────────────────────────────────────────────
|
|
9
|
+
search:
|
|
10
|
+
args: [keyword]
|
|
11
|
+
steps:
|
|
12
|
+
- open: "https://search.bilibili.com/all?keyword={{keyword}}&search_source=1"
|
|
13
|
+
- wait: 4000
|
|
14
|
+
- extract:
|
|
15
|
+
selector: ".bili-video-card"
|
|
16
|
+
fields:
|
|
17
|
+
link:
|
|
18
|
+
selector: "a[href*='/video/']"
|
|
19
|
+
attr: href
|
|
20
|
+
title: ".bili-video-card__info--tit,h3"
|
|
21
|
+
up: ".bili-video-card__info--author,.up-name"
|
|
22
|
+
|
|
23
|
+
# ── 点赞视频 ──────────────────────────────────────────────────────────────────
|
|
24
|
+
like:
|
|
25
|
+
args: [url]
|
|
26
|
+
steps:
|
|
27
|
+
- open: "{{url}}"
|
|
28
|
+
- wait: 4000
|
|
29
|
+
- capture:
|
|
30
|
+
name: before
|
|
31
|
+
eval: "String(document.querySelectorAll('.video-like.on').length > 0)"
|
|
32
|
+
- eval: >-
|
|
33
|
+
(function(){
|
|
34
|
+
var btn = document.querySelector(".video-like");
|
|
35
|
+
if(btn) { btn.click(); return true; }
|
|
36
|
+
return false;
|
|
37
|
+
})()
|
|
38
|
+
- wait: 1000
|
|
39
|
+
- capture:
|
|
40
|
+
name: after
|
|
41
|
+
eval: "String(document.querySelectorAll('.video-like.on').length > 0)"
|
|
42
|
+
- return:
|
|
43
|
+
- field: 操作
|
|
44
|
+
value: "{{before === 'true' ? '取消点赞' : '点赞成功'}}"
|
|
45
|
+
- field: 当前状态
|
|
46
|
+
value: "{{after === 'true' ? '已点赞' : '未点赞'}}"
|
|
47
|
+
|
|
48
|
+
# ── 关注 UP 主 ────────────────────────────────────────────────────────────────
|
|
49
|
+
follow:
|
|
50
|
+
args: [url]
|
|
51
|
+
steps:
|
|
52
|
+
- open: "{{url}}"
|
|
53
|
+
- wait: 4000
|
|
54
|
+
- capture:
|
|
55
|
+
name: is_not_follow
|
|
56
|
+
eval: "String(document.querySelectorAll('.follow-btn.not-follow').length > 0)"
|
|
57
|
+
# agent-browser click 发送真实鼠标事件(Vue 事件监听器需要)
|
|
58
|
+
- click: ".follow-btn"
|
|
59
|
+
- wait: 1500
|
|
60
|
+
- capture:
|
|
61
|
+
name: after_class
|
|
62
|
+
eval: "document.querySelector('.follow-btn') ? document.querySelector('.follow-btn').className : ''"
|
|
63
|
+
- return:
|
|
64
|
+
- field: 操作
|
|
65
|
+
value: "{{is_not_follow === 'true' ? '关注' : '取消关注'}}"
|
|
66
|
+
- field: 状态
|
|
67
|
+
value: "{{after_class.includes('not-follow') ? '❌ 未关注' : '✅ 已关注'}}"
|
|
68
|
+
|
|
69
|
+
# ── 发表评论 ──────────────────────────────────────────────────────────────────
|
|
70
|
+
# B站评论区是多层嵌套 shadow DOM:
|
|
71
|
+
# bili-comments → bili-comments-header-renderer → bili-comment-box
|
|
72
|
+
# → bili-comment-rich-textarea → div.brt-editor[contenteditable]
|
|
73
|
+
comment:
|
|
74
|
+
args: [url, text]
|
|
75
|
+
steps:
|
|
76
|
+
- open: "{{url}}"
|
|
77
|
+
- wait: 4000
|
|
78
|
+
# 滚动触发评论区懒加载(bili-comments 默认 lazy-load)
|
|
79
|
+
- eval: "window.scrollTo(0, 600)"
|
|
80
|
+
- wait: 2000
|
|
81
|
+
# 深度 focus 到 shadow DOM 内 .brt-editor(需要穿透多层 shadow root)
|
|
82
|
+
- eval: >-
|
|
83
|
+
(function(){
|
|
84
|
+
var host = document.querySelector("bili-comments");
|
|
85
|
+
if(!host) return false;
|
|
86
|
+
function deepFocus(root){
|
|
87
|
+
var sr = root.shadowRoot;
|
|
88
|
+
if(!sr) return null;
|
|
89
|
+
var editor = sr.querySelector(".brt-editor");
|
|
90
|
+
if(editor) { editor.click(); editor.focus(); return true; }
|
|
91
|
+
var els = sr.querySelectorAll("*");
|
|
92
|
+
for(var i=0; i<els.length; i++){
|
|
93
|
+
var r = deepFocus(els[i]);
|
|
94
|
+
if(r) return r;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return deepFocus(host) || false;
|
|
99
|
+
})()
|
|
100
|
+
- wait: 500
|
|
101
|
+
# CDP Input.insertText 直接插入文本,可穿透 shadow DOM
|
|
102
|
+
- insert_text: "{{text}}"
|
|
103
|
+
- wait: 500
|
|
104
|
+
- capture:
|
|
105
|
+
name: input_check
|
|
106
|
+
eval: "document.querySelector('bili-comments').shadowRoot.querySelector('bili-comments-header-renderer').shadowRoot.querySelector('bili-comment-box').shadowRoot.querySelector('bili-comment-rich-textarea').shadowRoot.querySelector('.brt-editor').textContent.trim()"
|
|
107
|
+
# 点击发布按钮
|
|
108
|
+
- eval: >-
|
|
109
|
+
(function(){
|
|
110
|
+
var btn = document.querySelector("bili-comments")
|
|
111
|
+
.shadowRoot.querySelector("bili-comments-header-renderer")
|
|
112
|
+
.shadowRoot.querySelector("bili-comment-box")
|
|
113
|
+
.shadowRoot.querySelector("#pub button");
|
|
114
|
+
if(btn) { btn.click(); return true; }
|
|
115
|
+
return false;
|
|
116
|
+
})()
|
|
117
|
+
- wait: 2000
|
|
118
|
+
- return:
|
|
119
|
+
- field: 状态
|
|
120
|
+
value: "✅ 评论成功"
|
|
121
|
+
- field: 评论内容
|
|
122
|
+
value: "{{text}}"
|
|
123
|
+
- field: 输入确认
|
|
124
|
+
value: "{{input_check}}"
|
|
125
|
+
|
|
126
|
+
# ── 回复评论 ──────────────────────────────────────────────────────────────────
|
|
127
|
+
# 点击第一条评论的"回复"按钮,然后输入文字
|
|
128
|
+
reply:
|
|
129
|
+
args: [url, text]
|
|
130
|
+
steps:
|
|
131
|
+
- open: "{{url}}"
|
|
132
|
+
- wait: 4000
|
|
133
|
+
# 滚动触发评论区懒加载
|
|
134
|
+
- eval: "window.scrollTo(0, 600)"
|
|
135
|
+
- wait: 2000
|
|
136
|
+
# 点击第一条评论的回复按钮
|
|
137
|
+
- eval: >-
|
|
138
|
+
(function(){
|
|
139
|
+
var cr = document.querySelector("bili-comments").shadowRoot;
|
|
140
|
+
var thread = cr.querySelector("#feed bili-comment-thread-renderer");
|
|
141
|
+
if(!thread) return false;
|
|
142
|
+
var tsr = thread.shadowRoot;
|
|
143
|
+
var renderer = tsr.querySelector("bili-comment-renderer");
|
|
144
|
+
if(!renderer) return false;
|
|
145
|
+
var rsr = renderer.shadowRoot;
|
|
146
|
+
var actions = rsr.querySelector("bili-comment-action-buttons-renderer");
|
|
147
|
+
if(!actions) return false;
|
|
148
|
+
var absr = actions.shadowRoot;
|
|
149
|
+
var replyBtn = absr.querySelector("#reply button");
|
|
150
|
+
if(replyBtn) { replyBtn.click(); return true; }
|
|
151
|
+
return false;
|
|
152
|
+
})()
|
|
153
|
+
- wait: 1000
|
|
154
|
+
# 找到回复输入框并 focus(在 reply-container 下展开的 bili-comment-box)
|
|
155
|
+
- eval: >-
|
|
156
|
+
(function(){
|
|
157
|
+
var cr = document.querySelector("bili-comments").shadowRoot;
|
|
158
|
+
var thread = cr.querySelector("#feed bili-comment-thread-renderer");
|
|
159
|
+
var tsr = thread.shadowRoot;
|
|
160
|
+
var replyContainer = tsr.querySelector("#reply-container");
|
|
161
|
+
if(!replyContainer) return false;
|
|
162
|
+
var box = replyContainer.querySelector("bili-comment-box");
|
|
163
|
+
if(!box) return false;
|
|
164
|
+
var bsr = box.shadowRoot;
|
|
165
|
+
var rta = bsr.querySelector("bili-comment-rich-textarea");
|
|
166
|
+
if(!rta) return false;
|
|
167
|
+
var editor = rta.shadowRoot.querySelector(".brt-editor");
|
|
168
|
+
if(!editor) return false;
|
|
169
|
+
editor.click();
|
|
170
|
+
editor.focus();
|
|
171
|
+
return true;
|
|
172
|
+
})()
|
|
173
|
+
- wait: 300
|
|
174
|
+
# CDP Input.insertText 直接插入(穿透 shadow DOM)
|
|
175
|
+
- insert_text: "{{text}}"
|
|
176
|
+
- wait: 500
|
|
177
|
+
# 点击发布
|
|
178
|
+
- eval: >-
|
|
179
|
+
(function(){
|
|
180
|
+
var cr = document.querySelector("bili-comments").shadowRoot;
|
|
181
|
+
var thread = cr.querySelector("#feed bili-comment-thread-renderer");
|
|
182
|
+
var tsr = thread.shadowRoot;
|
|
183
|
+
var replyContainer = tsr.querySelector("#reply-container");
|
|
184
|
+
if(!replyContainer) return false;
|
|
185
|
+
var box = replyContainer.querySelector("bili-comment-box");
|
|
186
|
+
if(!box) return false;
|
|
187
|
+
var btn = box.shadowRoot.querySelector("#pub button");
|
|
188
|
+
if(btn) { btn.click(); return true; }
|
|
189
|
+
return false;
|
|
190
|
+
})()
|
|
191
|
+
- wait: 2000
|
|
192
|
+
- return:
|
|
193
|
+
- field: 状态
|
|
194
|
+
value: "✅ 回复成功"
|
|
195
|
+
- field: 回复内容
|
|
196
|
+
value: "{{text}}"
|
|
197
|
+
|
|
198
|
+
# ── 投稿(发布视频)──────────────────────────────────────────────────────────
|
|
199
|
+
post:
|
|
200
|
+
args: [video, title, desc] # video: 本地视频路径(必填)
|
|
201
|
+
steps:
|
|
202
|
+
- open: "https://member.bilibili.com/platform/upload/video/frame"
|
|
203
|
+
- wait: 4000
|
|
204
|
+
# 关掉"开启通知"弹窗(知道了)
|
|
205
|
+
- eval: >-
|
|
206
|
+
(function(){
|
|
207
|
+
var btn = [...document.querySelectorAll("button")].find(function(b){ return b.textContent.trim() === "知道了"; });
|
|
208
|
+
if(btn) btn.click();
|
|
209
|
+
return !!btn;
|
|
210
|
+
})()
|
|
211
|
+
- wait: 500
|
|
212
|
+
# 上传视频
|
|
213
|
+
- upload:
|
|
214
|
+
selector: "input[accept*='.mp4']"
|
|
215
|
+
file: "{{video}}"
|
|
216
|
+
# 等待上传完成(标题输入框出现)
|
|
217
|
+
- wait:
|
|
218
|
+
selector: "input[placeholder='请输入稿件标题']"
|
|
219
|
+
- wait: 2000
|
|
220
|
+
# 关掉所有弹窗(二创计划 / 定时发布 / 通知)
|
|
221
|
+
- eval: >-
|
|
222
|
+
(function(){
|
|
223
|
+
["暂不考虑","知道了"].forEach(function(text){
|
|
224
|
+
var btn = [...document.querySelectorAll("button")].find(function(b){ return b.textContent.trim() === text; });
|
|
225
|
+
if(btn) btn.click();
|
|
226
|
+
});
|
|
227
|
+
document.querySelectorAll("button").forEach(function(b){
|
|
228
|
+
if(b.textContent.trim() === "取消") b.click();
|
|
229
|
+
});
|
|
230
|
+
})()
|
|
231
|
+
- wait: 800
|
|
232
|
+
# 填写标题(覆盖自动填充)
|
|
233
|
+
- fill:
|
|
234
|
+
selector: "input[placeholder='请输入稿件标题']"
|
|
235
|
+
value: "{{title}}"
|
|
236
|
+
- wait: 300
|
|
237
|
+
# 填写简介(Quill 富文本编辑器)
|
|
238
|
+
- eval: >-
|
|
239
|
+
(function(){
|
|
240
|
+
var el = document.querySelector(".ql-editor");
|
|
241
|
+
if(!el) return false;
|
|
242
|
+
el.focus();
|
|
243
|
+
document.execCommand("selectAll", false, null);
|
|
244
|
+
document.execCommand("delete", false, null);
|
|
245
|
+
document.execCommand("insertText", false, "{{desc}}");
|
|
246
|
+
return el.textContent.trim().slice(0,30);
|
|
247
|
+
})()
|
|
248
|
+
- wait: 500
|
|
249
|
+
# 点击"立即投稿"
|
|
250
|
+
- eval: >-
|
|
251
|
+
(function(){
|
|
252
|
+
var btn = document.querySelector("span.submit-add");
|
|
253
|
+
if(btn) { btn.click(); return true; }
|
|
254
|
+
return false;
|
|
255
|
+
})()
|
|
256
|
+
- wait: 5000
|
|
257
|
+
- capture:
|
|
258
|
+
name: result_url
|
|
259
|
+
eval: "location.href"
|
|
260
|
+
- capture:
|
|
261
|
+
name: success
|
|
262
|
+
eval: "document.body.innerText.includes('投递成功') || document.body.innerText.includes('投稿成功')"
|
|
263
|
+
- return:
|
|
264
|
+
- field: 状态
|
|
265
|
+
value: "{{success === 'true' ? '✅ 投稿成功' : '⚠️ 请检查页面'}}"
|
|
266
|
+
- field: 标题
|
|
267
|
+
value: "{{title}}"
|
|
268
|
+
- field: 描述
|
|
269
|
+
value: "{{desc}}"
|
|
270
|
+
- field: 页面URL
|
|
271
|
+
value: "{{result_url}}"
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
platform: douyin
|
|
2
|
+
login_url: https://www.douyin.com
|
|
3
|
+
login_check:
|
|
4
|
+
cookie: passport_csrf_token
|
|
5
|
+
|
|
6
|
+
commands:
|
|
7
|
+
|
|
8
|
+
search:
|
|
9
|
+
args: [keyword]
|
|
10
|
+
steps:
|
|
11
|
+
- open: "https://www.douyin.com/search/{{keyword}}?type=video"
|
|
12
|
+
- wait: 4000
|
|
13
|
+
- extract:
|
|
14
|
+
selector: "a[href*='/video/']"
|
|
15
|
+
fields:
|
|
16
|
+
link:
|
|
17
|
+
selector: "a[href*='/video/']"
|
|
18
|
+
attr: href
|
|
19
|
+
text: ".kgnD1hJB, [class*=title], [class*=desc]"
|
|
20
|
+
|
|
21
|
+
like:
|
|
22
|
+
args: [url]
|
|
23
|
+
steps:
|
|
24
|
+
- open: "{{url}}"
|
|
25
|
+
- wait: 4000
|
|
26
|
+
- capture:
|
|
27
|
+
name: state_before
|
|
28
|
+
eval: "document.querySelector('[data-e2e=\"video-player-digg\"]')?.getAttribute('data-e2e-state') || ''"
|
|
29
|
+
- key: "z"
|
|
30
|
+
- wait: 1000
|
|
31
|
+
- capture:
|
|
32
|
+
name: state_after
|
|
33
|
+
eval: "document.querySelector('[data-e2e=\"video-player-digg\"]')?.getAttribute('data-e2e-state') || ''"
|
|
34
|
+
- return:
|
|
35
|
+
- field: 操作
|
|
36
|
+
value: "{{state_before === 'video-player-no-digged' ? '点赞成功' : '取消点赞'}}"
|
|
37
|
+
- field: 状态
|
|
38
|
+
value: "{{state_after}}"
|
|
39
|
+
|
|
40
|
+
comment:
|
|
41
|
+
args: [url, text]
|
|
42
|
+
steps:
|
|
43
|
+
- open: "{{url}}"
|
|
44
|
+
- wait: 4000
|
|
45
|
+
# X 键打开评论区
|
|
46
|
+
- key: "x"
|
|
47
|
+
- wait: 1200
|
|
48
|
+
# PointerEvent 激活 Draft.js 输入框(触发 React 事件)
|
|
49
|
+
- eval: >-
|
|
50
|
+
(function(){
|
|
51
|
+
var el = document.querySelector('.lFk180Rt') || document.querySelector('.GXmFLge7');
|
|
52
|
+
if(!el) return false;
|
|
53
|
+
['pointerdown','mousedown','pointerup','mouseup','click'].forEach(function(evt){
|
|
54
|
+
el.dispatchEvent(new (evt.startsWith('pointer') ? PointerEvent : MouseEvent)(evt, {bubbles:true,cancelable:true,composed:true,view:window}));
|
|
55
|
+
});
|
|
56
|
+
return true;
|
|
57
|
+
})()
|
|
58
|
+
- wait: 1200
|
|
59
|
+
# focus Draft.js 编辑器
|
|
60
|
+
- eval: "document.querySelector('.notranslate.public-DraftEditor-content')?.focus()"
|
|
61
|
+
- wait: 300
|
|
62
|
+
# 插入文字(CDP char 事件,正确触发 Draft.js EditorState 更新)
|
|
63
|
+
- keyboard_insert: "{{text}}"
|
|
64
|
+
- wait: 600
|
|
65
|
+
- capture:
|
|
66
|
+
name: input_text
|
|
67
|
+
eval: "document.querySelector('.notranslate.public-DraftEditor-content')?.textContent?.trim() || ''"
|
|
68
|
+
# Enter 发送
|
|
69
|
+
- key: "Enter"
|
|
70
|
+
- wait: 2000
|
|
71
|
+
- return:
|
|
72
|
+
- field: 状态
|
|
73
|
+
value: "✅ 评论成功"
|
|
74
|
+
- field: 评论内容
|
|
75
|
+
value: "{{text}}"
|
|
76
|
+
- field: 输入确认
|
|
77
|
+
value: "{{input_text}}"
|
|
78
|
+
|
|
79
|
+
follow:
|
|
80
|
+
args: [url]
|
|
81
|
+
steps:
|
|
82
|
+
- open: "{{url}}"
|
|
83
|
+
- wait: 4000
|
|
84
|
+
# JbfEzak6 = the + follow button on the avatar in the right sidebar
|
|
85
|
+
# present = not yet following; absent = already following
|
|
86
|
+
- capture:
|
|
87
|
+
name: has_follow_btn
|
|
88
|
+
eval: "!!document.querySelector('.JbfEzak6')"
|
|
89
|
+
- eval: >-
|
|
90
|
+
(function(){
|
|
91
|
+
var el = document.querySelector('.JbfEzak6');
|
|
92
|
+
if(el) el.click();
|
|
93
|
+
return !!el;
|
|
94
|
+
})()
|
|
95
|
+
- wait: 1000
|
|
96
|
+
- capture:
|
|
97
|
+
name: btn_after
|
|
98
|
+
eval: "!!document.querySelector('.JbfEzak6')"
|
|
99
|
+
- return:
|
|
100
|
+
- field: 操作
|
|
101
|
+
value: "{{has_follow_btn === 'true' ? '关注成功' : '已在关注中(无操作)'}}"
|
|
102
|
+
- field: 关注按钮消失
|
|
103
|
+
value: "{{btn_after === 'false' ? '✅ 是' : '⚠️ 否'}}"
|
|
104
|
+
|
|
105
|
+
post:
|
|
106
|
+
args: [video, title, desc] # video: 本地视频路径(必填)
|
|
107
|
+
steps:
|
|
108
|
+
- open: "https://creator.douyin.com/creator-micro/content/upload"
|
|
109
|
+
- wait: 4000
|
|
110
|
+
# 上传视频
|
|
111
|
+
- upload:
|
|
112
|
+
selector: "input[type=file]"
|
|
113
|
+
file: "{{video}}"
|
|
114
|
+
# 等待编辑器渲染(标题输入框出现)
|
|
115
|
+
- wait:
|
|
116
|
+
selector: "input[placeholder='填写作品标题,为作品获得更多流量']"
|
|
117
|
+
- wait: 1000
|
|
118
|
+
# 填写标题
|
|
119
|
+
- fill:
|
|
120
|
+
selector: "input[placeholder='填写作品标题,为作品获得更多流量']"
|
|
121
|
+
value: "{{title}}"
|
|
122
|
+
- wait: 500
|
|
123
|
+
# 填写描述(contenteditable)
|
|
124
|
+
- type_rich:
|
|
125
|
+
selector: ".zone-container.editor-kit-container"
|
|
126
|
+
value: "{{desc}}"
|
|
127
|
+
- wait: 800
|
|
128
|
+
# 选择封面:点击"选择封面" → 封面编辑器弹出 → 点"完成"
|
|
129
|
+
- eval: >-
|
|
130
|
+
(function(){
|
|
131
|
+
var btn = document.querySelector('.cover-Jg3T4p');
|
|
132
|
+
if (btn) { btn.click(); return true; }
|
|
133
|
+
return false;
|
|
134
|
+
})()
|
|
135
|
+
- wait: 2000
|
|
136
|
+
- eval: >-
|
|
137
|
+
(function(){
|
|
138
|
+
var btn = [...document.querySelectorAll('button')].find(function(b){
|
|
139
|
+
return b.textContent.trim() === '完成';
|
|
140
|
+
});
|
|
141
|
+
if (btn) { btn.click(); return true; }
|
|
142
|
+
return false;
|
|
143
|
+
})()
|
|
144
|
+
- wait: 1500
|
|
145
|
+
# 如果弹出"暂不设置竖封面"提示,跳过
|
|
146
|
+
- eval: >-
|
|
147
|
+
(function(){
|
|
148
|
+
var btn = [...document.querySelectorAll('button')].find(function(b){
|
|
149
|
+
return b.textContent.trim() === '暂不设置';
|
|
150
|
+
});
|
|
151
|
+
if (btn) { btn.click(); return true; }
|
|
152
|
+
return false;
|
|
153
|
+
})()
|
|
154
|
+
- wait: 1000
|
|
155
|
+
# 点击发布
|
|
156
|
+
- eval: >-
|
|
157
|
+
(function(){
|
|
158
|
+
var btn = [...document.querySelectorAll('button')].find(function(b){
|
|
159
|
+
return b.textContent.trim() === '发布' && !b.disabled;
|
|
160
|
+
});
|
|
161
|
+
if (btn) { btn.click(); return true; }
|
|
162
|
+
return false;
|
|
163
|
+
})()
|
|
164
|
+
- wait: 5000
|
|
165
|
+
- capture:
|
|
166
|
+
name: result_url
|
|
167
|
+
eval: "location.href"
|
|
168
|
+
- return:
|
|
169
|
+
- field: 状态
|
|
170
|
+
value: "✅ 发布成功"
|
|
171
|
+
- field: 标题
|
|
172
|
+
value: "{{title}}"
|
|
173
|
+
- field: 描述
|
|
174
|
+
value: "{{desc}}"
|
|
175
|
+
- field: 跳转URL
|
|
176
|
+
value: "{{result_url}}"
|
package/adapters/x.yaml
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
platform: x
|
|
2
|
+
login_url: https://x.com
|
|
3
|
+
login_check:
|
|
4
|
+
cookie: auth_token
|
|
5
|
+
|
|
6
|
+
commands:
|
|
7
|
+
|
|
8
|
+
search:
|
|
9
|
+
args: [keyword]
|
|
10
|
+
steps:
|
|
11
|
+
- open: "https://x.com/search?q={{keyword}}&src=typed_query&f=top"
|
|
12
|
+
- wait: 4000
|
|
13
|
+
- extract:
|
|
14
|
+
selector: "[data-testid='tweet']"
|
|
15
|
+
fields:
|
|
16
|
+
text: "[data-testid='tweetText']"
|
|
17
|
+
user: "[data-testid='User-Name']"
|
|
18
|
+
link:
|
|
19
|
+
selector: "a[href*='/status/']"
|
|
20
|
+
attr: href
|
|
21
|
+
time:
|
|
22
|
+
selector: time
|
|
23
|
+
attr: datetime
|
|
24
|
+
|
|
25
|
+
like:
|
|
26
|
+
args: [url]
|
|
27
|
+
steps:
|
|
28
|
+
- open: "{{url}}"
|
|
29
|
+
- wait: 3000
|
|
30
|
+
- capture:
|
|
31
|
+
name: already_liked
|
|
32
|
+
eval: "!!document.querySelector('[data-testid=\"unlike\"]')"
|
|
33
|
+
- eval: >-
|
|
34
|
+
(function(){
|
|
35
|
+
var btn = document.querySelector('[data-testid="like"]') || document.querySelector('[data-testid="unlike"]');
|
|
36
|
+
if (btn) { btn.click(); return true; }
|
|
37
|
+
return false;
|
|
38
|
+
})()
|
|
39
|
+
- wait: 1000
|
|
40
|
+
- capture:
|
|
41
|
+
name: is_liked
|
|
42
|
+
eval: "!!document.querySelector('[data-testid=\"unlike\"]')"
|
|
43
|
+
- return:
|
|
44
|
+
- field: 操作
|
|
45
|
+
value: "{{already_liked == 'true' && '取消点赞' || '点赞成功'}}"
|
|
46
|
+
- field: 状态
|
|
47
|
+
value: "{{is_liked}}"
|
|
48
|
+
|
|
49
|
+
reply:
|
|
50
|
+
args: [url, text]
|
|
51
|
+
steps:
|
|
52
|
+
- open: "{{url}}"
|
|
53
|
+
- wait: 3000
|
|
54
|
+
- click:
|
|
55
|
+
selector: "[data-testid='reply']"
|
|
56
|
+
- wait: 1000
|
|
57
|
+
- type_rich:
|
|
58
|
+
selector: "[data-testid='tweetTextarea_0']"
|
|
59
|
+
value: "{{text}}"
|
|
60
|
+
- wait: 500
|
|
61
|
+
- capture:
|
|
62
|
+
name: input_text
|
|
63
|
+
eval: "document.querySelector('[data-testid=\"tweetTextarea_0\"]')?.textContent?.trim() || ''"
|
|
64
|
+
- click:
|
|
65
|
+
selector: "[data-testid='tweetButtonInline']"
|
|
66
|
+
- wait: 2000
|
|
67
|
+
- return:
|
|
68
|
+
- field: 状态
|
|
69
|
+
value: "✅ 回复成功"
|
|
70
|
+
- field: 回复内容
|
|
71
|
+
value: "{{text}}"
|
|
72
|
+
- field: 输入确认
|
|
73
|
+
value: "{{input_text}}"
|
|
74
|
+
|
|
75
|
+
post:
|
|
76
|
+
args: [text]
|
|
77
|
+
steps:
|
|
78
|
+
- open: "https://x.com/home"
|
|
79
|
+
- wait: 3000
|
|
80
|
+
- type_rich:
|
|
81
|
+
selector: "[data-testid='tweetTextarea_0']"
|
|
82
|
+
value: "{{text}}"
|
|
83
|
+
- wait: 500
|
|
84
|
+
- capture:
|
|
85
|
+
name: input_text
|
|
86
|
+
eval: "document.querySelector('[data-testid=\"tweetTextarea_0\"]')?.textContent?.trim() || ''"
|
|
87
|
+
- click:
|
|
88
|
+
selector: "[data-testid='tweetButton']"
|
|
89
|
+
- wait: 2500
|
|
90
|
+
- capture:
|
|
91
|
+
name: result_url
|
|
92
|
+
eval: "location.href"
|
|
93
|
+
- return:
|
|
94
|
+
- field: 状态
|
|
95
|
+
value: "✅ 发推成功"
|
|
96
|
+
- field: 内容
|
|
97
|
+
value: "{{text}}"
|
|
98
|
+
- field: 输入确认
|
|
99
|
+
value: "{{input_text}}"
|
|
100
|
+
|
|
101
|
+
retweet:
|
|
102
|
+
args: [url]
|
|
103
|
+
steps:
|
|
104
|
+
- open: "{{url}}"
|
|
105
|
+
- wait: 3000
|
|
106
|
+
- click:
|
|
107
|
+
selector: "[data-testid='retweet']"
|
|
108
|
+
- wait: 800
|
|
109
|
+
- click:
|
|
110
|
+
selector: "[data-testid='retweetConfirm']"
|
|
111
|
+
- wait: 1500
|
|
112
|
+
- return:
|
|
113
|
+
- field: 状态
|
|
114
|
+
value: "✅ 转推成功"
|
|
115
|
+
- field: URL
|
|
116
|
+
value: "{{url}}"
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
platform: xiaohongshu
|
|
2
|
+
login_url: https://www.xiaohongshu.com
|
|
3
|
+
login_check:
|
|
4
|
+
cookie: web_session
|
|
5
|
+
|
|
6
|
+
commands:
|
|
7
|
+
|
|
8
|
+
search:
|
|
9
|
+
args: [keyword]
|
|
10
|
+
steps:
|
|
11
|
+
- open: "https://www.xiaohongshu.com/search_result?keyword={{keyword}}&source=web_explore_feed"
|
|
12
|
+
- wait: 4000
|
|
13
|
+
- extract:
|
|
14
|
+
selector: .note-item
|
|
15
|
+
fields:
|
|
16
|
+
title: ".title, [class*=title]"
|
|
17
|
+
author: ".author .name, .nickname"
|
|
18
|
+
link:
|
|
19
|
+
selector: a
|
|
20
|
+
attr: href
|
|
21
|
+
|
|
22
|
+
hot:
|
|
23
|
+
args: []
|
|
24
|
+
steps:
|
|
25
|
+
- open: "https://www.xiaohongshu.com/explore"
|
|
26
|
+
- wait: 4000
|
|
27
|
+
- extract:
|
|
28
|
+
selector: .note-item
|
|
29
|
+
fields:
|
|
30
|
+
title: ".title, [class*=title]"
|
|
31
|
+
author: ".author .name, .nickname"
|
|
32
|
+
link:
|
|
33
|
+
selector: a
|
|
34
|
+
attr: href
|
|
35
|
+
|
|
36
|
+
like:
|
|
37
|
+
args: [url]
|
|
38
|
+
steps:
|
|
39
|
+
- open: "{{url}}"
|
|
40
|
+
- wait: 3000
|
|
41
|
+
- capture:
|
|
42
|
+
name: was_liked
|
|
43
|
+
eval: "document.querySelector('.like-wrapper') ? document.querySelector('.like-wrapper').classList.contains('like-active') : false"
|
|
44
|
+
- click:
|
|
45
|
+
selector: ".like-wrapper"
|
|
46
|
+
- wait: 500
|
|
47
|
+
- capture:
|
|
48
|
+
name: is_liked
|
|
49
|
+
eval: "document.querySelector('.like-wrapper') ? document.querySelector('.like-wrapper').classList.contains('like-active') : false"
|
|
50
|
+
- capture:
|
|
51
|
+
name: count
|
|
52
|
+
eval: "document.querySelector('.like-wrapper .count') ? document.querySelector('.like-wrapper .count').textContent.trim() : '?'"
|
|
53
|
+
- return:
|
|
54
|
+
- field: 操作
|
|
55
|
+
value: "{{was_liked == 'true' && '取消点赞' || '点赞成功'}}"
|
|
56
|
+
- field: 状态
|
|
57
|
+
value: "{{is_liked}}"
|
|
58
|
+
- field: 点赞数
|
|
59
|
+
value: "{{count}}"
|
|
60
|
+
|
|
61
|
+
comment:
|
|
62
|
+
args: [url, text]
|
|
63
|
+
steps:
|
|
64
|
+
- open: "{{url}}"
|
|
65
|
+
- wait: 3000
|
|
66
|
+
- click:
|
|
67
|
+
text: "说点什么..."
|
|
68
|
+
- wait: 800
|
|
69
|
+
- type_rich:
|
|
70
|
+
selector: ".content-input"
|
|
71
|
+
value: "{{text}}"
|
|
72
|
+
- wait: 500
|
|
73
|
+
- capture:
|
|
74
|
+
name: input_text
|
|
75
|
+
eval: "document.querySelector('.content-input') ? document.querySelector('.content-input').textContent.trim() : ''"
|
|
76
|
+
- click:
|
|
77
|
+
text: "发送"
|
|
78
|
+
- wait: 1500
|
|
79
|
+
- return:
|
|
80
|
+
- field: 状态
|
|
81
|
+
value: "✅ 评论成功"
|
|
82
|
+
- field: 评论内容
|
|
83
|
+
value: "{{text}}"
|
|
84
|
+
- field: 输入确认
|
|
85
|
+
value: "{{input_text}}"
|
|
86
|
+
|
|
87
|
+
post_video:
|
|
88
|
+
args: [video, title, desc] # video: 本地视频路径(必填)
|
|
89
|
+
steps:
|
|
90
|
+
- open: "https://creator.xiaohongshu.com/publish/publish?source=official"
|
|
91
|
+
- wait: 3000
|
|
92
|
+
# 默认就在「上传视频」tab,直接上传
|
|
93
|
+
- upload:
|
|
94
|
+
selector: "input.upload-input"
|
|
95
|
+
file: "{{video}}"
|
|
96
|
+
# 等待上传完成(标题输入框出现)
|
|
97
|
+
- wait:
|
|
98
|
+
selector: "input.d-text"
|
|
99
|
+
- wait: 1000
|
|
100
|
+
# 填写标题
|
|
101
|
+
- fill:
|
|
102
|
+
selector: "input.d-text"
|
|
103
|
+
value: "{{title}}"
|
|
104
|
+
- wait: 500
|
|
105
|
+
# 填写描述(ProseMirror 富文本)
|
|
106
|
+
- type_rich:
|
|
107
|
+
selector: ".tiptap.ProseMirror"
|
|
108
|
+
value: "{{desc}}"
|
|
109
|
+
- wait: 800
|
|
110
|
+
# 点击发布
|
|
111
|
+
- eval: >-
|
|
112
|
+
(function(){
|
|
113
|
+
var btns = Array.from(document.querySelectorAll("button"));
|
|
114
|
+
var btn = btns.filter(function(b){ return b.textContent.trim() === "发布"; }).pop();
|
|
115
|
+
if(btn && !btn.disabled) { btn.click(); return true; }
|
|
116
|
+
return false;
|
|
117
|
+
})()
|
|
118
|
+
- wait: 3000
|
|
119
|
+
- capture:
|
|
120
|
+
name: result_url
|
|
121
|
+
eval: "location.href"
|
|
122
|
+
- return:
|
|
123
|
+
- field: 状态
|
|
124
|
+
value: "✅ 发布成功"
|
|
125
|
+
- field: 标题
|
|
126
|
+
value: "{{title}}"
|
|
127
|
+
- field: 描述
|
|
128
|
+
value: "{{desc}}"
|
|
129
|
+
- field: 跳转URL
|
|
130
|
+
value: "{{result_url}}"
|
|
131
|
+
|
|
132
|
+
post:
|
|
133
|
+
args: [title, content, image] # image: 本地图片路径(必填,小红书图文必须有图)
|
|
134
|
+
steps:
|
|
135
|
+
- open: "https://creator.xiaohongshu.com/publish/publish?source=official"
|
|
136
|
+
- wait: 3000
|
|
137
|
+
# 切到图文 tab
|
|
138
|
+
- click:
|
|
139
|
+
text: "上传图文"
|
|
140
|
+
- wait: 1500
|
|
141
|
+
# 上传图片
|
|
142
|
+
- upload:
|
|
143
|
+
selector: "input[type=file][accept*=jpg]"
|
|
144
|
+
file: "{{image}}"
|
|
145
|
+
# 等待图片上传 + 编辑器渲染
|
|
146
|
+
- wait:
|
|
147
|
+
selector: "input.d-text"
|
|
148
|
+
# 填标题
|
|
149
|
+
- fill:
|
|
150
|
+
selector: "input.d-text"
|
|
151
|
+
value: "{{title}}"
|
|
152
|
+
- wait: 500
|
|
153
|
+
# 填正文
|
|
154
|
+
- type_rich:
|
|
155
|
+
selector: ".tiptap.ProseMirror"
|
|
156
|
+
value: "{{content}}"
|
|
157
|
+
- wait: 800
|
|
158
|
+
# 点发布(selector 更稳定,避免 text 匹配问题)
|
|
159
|
+
- eval: >-
|
|
160
|
+
(function(){
|
|
161
|
+
var btns = [...document.querySelectorAll('button')];
|
|
162
|
+
var btn = btns.reverse().find(function(b){ return b.textContent.trim() === '发布'; });
|
|
163
|
+
if (btn && !btn.disabled) { btn.click(); return true; }
|
|
164
|
+
return false;
|
|
165
|
+
})()
|
|
166
|
+
- wait: 2500
|
|
167
|
+
- capture:
|
|
168
|
+
name: result_url
|
|
169
|
+
eval: "location.href"
|
|
170
|
+
- return:
|
|
171
|
+
- field: 状态
|
|
172
|
+
value: "✅ 发布成功"
|
|
173
|
+
- field: 标题
|
|
174
|
+
value: "{{title}}"
|
|
175
|
+
- field: 正文
|
|
176
|
+
value: "{{content}}"
|
|
177
|
+
- field: 跳转URL
|
|
178
|
+
value: "{{result_url}}"
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
platform: xiaohongshu
|
|
2
|
+
login_url: https://www.xiaohongshu.com
|
|
3
|
+
login_check:
|
|
4
|
+
cookie: web_session
|
|
5
|
+
|
|
6
|
+
commands:
|
|
7
|
+
|
|
8
|
+
search:
|
|
9
|
+
args: [keyword]
|
|
10
|
+
steps:
|
|
11
|
+
- open: "https://www.xiaohongshu.com/search_result?keyword={{keyword}}&source=web_explore_feed"
|
|
12
|
+
- wait: 4000
|
|
13
|
+
- extract:
|
|
14
|
+
selector: .note-item
|
|
15
|
+
fields:
|
|
16
|
+
title: ".title, [class*=title]"
|
|
17
|
+
author: ".author .name, .nickname"
|
|
18
|
+
link:
|
|
19
|
+
selector: a
|
|
20
|
+
attr: href
|
|
21
|
+
|
|
22
|
+
hot:
|
|
23
|
+
args: []
|
|
24
|
+
steps:
|
|
25
|
+
- open: "https://www.xiaohongshu.com/explore"
|
|
26
|
+
- wait: 4000
|
|
27
|
+
- extract:
|
|
28
|
+
selector: .note-item
|
|
29
|
+
fields:
|
|
30
|
+
title: ".title, [class*=title]"
|
|
31
|
+
author: ".author .name, .nickname"
|
|
32
|
+
link:
|
|
33
|
+
selector: a
|
|
34
|
+
attr: href
|
|
35
|
+
|
|
36
|
+
like:
|
|
37
|
+
args: [url]
|
|
38
|
+
steps:
|
|
39
|
+
- open: "{{url}}"
|
|
40
|
+
- wait: 3000
|
|
41
|
+
- capture:
|
|
42
|
+
name: was_liked
|
|
43
|
+
eval: "document.querySelector('.like-wrapper') ? document.querySelector('.like-wrapper').classList.contains('like-active') : false"
|
|
44
|
+
- click:
|
|
45
|
+
selector: ".like-wrapper"
|
|
46
|
+
- wait: 500
|
|
47
|
+
- capture:
|
|
48
|
+
name: is_liked
|
|
49
|
+
eval: "document.querySelector('.like-wrapper') ? document.querySelector('.like-wrapper').classList.contains('like-active') : false"
|
|
50
|
+
- capture:
|
|
51
|
+
name: count
|
|
52
|
+
eval: "document.querySelector('.like-wrapper .count') ? document.querySelector('.like-wrapper .count').textContent.trim() : '?'"
|
|
53
|
+
- return:
|
|
54
|
+
- field: 操作
|
|
55
|
+
value: "{{was_liked == 'true' && '取消点赞' || '点赞成功'}}"
|
|
56
|
+
- field: 状态
|
|
57
|
+
value: "{{is_liked}}"
|
|
58
|
+
- field: 点赞数
|
|
59
|
+
value: "{{count}}"
|
|
60
|
+
|
|
61
|
+
comment:
|
|
62
|
+
args: [url, text]
|
|
63
|
+
steps:
|
|
64
|
+
- open: "{{url}}"
|
|
65
|
+
- wait: 3000
|
|
66
|
+
- click:
|
|
67
|
+
text: "说点什么..."
|
|
68
|
+
- wait: 800
|
|
69
|
+
- type_rich:
|
|
70
|
+
selector: ".content-input"
|
|
71
|
+
value: "{{text}}"
|
|
72
|
+
- wait: 500
|
|
73
|
+
- capture:
|
|
74
|
+
name: input_text
|
|
75
|
+
eval: "document.querySelector('.content-input') ? document.querySelector('.content-input').textContent.trim() : ''"
|
|
76
|
+
- click:
|
|
77
|
+
text: "发送"
|
|
78
|
+
- wait: 1500
|
|
79
|
+
- return:
|
|
80
|
+
- field: 状态
|
|
81
|
+
value: "✅ 评论成功"
|
|
82
|
+
- field: 评论内容
|
|
83
|
+
value: "{{text}}"
|
|
84
|
+
- field: 输入确认
|
|
85
|
+
value: "{{input_text}}"
|
|
86
|
+
|
|
87
|
+
post:
|
|
88
|
+
args: [title, content]
|
|
89
|
+
steps:
|
|
90
|
+
- open: "https://creator.xiaohongshu.com/publish/publish?source=official"
|
|
91
|
+
- wait: 3000
|
|
92
|
+
- click:
|
|
93
|
+
text: "上传图文"
|
|
94
|
+
- wait: 1500
|
|
95
|
+
- fill:
|
|
96
|
+
selector: "input.d-text[placeholder*='标题']"
|
|
97
|
+
value: "{{title}}"
|
|
98
|
+
- wait: 500
|
|
99
|
+
- type_rich:
|
|
100
|
+
selector: ".tiptap.ProseMirror"
|
|
101
|
+
value: "{{content}}"
|
|
102
|
+
- wait: 500
|
|
103
|
+
- capture:
|
|
104
|
+
name: current_url
|
|
105
|
+
eval: "location.href"
|
|
106
|
+
- click:
|
|
107
|
+
text: "发布"
|
|
108
|
+
- wait: 2000
|
|
109
|
+
- capture:
|
|
110
|
+
name: result_url
|
|
111
|
+
eval: "location.href"
|
|
112
|
+
- return:
|
|
113
|
+
- field: 状态
|
|
114
|
+
value: "✅ 发布成功"
|
|
115
|
+
- field: 标题
|
|
116
|
+
value: "{{title}}"
|
|
117
|
+
- field: 正文
|
|
118
|
+
value: "{{content}}"
|
|
119
|
+
- field: 跳转URL
|
|
120
|
+
value: "{{result_url}}"
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@harness.farm/social-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CDP-based social media automation CLI — X, 小红书, 抖音, B站, Temu",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"social-cli": "./dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"dist",
|
|
14
|
+
"adapters"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"dev": "tsx src/cli.ts",
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"x": "tsx src/cli.ts x",
|
|
20
|
+
"xhs": "tsx src/cli.ts xhs",
|
|
21
|
+
"douyin": "tsx src/cli.ts douyin",
|
|
22
|
+
"bilibili": "tsx src/cli.ts bilibili",
|
|
23
|
+
"temu": "tsx src/cli.ts temu"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"social-media",
|
|
27
|
+
"automation",
|
|
28
|
+
"cdp",
|
|
29
|
+
"xiaohongshu",
|
|
30
|
+
"douyin",
|
|
31
|
+
"bilibili",
|
|
32
|
+
"temu",
|
|
33
|
+
"cli"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/harness-farm/social-cli.git"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"ws": "^8.18.0",
|
|
42
|
+
"yaml": "^2.8.3"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/ws": "^8.5.12",
|
|
46
|
+
"tsx": "^4.19.2",
|
|
47
|
+
"typescript": "^5.8.2"
|
|
48
|
+
}
|
|
49
|
+
}
|