@iflyrpa/actions 0.0.1
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/dist/index.cjs +228 -0
- package/dist/index.mjs +220 -0
- package/package.json +22 -0
- package/src/actions/xiaohongshuPublish.ts +286 -0
- package/src/index.ts +5 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const https = require('node:https');
|
|
6
|
+
|
|
7
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
|
|
8
|
+
|
|
9
|
+
const path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
10
|
+
const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
|
|
11
|
+
const https__default = /*#__PURE__*/_interopDefaultCompat(https);
|
|
12
|
+
|
|
13
|
+
function ensureFile(filePath) {
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const dirPath = path__default.dirname(filePath);
|
|
16
|
+
fs__default.stat(dirPath, (err, stats) => {
|
|
17
|
+
if (err) {
|
|
18
|
+
if (err.code === "ENOENT") {
|
|
19
|
+
fs__default.mkdir(dirPath, { recursive: true }, (err2) => {
|
|
20
|
+
if (err2) {
|
|
21
|
+
return reject(err2);
|
|
22
|
+
}
|
|
23
|
+
createFile();
|
|
24
|
+
});
|
|
25
|
+
} else {
|
|
26
|
+
return reject(err);
|
|
27
|
+
}
|
|
28
|
+
} else if (stats.isDirectory()) {
|
|
29
|
+
checkFile();
|
|
30
|
+
} else {
|
|
31
|
+
reject(new Error(`${dirPath} is not a directory`));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
function checkFile() {
|
|
35
|
+
fs__default.stat(filePath, (err, stats) => {
|
|
36
|
+
if (err) {
|
|
37
|
+
if (err.code === "ENOENT") {
|
|
38
|
+
createFile();
|
|
39
|
+
} else {
|
|
40
|
+
reject(err);
|
|
41
|
+
}
|
|
42
|
+
} else if (stats.isFile()) {
|
|
43
|
+
resolve();
|
|
44
|
+
} else {
|
|
45
|
+
reject(new Error(`${filePath} is not a file`));
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function createFile() {
|
|
50
|
+
fs__default.writeFile(filePath, "", (err) => {
|
|
51
|
+
if (err) {
|
|
52
|
+
reject(err);
|
|
53
|
+
} else {
|
|
54
|
+
resolve();
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async function downloadImage(url, savePath) {
|
|
61
|
+
await ensureFile(savePath);
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
https__default.get(url, (response) => {
|
|
64
|
+
if (response.statusCode === 200) {
|
|
65
|
+
const fileStream = fs__default.createWriteStream(savePath);
|
|
66
|
+
response.pipe(fileStream);
|
|
67
|
+
fileStream.on("finish", () => {
|
|
68
|
+
fileStream.close();
|
|
69
|
+
console.log("\u4E0B\u8F7D\u5B8C\u6210\uFF0C\u6587\u4EF6\u5DF2\u4FDD\u5B58\u81F3:", savePath);
|
|
70
|
+
resolve(savePath);
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
console.log("\u56FE\u7247\u4E0B\u8F7D\u5931\u8D25:", response.statusCode);
|
|
74
|
+
response.resume();
|
|
75
|
+
reject();
|
|
76
|
+
}
|
|
77
|
+
}).on("error", (error) => {
|
|
78
|
+
console.error("\u8BF7\u6C42\u56FE\u7247\u65F6\u53D1\u751F\u9519\u8BEF:", error.message);
|
|
79
|
+
reject();
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function getFilenameFromUrl(imageUrl) {
|
|
84
|
+
const parsedUrl = new URL(imageUrl);
|
|
85
|
+
return path__default.basename(parsedUrl.pathname);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const visibleRangeTexts = {
|
|
89
|
+
public: "\u516C\u5F00",
|
|
90
|
+
private: "\u79C1\u5BC6"
|
|
91
|
+
};
|
|
92
|
+
const xiaohongshuPublish = async (task, params) => {
|
|
93
|
+
task.logger.info("\u5F00\u59CB\u5C0F\u7EA2\u4E66\u53D1\u5E03");
|
|
94
|
+
const commonCookies = {
|
|
95
|
+
path: "/",
|
|
96
|
+
sameSite: "lax",
|
|
97
|
+
secure: false,
|
|
98
|
+
domain: "xiaohongshu.com",
|
|
99
|
+
url: "https://creator.xiaohongshu.com",
|
|
100
|
+
httpOnly: true
|
|
101
|
+
};
|
|
102
|
+
const page = await task.createPage({
|
|
103
|
+
show: task.debug,
|
|
104
|
+
url: params.url || "https://creator.xiaohongshu.com/publish/publish",
|
|
105
|
+
cookies: params.cookies?.map((it) => Object.assign(commonCookies, it)) || []
|
|
106
|
+
});
|
|
107
|
+
const tmpCachePath = task.getTmpPath();
|
|
108
|
+
const selectAddress = async (selector, address) => {
|
|
109
|
+
const instance = typeof selector === "string" ? page.locator(selector) : selector;
|
|
110
|
+
await instance.click();
|
|
111
|
+
await instance.locator("input").fill(address);
|
|
112
|
+
const poperInstance = page.locator(
|
|
113
|
+
'.d-popover:not([style*="display: none"]) .d-options .d-grid-item'
|
|
114
|
+
);
|
|
115
|
+
await poperInstance.first().waitFor();
|
|
116
|
+
await poperInstance.first().click();
|
|
117
|
+
};
|
|
118
|
+
const selectDate = async (selector, date) => {
|
|
119
|
+
const instance = typeof selector === "string" ? page.locator(selector) : selector;
|
|
120
|
+
await instance.click();
|
|
121
|
+
await instance.fill(date);
|
|
122
|
+
await instance.blur();
|
|
123
|
+
};
|
|
124
|
+
await page.waitForSelector("#CreatorPlatform", { state: "visible" }).catch(() => {
|
|
125
|
+
throw new Error("\u767B\u5F55\u5931\u8D25");
|
|
126
|
+
});
|
|
127
|
+
await page.locator("#content-area .menu-container .publish-video a").click().catch(() => {
|
|
128
|
+
throw new Error("\u672A\u627E\u5230\u53D1\u5E03\u7B14\u8BB0\u6309\u94AE");
|
|
129
|
+
});
|
|
130
|
+
await page.locator(".creator-container .header .title").filter({ hasText: /^上传图文$/ }).click().catch(() => {
|
|
131
|
+
throw new Error("\u672A\u627E\u5230\u4E0A\u4F20\u56FE\u6587\u6309\u94AE");
|
|
132
|
+
});
|
|
133
|
+
const images = await Promise.all(
|
|
134
|
+
params.banners.map((url) => {
|
|
135
|
+
const fileName = getFilenameFromUrl(url);
|
|
136
|
+
return downloadImage(url, path__default.join(tmpCachePath, fileName));
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
const fileChooserPromise = page.waitForEvent("filechooser");
|
|
140
|
+
await page.getByRole("textbox").click();
|
|
141
|
+
const fileChooser = await fileChooserPromise;
|
|
142
|
+
await fileChooser.setFiles(images);
|
|
143
|
+
const titleInstance = page.locator(".input.titleInput input");
|
|
144
|
+
await titleInstance.click();
|
|
145
|
+
await titleInstance.fill(params.title);
|
|
146
|
+
const descInstance = page.locator("#post-textarea");
|
|
147
|
+
await descInstance.click();
|
|
148
|
+
await descInstance.fill(params.content);
|
|
149
|
+
const container = page.locator(".creator-container .content .scroll-content");
|
|
150
|
+
await container.focus();
|
|
151
|
+
await page.mouse.wheel(0, 500);
|
|
152
|
+
if (params.address) {
|
|
153
|
+
await selectAddress(
|
|
154
|
+
page.locator(".media-extension .address-input").filter({ hasText: "\u6DFB\u52A0\u5730\u70B9" }),
|
|
155
|
+
params.address
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
if (params.selfDeclaration) {
|
|
159
|
+
await page.locator(".declaration-wrapper").click();
|
|
160
|
+
const selfDeclarationInstance = page.locator(
|
|
161
|
+
".el-popper[aria-hidden=false] ul li[role=menuitem]"
|
|
162
|
+
);
|
|
163
|
+
if (params.selfDeclaration.type === "fictional-rendition") {
|
|
164
|
+
await selfDeclarationInstance.filter({ hasText: "\u865A\u6784\u6F14\u7ECE\uFF0C\u4EC5\u4F9B\u5A31\u4E50" }).click();
|
|
165
|
+
} else if (params.selfDeclaration.type === "ai-generated") {
|
|
166
|
+
await selfDeclarationInstance.filter({ hasText: "\u7B14\u8BB0\u542BAI\u5408\u6210\u5185\u5BB9" }).click();
|
|
167
|
+
} else if (params.selfDeclaration.type === "source-statement") {
|
|
168
|
+
await selfDeclarationInstance.filter({ hasText: "\u5185\u5BB9\u6765\u6E90\u58F0\u660E" }).click();
|
|
169
|
+
const selfDeclarationSecondaryMenuInstance = page.locator(".el-popper[aria-hidden=false] .el-cascader-menu").nth(1).locator("ul li[role=menuitem]");
|
|
170
|
+
await selfDeclarationSecondaryMenuInstance.first().waitFor();
|
|
171
|
+
if (params.selfDeclaration.childType === "self-labeling") {
|
|
172
|
+
await selfDeclarationSecondaryMenuInstance.filter({ hasText: "\u5DF2\u81EA\u4E3B\u6807\u6CE8" }).click();
|
|
173
|
+
} else if (params.selfDeclaration.childType === "self-shooting") {
|
|
174
|
+
const { shootingDate, shootingLocation } = params.selfDeclaration;
|
|
175
|
+
await selfDeclarationSecondaryMenuInstance.filter({ hasText: "\u81EA\u4E3B\u62CD\u6444" }).click();
|
|
176
|
+
const selfShootingPopup = page.locator(".el-overlay-dialog[aria-modal=true][role=dialog]").filter({ hasText: "\u81EA\u4E3B\u62CD\u6444" });
|
|
177
|
+
await selfShootingPopup.waitFor();
|
|
178
|
+
const hasCustomContent = shootingDate || shootingLocation;
|
|
179
|
+
if (shootingLocation) {
|
|
180
|
+
await selectAddress(
|
|
181
|
+
selfShootingPopup.locator(".address-input"),
|
|
182
|
+
shootingLocation
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
if (shootingDate) {
|
|
186
|
+
await selectDate(
|
|
187
|
+
selfShootingPopup.locator(".date-picker input"),
|
|
188
|
+
shootingDate
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
await selfShootingPopup.locator("footer button").filter({ hasText: hasCustomContent ? "\u786E\u8BA4" : "\u53D6\u6D88" }).click();
|
|
192
|
+
} else if (params.selfDeclaration.childType === "transshipment") {
|
|
193
|
+
await selfDeclarationSecondaryMenuInstance.filter({ hasText: "\u6765\u6E90\u8F6C\u8F7D" }).click();
|
|
194
|
+
const selfShootingPopup = page.locator(".el-overlay-dialog[aria-modal=true][role=dialog]").filter({ hasText: "\u6765\u6E90\u5A92\u4F53" });
|
|
195
|
+
await selfShootingPopup.waitFor();
|
|
196
|
+
const sourceMedia = params.selfDeclaration.sourceMedia;
|
|
197
|
+
if (sourceMedia) {
|
|
198
|
+
await selfShootingPopup.locator(".el-input input").fill(sourceMedia);
|
|
199
|
+
}
|
|
200
|
+
await selfShootingPopup.locator("footer button").filter({ hasText: sourceMedia ? "\u786E\u8BA4" : "\u53D6\u6D88" }).click();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const publicLabelInstance = page.locator("label").filter({ hasText: visibleRangeTexts[params.visibleRange] });
|
|
205
|
+
await publicLabelInstance.click();
|
|
206
|
+
const releaseTimeInstance = page.locator("label").filter({ hasText: params.isImmediatelyPublish ? "\u7ACB\u5373\u53D1\u5E03" : "\u5B9A\u65F6\u53D1\u5E03" });
|
|
207
|
+
await releaseTimeInstance.click();
|
|
208
|
+
if (params.scheduledPublish) {
|
|
209
|
+
await selectDate(".date-picker input", params.scheduledPublish);
|
|
210
|
+
}
|
|
211
|
+
const response = await new Promise((resolve) => {
|
|
212
|
+
const handleResponse = async (response2) => {
|
|
213
|
+
if (response2.url().includes("/web_api/sns/v2/note")) {
|
|
214
|
+
const jsonResponse = await response2.json();
|
|
215
|
+
page.off("response", handleResponse);
|
|
216
|
+
resolve(jsonResponse?.data?.id);
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
page.on("response", handleResponse);
|
|
220
|
+
page.locator(".submit .publishBtn").click();
|
|
221
|
+
});
|
|
222
|
+
await page.close();
|
|
223
|
+
return response;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const actions = { xiaohongshuPublish };
|
|
227
|
+
|
|
228
|
+
exports.actions = actions;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import https from 'node:https';
|
|
4
|
+
|
|
5
|
+
function ensureFile(filePath) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const dirPath = path.dirname(filePath);
|
|
8
|
+
fs.stat(dirPath, (err, stats) => {
|
|
9
|
+
if (err) {
|
|
10
|
+
if (err.code === "ENOENT") {
|
|
11
|
+
fs.mkdir(dirPath, { recursive: true }, (err2) => {
|
|
12
|
+
if (err2) {
|
|
13
|
+
return reject(err2);
|
|
14
|
+
}
|
|
15
|
+
createFile();
|
|
16
|
+
});
|
|
17
|
+
} else {
|
|
18
|
+
return reject(err);
|
|
19
|
+
}
|
|
20
|
+
} else if (stats.isDirectory()) {
|
|
21
|
+
checkFile();
|
|
22
|
+
} else {
|
|
23
|
+
reject(new Error(`${dirPath} is not a directory`));
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
function checkFile() {
|
|
27
|
+
fs.stat(filePath, (err, stats) => {
|
|
28
|
+
if (err) {
|
|
29
|
+
if (err.code === "ENOENT") {
|
|
30
|
+
createFile();
|
|
31
|
+
} else {
|
|
32
|
+
reject(err);
|
|
33
|
+
}
|
|
34
|
+
} else if (stats.isFile()) {
|
|
35
|
+
resolve();
|
|
36
|
+
} else {
|
|
37
|
+
reject(new Error(`${filePath} is not a file`));
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
function createFile() {
|
|
42
|
+
fs.writeFile(filePath, "", (err) => {
|
|
43
|
+
if (err) {
|
|
44
|
+
reject(err);
|
|
45
|
+
} else {
|
|
46
|
+
resolve();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function downloadImage(url, savePath) {
|
|
53
|
+
await ensureFile(savePath);
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
https.get(url, (response) => {
|
|
56
|
+
if (response.statusCode === 200) {
|
|
57
|
+
const fileStream = fs.createWriteStream(savePath);
|
|
58
|
+
response.pipe(fileStream);
|
|
59
|
+
fileStream.on("finish", () => {
|
|
60
|
+
fileStream.close();
|
|
61
|
+
console.log("\u4E0B\u8F7D\u5B8C\u6210\uFF0C\u6587\u4EF6\u5DF2\u4FDD\u5B58\u81F3:", savePath);
|
|
62
|
+
resolve(savePath);
|
|
63
|
+
});
|
|
64
|
+
} else {
|
|
65
|
+
console.log("\u56FE\u7247\u4E0B\u8F7D\u5931\u8D25:", response.statusCode);
|
|
66
|
+
response.resume();
|
|
67
|
+
reject();
|
|
68
|
+
}
|
|
69
|
+
}).on("error", (error) => {
|
|
70
|
+
console.error("\u8BF7\u6C42\u56FE\u7247\u65F6\u53D1\u751F\u9519\u8BEF:", error.message);
|
|
71
|
+
reject();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function getFilenameFromUrl(imageUrl) {
|
|
76
|
+
const parsedUrl = new URL(imageUrl);
|
|
77
|
+
return path.basename(parsedUrl.pathname);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const visibleRangeTexts = {
|
|
81
|
+
public: "\u516C\u5F00",
|
|
82
|
+
private: "\u79C1\u5BC6"
|
|
83
|
+
};
|
|
84
|
+
const xiaohongshuPublish = async (task, params) => {
|
|
85
|
+
task.logger.info("\u5F00\u59CB\u5C0F\u7EA2\u4E66\u53D1\u5E03");
|
|
86
|
+
const commonCookies = {
|
|
87
|
+
path: "/",
|
|
88
|
+
sameSite: "lax",
|
|
89
|
+
secure: false,
|
|
90
|
+
domain: "xiaohongshu.com",
|
|
91
|
+
url: "https://creator.xiaohongshu.com",
|
|
92
|
+
httpOnly: true
|
|
93
|
+
};
|
|
94
|
+
const page = await task.createPage({
|
|
95
|
+
show: task.debug,
|
|
96
|
+
url: params.url || "https://creator.xiaohongshu.com/publish/publish",
|
|
97
|
+
cookies: params.cookies?.map((it) => Object.assign(commonCookies, it)) || []
|
|
98
|
+
});
|
|
99
|
+
const tmpCachePath = task.getTmpPath();
|
|
100
|
+
const selectAddress = async (selector, address) => {
|
|
101
|
+
const instance = typeof selector === "string" ? page.locator(selector) : selector;
|
|
102
|
+
await instance.click();
|
|
103
|
+
await instance.locator("input").fill(address);
|
|
104
|
+
const poperInstance = page.locator(
|
|
105
|
+
'.d-popover:not([style*="display: none"]) .d-options .d-grid-item'
|
|
106
|
+
);
|
|
107
|
+
await poperInstance.first().waitFor();
|
|
108
|
+
await poperInstance.first().click();
|
|
109
|
+
};
|
|
110
|
+
const selectDate = async (selector, date) => {
|
|
111
|
+
const instance = typeof selector === "string" ? page.locator(selector) : selector;
|
|
112
|
+
await instance.click();
|
|
113
|
+
await instance.fill(date);
|
|
114
|
+
await instance.blur();
|
|
115
|
+
};
|
|
116
|
+
await page.waitForSelector("#CreatorPlatform", { state: "visible" }).catch(() => {
|
|
117
|
+
throw new Error("\u767B\u5F55\u5931\u8D25");
|
|
118
|
+
});
|
|
119
|
+
await page.locator("#content-area .menu-container .publish-video a").click().catch(() => {
|
|
120
|
+
throw new Error("\u672A\u627E\u5230\u53D1\u5E03\u7B14\u8BB0\u6309\u94AE");
|
|
121
|
+
});
|
|
122
|
+
await page.locator(".creator-container .header .title").filter({ hasText: /^上传图文$/ }).click().catch(() => {
|
|
123
|
+
throw new Error("\u672A\u627E\u5230\u4E0A\u4F20\u56FE\u6587\u6309\u94AE");
|
|
124
|
+
});
|
|
125
|
+
const images = await Promise.all(
|
|
126
|
+
params.banners.map((url) => {
|
|
127
|
+
const fileName = getFilenameFromUrl(url);
|
|
128
|
+
return downloadImage(url, path.join(tmpCachePath, fileName));
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
const fileChooserPromise = page.waitForEvent("filechooser");
|
|
132
|
+
await page.getByRole("textbox").click();
|
|
133
|
+
const fileChooser = await fileChooserPromise;
|
|
134
|
+
await fileChooser.setFiles(images);
|
|
135
|
+
const titleInstance = page.locator(".input.titleInput input");
|
|
136
|
+
await titleInstance.click();
|
|
137
|
+
await titleInstance.fill(params.title);
|
|
138
|
+
const descInstance = page.locator("#post-textarea");
|
|
139
|
+
await descInstance.click();
|
|
140
|
+
await descInstance.fill(params.content);
|
|
141
|
+
const container = page.locator(".creator-container .content .scroll-content");
|
|
142
|
+
await container.focus();
|
|
143
|
+
await page.mouse.wheel(0, 500);
|
|
144
|
+
if (params.address) {
|
|
145
|
+
await selectAddress(
|
|
146
|
+
page.locator(".media-extension .address-input").filter({ hasText: "\u6DFB\u52A0\u5730\u70B9" }),
|
|
147
|
+
params.address
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
if (params.selfDeclaration) {
|
|
151
|
+
await page.locator(".declaration-wrapper").click();
|
|
152
|
+
const selfDeclarationInstance = page.locator(
|
|
153
|
+
".el-popper[aria-hidden=false] ul li[role=menuitem]"
|
|
154
|
+
);
|
|
155
|
+
if (params.selfDeclaration.type === "fictional-rendition") {
|
|
156
|
+
await selfDeclarationInstance.filter({ hasText: "\u865A\u6784\u6F14\u7ECE\uFF0C\u4EC5\u4F9B\u5A31\u4E50" }).click();
|
|
157
|
+
} else if (params.selfDeclaration.type === "ai-generated") {
|
|
158
|
+
await selfDeclarationInstance.filter({ hasText: "\u7B14\u8BB0\u542BAI\u5408\u6210\u5185\u5BB9" }).click();
|
|
159
|
+
} else if (params.selfDeclaration.type === "source-statement") {
|
|
160
|
+
await selfDeclarationInstance.filter({ hasText: "\u5185\u5BB9\u6765\u6E90\u58F0\u660E" }).click();
|
|
161
|
+
const selfDeclarationSecondaryMenuInstance = page.locator(".el-popper[aria-hidden=false] .el-cascader-menu").nth(1).locator("ul li[role=menuitem]");
|
|
162
|
+
await selfDeclarationSecondaryMenuInstance.first().waitFor();
|
|
163
|
+
if (params.selfDeclaration.childType === "self-labeling") {
|
|
164
|
+
await selfDeclarationSecondaryMenuInstance.filter({ hasText: "\u5DF2\u81EA\u4E3B\u6807\u6CE8" }).click();
|
|
165
|
+
} else if (params.selfDeclaration.childType === "self-shooting") {
|
|
166
|
+
const { shootingDate, shootingLocation } = params.selfDeclaration;
|
|
167
|
+
await selfDeclarationSecondaryMenuInstance.filter({ hasText: "\u81EA\u4E3B\u62CD\u6444" }).click();
|
|
168
|
+
const selfShootingPopup = page.locator(".el-overlay-dialog[aria-modal=true][role=dialog]").filter({ hasText: "\u81EA\u4E3B\u62CD\u6444" });
|
|
169
|
+
await selfShootingPopup.waitFor();
|
|
170
|
+
const hasCustomContent = shootingDate || shootingLocation;
|
|
171
|
+
if (shootingLocation) {
|
|
172
|
+
await selectAddress(
|
|
173
|
+
selfShootingPopup.locator(".address-input"),
|
|
174
|
+
shootingLocation
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (shootingDate) {
|
|
178
|
+
await selectDate(
|
|
179
|
+
selfShootingPopup.locator(".date-picker input"),
|
|
180
|
+
shootingDate
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
await selfShootingPopup.locator("footer button").filter({ hasText: hasCustomContent ? "\u786E\u8BA4" : "\u53D6\u6D88" }).click();
|
|
184
|
+
} else if (params.selfDeclaration.childType === "transshipment") {
|
|
185
|
+
await selfDeclarationSecondaryMenuInstance.filter({ hasText: "\u6765\u6E90\u8F6C\u8F7D" }).click();
|
|
186
|
+
const selfShootingPopup = page.locator(".el-overlay-dialog[aria-modal=true][role=dialog]").filter({ hasText: "\u6765\u6E90\u5A92\u4F53" });
|
|
187
|
+
await selfShootingPopup.waitFor();
|
|
188
|
+
const sourceMedia = params.selfDeclaration.sourceMedia;
|
|
189
|
+
if (sourceMedia) {
|
|
190
|
+
await selfShootingPopup.locator(".el-input input").fill(sourceMedia);
|
|
191
|
+
}
|
|
192
|
+
await selfShootingPopup.locator("footer button").filter({ hasText: sourceMedia ? "\u786E\u8BA4" : "\u53D6\u6D88" }).click();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const publicLabelInstance = page.locator("label").filter({ hasText: visibleRangeTexts[params.visibleRange] });
|
|
197
|
+
await publicLabelInstance.click();
|
|
198
|
+
const releaseTimeInstance = page.locator("label").filter({ hasText: params.isImmediatelyPublish ? "\u7ACB\u5373\u53D1\u5E03" : "\u5B9A\u65F6\u53D1\u5E03" });
|
|
199
|
+
await releaseTimeInstance.click();
|
|
200
|
+
if (params.scheduledPublish) {
|
|
201
|
+
await selectDate(".date-picker input", params.scheduledPublish);
|
|
202
|
+
}
|
|
203
|
+
const response = await new Promise((resolve) => {
|
|
204
|
+
const handleResponse = async (response2) => {
|
|
205
|
+
if (response2.url().includes("/web_api/sns/v2/note")) {
|
|
206
|
+
const jsonResponse = await response2.json();
|
|
207
|
+
page.off("response", handleResponse);
|
|
208
|
+
resolve(jsonResponse?.data?.id);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
page.on("response", handleResponse);
|
|
212
|
+
page.locator(".submit .publishBtn").click();
|
|
213
|
+
});
|
|
214
|
+
await page.close();
|
|
215
|
+
return response;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const actions = { xiaohongshuPublish };
|
|
219
|
+
|
|
220
|
+
export { actions };
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@iflyrpa/actions",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"src"
|
|
11
|
+
],
|
|
12
|
+
"author": "bijinfeng",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"unbuild": "^2.0.0",
|
|
16
|
+
"@iflyrpa/share": "0.0.1"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "unbuild",
|
|
20
|
+
"dev": "unbuild --stub"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import type {
|
|
3
|
+
AutomateTask,
|
|
4
|
+
CommonAction,
|
|
5
|
+
CookieMap,
|
|
6
|
+
CookiesSetDetails,
|
|
7
|
+
Locator,
|
|
8
|
+
Response,
|
|
9
|
+
} from "@iflyrpa/share";
|
|
10
|
+
import { downloadImage, getFilenameFromUrl } from "@iflyrpa/share";
|
|
11
|
+
|
|
12
|
+
interface FictionalRendition {
|
|
13
|
+
type: "fictional-rendition"; // 虚拟演绎,仅供娱乐
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface AIGenerated {
|
|
17
|
+
type: "ai-generated"; // 笔记含AI合成内容
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface SourceStatement {
|
|
21
|
+
type: "source-statement"; // 内容来源声明
|
|
22
|
+
childType: "self-labeling" | "self-shooting" | "transshipment"; // 已自主标注 / 自助拍摄 / 转载
|
|
23
|
+
shootingLocation?: string; // 拍摄地点
|
|
24
|
+
shootingDate?: string; // 拍摄日期
|
|
25
|
+
sourceMedia?: string; // 来源媒体
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
type SelfDeclaration = FictionalRendition | AIGenerated | SourceStatement;
|
|
29
|
+
|
|
30
|
+
export interface ActiomCommonParams {
|
|
31
|
+
url?: string;
|
|
32
|
+
cookies?: CookieMap;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface XiaohongshuPublishParams
|
|
36
|
+
extends Omit<ActiomCommonParams, "cookies"> {
|
|
37
|
+
cookies: Partial<CookiesSetDetails>[];
|
|
38
|
+
banners: string[]; // 图片
|
|
39
|
+
title: string; // 标题
|
|
40
|
+
content: string; // 正文
|
|
41
|
+
address?: string; // 地点
|
|
42
|
+
selfDeclaration?: SelfDeclaration; // 自主声明
|
|
43
|
+
visibleRange: "public" | "private"; // 可见范围
|
|
44
|
+
isImmediatelyPublish?: boolean; // 是否立即发布
|
|
45
|
+
scheduledPublish?: string; // 定时发布时间
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const visibleRangeTexts: Record<
|
|
49
|
+
XiaohongshuPublishParams["visibleRange"],
|
|
50
|
+
string
|
|
51
|
+
> = {
|
|
52
|
+
public: "公开",
|
|
53
|
+
private: "私密",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const xiaohongshuPublish: CommonAction<
|
|
57
|
+
XiaohongshuPublishParams,
|
|
58
|
+
string
|
|
59
|
+
> = async (task, params) => {
|
|
60
|
+
task.logger.info("开始小红书发布");
|
|
61
|
+
|
|
62
|
+
const commonCookies: CookieMap[number] = {
|
|
63
|
+
path: "/",
|
|
64
|
+
sameSite: "lax",
|
|
65
|
+
secure: false,
|
|
66
|
+
domain: "xiaohongshu.com",
|
|
67
|
+
url: "https://creator.xiaohongshu.com",
|
|
68
|
+
httpOnly: true,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const page = await task.createPage({
|
|
72
|
+
show: task.debug,
|
|
73
|
+
url: params.url || "https://creator.xiaohongshu.com/publish/publish",
|
|
74
|
+
cookies:
|
|
75
|
+
params.cookies?.map((it) => Object.assign(commonCookies, it)) || [],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const tmpCachePath = task.getTmpPath();
|
|
79
|
+
|
|
80
|
+
// 通用的选择地点
|
|
81
|
+
const selectAddress = async (selector: Locator, address: string) => {
|
|
82
|
+
const instance =
|
|
83
|
+
typeof selector === "string" ? page.locator(selector) : selector;
|
|
84
|
+
await instance.click();
|
|
85
|
+
await instance.locator("input").fill(address);
|
|
86
|
+
const poperInstance = page.locator(
|
|
87
|
+
'.d-popover:not([style*="display: none"]) .d-options .d-grid-item',
|
|
88
|
+
);
|
|
89
|
+
await poperInstance.first().waitFor();
|
|
90
|
+
await poperInstance.first().click();
|
|
91
|
+
};
|
|
92
|
+
// 通用的选择日期
|
|
93
|
+
const selectDate = async (selector: string | Locator, date: string) => {
|
|
94
|
+
const instance =
|
|
95
|
+
typeof selector === "string" ? page.locator(selector) : selector;
|
|
96
|
+
await instance.click();
|
|
97
|
+
await instance.fill(date);
|
|
98
|
+
await instance.blur();
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
// 自动化脚本
|
|
102
|
+
// ----------------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
// 等待登录成功
|
|
105
|
+
await page
|
|
106
|
+
.waitForSelector("#CreatorPlatform", { state: "visible" })
|
|
107
|
+
.catch(() => {
|
|
108
|
+
throw new Error("登录失败");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// 跳转到发布页面
|
|
112
|
+
await page
|
|
113
|
+
.locator("#content-area .menu-container .publish-video a")
|
|
114
|
+
.click()
|
|
115
|
+
.catch(() => {
|
|
116
|
+
throw new Error("未找到发布笔记按钮");
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// 点击上传图文
|
|
120
|
+
await page
|
|
121
|
+
.locator(".creator-container .header .title")
|
|
122
|
+
.filter({ hasText: /^上传图文$/ })
|
|
123
|
+
.click()
|
|
124
|
+
.catch(() => {
|
|
125
|
+
throw new Error("未找到上传图文按钮");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// 获取待上传图片的本地路径
|
|
129
|
+
const images = await Promise.all(
|
|
130
|
+
params.banners.map((url) => {
|
|
131
|
+
const fileName = getFilenameFromUrl(url);
|
|
132
|
+
return downloadImage(url, path.join(tmpCachePath, fileName));
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// 选择文件
|
|
137
|
+
const fileChooserPromise = page.waitForEvent("filechooser");
|
|
138
|
+
await page.getByRole("textbox").click();
|
|
139
|
+
const fileChooser = await fileChooserPromise;
|
|
140
|
+
await fileChooser.setFiles(images);
|
|
141
|
+
|
|
142
|
+
// 填写标题
|
|
143
|
+
const titleInstance = page.locator(".input.titleInput input");
|
|
144
|
+
await titleInstance.click();
|
|
145
|
+
await titleInstance.fill(params.title);
|
|
146
|
+
|
|
147
|
+
// 填写正文
|
|
148
|
+
const descInstance = page.locator("#post-textarea");
|
|
149
|
+
await descInstance.click();
|
|
150
|
+
await descInstance.fill(params.content);
|
|
151
|
+
|
|
152
|
+
// 滚动到底部
|
|
153
|
+
const container = page.locator(".creator-container .content .scroll-content");
|
|
154
|
+
await container.focus();
|
|
155
|
+
await page.mouse.wheel(0, 500); // 向下滚动 500 像素
|
|
156
|
+
|
|
157
|
+
if (params.address) {
|
|
158
|
+
// 填写地点
|
|
159
|
+
await selectAddress(
|
|
160
|
+
page
|
|
161
|
+
.locator(".media-extension .address-input")
|
|
162
|
+
.filter({ hasText: "添加地点" }),
|
|
163
|
+
params.address,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (params.selfDeclaration) {
|
|
168
|
+
// 自主声明
|
|
169
|
+
await page.locator(".declaration-wrapper").click();
|
|
170
|
+
const selfDeclarationInstance = page.locator(
|
|
171
|
+
".el-popper[aria-hidden=false] ul li[role=menuitem]",
|
|
172
|
+
);
|
|
173
|
+
if (params.selfDeclaration.type === "fictional-rendition") {
|
|
174
|
+
await selfDeclarationInstance
|
|
175
|
+
.filter({ hasText: "虚构演绎,仅供娱乐" })
|
|
176
|
+
.click();
|
|
177
|
+
} else if (params.selfDeclaration.type === "ai-generated") {
|
|
178
|
+
await selfDeclarationInstance
|
|
179
|
+
.filter({ hasText: "笔记含AI合成内容" })
|
|
180
|
+
.click();
|
|
181
|
+
} else if (params.selfDeclaration.type === "source-statement") {
|
|
182
|
+
await selfDeclarationInstance.filter({ hasText: "内容来源声明" }).click();
|
|
183
|
+
// 内容来源声明二级菜单
|
|
184
|
+
const selfDeclarationSecondaryMenuInstance = page
|
|
185
|
+
.locator(".el-popper[aria-hidden=false] .el-cascader-menu")
|
|
186
|
+
.nth(1)
|
|
187
|
+
.locator("ul li[role=menuitem]");
|
|
188
|
+
// 等待元素出现
|
|
189
|
+
await selfDeclarationSecondaryMenuInstance.first().waitFor();
|
|
190
|
+
if (params.selfDeclaration.childType === "self-labeling") {
|
|
191
|
+
await selfDeclarationSecondaryMenuInstance
|
|
192
|
+
.filter({ hasText: "已自主标注" })
|
|
193
|
+
.click();
|
|
194
|
+
} else if (params.selfDeclaration.childType === "self-shooting") {
|
|
195
|
+
const { shootingDate, shootingLocation } = params.selfDeclaration;
|
|
196
|
+
await selfDeclarationSecondaryMenuInstance
|
|
197
|
+
.filter({ hasText: "自主拍摄" })
|
|
198
|
+
.click();
|
|
199
|
+
|
|
200
|
+
// 自主拍摄弹窗
|
|
201
|
+
const selfShootingPopup = page
|
|
202
|
+
.locator(".el-overlay-dialog[aria-modal=true][role=dialog]")
|
|
203
|
+
.filter({ hasText: "自主拍摄" });
|
|
204
|
+
await selfShootingPopup.waitFor();
|
|
205
|
+
|
|
206
|
+
const hasCustomContent = shootingDate || shootingLocation;
|
|
207
|
+
|
|
208
|
+
// 选择拍摄地点
|
|
209
|
+
if (shootingLocation) {
|
|
210
|
+
await selectAddress(
|
|
211
|
+
selfShootingPopup.locator(".address-input"),
|
|
212
|
+
shootingLocation,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
// 选择拍摄日期
|
|
216
|
+
if (shootingDate) {
|
|
217
|
+
await selectDate(
|
|
218
|
+
selfShootingPopup.locator(".date-picker input"),
|
|
219
|
+
shootingDate,
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await selfShootingPopup
|
|
224
|
+
.locator("footer button")
|
|
225
|
+
.filter({ hasText: hasCustomContent ? "确认" : "取消" })
|
|
226
|
+
.click();
|
|
227
|
+
} else if (params.selfDeclaration.childType === "transshipment") {
|
|
228
|
+
await selfDeclarationSecondaryMenuInstance
|
|
229
|
+
.filter({ hasText: "来源转载" })
|
|
230
|
+
.click();
|
|
231
|
+
// 来源媒体弹窗
|
|
232
|
+
const selfShootingPopup = page
|
|
233
|
+
.locator(".el-overlay-dialog[aria-modal=true][role=dialog]")
|
|
234
|
+
.filter({ hasText: "来源媒体" });
|
|
235
|
+
await selfShootingPopup.waitFor();
|
|
236
|
+
|
|
237
|
+
const sourceMedia = params.selfDeclaration.sourceMedia;
|
|
238
|
+
if (sourceMedia) {
|
|
239
|
+
await selfShootingPopup.locator(".el-input input").fill(sourceMedia);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
await selfShootingPopup
|
|
243
|
+
.locator("footer button")
|
|
244
|
+
.filter({ hasText: sourceMedia ? "确认" : "取消" })
|
|
245
|
+
.click();
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 可见范围
|
|
251
|
+
const publicLabelInstance = page
|
|
252
|
+
.locator("label")
|
|
253
|
+
.filter({ hasText: visibleRangeTexts[params.visibleRange] });
|
|
254
|
+
await publicLabelInstance.click();
|
|
255
|
+
|
|
256
|
+
const releaseTimeInstance = page
|
|
257
|
+
.locator("label")
|
|
258
|
+
.filter({ hasText: params.isImmediatelyPublish ? "立即发布" : "定时发布" });
|
|
259
|
+
await releaseTimeInstance.click();
|
|
260
|
+
|
|
261
|
+
if (params.scheduledPublish) {
|
|
262
|
+
// 自定义发布时间
|
|
263
|
+
await selectDate(".date-picker input", params.scheduledPublish);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const response = await new Promise<string>((resolve) => {
|
|
267
|
+
// 定义响应处理函数
|
|
268
|
+
const handleResponse = async (response: Response) => {
|
|
269
|
+
if (response.url().includes("/web_api/sns/v2/note")) {
|
|
270
|
+
const jsonResponse = await response.json();
|
|
271
|
+
page.off("response", handleResponse);
|
|
272
|
+
resolve(jsonResponse?.data?.id);
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// 开始监听响应事件
|
|
277
|
+
page.on("response", handleResponse);
|
|
278
|
+
|
|
279
|
+
page.locator(".submit .publishBtn").click();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// 关闭页面
|
|
283
|
+
await page.close();
|
|
284
|
+
|
|
285
|
+
return response;
|
|
286
|
+
};
|