Skip to content

Commit cb7ebf2

Browse files
committed
feat(zhihu): improve dynamic content publishing with enhanced image upload and button interaction
- Refactored image upload process to directly paste images into the editor. - Updated button selection logic to support both "写想法" and "发想法" buttons. - Improved event dispatching for input and change events to ensure proper content handling. - Added loading checks for image uploads to enhance reliability before publishing.
1 parent fad2715 commit cb7ebf2

File tree

1 file changed

+67
-114
lines changed

1 file changed

+67
-114
lines changed

src/sync/dynamic/zhihu.ts

Lines changed: 67 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -31,53 +31,38 @@ export async function DynamicZhihu(data: SyncData) {
3131
});
3232
}
3333

34-
async function uploadFiles(files: File[]) {
35-
const fileInput = (await waitForElement('input[type="file"][accept="image/*"]')) as HTMLInputElement;
36-
if (!fileInput) {
37-
console.error('未找到文件输入元素');
38-
return;
39-
}
40-
41-
const dataTransfer = new DataTransfer();
42-
for (const file of files) {
43-
if (file.type.startsWith('image/')) {
44-
dataTransfer.items.add(file);
45-
} else {
46-
console.warn(`跳过非图片文件: ${file.name}`);
47-
}
48-
}
49-
50-
fileInput.files = dataTransfer.files;
51-
fileInput.dispatchEvent(new Event('change', { bubbles: true }));
52-
fileInput.dispatchEvent(new Event('input', { bubbles: true }));
53-
54-
console.debug('文件上传操作完成');
55-
}
56-
5734
try {
58-
// 等待并点击"写想法"元素
59-
await waitForElement('div.GlobalWriteV2-topTitle');
60-
const writeThoughtButton = Array.from(document.querySelectorAll('div.GlobalWriteV2-topTitle')).find(
61-
(el) => el.textContent?.includes('写想法'),
35+
await waitForElement('input');
36+
await new Promise((resolve) => setTimeout(resolve, 1000));
37+
38+
// 查找并点击"写想法"或"发想法"按钮
39+
const buttons = document.querySelectorAll('button');
40+
const postButton = Array.from(buttons).find(
41+
(el) => el.textContent?.includes('写想法') || el.textContent?.includes('发想法'),
6242
);
6343

64-
if (!writeThoughtButton) {
44+
if (!postButton) {
6545
console.debug('未找到"写想法"元素');
6646
return;
6747
}
6848

69-
(writeThoughtButton as HTMLElement).click();
49+
console.debug('postButton', postButton);
50+
postButton.click();
7051
await new Promise((resolve) => setTimeout(resolve, 1000));
7152

72-
// 填写标题(如果有)
73-
const titleInput = (await waitForElement('textarea[placeholder="添加标题(选填)"]')) as HTMLTextAreaElement;
53+
// 等待并填写标题
54+
await waitForElement('textarea[placeholder="添加标题(选填)"]');
55+
const titleInput = document.querySelector('textarea[placeholder="添加标题(选填)"]') as HTMLTextAreaElement;
56+
console.debug('titleInput', titleInput);
7457
if (titleInput && title) {
7558
titleInput.value = title;
7659
titleInput.dispatchEvent(new Event('input', { bubbles: true }));
60+
titleInput.dispatchEvent(new Event('change', { bubbles: true }));
7761
}
7862

79-
// 填写内容
80-
const editorElement = (await waitForElement('div[data-contents="true"]')) as HTMLDivElement;
63+
// 查找编辑器并填写内容
64+
const editorElement = document.querySelector('div[data-contents="true"]') as HTMLDivElement;
65+
console.debug('qlEditor', editorElement);
8166
if (!editorElement) {
8267
console.debug('未找到编辑器元素');
8368
return;
@@ -89,98 +74,66 @@ export async function DynamicZhihu(data: SyncData) {
8974
cancelable: true,
9075
clipboardData: new DataTransfer(),
9176
});
92-
pasteEvent.clipboardData.setData('text/plain', content || '');
77+
pasteEvent.clipboardData?.setData('text/plain', content || '');
9378
editorElement.dispatchEvent(pasteEvent);
94-
editorElement.dispatchEvent(new Event('input', { bubbles: true }));
95-
editorElement.dispatchEvent(new Event('change', { bubbles: true }));
96-
9779
await new Promise((resolve) => setTimeout(resolve, 1000));
9880

9981
// 处理图片上传
10082
if (images && images.length > 0) {
101-
const sendButton = Array.from(document.querySelectorAll('button')).find((el) => el.textContent?.includes('发布'));
102-
103-
if (sendButton) {
104-
const uploadButton = sendButton.parentElement?.previousElementSibling?.children[1] as HTMLElement;
105-
if (uploadButton) {
106-
uploadButton.click();
107-
await new Promise((resolve) => setTimeout(resolve, 1000));
108-
109-
await waitForElement('input[type="file"][accept="image/*"]');
110-
const fileInput = document.querySelector('input[type="file"][accept="image/*"]') as HTMLInputElement;
111-
if (fileInput) {
112-
const imageFiles = await Promise.all(
113-
images.map(async (file) => {
114-
const response = await fetch(file.url);
115-
const blob = await response.blob();
116-
return new File([blob], file.name, { type: file.type });
117-
}),
118-
);
119-
await uploadFiles(imageFiles);
120-
121-
// 等待图片上传完成
122-
for (let i = 0; i < 30; i++) {
123-
const uploadedCountElement = document.evaluate(
124-
"//*[contains(text(), '已上传')]",
125-
document,
126-
null,
127-
XPathResult.FIRST_ORDERED_NODE_TYPE,
128-
null,
129-
).singleNodeValue as HTMLElement;
130-
131-
if (uploadedCountElement) {
132-
const match = uploadedCountElement.textContent?.match(/ (\d+) /);
133-
if (match && parseInt(match[1]) >= images.length) {
134-
console.debug(`图片上传完成:${match[1]}张`);
135-
break;
136-
}
137-
}
138-
await new Promise((resolve) => setTimeout(resolve, 1000));
139-
}
140-
141-
const insertButton = Array.from(document.querySelectorAll('button')).find(
142-
(el) => el.textContent?.includes('插入图片'),
143-
);
144-
if (insertButton) {
145-
insertButton.click();
146-
}
147-
}
83+
for (let i = 0; i < images.length; i++) {
84+
const image = images[i];
85+
if (i >= 9) {
86+
console.debug('Zhihu 最多支持 9 张,跳过');
87+
break;
14888
}
89+
console.debug('try upload file', image);
90+
const response = await fetch(image.url);
91+
const arrayBuffer = await response.arrayBuffer();
92+
const file = new File([arrayBuffer], image.name, { type: image.type });
93+
94+
const imagePasteEvent = new ClipboardEvent('paste', {
95+
bubbles: true,
96+
cancelable: true,
97+
clipboardData: new DataTransfer(),
98+
});
99+
imagePasteEvent.clipboardData?.items.add(file);
100+
editorElement.dispatchEvent(imagePasteEvent);
149101
}
150102
}
151103

152-
// 发布内容
153-
if (data.isAutoPublish) {
154-
const maxRetries = 3;
155-
const retryInterval = 2000; // 2秒
156-
157-
const attemptPublish = async (): Promise<boolean> => {
158-
const publishButton = Array.from(document.querySelectorAll('button')).find(
159-
(el) => el.textContent?.includes('发布'),
160-
);
161-
if (publishButton) {
162-
console.debug('发布按钮被点击');
163-
publishButton.click();
164-
return true;
165-
}
166-
return false;
167-
};
168-
169-
let isPublished = false;
170-
for (let i = 0; i < maxRetries; i++) {
171-
isPublished = await attemptPublish();
172-
if (isPublished) {
173-
await new Promise((resolve) => setTimeout(resolve, 3000));
174-
window.location.reload();
175-
break;
176-
}
177-
console.debug(`未找到"发布"按钮,重试第 ${i + 1} 次`);
178-
await new Promise((resolve) => setTimeout(resolve, retryInterval));
179-
}
104+
editorElement.dispatchEvent(new Event('input', { bubbles: true }));
105+
editorElement.dispatchEvent(new Event('change', { bubbles: true }));
106+
await new Promise((resolve) => setTimeout(resolve, 3000));
107+
108+
// 等待图片上传完成(检查是否有 blob 图片正在加载)
109+
let loadingCount = 0;
110+
while (loadingCount < 30) {
111+
const uploadingImages = document.querySelectorAll('div.DraggableTags-tag-drag img');
112+
if (uploadingImages.length === 0) break;
113+
114+
const loadingImg = Array.from(uploadingImages).find((img) => (img as HTMLImageElement).src.startsWith('blob'));
115+
console.debug('loadingImg', loadingImg);
116+
if (!loadingImg) break;
180117

181-
if (!isPublished) {
182-
console.error(`在 ${maxRetries} 次尝试后仍未能发布内容`);
118+
await new Promise((resolve) => setTimeout(resolve, 2000));
119+
loadingCount++;
120+
}
121+
122+
// 发布内容
123+
const allButtons = document.querySelectorAll('button');
124+
const sendButton = Array.from(allButtons).find((el) => el.textContent?.includes('发布'));
125+
console.debug('sendButton', sendButton);
126+
127+
if (sendButton) {
128+
if (data.isAutoPublish) {
129+
console.debug('sendButton clicked');
130+
const clickEvent = new Event('click', { bubbles: true });
131+
sendButton.dispatchEvent(clickEvent);
132+
await new Promise((resolve) => setTimeout(resolve, 3000));
133+
window.location.href = 'https://www.zhihu.com/follow';
183134
}
135+
} else {
136+
console.debug('未找到"发送"按钮');
184137
}
185138

186139
console.debug('成功填入知乎内容和图片');

0 commit comments

Comments
 (0)