Skip to content

Commit b9c487e

Browse files
committed
copy pasting works
1 parent 0657ab1 commit b9c487e

File tree

4 files changed

+202
-42
lines changed

4 files changed

+202
-42
lines changed

electron/ipcHandlers.js

Lines changed: 147 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ module.exports = {
99
try {
1010
if (data.channel === "xiaohongshu") {
1111
await publishXiaohongshu(data);
12+
} else if (data.channel === "bilibili") {
13+
await publishBilibili(data);
1214
}
1315
} catch (error) {
1416
console.error("Error in publish post:", error);
@@ -35,12 +37,7 @@ let browser;
3537
*/
3638
async function publishXiaohongshu(data) {
3739
if (!browser) {
38-
browser = await chromium.launchPersistentContext(
39-
path.join(userDataDir, "browser_data"),
40-
{
41-
headless: false,
42-
}
43-
);
40+
browser = await launchBrowser();
4441
}
4542
const page = await browser.newPage();
4643
try {
@@ -76,37 +73,157 @@ async function publishXiaohongshu(data) {
7673
// Wait for upload progress to appear
7774
await page.waitForSelector(".uploading", { timeout: 10000 });
7875

79-
// Wait for upload to complete (100%)
80-
while (true) {
81-
const progressText = await page.$eval(
82-
".uploading .stage",
83-
(el) => el.textContent
84-
);
85-
// Match the text that contains "上传中" followed by a percentage
86-
const progressMatch = progressText?.match(/\s*(\d+)%/);
87-
88-
if (!progressMatch) {
89-
throw new Error("Could not find upload progress percentage");
90-
}
91-
92-
const progress = parseInt(progressMatch[1]);
93-
console.log(`⏳Upload progress: ${progress}%`);
76+
// Wait a bit more to ensure the upload is fully processed
77+
await page.waitForTimeout(1000);
9478

95-
if (progress === 100) {
96-
console.log("Upload completed!");
97-
break;
98-
}
79+
const [content, uploadComplete] = await Promise.all([
80+
fillXiaohongshuContent(page, data.title, data.content),
81+
waitForXiaohongshuUploadComplete(page),
82+
]);
9983

100-
// Wait a bit before checking again
101-
await page.waitForTimeout(1000);
102-
}
84+
console.log("🦄🦄uploadComplete:", uploadComplete);
10385

104-
// Wait a bit more to ensure the upload is fully processed
105-
await page.waitForTimeout(5000);
86+
// Wait a bit to ensure content is properly set
87+
await page.waitForTimeout(1000);
10688
} catch (error) {
10789
console.error("Error during video upload:", error);
10890
throw error;
10991
} finally {
11092
// await page.close();
11193
}
11294
}
95+
96+
async function fillXiaohongshuContent(page, title, content) {
97+
// fill in title
98+
await page.waitForSelector(
99+
'input.d-text[placeholder="填写标题会有更多赞哦~"]',
100+
{ timeout: 10000 } // Increase timeout if necessary
101+
);
102+
103+
// Focus on the input field
104+
await page.focus('input.d-text[placeholder="填写标题会有更多赞哦~"]');
105+
await page.fill(
106+
'input.d-text[placeholder="填写标题会有更多赞哦~"]',
107+
title || ""
108+
);
109+
// fill in title by typing
110+
// await page.keyboard.type(title || "", { delay: 100 });
111+
112+
// Fill in the content by clipboard copying pasting
113+
await page.evaluate(async (text) => {
114+
await navigator.clipboard.writeText(text);
115+
}, content || "");
116+
await page.waitForTimeout(1000);
117+
await page.waitForSelector(".ql-editor");
118+
await page.focus(".ql-editor");
119+
// await page.keyboard.type(content || "", { delay: 100 });
120+
console.log("platform:", process.platform);
121+
await page.keyboard.press(
122+
process.platform === "darwin" ? "Meta+V" : "Control+V"
123+
);
124+
await page.waitForTimeout(2000);
125+
// add hashtags
126+
const tags = getTagsFromContent(content || "");
127+
console.log("🦄🦄tags:", tags);
128+
for (const tag of tags) {
129+
await page.keyboard.type("#");
130+
await page.waitForTimeout(100);
131+
await page.keyboard.type(tag, { delay: 300 });
132+
await page.waitForTimeout(1000);
133+
await page.keyboard.press("Enter");
134+
}
135+
136+
await page.waitForTimeout(1000);
137+
138+
return true;
139+
}
140+
141+
async function waitForXiaohongshuUploadComplete(page) {
142+
// Wait for upload to complete (100%)
143+
while (true) {
144+
const progressText = await page.evaluate(() => {
145+
return document.querySelector(".stage")?.textContent || "";
146+
});
147+
148+
// Check if the text contains "上传成功" (Upload Successful)
149+
if (progressText.includes("上传成功")) {
150+
console.log("Upload completed!");
151+
return true;
152+
}
153+
154+
// Match the text that contains "上传中" followed by a percentage
155+
const progressMatch = progressText.match(/\s*(\d+)%/);
156+
157+
if (!progressMatch) {
158+
throw new Error("Could not find upload progress percentage");
159+
}
160+
161+
const progress = parseInt(progressMatch[1]);
162+
console.log(`⏳Upload progress: ${progress}%`);
163+
164+
if (progress === 99) {
165+
console.log("Upload completed!");
166+
break;
167+
}
168+
169+
// Wait a bit before checking again
170+
await page.waitForTimeout(3000);
171+
}
172+
return false;
173+
}
174+
175+
async function launchBrowser() {
176+
return await chromium.launchPersistentContext(
177+
path.join(userDataDir, "browser_data"),
178+
{
179+
headless: false,
180+
channel: "chrome", // Use real Chrome
181+
}
182+
);
183+
}
184+
185+
/**
186+
* @param {PublishData} data - The data for publishing the post
187+
*/
188+
189+
async function publishBilibili(data) {
190+
if (!browser) {
191+
browser = await launchBrowser();
192+
}
193+
const page = await browser.newPage();
194+
try {
195+
await page.goto("https://member.bilibili.com/platform/upload/video/frame");
196+
await page.waitForTimeout(3000); // Let Vue UI settle
197+
198+
// Ensure the "上传视频" button is visible and clickable
199+
const uploadButton = await page.waitForSelector(".bcc-upload-wrapper", {
200+
timeout: 10000,
201+
state: "visible",
202+
});
203+
204+
// Listen for the file chooser BEFORE clicking
205+
const [fileChooser] = await Promise.all([
206+
page.waitForEvent("filechooser"),
207+
uploadButton.click(), // This triggers file picker
208+
]);
209+
210+
// Use the filechooser to set your file
211+
await fileChooser.setFiles(data.video);
212+
213+
console.log("File selected via file chooser");
214+
} catch (err) {
215+
console.error("Upload error:", err);
216+
throw err;
217+
}
218+
}
219+
220+
/**
221+
* @param {string} content - The content of the post
222+
* @returns {string[]} - The tags of the post
223+
*/
224+
function getTagsFromContent(content) {
225+
const tags = content.match(/#(\w+)/g);
226+
const ret = tags ? tags.map((tag) => tag.slice(1)) : [];
227+
console.log("🦄🦄ret:", ret);
228+
return ret;
229+
}

package-lock.json

Lines changed: 48 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
"electron-builder": "^24.0.0"
6969
},
7070
"dependencies": {
71-
"playwright": "^1.52.0"
71+
"playwright": "^1.52.0",
72+
"playwright-extra": "^4.3.6",
73+
"playwright-extra-plugin-stealth": "^0.0.1"
7274
}
7375
}

react/src/PostEditor.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,10 @@ export default function PostEditor({
239239
toast.error("Failed to pick video");
240240
}
241241
};
242-
const publishPost = async () => {
242+
const publishPost = async (platformId: string) => {
243243
const result = await window.electronAPI.publishPost({
244-
channel: "xiaohongshu",
245-
title: "New post",
244+
channel: platformId as any,
245+
title: editorTitle,
246246
content: editorContent,
247247
images: mediaFiles
248248
.filter((file) => file.type == "image")
@@ -283,7 +283,7 @@ export default function PostEditor({
283283
<DropdownMenuItem
284284
key={platform.name}
285285
className="text-base font-semibold"
286-
onClick={publishPost}
286+
onClick={() => publishPost(platform.id)}
287287
>
288288
<Checkbox checked className="mr-3" />
289289
<img

0 commit comments

Comments
 (0)