24 |
25 |
26 |
29 |
30 |
2 |
3 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
20 | 正在持续迭代中... 21 |
22 | 23 | ## PostBot 内容同步助手 介绍 24 |  25 |
98 |
99 | (扫码添加微信,备注:PostBot,邀您加入群聊)
100 |
101 | ## 🌍 开源生态
102 |
103 | [开源生态](https://postbot.exmay.com/docs/opensource)
104 |
--------------------------------------------------------------------------------
/src/plugins/it/media/publisher/platform/article/cnblogs.publisher.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2025-2099 GitCoffee All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { state } from "~contents/components/postbot.data";
18 |
19 | export const cnblogsArticlePublisher = async (data) => {
20 | console.log('cnblogsArticlePublisher data', data);
21 |
22 | // const { contentData, processedData } = data;
23 |
24 | const contentData = data?.data;
25 | const processedData = data?.data;
26 |
27 | const sleep = async (time) => {
28 | console.log('sleep', time);
29 | return new Promise((resolve) => setTimeout(resolve, time));
30 | }
31 |
32 | const pasteEvent = (): ClipboardEvent => {
33 | console.log('pasteEvent');
34 | return new ClipboardEvent('paste', {
35 | bubbles: true,
36 | cancelable: true,
37 | clipboardData: new DataTransfer(),
38 | });
39 | }
40 |
41 | const observeElement = (selector, timeout = 10000) => {
42 | console.log('observeElement', selector);
43 | return new Promise((resolve, reject) => {
44 | // const checkElement = () => document.querySelector(selector);
45 |
46 | let checkElement = null;
47 | if (selector instanceof Function) {
48 | checkElement = selector;
49 | } else {
50 | checkElement = () => document.querySelector(selector);
51 | }
52 |
53 | // 立即检查元素
54 | let element = checkElement();
55 | console.log('element', element);
56 | if (element) {
57 | resolve(element);
58 | return;
59 | }
60 |
61 | // 创建 MutationObserver 进行监听
62 | const observer = new MutationObserver(() => {
63 | element = checkElement();
64 | console.log('element', element);
65 | if (element) {
66 | resolve(element);
67 | observer.disconnect();
68 | }
69 | });
70 |
71 | // 启动观察
72 | observer.observe(document.body, {
73 | childList: true,
74 | subtree: true,
75 | });
76 |
77 | // 如果超时,拒绝 Promise,并返回中文错误提示
78 | setTimeout(() => {
79 | observer.disconnect();
80 | reject(new Error(`未能在 ${timeout} 毫秒内找到选择器为 "${selector}" 的元素`));
81 | }, timeout);
82 | });
83 | };
84 |
85 | const formElement = {
86 | title: '#post-title',
87 | editor: '#md-editor',
88 | submitButtons: 'button.cnb-button',
89 | submitButtonText: '发布',
90 | }
91 |
92 | const fromRule = {
93 | title: {
94 | min: 2,
95 | max: 100,
96 | }
97 | }
98 |
99 | const autoFillContent = async (contentData) => {
100 | console.log('autoFillContent');
101 | const titleInput = await observeElement(formElement.title) as HTMLElement;
102 | console.log('titleInput', titleInput);
103 | if (!titleInput) {
104 | console.log('未找到标题输入框');
105 | return;
106 | }
107 | (titleInput as HTMLTextAreaElement).value = contentData?.title?.slice(0, fromRule.title.max) || '';
108 | titleInput.dispatchEvent(new Event('input', { bubbles: true }));
109 | titleInput.dispatchEvent(new Event('change', { bubbles: true }));
110 | await sleep(1000);
111 |
112 | const editor = (await observeElement(formElement.editor)) as HTMLElement;
113 | console.log('editor', editor);
114 | if (!editor) {
115 | console.log('未找到编辑器');
116 | return;
117 | }
118 | editor.focus();
119 |
120 | (editor as HTMLTextAreaElement).value = contentData?.content;
121 |
122 | editor.dispatchEvent(new Event('input', { bubbles: true }));
123 | editor.dispatchEvent(new Event('change', { bubbles: true }));
124 |
125 | // const editorPasteEvent = pasteEvent();
126 | // editorPasteEvent.clipboardData.setData('text/html', contentData?.content);
127 | // editor.dispatchEvent(editorPasteEvent);
128 | // editor.dispatchEvent(new Event('input', { bubbles: true }));
129 | // editor.dispatchEvent(new Event('change', { bubbles: true }));
130 | };
131 |
132 | const autoPublish = () => {
133 | const submitButtons = document.querySelectorAll(formElement.submitButtons);
134 | console.log('submitButtons', submitButtons);
135 | if (!submitButtons) {
136 | return;
137 | }
138 | const submitButton = Array.from(submitButtons).find(button => button.textContent?.includes(formElement.submitButtonText));
139 | console.log('submitButton', submitButton);
140 | if (!submitButton) {
141 | return;
142 | }
143 | (submitButton as HTMLElement).click();
144 | }
145 |
146 | await autoFillContent(contentData);
147 | await sleep(5000);
148 |
149 | if (contentData.isAutoPublish) {
150 | autoPublish();
151 | }
152 |
153 | }
--------------------------------------------------------------------------------
/src/plugins/it/media/publisher/platform/article/csdn.publisher.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2025-2099 GitCoffee All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { state } from "~contents/components/postbot.data";
18 |
19 | export const csdnArticlePublisher = async (data) => {
20 | console.log('csdnArticlePublisher data', data);
21 |
22 | // const { contentData, processedData } = data;
23 |
24 | const contentData = data?.data;
25 | const processedData = data?.data;
26 |
27 | const sleep = async (time) => {
28 | console.log('sleep', time);
29 | return new Promise((resolve) => setTimeout(resolve, time));
30 | }
31 |
32 | const pasteEvent = (): ClipboardEvent => {
33 | console.log('pasteEvent');
34 | return new ClipboardEvent('paste', {
35 | bubbles: true,
36 | cancelable: true,
37 | clipboardData: new DataTransfer(),
38 | });
39 | }
40 |
41 | const observeElement = (selector, timeout = 10000) => {
42 | console.log('observeElement', selector);
43 | return new Promise((resolve, reject) => {
44 | // const checkElement = () => document.querySelector(selector);
45 |
46 | let checkElement = null;
47 | if (selector instanceof Function) {
48 | checkElement = selector;
49 | } else {
50 | checkElement = () => document.querySelector(selector);
51 | }
52 |
53 | // 立即检查元素
54 | let element = checkElement();
55 | console.log('element', element);
56 | if (element) {
57 | resolve(element);
58 | return;
59 | }
60 |
61 | // 创建 MutationObserver 进行监听
62 | const observer = new MutationObserver(() => {
63 | element = checkElement();
64 | console.log('element', element);
65 | if (element) {
66 | resolve(element);
67 | observer.disconnect();
68 | }
69 | });
70 |
71 | // 启动观察
72 | observer.observe(document.body, {
73 | childList: true,
74 | subtree: true,
75 | });
76 |
77 | // 如果超时,拒绝 Promise,并返回中文错误提示
78 | setTimeout(() => {
79 | observer.disconnect();
80 | reject(new Error(`未能在 ${timeout} 毫秒内找到选择器为 "${selector}" 的元素`));
81 | }, timeout);
82 | });
83 | };
84 |
85 | const formElement = {
86 | title: '#txtTitle',
87 | editor: '[contenteditable="true"]',
88 | submitButtons: 'button.btn-outline-danger',
89 | submitButtonText: '发布博客',
90 | }
91 |
92 | const fromRule = {
93 | title: {
94 | min: 2,
95 | max: 100,
96 | }
97 | }
98 |
99 | const getEditor = () => {
100 | const iframe = document.querySelector('iframe');
101 | console.log('iframe', iframe);
102 | const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
103 | console.log('iframeDoc', iframeDoc);
104 |
105 | // const editor = await observeElement(formElement.editor) as HTMLElement;
106 | const editor = iframeDoc.querySelector(formElement.editor) as HTMLElement;
107 | return editor;
108 | }
109 |
110 | const autoFillContent = async (contentData) => {
111 | console.log('autoFillContent');
112 | const titleTextarea = await observeElement(formElement.title) as HTMLElement;
113 | console.log('titleTextarea', titleTextarea);
114 | if (!titleTextarea) {
115 | console.log('未找到标题输入框');
116 | return;
117 | }
118 | (titleTextarea as HTMLTextAreaElement).value = contentData?.title?.slice(0, fromRule.title.max) || '';
119 | titleTextarea.dispatchEvent(new Event('input', { bubbles: true }));
120 | titleTextarea.dispatchEvent(new Event('change', { bubbles: true }));
121 | await sleep(1000);
122 |
123 | const editor = (await observeElement(getEditor)) as HTMLElement;
124 | console.log('editor', editor);
125 | if (!editor) {
126 | console.log('未找到编辑器');
127 | return;
128 | }
129 | await sleep(5000);
130 | editor.focus();
131 | const editorPasteEvent = pasteEvent();
132 | editorPasteEvent.clipboardData.setData('text/html', contentData?.content);
133 | editor.dispatchEvent(editorPasteEvent);
134 | editor.dispatchEvent(new Event('input', { bubbles: true }));
135 | editor.dispatchEvent(new Event('change', { bubbles: true }));
136 | };
137 |
138 | const autoPublish = () => {
139 | const submitButtons = document.querySelectorAll(formElement.submitButtons);
140 | console.log('submitButtons', submitButtons);
141 | if (!submitButtons) {
142 | return;
143 | }
144 | const submitButton = Array.from(submitButtons).find(button => button.textContent?.includes(formElement.submitButtonText));
145 | console.log('submitButton', submitButton);
146 | if (!submitButton) {
147 | return;
148 | }
149 | (submitButton as HTMLElement).click();
150 | }
151 |
152 | await autoFillContent(contentData);
153 | await sleep(5000);
154 |
155 | if (contentData.isAutoPublish) {
156 | autoPublish();
157 | }
158 |
159 | }
--------------------------------------------------------------------------------
/src/plugins/it/media/publisher/platform/article/oschina.publisher.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2025-2099 GitCoffee All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import { state } from "~contents/components/postbot.data";
18 |
19 | export const oschinaBlogPublishUrl = () => {
20 | const user = state.metaInfoList?.oschina;
21 | if (!user) {
22 | return null;
23 | }
24 | const publishUrl = `https://my.oschina.net/u/${user.userId}/blog/write`;
25 | console.log('publishUrl', publishUrl);
26 | return publishUrl;
27 | }
28 |
29 | export const oschinaArticlePublisher = async (data) => {
30 | console.log('oschinaArticlePublisher data', data);
31 |
32 | // const { contentData, processedData } = data;
33 |
34 | const contentData = data?.data;
35 | const processedData = data?.data;
36 |
37 | const sleep = async (time) => {
38 | console.log('sleep', time);
39 | return new Promise((resolve) => setTimeout(resolve, time));
40 | }
41 |
42 | const pasteEvent = (): ClipboardEvent => {
43 | console.log('pasteEvent');
44 | return new ClipboardEvent('paste', {
45 | bubbles: true,
46 | cancelable: true,
47 | clipboardData: new DataTransfer(),
48 | });
49 | }
50 |
51 | const observeElement = (selector, timeout = 10000) => {
52 | console.log('observeElement', selector);
53 | return new Promise((resolve, reject) => {
54 | // const checkElement = () => document.querySelector(selector);
55 |
56 | let checkElement = null;
57 | if (selector instanceof Function) {
58 | checkElement = selector;
59 | } else {
60 | checkElement = () => document.querySelector(selector);
61 | }
62 |
63 | // 立即检查元素
64 | let element = checkElement();
65 | console.log('element', element);
66 | if (element) {
67 | resolve(element);
68 | return;
69 | }
70 |
71 | // 创建 MutationObserver 进行监听
72 | const observer = new MutationObserver(() => {
73 | element = checkElement();
74 | console.log('element', element);
75 | if (element) {
76 | resolve(element);
77 | observer.disconnect();
78 | }
79 | });
80 |
81 | // 启动观察
82 | observer.observe(document.body, {
83 | childList: true,
84 | subtree: true,
85 | });
86 |
87 | // 如果超时,拒绝 Promise,并返回中文错误提示
88 | setTimeout(() => {
89 | observer.disconnect();
90 | reject(new Error(`未能在 ${timeout} 毫秒内找到选择器为 "${selector}" 的元素`));
91 | }, timeout);
92 | });
93 | };
94 |
95 | const formElement = {
96 | title: '#titleGrid input[name=title]',
97 | editor: '[contenteditable="true"]',
98 | submitButtons: 'div.submit',
99 | submitButtonText: '发布文章',
100 | }
101 |
102 | const fromRule = {
103 | title: {
104 | min: 2,
105 | max: 100,
106 | }
107 | }
108 |
109 | const getEditor = () => {
110 | const iframe = document.querySelector('iframe');
111 | console.log('iframe', iframe);
112 | const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
113 | console.log('iframeDoc', iframeDoc);
114 |
115 | // const editor = await observeElement(formElement.editor) as HTMLElement;
116 | const editor = iframeDoc.querySelector(formElement.editor) as HTMLElement;
117 | return editor;
118 | }
119 |
120 | const autoFillContent = async (contentData) => {
121 | console.log('autoFillContent');
122 | const titleInput = await observeElement(formElement.title) as HTMLElement;
123 | console.log('titleInput', titleInput);
124 | if (!titleInput) {
125 | console.log('未找到标题输入框');
126 | return;
127 | }
128 | (titleInput as HTMLTextAreaElement).value = contentData?.title?.slice(0, fromRule.title.max) || '';
129 | titleInput.dispatchEvent(new Event('input', { bubbles: true }));
130 | titleInput.dispatchEvent(new Event('change', { bubbles: true }));
131 | await sleep(1000);
132 |
133 | const editor = (await observeElement(getEditor)) as HTMLElement;
134 | console.log('editor', editor);
135 | if (!editor) {
136 | console.log('未找到编辑器');
137 | return;
138 | }
139 | editor.focus();
140 | const editorPasteEvent = pasteEvent();
141 | editorPasteEvent.clipboardData.setData('text/html', contentData?.content);
142 | editor.dispatchEvent(editorPasteEvent);
143 | editor.dispatchEvent(new Event('input', { bubbles: true }));
144 | editor.dispatchEvent(new Event('change', { bubbles: true }));
145 | };
146 |
147 | const autoPublish = () => {
148 | const submitButtons = document.querySelectorAll(formElement.submitButtons);
149 | console.log('submitButtons', submitButtons);
150 | if (!submitButtons) {
151 | return;
152 | }
153 | const submitButton = Array.from(submitButtons).find(button => button.textContent?.includes(formElement.submitButtonText));
154 | console.log('submitButton', submitButton);
155 | if (!submitButton) {
156 | return;
157 | }
158 | (submitButton as HTMLElement).click();
159 | }
160 |
161 | await autoFillContent(contentData);
162 | await sleep(5000);
163 |
164 | if (contentData.isAutoPublish) {
165 | autoPublish();
166 | }
167 |
168 | }
--------------------------------------------------------------------------------
/src/background/message.background.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2025-2099 GitCoffee All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import { state } from "~contents/components/postbot.data";
17 |
18 | import { POSTBOT_ACTION } from "../message/postbot.action";
19 |
20 | // import { checkExtensionStatus } from "~utils/extenstion";
21 |
22 | import type { PlasmoCSConfig } from "plasmo"
23 | import { getPlatforms } from "~media/platform";
24 | import { getMetaInfoList } from "~media/meta";
25 | import { windowPublish } from "~media/publisher";
26 |
27 | import { isLoginApi } from "~api/media/user.api";
28 |
29 | export const handleMessage = async (request, sender, sendResponse) => {
30 | let message = {};
31 | const data = request?.data;
32 | switch (request.action) {
33 | case POSTBOT_ACTION.CHECK_EXTENSION:
34 | message = {
35 | extensionId: chrome.runtime.id,
36 | // enabled: enabled,
37 | };
38 | sendResponse(message);
39 | break;
40 | case POSTBOT_ACTION.PLATFORM_LIST:
41 | const platforms = getPlatforms();
42 | console.log('platforms', platforms);
43 | message = {
44 | platforms: platforms[data?.type],
45 | }
46 | sendResponse(message);
47 | break;
48 | case POSTBOT_ACTION.META_INFO_LIST:
49 |
50 | // chrome.runtime.sendMessage({ action: 'getMetaInfoList' }, (response) => {
51 | // console.log('response', response);
52 | const metaInfoList = await getMetaInfoList();
53 | state.metaInfoList = metaInfoList;
54 | message = {
55 | metaInfoList: metaInfoList,
56 | }
57 | console.log('message', message);
58 | sendResponse(message);
59 | // return true;
60 | // });
61 | break;
62 | case POSTBOT_ACTION.PUBLISH_SYNC_DATA:
63 | console.debug('request.data', request.data);
64 | state.contentData = request.data;
65 | break;
66 | case POSTBOT_ACTION.PUBLISH_SYNC_CONTENT:
67 | console.debug('state.contentData', state.contentData);
68 | message = state.contentData;
69 |
70 | sendResponse(message);
71 |
72 | state.contentData = null;
73 | break;
74 | case POSTBOT_ACTION.PUBLISH_NOW:
75 | const mediaType = data.mediaType || 'article'
76 | const platformCodes = data.platformCodes;
77 | console.debug('platformCodes', platformCodes);
78 | const publishPlatforms = getPlatforms();
79 | console.debug('publishPlatforms', publishPlatforms);
80 |
81 | let allPlatforms = Object.values(publishPlatforms[mediaType]);
82 | const checkedPlatforms = allPlatforms.filter(item => platformCodes.includes(item.code));
83 | console.debug('checkedPlatforms', checkedPlatforms);
84 |
85 | checkedPlatforms.forEach(item => {
86 | item['metaInfo'] = state.metaInfoList[item.code];
87 | });
88 |
89 | const publishData = {
90 | platforms: checkedPlatforms,
91 | data: data,
92 | }
93 | windowPublish(publishData);
94 | break;
95 | case 'fetchImage':
96 | let imageType = null;
97 | fetch(data.imageUrl)
98 | .then((response) => {
99 | const imageName = getFileName(response);
100 | // 获取图片的 Blob
101 | response.blob().then((blob) => {
102 | imageType = blob.type;
103 | console.log('blob.type', blob.type);
104 | console.log('blob.size', blob.size);
105 | // sendResponse({ imageName: imageName, imageBlob: blob });
106 | // blobToArrayBuffer(blob)
107 | // .then(arrayBuffer => {
108 | // 发送 ArrayBuffer 到 content.js
109 |
110 | // })
111 | // .catch(err => {
112 | // console.error('转换 Blob 为 ArrayBuffer 失败:', err);
113 | // });
114 |
115 | blob2base64(blob).then((base64data) => {
116 | sendResponse({ imageName: imageName, base64data: base64data, imageType: imageType });
117 | });
118 |
119 |
120 | })
121 | })
122 | .catch((error) => {
123 | console.error('获取图片失败:', error);
124 | sendResponse({ error: error.message });
125 | });
126 | break;
127 | case 'checkLogin':
128 | const res = await isLoginApi({});
129 | console.debug('res', res);
130 | sendResponse({
131 | isLogin: res?.data?.login,
132 | });
133 | break;
134 | default:
135 | break;
136 | }
137 | }
138 |
139 | const getFileName = (response) => {
140 | const disposition = response.headers.get('Content-Disposition');
141 | let filename = null;
142 |
143 | if (disposition && disposition.indexOf('attachment') !== -1) {
144 | const matches = /filename="([^;]+)"/.exec(disposition);
145 | if (matches != null && matches[1]) {
146 | filename = matches[1];
147 | }
148 | }
149 | return filename;
150 | }
151 |
152 | // function blobToArrayBuffer(blob) {
153 | // return new Promise((resolve, reject) => {
154 | // const reader = new FileReader();
155 | // reader.onloadend = () => resolve(reader.result);
156 | // reader.onerror = reject;
157 | // reader.readAsArrayBuffer(blob);
158 | // });
159 | // }
160 |
161 | const blob2base64 = async (blob) => {
162 | return new Promise((resolve, reject) => {
163 | // 创建 FileReader 实例
164 | const reader = new FileReader();
165 |
166 | // 读取 Blob 数据为 Base64
167 | reader.onloadend = () => {
168 | const base64data = reader.result;
169 | console.log(base64data); // 输出 Base64 编码的字符串
170 | resolve(base64data);
171 | };
172 |
173 | reader.onerror = function (e) {
174 | reject(e)
175 | };
176 |
177 | // 开始读取 Blob 数据
178 | reader.readAsDataURL(blob);
179 | });
180 | }
--------------------------------------------------------------------------------
/src/media/publisher/publisher.script.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2025-2099 GitCoffee All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import { reactive } from "vue";
17 |
18 | import { weixinArticlePublisher } from "./platform/article/weixin.publisher";
19 | import { toutiaoArticlePublisher } from "./platform/article/toutiao.publisher";
20 | import { xiaohongshuMomentPublisher } from "./platform/moment/xiaohongshu.publisher";
21 | import { zhihuArticlePublisher } from "./platform/article/zhihu.publisher";
22 | import { weiboArticlePublisher } from "./platform/article/weibo.publisher";
23 | import { baijiahaoArticlePublisher } from "./platform/article/baijiahao.publisher";
24 | import { qqOmArticlePublisher } from "./platform/article/qqOm.publisher";
25 | import { douyinArticlePublisher } from "./platform/article/douyin.publisher";
26 | import { bilibiliArticlePublisher } from "./platform/article/bilibili.publisher";
27 | import { doubanArticlePublisher } from "./platform/article/douban.publisher";
28 | import { jianshuArticlePublisher } from "./platform/article/jianshu.publisher";
29 | import { zsxqArticlePublisher } from "./platform/article/zsxq.publisher";
30 |
31 | import { weiboMomentPublisher } from "./platform/moment/weibo.publisher";
32 | import { toutiaoMomentPublisher } from "./platform/moment/toutiao.publisher";
33 | import { baijiahaoMomentPublisher } from "./platform/moment/baijiahao.publisher";
34 | import { zhihuMomentPublisher } from "./platform/moment/zhihu.publisher";
35 | import { weixinMomentPublisher } from "./platform/moment/weixin.publisher";
36 | import { weixinChannelsMomentPublisher } from "./platform/moment/weixinChannels.publisher";
37 | import { douyinMonmentPublisher } from "./platform/moment/douyin.publisher";
38 | import { kuaishouMomentPublisher } from "./platform/moment/kuaishou.publisher";
39 | import { doubanMomentPublisher } from "./platform/moment/douban.publisher";
40 | import { zsxqMonmentPublisher } from "./platform/moment/zsxq.publisher";
41 |
42 | import { douyinVideoPublisher } from "./platform/video/douyin.publisher";
43 | import { kuaishouVideoPublisher } from "./platform/video/kuaishou.publisher";
44 | import { bilibiliVideoPublisher } from "./platform/video/bilibili.publisher";
45 | import { toutiaoVideoPublisher } from "./platform/video/toutiao.publisher";
46 | import { weixinChannelsVideoPublisher } from "./platform/video/weixinChannels.publisher";
47 | import { qqOmVideoPublisher } from "./platform/video/qqOm.publisher";
48 | import { xiaohongshuVideoPublisher } from "./platform/video/xiaohongshu.publisher";
49 | import { weiboVideoPublisher } from "./platform/video/weibo.publisher";
50 | import { zhihuVideoPublisher } from "./platform/video/zhihu.publisher";
51 |
52 | import { music163AudioPublisher } from "./platform/audio/music163.publisher";
53 | import { qqmusicAudioPublisher } from "./platform/audio/qqmusic.publisher";
54 | import { ximalayaAudioPublisher } from "./platform/audio/ximalaya.publisher";
55 | import { qingtingAudioPublisher } from "./platform/audio/qingting.publisher";
56 | import { lizhiAudioPublisher } from "./platform/audio/lizhi.publisher";
57 | import { xiaoyuzhoufmAudioPublisher } from "./platform/audio/xiaoyuzhoufm.publisher";
58 |
59 | export const publisher = reactive({
60 | article: {
61 | weixin: weixinArticlePublisher,
62 | toutiao: toutiaoArticlePublisher,
63 | xiaohongshu: xiaohongshuMomentPublisher,
64 | zhihu: zhihuArticlePublisher,
65 | weibo: weiboArticlePublisher,
66 | baijiahao: baijiahaoArticlePublisher,
67 | qq_om: qqOmArticlePublisher,
68 | weixin_channels: weixinChannelsMomentPublisher,
69 | douyin: douyinArticlePublisher,
70 | bilibili: bilibiliArticlePublisher,
71 | kuaishou: kuaishouMomentPublisher,
72 | douban: doubanArticlePublisher,
73 | jianshu: jianshuArticlePublisher,
74 | zsxq: zsxqArticlePublisher,
75 | },
76 | moment: {
77 | weibo: weiboMomentPublisher,
78 | toutiao: toutiaoMomentPublisher,
79 | xiaohongshu: xiaohongshuMomentPublisher,
80 | baijiahao: baijiahaoMomentPublisher,
81 | zhihu: zhihuMomentPublisher,
82 | weixin: weixinMomentPublisher,
83 | weixin_channels: weixinChannelsMomentPublisher,
84 | douyin: douyinMonmentPublisher,
85 | bilibili: bilibiliArticlePublisher,
86 | kuaishou: kuaishouMomentPublisher,
87 | douban: doubanMomentPublisher,
88 | zsxq: zsxqMonmentPublisher,
89 | },
90 | video: {
91 | douyin: douyinVideoPublisher,
92 | kuaishou: kuaishouVideoPublisher,
93 | bilibili: bilibiliVideoPublisher,
94 | toutiao: toutiaoVideoPublisher,
95 | weixin_channels: weixinChannelsVideoPublisher,
96 | qq_om: qqOmVideoPublisher,
97 | xiaohongshu: xiaohongshuVideoPublisher,
98 | weibo: weiboVideoPublisher,
99 | zhihu: zhihuVideoPublisher,
100 | },
101 | audio: {
102 | music163: music163AudioPublisher,
103 | qqmusic: qqmusicAudioPublisher,
104 | ximalaya: ximalayaAudioPublisher,
105 | qingting: qingtingAudioPublisher,
106 | lizhi: lizhiAudioPublisher,
107 | xiaoyuzhou: xiaoyuzhoufmAudioPublisher,
108 | }
109 | });
110 |
111 | export const executeScriptsToTabs = (tabs, data) => {
112 | console.log('executeScriptsToTabs');
113 | tabs?.forEach(item => {
114 | const { tab, platform } = item;
115 | if (!tab?.id) {
116 | return;
117 | }
118 | chrome.tabs.onUpdated.addListener(function listener(tabId, info) {
119 | console.log('tabId', tab.id);
120 | console.log('info.status', info.status);
121 | if (tabId === tab.id && info.status === 'complete') {
122 | chrome.tabs.onUpdated.removeListener(listener);
123 | console.log('tab.id', tab.id);
124 | console.log('platform.type', platform.type);
125 | console.log('platform.code', platform.code);
126 | if (platform) {
127 | platform['executeScript'] = publisher[platform.type][platform.code] || publisher['article'][platform.code];
128 | const publisherData = {
129 | data: data?.data,
130 | platform: platform,
131 | };
132 | chrome.scripting.executeScript({
133 | target: { tabId: tab.id },
134 | func: platform.executeScript,
135 | args: [publisherData]
136 | });
137 | }
138 | }
139 | });
140 | });
141 | }
--------------------------------------------------------------------------------
/src/contents/components/PostbotModal.tsx:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2025-2099 GitCoffee All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | import { computed, defineComponent, h, ref } from 'vue'
17 | import { Button, Modal, Avatar } from 'ant-design-vue'
18 | // import { PlasmoCSConfig } from "plasmo";
19 |
20 | import { SyncOutlined } from '@ant-design/icons-vue';
21 |
22 | import PostbotButton from './PostbotButton'
23 |
24 | import PostbotFloatButton from './PostBotFloatButton';
25 |
26 | import { getReaderData } from '~media/parser'
27 |
28 | import { state } from './postbot.data';
29 |
30 | import iconUrl from "~assets/icon.png";
31 |
32 | import { POSTBOT_ACTION } from '~message/postbot.action';
33 |
34 | import { BASE_URL } from '~config/config';
35 |
36 | // import { state } from './postbot.data';
37 |
38 | // import cssText from 'data-text:./postbot.styles.css';
39 |
40 | // export const config: PlasmoCSConfig = {
41 | // // matches: ["https://www.plasmo.com/*"],
42 | // // css: ["font.css"]
43 | // };
44 |
45 | // export const getStyle = () => {
46 | // const style = document.createElement("style");
47 | // style.textContent = cssText;
48 | // return style;
49 | // };
50 |
51 | export default defineComponent({
52 | name: 'PostbotModal',
53 | setup() {
54 | // 创建一个变量来控制 Modal 是否显示
55 | // const isModalVisible = ref(false)
56 |
57 | const contentData = ref({});
58 | const content = ref('');
59 | const title = ref('');
60 |
61 | // 点击按钮时打开 Modal
62 | const handleClick = () => {
63 | chrome.runtime.sendMessage({ type: 'request', action: 'checkLogin' }, (response) => {
64 | console.log('response', response);
65 | if (response.isLogin) {
66 | state.isModalVisible = true;
67 | } else {
68 | window.open(`${BASE_URL}/exmay/postbot/media/publish`, '_blank');
69 | }
70 | });
71 |
72 | }
73 |
74 | // 关闭 Modal
75 | const handleCancel = () => {
76 | state.isModalVisible = false
77 | }
78 |
79 | const onOk = () => {
80 |
81 | chrome.runtime.sendMessage({
82 | type: 'request',
83 | action: POSTBOT_ACTION.PUBLISH_SYNC_DATA,
84 | data: contentData.value,
85 | });
86 |
87 | const newWindow = window.open(`${BASE_URL}/exmay/postbot/media/publish`, '_blank');
88 | if (newWindow) {
89 | newWindow.onload = () => {
90 | console.log('同步数据');
91 | contentData.value = null;
92 | }
93 | }
94 |
95 | state.isModalVisible = false;
96 | }
97 |
98 | const getContent = () => {
99 | const data = getReaderData();
100 | contentData.value = data;
101 | title.value = data?.title;
102 | console.log('title.value', title.value);
103 | return data?.content + '';
104 | }
105 |
106 | return () =>
107 | h('div', {
108 | style: {
109 | width: '100%',
110 | height: '100%',
111 | }
112 | }, [
113 | // h(Button, { type: 'primary', onClick: handleClick }, '点击打开 Modal'),
114 | // h(PostbotButton, { onClick: handleClick }),
115 | state.showFlowButton ? h(PostbotFloatButton, { onClick: handleClick }) : null,
116 | state.isModalVisible ?
117 | // 使用 Ant Design Vue 的 Modal 组件
118 | h(Modal, {
119 | open: state.isModalVisible,
120 | onCancel: handleCancel,
121 | destroyOnClose: true,
122 | // footer: null,
123 | okText: '立即同步',
124 | cancelText: '取消',
125 | width: '80vw',
126 | wrapClassName: 'em-ui-postbot-modal',
127 | bodyStyle: {
128 | height: '70vh', // 设置内容区域的高度
129 | // overflowY: 'auto' // 设置垂直滚动条,如果内容超出高度
130 | },
131 | style: {
132 | // border: '1px solid #1AAD19',
133 | // boxShadow: '0 1px 2px -2px #8bc34a, 0 3px 6px 0 #8bc34a, 0 5px 12px 4px #8bc34a',
134 | },
135 | okButtonProps: {
136 | icon: h(SyncOutlined),
137 | style: {
138 | // backgroundColor: '#1AAD19',
139 | backgroundColor: '#ff4d4f',
140 | }
141 | },
142 | // title: `${title.value} - 内容预览`,
143 | // title: () => h('div', {}, [
144 | // h(Avatar, {
145 | // src: iconUrl,
146 | // size: 18,
147 | // // size: 26,
148 | // }),
149 | // h('div', {}, 'PostBot内容同步助手'),
150 | // h('div', {}, `${title.value} - 内容预览`)
151 | // ]),
152 | onOk: onOk,
153 | zIndex: 10000,
154 | // content: computed(() => getContent()),
155 | }, {
156 | title: () => h('div', {}, [
157 | h('div', {
158 | style: {
159 | position: 'absolute',
160 | }
161 | }, [
162 | h(Avatar, {
163 | src: iconUrl,
164 | size: 26,
165 | // size: 26,
166 | }),
167 | h('span', {
168 | style: {
169 | // color: '#1AAD19',
170 | color: '#bd34fe',
171 | // fontWeight: 'bold',
172 | display: 'inline-block',
173 | marginLeft: '5px',
174 | }
175 | }, 'PostBot内容同步助手'),
176 | ]),
177 | h('div', {
178 | style: {
179 | textAlign: 'center',
180 | // color: '#1AAD19',
181 | color: '#bd34fe',
182 | }
183 | }, '内容预览'),
184 | h('div', {
185 | style: {
186 | textAlign: 'center',
187 | marginTop: '10px',
188 | }
189 | }, `${title.value}`)
190 | ]),
191 | // default: () => h('p', '')
192 | // default: () => h('div', { innerHTML: getContent() })
193 | default: () =>
194 | h('div', {
195 | style: {
196 | width: '100%',
197 | height: '100%' // 确保父容器高度为 100%
198 | }
199 | }, [
200 | h('iframe', {
201 | srcdoc: getContent(), // 将 htmlContent 插入到 iframe 中
202 | width: '100%',
203 | height: '100%',
204 | frameborder: '0'
205 | })
206 | ])
207 | }) : null
208 | ])
209 | }
210 | })
--------------------------------------------------------------------------------
/src/components/sidepanel/TabsManage.vue:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2025-2099 GitCoffee All Rights Reserved.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |