Skip to content

Commit f28cf72

Browse files
authored
182 feat xiaoheihe (#184)
* feat(xiaoheihe): add support for Xiaoheihe platform with image and video upload functionality - Added new platform support for Xiaoheihe in both English and Chinese locales. - Implemented image and video upload handling for Xiaoheihe. - Updated dynamic information map to include Xiaoheihe platform details. * feat(xiaoheihe): add video upload functionality for Xiaoheihe platform - Introduced VideoXiaoheihe function to handle video uploads and content publishing. - Updated VideoInfoMap to include Xiaoheihe platform details.
1 parent c67bdc4 commit f28cf72

File tree

8 files changed

+379
-1
lines changed

8 files changed

+379
-1
lines changed

locales/en/messages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,10 @@
529529
"message": "Webhook",
530530
"description": "Platform name for Webhook"
531531
},
532+
"platformXiaoheihe": {
533+
"message": "Xiaoheihe",
534+
"description": "Platform name for Xiaoheihe"
535+
},
532536
"optionsFeedbackPrefix": {
533537
"message": "Missing a platform you need?"
534538
},

locales/zh_CN/messages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,10 @@
536536
"message": "Webhook",
537537
"description": "Webhook 平台名称"
538538
},
539+
"platformXiaoheihe": {
540+
"message": "小黑盒",
541+
"description": "小黑盒平台名称"
542+
},
539543
"optionsFeedbackPrefix": {
540544
"message": "没有你需要的平台?"
541545
},

src/contents/helper.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@ export {};
44
import type { PlasmoCSConfig } from 'plasmo';
55
import { handleBilibiliImageUpload } from './helper/bilibili';
66
import { handleBlueskyVideoUpload, handleBlueskyImageUpload } from './helper/bluesky';
7+
import { handleXiaoheiheImageUpload, handleXiaoheiheVideoUpload } from './helper/xiaoheihe';
78

89
export const config: PlasmoCSConfig = {
9-
matches: ['https://t.bilibili.com/*', 'https://bsky.app/*', 'https://www.v2ex.com/write*', 'https://v2ex.com/write*'],
10+
matches: [
11+
'https://t.bilibili.com/*',
12+
'https://bsky.app/*',
13+
'https://www.v2ex.com/write*',
14+
'https://v2ex.com/write*',
15+
'https://www.xiaoheihe.cn/*',
16+
],
1017
world: 'MAIN',
1118
run_at: 'document_start',
1219
};
@@ -22,8 +29,10 @@ export let createdInputs: HTMLInputElement[] = [];
2229

2330
document.createElement = function (tagName, options) {
2431
let element = originalCreateElement(tagName, options);
32+
2533
if (tagName.toLowerCase() === 'input') {
2634
createdInputs.push(element);
35+
console.log('element', element);
2736
}
2837
return element;
2938
};
@@ -42,6 +51,10 @@ function handleMessage(event: MessageEvent) {
4251
if (editor) {
4352
editor.CodeMirror.setValue(data.content);
4453
}
54+
} else if (data.type === 'XIAOHEIHE_IMAGE_UPLOAD') {
55+
handleXiaoheiheImageUpload(event);
56+
} else if (data.type === 'XIAOHEIHE_VIDEO_UPLOAD') {
57+
handleXiaoheiheVideoUpload(event);
4558
}
4659
}
4760

src/contents/helper/xiaoheihe.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { createdInputs } from '../helper';
2+
import { waitForElement } from './common';
3+
4+
let isProcessingVideo = false;
5+
6+
export async function handleXiaoheiheVideoUpload(event: MessageEvent) {
7+
if (isProcessingVideo) {
8+
return;
9+
}
10+
isProcessingVideo = true;
11+
const video = event.data.video;
12+
if (!video) {
13+
console.error('未找到视频');
14+
return;
15+
}
16+
17+
const uploadVideoButton = await waitForElement('button[class="video-uploader__unload"]');
18+
if (!uploadVideoButton) {
19+
console.error('未找到上传视频按钮');
20+
return;
21+
}
22+
23+
(uploadVideoButton as HTMLElement).click();
24+
await new Promise((resolve) => setTimeout(resolve, 500));
25+
26+
const uploadInput = createdInputs.find((input) => input.type === 'file');
27+
if (!uploadInput) {
28+
console.error('未找到上传输入框');
29+
return;
30+
}
31+
32+
const dataTransfer = new DataTransfer();
33+
dataTransfer.items.add(video);
34+
uploadInput.files = dataTransfer.files;
35+
36+
uploadInput.disabled = true;
37+
38+
await new Promise((resolve) => setTimeout(resolve, 1000));
39+
40+
uploadInput.disabled = false;
41+
uploadInput.dispatchEvent(new Event('change', { bubbles: true }));
42+
43+
isProcessingVideo = false;
44+
}
45+
46+
let isProcessingImage = false;
47+
48+
export async function handleXiaoheiheImageUpload(event: MessageEvent) {
49+
if (isProcessingImage) {
50+
return;
51+
}
52+
isProcessingImage = true;
53+
const images = event.data.images;
54+
55+
if (!images || images.length === 0) {
56+
console.error('未找到图片');
57+
return;
58+
}
59+
60+
const uploadButton = (await waitForElement('div.upload')) as HTMLElement;
61+
if (!uploadButton) {
62+
console.error('未找到上传按钮');
63+
return;
64+
}
65+
66+
uploadButton.click();
67+
68+
await new Promise((resolve) => setTimeout(resolve, 500));
69+
70+
const uploadInput = createdInputs.find((input) => input.type === 'file');
71+
if (!uploadInput) {
72+
console.error('未找到上传输入框');
73+
return;
74+
}
75+
76+
const dataTransfer = new DataTransfer();
77+
images.forEach((image) => {
78+
dataTransfer.items.add(image);
79+
});
80+
uploadInput.files = dataTransfer.files;
81+
82+
uploadInput.disabled = true;
83+
84+
await new Promise((resolve) => setTimeout(resolve, 1000));
85+
86+
uploadInput.disabled = false;
87+
uploadInput.dispatchEvent(new Event('change', { bubbles: true }));
88+
89+
isProcessingImage = false;
90+
}

src/sync/dynamic.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { DynamicX } from './dynamic/x';
2323
import { DynamicXueqiu } from './dynamic/xueqiu';
2424
import { DynamicZhihu } from './dynamic/zhihu';
2525
import { DynamicZSXQ } from './dynamic/zsxq';
26+
import { DynamicXiaoheihe } from './dynamic/xiaoheihe';
2627

2728
export const DynamicInfoMap: Record<string, PlatformInfo> = {
2829
DYNAMIC_BILIBILI: {
@@ -295,4 +296,15 @@ export const DynamicInfoMap: Record<string, PlatformInfo> = {
295296
tags: ['CN'],
296297
accountKey: 'zsxq',
297298
},
299+
DYNAMIC_XIAOHEIHE: {
300+
type: 'DYNAMIC',
301+
name: 'DYNAMIC_XIAOHEIHE',
302+
homeUrl: 'https://www.xiaoheihe.cn/',
303+
faviconUrl: 'https://www.xiaoheihe.cn/favicon.ico',
304+
platformName: chrome.i18n.getMessage('platformXiaoheihe'),
305+
injectUrl: 'https://www.xiaoheihe.cn/creator/editor/draft/image_text',
306+
injectFunction: DynamicXiaoheihe,
307+
tags: ['CN'],
308+
accountKey: 'xiaoheihe',
309+
},
298310
};

src/sync/dynamic/xiaoheihe.ts

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import type { DynamicData, SyncData } from '../common';
2+
3+
export async function DynamicXiaoheihe(data: SyncData) {
4+
const { title, content, images } = data.data as DynamicData;
5+
// 辅助函数:等待元素出现
6+
function waitForElement(selector: string, timeout = 10000): Promise<Element> {
7+
return new Promise((resolve, reject) => {
8+
const element = document.querySelector(selector);
9+
if (element) {
10+
resolve(element);
11+
return;
12+
}
13+
14+
const observer = new MutationObserver(() => {
15+
const element = document.querySelector(selector);
16+
if (element) {
17+
resolve(element);
18+
observer.disconnect();
19+
}
20+
});
21+
22+
observer.observe(document.body, {
23+
childList: true,
24+
subtree: true,
25+
});
26+
27+
setTimeout(() => {
28+
observer.disconnect();
29+
reject(new Error(`Element with selector "${selector}" not found within ${timeout}ms`));
30+
}, timeout);
31+
});
32+
}
33+
34+
try {
35+
const titleEditorSelector = 'div.hb-cpt__editor-title .ProseMirror.hb-editor';
36+
const contentEditorSelector = 'div.image-text__edit-content .ProseMirror.hb-editor';
37+
38+
// 等待编辑器元素出现
39+
await waitForElement(contentEditorSelector);
40+
await new Promise((resolve) => setTimeout(resolve, 1000));
41+
42+
// 填写标题
43+
if (title) {
44+
try {
45+
await waitForElement(titleEditorSelector);
46+
const titleEditor = document.querySelector(titleEditorSelector);
47+
if (titleEditor) {
48+
(titleEditor as HTMLElement).focus();
49+
const titlePasteEvent = new ClipboardEvent('paste', {
50+
bubbles: true,
51+
cancelable: true,
52+
clipboardData: new DataTransfer(),
53+
});
54+
titlePasteEvent.clipboardData!.setData('text/plain', title);
55+
titleEditor.dispatchEvent(titlePasteEvent);
56+
await new Promise((resolve) => setTimeout(resolve, 500));
57+
}
58+
} catch {
59+
console.debug('未找到标题编辑器元素, 跳过标题填写');
60+
}
61+
}
62+
63+
// 填写正文
64+
const contentEditor = document.querySelector(contentEditorSelector);
65+
if (!contentEditor) {
66+
console.debug('未找到正文编辑器元素');
67+
return;
68+
}
69+
70+
(contentEditor as HTMLElement).focus();
71+
72+
const contentPasteEvent = new ClipboardEvent('paste', {
73+
bubbles: true,
74+
cancelable: true,
75+
clipboardData: new DataTransfer(),
76+
});
77+
contentPasteEvent.clipboardData!.setData('text/plain', content || '');
78+
contentEditor.dispatchEvent(contentPasteEvent);
79+
80+
// 处理媒体上传(图片和视频)
81+
if (images.length > 0) {
82+
const imageData = [];
83+
for (const file of images) {
84+
const response = await fetch(file.url);
85+
const blob = await response.blob();
86+
const imageFile = new File([blob], file.name, { type: file.type });
87+
console.log(`文件: ${imageFile.name} ${imageFile.type} ${imageFile.size}`);
88+
imageData.push(imageFile);
89+
}
90+
await new Promise((resolve) => setTimeout(resolve, 1000));
91+
92+
window.postMessage({ type: 'XIAOHEIHE_IMAGE_UPLOAD', images: imageData }, '*');
93+
}
94+
95+
// 判断是否自动发布
96+
if (!data.isAutoPublish) return;
97+
98+
// 等待一段时间确保文件上传完成
99+
await new Promise((resolve) => setTimeout(resolve, 3000));
100+
101+
// 查找发布按钮
102+
const publishButton = document.querySelector<HTMLButtonElement>('button.editor-publish__btn');
103+
104+
console.debug('sendButton', publishButton);
105+
106+
if (publishButton) {
107+
// 如果找到发布按钮,检查是否可点击
108+
let attempts = 0;
109+
while (publishButton.disabled && attempts < 10) {
110+
await new Promise((resolve) => setTimeout(resolve, 3000));
111+
attempts++;
112+
console.debug(`Waiting for send button to be enabled. Attempt ${attempts}/10`);
113+
}
114+
115+
if (publishButton.disabled) {
116+
console.debug('Send button is still disabled after 10 attempts');
117+
return;
118+
}
119+
120+
console.debug('sendButton clicked');
121+
// 点击发布按钮
122+
const clickEvent = new Event('click', { bubbles: true });
123+
publishButton.dispatchEvent(clickEvent);
124+
} else {
125+
throw new Error('未找到发布按钮');
126+
}
127+
} catch (error) {
128+
console.error('小黑盒发布过程中出错:', error);
129+
}
130+
}

src/sync/video.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { VideoRednote } from './video/rednote';
1010
import { VideoTiktok } from './video/tiktok';
1111
import { VideoWeibo } from './video/weibo';
1212
import { VideoWeiXinChannel } from './video/weixinchannel';
13+
import { VideoXiaoheihe } from './video/xiaoheihe';
1314
import { VideoYoutube } from './video/youtube';
1415
import { VideoZhihu } from './video/zhihu';
1516

@@ -159,4 +160,15 @@ export const VideoInfoMap: Record<string, PlatformInfo> = {
159160
tags: ['CN'],
160161
accountKey: 'eastmoney',
161162
},
163+
VIDEO_XIAOHEIHE: {
164+
type: 'VIDEO',
165+
name: 'VIDEO_XIAOHEIHE',
166+
homeUrl: 'https://www.xiaoheihe.cn/',
167+
faviconUrl: 'https://www.xiaoheihe.cn/favicon.ico',
168+
platformName: chrome.i18n.getMessage('platformXiaoheihe'),
169+
injectUrl: 'https://www.xiaoheihe.cn/creator/editor/draft/video',
170+
injectFunction: VideoXiaoheihe,
171+
tags: ['CN'],
172+
accountKey: 'xiaoheihe',
173+
},
162174
};

0 commit comments

Comments
 (0)