├── assets ├── icon.png ├── icon2.png ├── icons.png ├── icon.development.png └── icon.development2.png ├── .gitignore ├── src ├── style.css ├── utils │ ├── sleep.ts │ ├── dayjs.ts │ ├── html.ts │ ├── content.ts │ ├── extenstion.ts │ └── image.ts ├── media │ ├── adapter │ │ └── index.ts │ ├── processor │ │ └── index.ts │ ├── publisher │ │ ├── index.ts │ │ ├── publisher.script.ts │ │ └── platform │ │ │ └── moment │ │ │ └── zsxq.publisher.ts │ ├── reader │ │ ├── default.reader.ts │ │ └── index.ts │ ├── meta │ │ ├── zhihu.meta.ts │ │ ├── jianshu.meta.ts │ │ ├── baijiahao.meta.ts │ │ ├── toutiao.meta.ts │ │ ├── douyin.meta.ts │ │ ├── qqOm.meta.ts │ │ ├── douban.meta.ts │ │ ├── bilibili.meta.ts │ │ ├── kuaishou.meta.ts │ │ ├── weibo.meta.ts │ │ ├── weixinChannels.meta.ts │ │ ├── xiaohongshu.meta.ts │ │ ├── index.ts │ │ ├── zsxq.meta.ts │ │ └── weixin.meta.ts │ ├── parser │ │ └── index.ts │ └── handler │ │ └── image.handler.ts ├── sidepanel │ ├── sidepanel.ts │ └── index.vue ├── contents │ ├── components │ │ ├── postbot.styles.css │ │ ├── postbot.data.ts │ │ ├── PostbotButton.tsx │ │ ├── PostbotModal.tsx │ │ └── PostbotFloatButton.tsx │ ├── services │ │ ├── meta.services.ts │ │ └── message.services.ts │ ├── messageEventListener.ts │ └── index.ts ├── events │ ├── index.ts │ ├── copy.event.ts │ └── contextMenus.event.ts ├── config │ └── config.ts ├── message │ └── postbot.action.ts ├── stores │ └── index.ts ├── api │ ├── media │ │ ├── account.api.ts │ │ ├── client.api.ts │ │ ├── platform.api.ts │ │ ├── task.api.ts │ │ └── user.api.ts │ └── index.ts ├── popup │ └── index.vue ├── plugins │ ├── it │ │ ├── media │ │ │ ├── meta │ │ │ │ ├── index.ts │ │ │ │ ├── juejin.meta.ts │ │ │ │ ├── csdn.meta.ts │ │ │ │ ├── 51cto.meta.ts │ │ │ │ ├── oschina.meta.ts │ │ │ │ ├── cnblogs.meta.ts │ │ │ │ └── segmentfault.meta.ts │ │ │ └── publisher │ │ │ │ ├── index.ts │ │ │ │ └── platform │ │ │ │ └── article │ │ │ │ ├── juejin.publisher.ts │ │ │ │ ├── 51cto.publisher.ts │ │ │ │ ├── segmentfault.publisher.ts │ │ │ │ ├── cnblogs.publisher.ts │ │ │ │ ├── csdn.publisher.ts │ │ │ │ └── oschina.publisher.ts │ │ └── index.ts │ ├── index.ts │ ├── registry.ts │ ├── types.ts │ └── injector.ts ├── background │ ├── index.ts │ └── message.background.ts ├── components │ └── sidepanel │ │ ├── ImageList.vue │ │ ├── UserCard.vue │ │ ├── TaskList.vue │ │ └── TabsManage.vue └── tabs │ └── index.ts ├── postcss.config.js ├── tailwind.config.js ├── tsconfig.json ├── package.json └── README.md /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitcoffee-os/postbot/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitcoffee-os/postbot/HEAD/assets/icon2.png -------------------------------------------------------------------------------- /assets/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitcoffee-os/postbot/HEAD/assets/icons.png -------------------------------------------------------------------------------- /assets/icon.development.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitcoffee-os/postbot/HEAD/assets/icon.development.png -------------------------------------------------------------------------------- /assets/icon.development2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitcoffee-os/postbot/HEAD/assets/icon.development2.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | #cache 12 | .turbo 13 | 14 | # misc 15 | .DS_Store 16 | *.pem 17 | 18 | # debug 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | .pnpm-debug.log* 23 | 24 | # local env files 25 | .env* 26 | 27 | yarn.lock 28 | out/ 29 | build/ 30 | dist/ 31 | *.zip 32 | *.crx 33 | 34 | # plasmo - https://www.plasmo.com 35 | .plasmo 36 | 37 | # bpp - http://bpp.browser.market/ 38 | keys.json 39 | 40 | # typescript 41 | .tsbuildinfo 42 | 43 | # typescript 44 | .tsbuildinfo 45 | tsconfig.tsbuildinfo 46 | 47 | ._* -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 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 | @tailwind base; 17 | @tailwind components; 18 | @tailwind utilities; -------------------------------------------------------------------------------- /src/utils/sleep.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 | export const sleep = async (time) => { 17 | return new Promise((resolve) => setTimeout(resolve, time)); 18 | } -------------------------------------------------------------------------------- /src/utils/dayjs.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 dayjs from "dayjs" 17 | 18 | export const formatTime = (t = new Date()) => 19 | dayjs(t).format("YYYY-MM-DD HH:mm:ss") 20 | -------------------------------------------------------------------------------- /src/media/adapter/index.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 { defaultReader } from "~media/reader/default.reader" 17 | 18 | export const readers = { 19 | 'default': defaultReader, 20 | } -------------------------------------------------------------------------------- /src/media/processor/index.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 { handleContentImage } from "~media/handler/image.handler"; 17 | 18 | export const process = (content) => { 19 | return handleContentImage(content); 20 | } -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 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 | /** 18 | * @type {import('postcss').ProcessOptions} 19 | */ 20 | module.exports = { 21 | plugins: { 22 | tailwindcss: {}, 23 | autoprefixer: {} 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/sidepanel/sidepanel.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 | export const openSiderPanel = async () => { 17 | const window = await chrome.windows.getCurrent({ populate: true }); 18 | await chrome.sidePanel.open({ windowId: window.id }); 19 | } -------------------------------------------------------------------------------- /src/utils/html.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 | export const getDocument = (html) => { 17 | // 使用 DOMParser 将 HTML 字符串转换为 DOM 文档 18 | const parser = new DOMParser(); 19 | const doc = parser.parseFromString(html, 'text/html'); 20 | return doc; 21 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 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 | /** @type {import('tailwindcss').Config} */ 18 | module.exports = { 19 | // content: [], 20 | content: ["./src/**/*.{vue,tsx,ts,jsx,js}"], 21 | theme: { 22 | extend: {} 23 | }, 24 | plugins: [] 25 | } 26 | -------------------------------------------------------------------------------- /src/contents/components/postbot.styles.css: -------------------------------------------------------------------------------- 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 | /* .em-ui-postbot-modal :deep(.ant-modal-content) { 17 | border: 1px solid #1AAD19 !important; 18 | box-shadow: 0 1px 2px -2px #8bc34a, 0 3px 6px 0 #8bc34a, 0 5px 12px 4px #8bc34a !important; 19 | } */ -------------------------------------------------------------------------------- /src/events/index.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 { initContextMenusEvent } from "./contextMenus.event"; 17 | // import { initCopyEvent } from "./copy.event"; 18 | 19 | export const initEvents = () => { 20 | initContextMenusEvent(); 21 | // initCopyEvent(); 22 | } -------------------------------------------------------------------------------- /src/config/config.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 | // export const BASE_URL = process.env.NODE_ENV === 'development' ? 'http://localhost:3000' : 'https://postbot.exmay.com'; 17 | export const BASE_URL = 'https://postbot.exmay.com'; 18 | export const BASE_API = `${BASE_URL}/exmay/authority/api`; -------------------------------------------------------------------------------- /src/contents/components/postbot.data.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 | export const state = reactive({ 19 | showFlowButton: true, 20 | rangType: 'content', 21 | isModalVisible: false, 22 | contentData: {}, 23 | metaInfoList: {}, 24 | }); 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 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 | "extends": "plasmo/templates/tsconfig.base", 18 | "exclude": ["node_modules"], 19 | "include": [".plasmo/index.d.ts", "./**/*.ts", "./**/*.vue"], 20 | "compilerOptions": { 21 | "paths": { 22 | "~*": ["./src/*"] 23 | }, 24 | "types": ["vue"], 25 | "baseUrl": "." 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/media/publisher/index.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 { createTabsForPlatforms } from "~tabs"; 17 | 18 | export const windowPublish = (data) => { 19 | console.log('windowPublish'); 20 | if (!data?.platforms) { 21 | return; 22 | } 23 | if (data?.platforms.length === 0) { 24 | return; 25 | } 26 | createTabsForPlatforms(data); 27 | } -------------------------------------------------------------------------------- /src/media/reader/default.reader.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 | export const defaultReader = () => { 17 | const contentElements = document.querySelectorAll('body'); 18 | 19 | let content = ''; 20 | 21 | if (contentElements.length > 0) { 22 | content = contentElements[0]?.innerHTML; 23 | } 24 | 25 | console.debug('content', content); 26 | 27 | const data = { 28 | content: content, 29 | } 30 | 31 | return content; 32 | } -------------------------------------------------------------------------------- /src/message/postbot.action.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 | export const POSTBOT_ACTION = { 17 | CHECK_EXTENSION: 'POSTBOT_EXTENSION.CHECK_EXTENSION', 18 | PLATFORM_LIST: 'POSTBOT_EXTENSION.PLATFORM_LIST', 19 | META_INFO_LIST: 'POSTBOT_EXTENSION.META_INFO_LIST', 20 | PUBLISH_SYNC_DATA: 'POSTBOT_EXTENSION.PUBLISH_SYNC_DATA', 21 | PUBLISH_SYNC_CONTENT: 'POSTBOT_EXTENSION.PUBLISH_SYNC_CONTENT', 22 | PUBLISH_NOW: 'POSTBOT_EXTENSION.PUBLISH_NOW', 23 | } -------------------------------------------------------------------------------- /src/stores/index.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 { Storage } from '@plasmohq/storage' 17 | 18 | const storage = new Storage({ area: 'local' }); 19 | 20 | const TRUSTE_DOMAINS = 'TPOSTBOT_RUSTE_DOMAINS'; 21 | 22 | export const setTrustedDomains = async (domains) => { 23 | await storage.set(TRUSTE_DOMAINS, domains) 24 | } 25 | 26 | export const getTrustedDomains = async () => { 27 | const token = await storage.get(TRUSTE_DOMAINS) 28 | console.log('trustedDomains', token) 29 | return token 30 | } -------------------------------------------------------------------------------- /src/api/media/account.api.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 { BASE_API } from "~config/config"; 17 | 18 | export const listingApi = async(params) => { 19 | const response = await fetch(`${BASE_API}/postbot/media/meta/listing`, { 20 | method: 'POST', 21 | headers: { 22 | 'Content-Type': 'application/json', 23 | }, 24 | credentials: 'include', 25 | body: JSON.stringify(params), 26 | }); 27 | if (response.ok) { 28 | const body = await response.json(); 29 | return body; 30 | } 31 | return null; 32 | } -------------------------------------------------------------------------------- /src/api/media/client.api.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 { BASE_API } from "~config/config"; 17 | 18 | export const updateApi = async(params) => { 19 | const response = await fetch(`${BASE_API}/postbot/media/client/update`, { 20 | method: 'POST', 21 | headers: { 22 | 'Content-Type': 'application/json', 23 | }, 24 | credentials: 'include', 25 | body: JSON.stringify(params), 26 | }); 27 | if (response.ok) { 28 | const body = await response.json(); 29 | return body; 30 | } 31 | return null; 32 | } -------------------------------------------------------------------------------- /src/api/media/platform.api.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 { BASE_API } from "~config/config"; 17 | 18 | export const listingApi = async(params) => { 19 | const response = await fetch(`${BASE_API}/postbot/media/platform/listing`, { 20 | method: 'POST', 21 | headers: { 22 | 'Content-Type': 'application/json', 23 | }, 24 | credentials: 'include', 25 | body: JSON.stringify(params), 26 | }); 27 | if (response.ok) { 28 | const body = await response.json(); 29 | return body; 30 | } 31 | return null; 32 | } -------------------------------------------------------------------------------- /src/api/media/task.api.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 { BASE_API } from "~config/config"; 17 | 18 | export const listApi = async(params) => { 19 | const query = new URLSearchParams(params).toString(); 20 | const response = await fetch(`${BASE_API}/postbot/media/task/list?${query}`, { 21 | method: 'GET', 22 | headers: { 23 | 'Content-Type': 'application/json', 24 | }, 25 | credentials: 'include', 26 | }); 27 | if (response.ok) { 28 | const body = await response.json(); 29 | return body; 30 | } 31 | return null; 32 | } -------------------------------------------------------------------------------- /src/popup/index.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 | 19 | 20 | 35 | 36 | -------------------------------------------------------------------------------- /src/sidepanel/index.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 | 19 | 20 | -------------------------------------------------------------------------------- /src/plugins/it/media/meta/index.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 { reactive } from 'vue'; 18 | 19 | import { juejinMetaInfo } from './juejin.meta'; 20 | import { csdnMetaInfo } from './csdn.meta'; 21 | import { oschinaMetaInfo } from './oschina.meta'; 22 | import { cnblogsMetaInfo } from './cnblogs.meta'; 23 | import { segmentfaultMetaInfo } from './segmentfault.meta'; 24 | import { $51ctoMetaInfo } from './51cto.meta'; 25 | 26 | export const metaInfoList = reactive({ 27 | juejin: juejinMetaInfo, 28 | csdn: csdnMetaInfo, 29 | oschina: oschinaMetaInfo, 30 | cnblogs: cnblogsMetaInfo, 31 | segmentfault: segmentfaultMetaInfo, 32 | $51cto: $51ctoMetaInfo, 33 | }); -------------------------------------------------------------------------------- /src/contents/services/meta.services.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 { getMetaInfoList } from "~media/meta" 17 | import { getWeixinMetaInfo } from "~media/meta/weixin.meta" 18 | 19 | export const handleMetaMessage = (request, sender, sendResponse) => { 20 | let metaInfo = {}; 21 | const html = request.data?.html; 22 | switch (request.action) { 23 | case 'getMetaInfoList': 24 | const metaInfoList = getMetaInfoList(); 25 | sendResponse(metaInfoList); 26 | break; 27 | case 'getWeixinMetaInfo': 28 | metaInfo = getWeixinMetaInfo(html); 29 | sendResponse(metaInfo); 30 | break; 31 | default: 32 | break; 33 | } 34 | } -------------------------------------------------------------------------------- /src/media/meta/zhihu.meta.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 { platformMetas } from "~media/platform"; 17 | 18 | const Api = { 19 | MediaInfo: platformMetas.zhihu.mediaInfoUrl, 20 | }; 21 | 22 | const getMediaInfo = async () => { 23 | const response = await fetch(Api.MediaInfo, { 24 | method: 'GET', 25 | }); 26 | if (response.ok) { 27 | const body = await response.json(); 28 | console.log('body', body); 29 | const data = body; 30 | const { id, uid, name, avatar_url, phone } = data; 31 | return { 32 | userId: uid, 33 | name: name, 34 | avatarUrl: avatar_url, 35 | // phone: phone, 36 | } 37 | } 38 | return null; 39 | } 40 | 41 | export const zhihuMetaInfo = { 42 | getMediaInfo, 43 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PostBot", 3 | "displayName": "PostBot 内容同步助手", 4 | "version": "1.1.7", 5 | "description": "PostBot 内容同步助手", 6 | "author": "GitCoffee Team @GitCoffee", 7 | "scripts": { 8 | "dev": "plasmo dev", 9 | "build": "plasmo build", 10 | "package": "plasmo package" 11 | }, 12 | "dependencies": { 13 | "@ant-design/icons-vue": "^7.0.1", 14 | "@mozilla/readability": "^0.6.0", 15 | "@plasmohq/storage": "^1.11.0", 16 | "ant-design-vue": "^4.2.5", 17 | "dayjs": "^1.11.13", 18 | "plasmo": "0.90.5", 19 | "vue": "3.4.19" 20 | }, 21 | "devDependencies": { 22 | "@ianvs/prettier-plugin-sort-imports": "4.1.1", 23 | "@types/chrome": "0.0.258", 24 | "@types/node": "20.11.5", 25 | "autoprefixer": "^10.4.20", 26 | "postcss": "^8.4.45", 27 | "prettier": "3.2.4", 28 | "stream-browserify": "^3.0.0", 29 | "tailwindcss": "^3.4.10", 30 | "typescript": "5.3.3" 31 | }, 32 | "manifest": { 33 | "host_permissions": [ 34 | "https://*/*", 35 | "http://127.0.0.1/*", 36 | "http://localhost/*" 37 | ], 38 | "permissions": [ 39 | "tabs", 40 | "commands", 41 | "contextMenus", 42 | "clipboardRead", 43 | "background", 44 | "scripting", 45 | "activeTab", 46 | "tabGroups", 47 | "sidePanel", 48 | "storage", 49 | "downloads" 50 | ] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/utils/content.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 { ref } from 'vue'; 17 | 18 | export const contentImages = ref([]); 19 | 20 | export const content = ref(''); 21 | 22 | export const getSelectionContent = () => { 23 | // // 获取选中的内容 24 | // const selection = window.getSelection(); 25 | // const range = selection.getRangeAt(0); // 获取选中的第一个范围 26 | // const selectedContent = range.cloneContents(); // 获取选中的内容的副本 27 | 28 | // // 创建一个临时容器来解析选中的内容 29 | // const tempContainer = document.createElement('div'); 30 | // tempContainer.appendChild(selectedContent); 31 | 32 | // // 查找选中内容中的所有图片 33 | // const contentImages = tempContainer.querySelectorAll('img'); 34 | 35 | // // 输出图片的链接 36 | // contentImages.forEach(img => { 37 | // console.log(img.src); // 或者进行其他处理 38 | // }); 39 | } -------------------------------------------------------------------------------- /src/media/meta/jianshu.meta.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 { platformMetas } from "~media/platform"; 18 | import { getImageUrl } from "~utils/image"; 19 | 20 | const Api = { 21 | MediaInfo: platformMetas.jianshu.mediaInfoUrl, 22 | }; 23 | 24 | const getMediaInfo = async () => { 25 | const response = await fetch(Api.MediaInfo, { 26 | method: 'GET', 27 | }); 28 | if (response.ok) { 29 | const body = await response.json(); 30 | console.log('body', body); 31 | if (body?.data) { 32 | const data = body?.data; 33 | const { nickname, avatar } = data; 34 | return { 35 | name: nickname, 36 | avatarUrl: getImageUrl(avatar), 37 | } 38 | } 39 | return null; 40 | } 41 | return null; 42 | } 43 | 44 | export const jianshuMetaInfo = { 45 | getMediaInfo, 46 | } -------------------------------------------------------------------------------- /src/media/meta/baijiahao.meta.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 { platformMetas } from "~media/platform"; 18 | 19 | const Api = { 20 | MediaInfo: platformMetas.baijiahao.mediaInfoUrl, 21 | }; 22 | 23 | const getMediaInfo = async () => { 24 | const response = await fetch(Api.MediaInfo, { 25 | method: 'GET', 26 | }); 27 | if (response.ok) { 28 | const body = await response.json(); 29 | console.log('body', body); 30 | if (body.errno === 0) { 31 | const data = body?.data; 32 | const user = data.user; 33 | const { userid, name, username, avatar } = user; 34 | return { 35 | userId: userid, 36 | name: name, 37 | avatarUrl: avatar, 38 | } 39 | } 40 | return null; 41 | } 42 | return null; 43 | } 44 | 45 | export const baijiahaoMetaInfo = { 46 | getMediaInfo, 47 | } -------------------------------------------------------------------------------- /src/plugins/it/media/meta/juejin.meta.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 { platformMetas } from "../platform"; 18 | 19 | const Api = { 20 | MediaInfo: platformMetas.juejin.mediaInfoUrl, 21 | }; 22 | 23 | const getMediaInfo = async () => { 24 | const response = await fetch(Api.MediaInfo, { 25 | method: 'GET', 26 | }); 27 | if (response.ok) { 28 | const body = await response.json(); 29 | console.log('body', body); 30 | if (body.err_no === 0) { 31 | const data = body?.data; 32 | const { user_id, user_name, avatar_large, phone } = data; 33 | return { 34 | userId: user_id, 35 | name: user_name, 36 | avatarUrl: avatar_large, 37 | phone: phone, 38 | } 39 | } 40 | return null; 41 | } 42 | return null; 43 | } 44 | 45 | export const juejinMetaInfo = { 46 | getMediaInfo, 47 | } -------------------------------------------------------------------------------- /src/contents/components/PostbotButton.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 { defineComponent, h } from 'vue' 17 | import { Button } from 'ant-design-vue' 18 | import { SyncOutlined } from '@ant-design/icons-vue'; 19 | 20 | export default defineComponent({ 21 | name: 'PostbotButton', 22 | setup(props, { emit }) { 23 | 24 | // 点击按钮的处理逻辑 25 | const handleClick = () => { 26 | emit('click'); // 触发父组件传递的 show 事件 27 | // alert('按钮点击了!') 28 | } 29 | 30 | return () => 31 | h('div', { style: { position: 'fixed', top: '200px', right: '10px', zIndex: 9999 } }, [ 32 | // h(Button, { type: 'primary', icon: h(SyncOutlined), style: {backgroundColor: '#1AAD19'}, onClick: handleClick }, '内容同步') 33 | h(Button, { type: 'primary', icon: h(SyncOutlined), style: { backgroundColor: '#bd34fe' }, onClick: handleClick }, '内容同步') 34 | ]) 35 | } 36 | }) -------------------------------------------------------------------------------- /src/media/meta/toutiao.meta.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 { platformMetas } from "~media/platform"; 17 | 18 | const Api = { 19 | MediaInfo: platformMetas.toutiao.mediaInfoUrl, 20 | }; 21 | 22 | const getMediaInfo = async () => { 23 | const response = await fetch(Api.MediaInfo, { 24 | method: 'GET', 25 | }); 26 | if (response.ok) { 27 | const body = await response.json(); 28 | console.log('body', body); 29 | if (body.code === 0) { 30 | const data = body?.data; 31 | const { user, media } = data; 32 | return { 33 | name: user?.screen_name || media?.display_name, 34 | avatarUrl: user?.https_avatar_url || media?.https_avatar_url, 35 | userId: user?.id_str || media?.id_str, 36 | } 37 | } 38 | return null; 39 | } 40 | return null; 41 | } 42 | 43 | export const toutiaoMetaInfo = { 44 | getMediaInfo, 45 | } -------------------------------------------------------------------------------- /src/plugins/it/media/meta/csdn.meta.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 { platformMetas } from "../platform"; 18 | 19 | import { getImageUrl } from "~utils/image"; 20 | 21 | const Api = { 22 | MediaInfo: platformMetas.csdn.mediaInfoUrl, 23 | }; 24 | 25 | const getMediaInfo = async () => { 26 | const response = await fetch(Api.MediaInfo, { 27 | method: 'GET', 28 | }); 29 | if (response.ok) { 30 | const body = await response.json(); 31 | console.log('body', body); 32 | if (body.code === 200) { 33 | const data = body?.data; 34 | 35 | return { 36 | name: data?.nickName, 37 | avatarUrl: getImageUrl(data?.avatar), 38 | userId: data?.username, 39 | blogUrl: data?.blog_url, 40 | } 41 | } 42 | return null; 43 | } 44 | return null; 45 | } 46 | 47 | export const csdnMetaInfo = { 48 | getMediaInfo, 49 | } -------------------------------------------------------------------------------- /src/media/meta/douyin.meta.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 { platformMetas } from "~media/platform"; 18 | 19 | const Api = { 20 | MediaInfo: platformMetas.douyin.mediaInfoUrl, 21 | }; 22 | 23 | const USER_BASE_URL = 'https://www.douyin.com/user' 24 | 25 | const getMediaInfo = async () => { 26 | const response = await fetch(Api.MediaInfo, { 27 | method: 'GET', 28 | }); 29 | if (response.ok) { 30 | const body = await response.json(); 31 | console.log('body', body); 32 | if (body.status_code === 0) { 33 | const user = body?.user; 34 | const { uid, nickname, avatar_thumb } = user; 35 | return { 36 | userId: uid, 37 | name: nickname, 38 | avatarUrl: avatar_thumb.url_list[0], 39 | // profile: signature, 40 | } 41 | } 42 | return null; 43 | } 44 | return null; 45 | } 46 | 47 | export const douyinMetaInfo = { 48 | getMediaInfo, 49 | } -------------------------------------------------------------------------------- /src/background/index.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 { initPostBot } from "~api" 17 | 18 | // 初始化插件系统 19 | import "~plugins"; 20 | 21 | import { initEvents } from "~events"; 22 | 23 | import { handleMessage } from "./message.background"; 24 | 25 | export const config: PlasmoCSConfig = { 26 | // matches: ["https://www.plasmo.com/*"] 27 | } 28 | 29 | console.log( 30 | "Live now; make now always the most precious time. Now will never come again." 31 | ) 32 | 33 | initPostBot(); 34 | 35 | initEvents(); 36 | 37 | console.log('PostBot chrome.runtime.onMessage.addListener'); 38 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 39 | console.debug('request', request); 40 | console.log('addListener request.action', request.action); 41 | 42 | if (request.type === 'request') { 43 | handleMessage(request, sender, sendResponse); 44 | } 45 | 46 | return true; 47 | }); 48 | 49 | export { } 50 | -------------------------------------------------------------------------------- /src/plugins/it/media/meta/51cto.meta.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 { platformMetas } from "../platform"; 18 | 19 | import { imageToBase64 } from "~utils/image"; 20 | 21 | const Api = { 22 | MediaInfo: platformMetas.$51cto.mediaInfoUrl, 23 | }; 24 | 25 | const getMediaInfo = async () => { 26 | const response = await fetch(Api.MediaInfo, { 27 | method: 'GET', 28 | }); 29 | if (response.ok) { 30 | const body = await response.json(); 31 | console.log('body', body); 32 | if (body.data.status === 'success') { 33 | const data = body?.data?.data; 34 | const { user_id, nickname, avatar } = data; 35 | return { 36 | userId: user_id, 37 | name: nickname, 38 | avatarUrl: avatar, 39 | // avatar: imageToBase64(avatar), 40 | } 41 | } 42 | return null; 43 | } 44 | return null; 45 | } 46 | 47 | export const $51ctoMetaInfo = { 48 | getMediaInfo, 49 | } -------------------------------------------------------------------------------- /src/media/meta/qqOm.meta.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 { platformMetas } from "~media/platform"; 17 | 18 | import { getImageUrl } from "~utils/image"; 19 | 20 | const Api = { 21 | MediaInfo: platformMetas.qq_om.mediaInfoUrl, 22 | }; 23 | 24 | const getMediaInfo = async () => { 25 | const response = await fetch(Api.MediaInfo, { 26 | method: 'GET', 27 | }); 28 | if (response.ok) { 29 | const body = await response.json(); 30 | console.log('body', body); 31 | if (body?.response?.code === 0) { 32 | const data = body?.data; 33 | const cpInfo = data?.cpInfo; 34 | const { mediaId, mediaName, header, mobile } = cpInfo; 35 | return { 36 | userId: mediaId, 37 | name: mediaName, 38 | avatarUrl: getImageUrl(header), 39 | phone: mobile, 40 | } 41 | } 42 | return null; 43 | } 44 | return null; 45 | } 46 | 47 | export const qqOmMetaInfo = { 48 | getMediaInfo, 49 | } -------------------------------------------------------------------------------- /src/media/parser/index.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 { reader } from "~media/reader"; 17 | import { process } from "~media/processor"; 18 | 19 | export const getReaderData = () => { 20 | 21 | let data = reader(); 22 | 23 | if (data.content) { 24 | data.content = process(data.content); 25 | } 26 | 27 | const content = data.content; 28 | 29 | let contentImages = data.contentImages; 30 | 31 | if (data?.contentImages) { 32 | data.contentImages = Array.from(data.contentImages).map(img => ({ src: img.src })); 33 | } else { 34 | data.contentImages = []; 35 | } 36 | 37 | // contentImages.value = contentImages; 38 | 39 | Array.from(data.contentImages).forEach((img) => { 40 | const src = img.src; 41 | console.debug(`Image source: ${src}`); 42 | }); 43 | 44 | console.debug('content', content); 45 | 46 | if (!data?.title) { 47 | data.title = ''; 48 | } 49 | if (!data?.content) { 50 | data.content = ''; 51 | } 52 | return data; 53 | } -------------------------------------------------------------------------------- /src/utils/extenstion.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 | export const checkExtensionStatus = async () => { 17 | const extensionInfo = await getExtensionInfo(); 18 | return extensionInfo?.enabled; 19 | } 20 | 21 | export const getExtensionInfo = async () => { 22 | try { 23 | const extensionInfo = await new Promise((resolve, reject) => { 24 | chrome.management.getSelf(function (extensionInfo) { 25 | if (extensionInfo) { 26 | resolve(extensionInfo); 27 | } else { 28 | reject("Failed to retrieve extension info"); 29 | } 30 | }); 31 | }); 32 | 33 | if (extensionInfo.enabled) { 34 | console.log("This extension is enabled."); 35 | } else { 36 | console.log("This extension is disabled."); 37 | } 38 | return extensionInfo; 39 | } catch (error) { 40 | console.error(error); 41 | } 42 | return null; 43 | } -------------------------------------------------------------------------------- /src/media/meta/douban.meta.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 { platformMetas } from "~media/platform"; 18 | 19 | const Api = { 20 | MediaInfo: platformMetas.douban.mediaInfoUrl, 21 | }; 22 | 23 | const getMediaInfo = async () => { 24 | const response = await fetch(Api.MediaInfo, { 25 | method: 'GET', 26 | }); 27 | if (response.ok) { 28 | const body = await response.text(); 29 | console.log('body', body); 30 | 31 | const userNameMatch = body.match(/_USER_NAME\s*=\s*'([^']+)'/); 32 | const userAvatarMatch = body.match(/_USER_AVATAR\s*=\s*'([^']+)'/); 33 | 34 | const userName = userNameMatch ? userNameMatch[1] : null; 35 | const userAvatar = userAvatarMatch ? userAvatarMatch[1] : null; 36 | 37 | if (userName && userAvatar) { 38 | return { 39 | name: userName, 40 | avatarUrl: userAvatar, 41 | } 42 | } 43 | 44 | return null; 45 | } 46 | return null; 47 | } 48 | 49 | export const doubanMetaInfo = { 50 | getMediaInfo, 51 | } -------------------------------------------------------------------------------- /src/events/copy.event.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 | export const initCopyEvent = () => { 17 | document.addEventListener('copy', (event) => { 18 | const clipboardData = event.clipboardData || window.clipboardData; 19 | 20 | if (clipboardData) { 21 | // 获取剪贴板中的文本内容 22 | const text = clipboardData.getData('text/plain'); 23 | const html = clipboardData.getData('text/html'); 24 | 25 | // 查找图片链接 26 | let contentImages = []; 27 | if (html) { 28 | // 解析HTML内容,提取其中的图片链接 29 | const parser = new DOMParser(); 30 | const doc = parser.parseFromString(html, 'text/html'); 31 | const imgElements = doc.querySelectorAll('img'); 32 | imgElements.forEach(img => { 33 | contentImages.push(img.src); 34 | }); 35 | } 36 | 37 | // 打印图片链接 38 | if (contentImages.length > 0) { 39 | console.log('复制的图片链接:', contentImages); 40 | } else { 41 | console.log('没有图片'); 42 | } 43 | } 44 | }); 45 | } -------------------------------------------------------------------------------- /src/plugins/it/media/publisher/index.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 { juejinArticlePublisher } from './platform/article/juejin.publisher'; 18 | import { csdnArticlePublisher } from './platform/article/csdn.publisher'; 19 | import { oschinaArticlePublisher } from './platform/article/oschina.publisher'; 20 | import { cnblogsArticlePublisher } from './platform/article/cnblogs.publisher'; 21 | import { segmentfaultArticlePublisher } from "./platform/article/segmentfault.publisher"; 22 | import { $51ctoArticlePublisher } from "./platform/article/51cto.publisher"; 23 | 24 | 25 | export const publisher = { 26 | article: { 27 | juejin: juejinArticlePublisher, 28 | csdn: csdnArticlePublisher, 29 | oschina: oschinaArticlePublisher, 30 | cnblogs: cnblogsArticlePublisher, 31 | segmentfault: segmentfaultArticlePublisher, 32 | $51cto: $51ctoArticlePublisher, 33 | }, 34 | moment: { 35 | 36 | } 37 | }; 38 | 39 | export const getPublisher = () => { 40 | return publisher; 41 | }; 42 | -------------------------------------------------------------------------------- /src/api/media/user.api.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 { BASE_URL } from "~config/config"; 17 | 18 | export const isLoginApi = async(params) => { 19 | const response = await fetch(`${BASE_URL}/exmay/api/ums/member/islogin`, { 20 | method: 'POST', 21 | headers: { 22 | 'Content-Type': 'application/json', 23 | }, 24 | credentials: 'include', 25 | body: JSON.stringify(params), 26 | }); 27 | if (response.ok) { 28 | const body = await response.json(); 29 | return body; 30 | } 31 | return null; 32 | } 33 | 34 | export const userInfoApi = async(params) => { 35 | const response = await fetch(`${BASE_URL}/exmay/api/member/center/info`, { 36 | method: 'GET', 37 | headers: { 38 | 'Content-Type': 'application/json', 39 | }, 40 | credentials: 'include', 41 | }); 42 | if (response.ok) { 43 | const body = await response.json(); 44 | return body; 45 | } 46 | return null; 47 | } -------------------------------------------------------------------------------- /src/media/meta/bilibili.meta.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 { platformMetas } from "~media/platform"; 18 | import { imageToBase64 } from "~utils/image"; 19 | 20 | const Api = { 21 | MediaInfo: platformMetas.bilibili.mediaInfoUrl, 22 | }; 23 | 24 | const getMediaInfo = async () => { 25 | const response = await fetch(Api.MediaInfo, { 26 | method: 'GET', 27 | }); 28 | if (response.ok) { 29 | const body = await response.json(); 30 | console.log('body', body); 31 | if (body.code === 0 && body.data.isLogin) { 32 | const data = body?.data; 33 | const { mid, uname, face } = data; 34 | 35 | const avatar = await imageToBase64(face); 36 | 37 | return { 38 | userId: mid, 39 | name: uname, 40 | avatarUrl: face, 41 | avatar: avatar, 42 | profile: `https://space.bilibili.com/${mid}`, 43 | } 44 | } 45 | return null; 46 | } 47 | return null; 48 | } 49 | 50 | export const bilibiliMetaInfo = { 51 | getMediaInfo, 52 | } -------------------------------------------------------------------------------- /src/api/index.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 { getTrustedDomains, setTrustedDomains } from "~stores" 17 | 18 | import { isLoginApi } from "./media/user.api"; 19 | 20 | import { listingApi } from "./media/platform.api"; 21 | 22 | import { updateApi } from "./media/client.api"; 23 | 24 | import { state } from "~contents/components/postbot.data"; 25 | 26 | const intervalTime: number = 1000 * 30; 27 | 28 | const initDefaultTrustedDomains = async() => { 29 | const trustedDomains = await getTrustedDomains(); 30 | if (!trustedDomains) { 31 | await setTrustedDomains([ 32 | { 33 | id: crypto.randomUUID(), 34 | domain: 'exmay.com', 35 | }, 36 | ]); 37 | } 38 | } 39 | 40 | export const initPostBot = async() => { 41 | 42 | initDefaultTrustedDomains(); 43 | 44 | await isLoginApi({}); 45 | 46 | const mediaPlatformList = await listingApi({}); 47 | 48 | startPostBot(intervalTime); 49 | } 50 | 51 | export const startPostBot = async(intervalTime: number) => { 52 | updateApi({}); 53 | } -------------------------------------------------------------------------------- /src/media/handler/image.handler.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 | export const getDocument = (html) => { 17 | // 使用 DOMParser 将 HTML 字符串转换为 DOM 文档 18 | let parser = new DOMParser(); 19 | let doc = parser.parseFromString(html, 'text/html'); 20 | return doc; 21 | } 22 | 23 | export const getImgElements = (html) => { 24 | const doc = getDocument(html); 25 | const imgElements = doc?.querySelectorAll('img'); 26 | return imgElements; 27 | } 28 | 29 | export const handleContentImage = (html) => { 30 | let doc = getDocument(html); 31 | 32 | // 获取所有的 标签 33 | const contentImages = doc.querySelectorAll('img'); 34 | 35 | // 遍历所有 标签并处理相对路径 36 | contentImages.forEach(img => { 37 | const relativePath = img.getAttribute('src'); 38 | if (relativePath && !relativePath.startsWith('http://') && !relativePath.startsWith('https://')) { 39 | const absolutePath = new URL(relativePath, window.location.href).href; 40 | img.setAttribute('src', absolutePath); 41 | } 42 | }); 43 | 44 | // 获取更新后的 HTML 内容 45 | return doc.body.innerHTML; 46 | }; -------------------------------------------------------------------------------- /src/media/meta/kuaishou.meta.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 { platformMetas } from "~media/platform"; 18 | 19 | const Api = { 20 | MediaInfo: platformMetas.kuaishou.mediaInfoUrl, 21 | }; 22 | 23 | const getMediaInfo = async () => { 24 | const response = await fetch(Api.MediaInfo, { 25 | "headers": { 26 | "content-type": "application/json", 27 | }, 28 | "body": JSON.stringify({"operationName":"userInfoQuery","variables":{},"query":"query userInfoQuery {\n userInfo {\n id\n name\n avatar\n eid\n userId\n __typename\n }\n}\n"}), 29 | "method": "POST", 30 | "credentials": "include" 31 | }); 32 | if (response.ok) { 33 | const body = await response.json(); 34 | console.log('body', body); 35 | if (body.data && body.data.userInfo) { 36 | const user = body.data.userInfo; 37 | const { id, eid, userId, name, avatar } = user; 38 | return { 39 | userId: userId, 40 | name: name, 41 | avatarUrl: avatar, 42 | } 43 | } 44 | return null; 45 | } 46 | return null; 47 | } 48 | 49 | export const kuaishouMetaInfo = { 50 | getMediaInfo, 51 | } -------------------------------------------------------------------------------- /src/components/sidepanel/ImageList.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 | 31 | 32 | 38 | 39 | -------------------------------------------------------------------------------- /src/events/contextMenus.event.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Copyright (c) 2025-2099 GitCoffee All Rights Reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { getSelectionContent } from "~utils/content"; 18 | 19 | import { isLoginApi } from "~api/media/user.api"; 20 | 21 | export const initContextMenusEvent = () => { 22 | 23 | chrome.runtime.onInstalled.addListener(() => { 24 | chrome.contextMenus.create({ 25 | id: "syncMenuItem", 26 | title: "提取内容并同步", 27 | contexts: ["all"], // 你可以限制菜单项显示的上下文 28 | }); 29 | }); 30 | 31 | chrome.contextMenus.onClicked.addListener((info, tab) => { 32 | if (info.menuItemId === "syncMenuItem") { 33 | // 在这里处理菜单项被点击时的操作 34 | console.log("提取内容并同步 菜单项被点击"); 35 | 36 | handelSyncData(info, tab); 37 | 38 | } 39 | }); 40 | 41 | const handelSyncData = async (info, tab) => { 42 | 43 | const res = await isLoginApi({}); 44 | console.log('res', res); 45 | if (!res?.data?.login) { 46 | chrome.tabs.sendMessage(tab.id, { action: "doLogin" }, (response) => { 47 | console.log(response.data); // 在这里处理返回的数据 48 | }); 49 | return; 50 | } 51 | 52 | const url = info.linkUrl || info.frameUrl || info.pageUrl; 53 | 54 | chrome.tabs.sendMessage(tab.id, { action: "previewContent", tabTitle: tab.title, tabUrl: url }, (response) => { 55 | console.log(response.content); // 在这里处理返回的网页内容 56 | }); 57 | } 58 | } -------------------------------------------------------------------------------- /src/plugins/index.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 | // 插件列表,添加新插件时只需要在这个数组中添加插件代码 18 | const pluginList = [ 19 | 'it' 20 | // 在这里添加新的插件代码,例如:'ai', 'tool' 21 | ]; 22 | 23 | // 静态导入所有插件 24 | import { pluginRegistry } from './registry'; 25 | import { setupInjection } from './injector'; 26 | 27 | // 导入IT插件 28 | import itPlugin from './it/index'; 29 | 30 | // 插件映射,只包含插件本身 31 | const pluginModulesMap = { 32 | it: itPlugin 33 | // 在这里添加新的插件映射,例如: 34 | // ai: aiPlugin 35 | }; 36 | 37 | // 注册所有插件 38 | const registerPlugins = () => { 39 | try { 40 | pluginList.forEach(pluginCode => { 41 | try { 42 | const plugin = pluginModulesMap[pluginCode]; 43 | 44 | if (!plugin) { 45 | console.warn(`插件 ${pluginCode} 没有正确的映射`); 46 | return; 47 | } 48 | 49 | const { base, config, implementation, modules = {} } = plugin; 50 | 51 | // 注册插件 52 | pluginRegistry.register(base, config, implementation, modules); 53 | 54 | } catch (error) { 55 | console.error(`加载插件 ${pluginCode} 失败:`, error); 56 | } 57 | }); 58 | 59 | console.log(`已加载 ${pluginRegistry.getAllPlugins().length} 个插件`); 60 | } catch (error) { 61 | console.error('插件系统初始化失败:', error); 62 | } 63 | }; 64 | 65 | // 先初始化注入器 66 | setupInjection(); 67 | 68 | // 然后注册所有插件 69 | registerPlugins(); 70 | 71 | // 导出插件注册表供其他模块使用 72 | export { pluginRegistry } from './registry'; 73 | -------------------------------------------------------------------------------- /src/plugins/registry.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 { PluginType, RegisteredPlugin, PluginBase, PluginConfig, PluginImplementation } from './types'; 18 | import { injectPluginPlatforms } from './injector'; 19 | 20 | class PluginRegistry { 21 | private plugins = new Map(); 22 | 23 | register(base: PluginBase, config: PluginConfig, implementation: PluginImplementation, modules?: any) { 24 | const registeredPlugin: RegisteredPlugin = { 25 | base, 26 | config, 27 | implementation, 28 | modules 29 | }; 30 | 31 | this.plugins.set(base.code, registeredPlugin); 32 | console.log(`插件 ${base.name} (${base.code}) 已注册,类型: ${base.type}`); 33 | 34 | // 如果是发布器插件,立即注入其平台信息 35 | if (base.type === PluginType.PUBLISHER) { 36 | injectPluginPlatforms(); 37 | } 38 | } 39 | 40 | getAllPlugins(): RegisteredPlugin[] { 41 | return Array.from(this.plugins.values()); 42 | } 43 | 44 | getPlugin(code: string): RegisteredPlugin | undefined { 45 | return this.plugins.get(code); 46 | } 47 | 48 | getPluginsByType(type: PluginType): RegisteredPlugin[] { 49 | return this.getAllPlugins().filter(plugin => plugin.base.type === type); 50 | } 51 | 52 | hasPlugin(code: string): boolean { 53 | return this.plugins.has(code); 54 | } 55 | 56 | unregister(code: string): boolean { 57 | return this.plugins.delete(code); 58 | } 59 | } 60 | 61 | export const pluginRegistry = new PluginRegistry(); -------------------------------------------------------------------------------- /src/plugins/it/index.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 { PluginType, PluginBase, PublisherConfig, PluginImplementation } from '../types'; 18 | 19 | // 导入插件的媒体模块(使用命名导入,因为这些模块没有默认导出) 20 | import * as platformModule from './media/platform/index'; 21 | import * as metaModule from './media/meta/index'; 22 | import * as publisherModule from './media/publisher/index'; 23 | 24 | // 注意:PublisherMeta 不再需要,因为那是平台级别的配置 25 | // 具体平台的元数据在 media/platform/index.ts 中定义 26 | 27 | // 插件基础信息 28 | const pluginBase: PluginBase = { 29 | code: 'it', 30 | name: 'IT技术平台同步插件', 31 | version: '1.0.0', 32 | type: PluginType.PUBLISHER, 33 | description: '包含掘金、CSDN等IT技术平台的发布支持', 34 | author: 'GitCoffee' 35 | }; 36 | 37 | // 插件配置(插件级别的通用配置,具体平台的配置在 media/platform/index.ts 中定义) 38 | const pluginConfig: PublisherConfig = { 39 | // 插件支持的内容类型(这个插件包含的平台支持哪些类型的内容) 40 | types: ['article'], 41 | 42 | // 注意:具体的平台元数据(platformName, site, homepage等)在各平台的 media/platform/index.ts 中定义 43 | // 这里不重复定义,因为一个插件可能包含多个不同的平台 44 | }; 45 | 46 | // 插件实现 47 | const pluginImplementation: PluginImplementation = { 48 | initialize: async () => { 49 | console.log('IT插件初始化完成'); 50 | }, 51 | 52 | destroy: async () => { 53 | console.log('IT插件销毁完成'); 54 | }, 55 | 56 | // 这里可以添加插件级别的通用方法 57 | getSupportedPlatforms: () => { 58 | return ['juejin', 'csdn', 'segmentfault']; // 未来支持的平台 59 | } 60 | }; 61 | 62 | // 插件定义导出 63 | export default { 64 | base: pluginBase, 65 | config: pluginConfig, 66 | implementation: pluginImplementation, 67 | modules: { 68 | platform: platformModule, 69 | meta: metaModule, 70 | publisher: publisherModule 71 | } 72 | }; -------------------------------------------------------------------------------- /src/contents/messageEventListener.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 { POSTBOT_ACTION } from "~message/postbot.action"; 17 | // import { getMetaInfoList } from "~media/meta"; 18 | 19 | window.addEventListener('message', async (event) => { 20 | const request = event.data; 21 | console.log('addEventListener', request); 22 | console.log('event.origin', event.origin); 23 | if (request?.type === 'response') { 24 | return; 25 | } 26 | if (!request?.action) { 27 | return; 28 | } 29 | // 验证消息来源是否可信 30 | // 转发消息 31 | dispatchSendMessage(request, event); 32 | }); 33 | 34 | const dispatchSendMessage = (request, event) => { 35 | console.log('dispatchSendMessage', request); 36 | 37 | switch (request.action) { 38 | // case POSTBOT_ACTION.META_INFO_LIST: 39 | // const metaInfoList = getMetaInfoList(); 40 | // event.source.postMessage(successResponse(request, metaInfoList)); 41 | // break; 42 | default: 43 | chrome.runtime.sendMessage(request).then((response) => { 44 | console.log('dispatchSendMessage response', response); 45 | event.source.postMessage(successResponse(request, response)); 46 | }); 47 | break; 48 | } 49 | } 50 | 51 | const successResponse = (request, data) => { 52 | return { 53 | type: 'response', 54 | traceId: request.traceId, 55 | action: request.action, 56 | code: 0, 57 | message: 'success', 58 | data, 59 | }; 60 | } 61 | 62 | // const errorResponse = (request, data) => { 63 | // return { 64 | // type: 'response', 65 | // traceId: request.traceId, 66 | // action: request.action, 67 | // code: 403, 68 | // message: 'Untrusted origin', 69 | // data: null, 70 | // }; 71 | // } -------------------------------------------------------------------------------- /src/media/meta/weibo.meta.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 { platformMetas } from "~media/platform"; 18 | import { getImageUrl, imageDownloadToBase64 } from "~utils/image"; 19 | 20 | const Api = { 21 | MediaInfo: platformMetas.weibo.mediaInfoUrl, 22 | }; 23 | 24 | const getWeiboMetaInfo = async(html) => { 25 | const regex = /window\.__WB_GET_CONFIG\s*=\s*function\s*\(\)\s*\{\s*var\s+configData\s*=\s*\{\s*config:\s*JSON\.parse\('([^']+)'\)/; 26 | const match = html.match(regex); 27 | 28 | if (!match) { 29 | return null; 30 | } 31 | 32 | try { 33 | const config = JSON.parse(match[1]); 34 | 35 | const { uid, nick, avatar_large } = config; 36 | 37 | const avatarUrl = decodeURI(avatar_large); 38 | 39 | let avatar = null; 40 | if(avatarUrl) { 41 | avatar = await imageDownloadToBase64(avatarUrl); 42 | } 43 | 44 | const userInfo = { 45 | userId: uid, 46 | name: nick, 47 | avatarUrl: getImageUrl(avatarUrl), 48 | avatar: avatar, 49 | }; 50 | 51 | return userInfo; 52 | } catch (error) { 53 | console.error('解析信息失败:', error); 54 | return null; 55 | } 56 | } 57 | 58 | const getMediaInfo = async () => { 59 | const response = await fetch(Api.MediaInfo, { 60 | method: 'GET', 61 | }); 62 | if (response.ok) { 63 | const body = await response.text(); 64 | console.log('body', body); 65 | 66 | // const userInfo = await getMetaInfo(body); 67 | const userInfo = await getWeiboMetaInfo(body); 68 | console.log('userInfo', userInfo); 69 | return userInfo; 70 | } 71 | return null; 72 | } 73 | 74 | export const weiboMetaInfo = { 75 | getMediaInfo, 76 | } -------------------------------------------------------------------------------- /src/plugins/it/media/meta/oschina.meta.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 { getDocument } from "~utils/html"; 18 | import { platformMetas } from "../platform"; 19 | import { getImageUrl } from "~utils/image"; 20 | 21 | const Api = { 22 | MediaInfo: platformMetas.oschina.mediaInfoUrl, 23 | }; 24 | 25 | export const getOsChinaMetaInfo = (html) => { 26 | const userNameMatch = html.match(/data-name="g_user_name" data-value="([^"]+)"/); 27 | const userIdMatch = html.match(/data-name="g_user_id" data-value="([^"]+)"/); 28 | const userPortraitSmallMatch = html.match(/data-name="g_user_small_portrait" data-value="([^"]+)"/); 29 | const userPortraitLargeMatch = html.match(/data-name="g_user_large_portrait" data-value="([^"]+)"/); 30 | 31 | // 获取结果 32 | const userName = userNameMatch ? userNameMatch[1] : null; 33 | const userId = userIdMatch ? userIdMatch[1] : null; 34 | const userPortraitSmall = userPortraitSmallMatch ? userPortraitSmallMatch[1] : null; 35 | // const userPortraitLarge = userPortraitLargeMatch ? userPortraitLargeMatch[1] : null; 36 | 37 | const userInfo = { 38 | userId: userId, 39 | name: userName, 40 | avatarUrl: getImageUrl(userPortraitSmall), 41 | } 42 | console.log('userInfo', userInfo); 43 | return userInfo; 44 | } 45 | 46 | const getMediaInfo = async () => { 47 | const response = await fetch(Api.MediaInfo, { 48 | method: 'GET', 49 | }); 50 | if (response.ok) { 51 | const body = await response.text(); 52 | console.log('body', body); 53 | 54 | const userInfo = await getOsChinaMetaInfo(body); 55 | console.log('userInfo', userInfo); 56 | return userInfo; 57 | } 58 | return null; 59 | } 60 | 61 | export const oschinaMetaInfo = { 62 | getMediaInfo, 63 | } -------------------------------------------------------------------------------- /src/contents/index.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 type { PlasmoCSConfig } from "plasmo" 17 | 18 | import { initCopyEvent } from "~events/copy.event" 19 | 20 | import { getReaderData } from "~media/parser" 21 | 22 | export const config: PlasmoCSConfig = { 23 | // matches: ["https://www.plasmo.com/*"] 24 | } 25 | 26 | import { createApp } from 'vue' 27 | import PostbotModal from './components/PostbotModal'; 28 | // import 'ant-design-vue/dist/reset.css'; 29 | 30 | import { handleMessage } from "./services/message.services"; 31 | 32 | // 创建并挂载 Vue 应用到页面 33 | const app = createApp(PostbotModal) 34 | 35 | // 创建一个容器 div,并添加到页面 body 中 36 | const container = document.createElement('div') 37 | container.id = 'postbot-container' 38 | document.body.appendChild(container) 39 | 40 | // 将 Vue 应用挂载到刚创建的 div 上 41 | app.mount(container) 42 | 43 | let data = { 44 | content: '', 45 | contentImages: [], 46 | } 47 | 48 | window.addEventListener("load", () => { 49 | console.log( 50 | "You may find that having is not so pleasing a thing as wanting. This is not logical, but it is often true." 51 | ) 52 | 53 | // initCopyEvent(); 54 | 55 | const data = getReaderData(); 56 | const { content, contentImages } = data; 57 | 58 | chrome.runtime.sendMessage({ 59 | type: "IMAGE_DETECTED", 60 | contentImages: Array.from(contentImages).map(img => ({ src: img.src })), 61 | }) 62 | 63 | chrome.runtime.sendMessage({ 64 | type: "CONTENT_DETECTED", 65 | content: content, 66 | }); 67 | 68 | // document.body.style.background = "pink" 69 | }) 70 | 71 | // 发送获取到的图片URL到后台脚本 72 | 73 | chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { 74 | console.log('addListener request.action', request.action); 75 | 76 | handleMessage(request, sender, sendResponse); 77 | 78 | return true; 79 | }); -------------------------------------------------------------------------------- /src/plugins/it/media/meta/cnblogs.meta.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 { platformMetas } from "../platform"; 18 | 19 | import { getImageUrl } from "~utils/image"; 20 | 21 | const Api = { 22 | MediaInfo: platformMetas.cnblogs.mediaInfoUrl, 23 | }; 24 | 25 | export const getCnBlogsMetaInfo = (html) => { 26 | 27 | const usernameRegex = /欢迎你,(\S+)\s*<\/h1>/; 28 | const usernameMatch = html.match(usernameRegex); 29 | const username = usernameMatch ? usernameMatch[1] : ''; 30 | 31 | const avatarUrlRegex = /([^<]+)<\/a>/; 36 | const nicknameMatch = html.match(nicknameRegex); 37 | const nickname = nicknameMatch ? nicknameMatch[1] : ''; 38 | 39 | const userIdRegex = //; 40 | const userIdMatch = html.match(userIdRegex); 41 | const userId = userIdMatch ? userIdMatch[1] : ''; 42 | 43 | const avatar = `https:${avatarUrl}`; 44 | 45 | const userInfo = { 46 | userId: userId, 47 | name: username, 48 | avatarUrl: getImageUrl(avatar), 49 | } 50 | console.log('userInfo', userInfo); 51 | return userInfo; 52 | } 53 | 54 | const getMediaInfo = async () => { 55 | const response = await fetch(Api.MediaInfo, { 56 | method: 'GET', 57 | }); 58 | if (response.ok) { 59 | const body = await response.text(); 60 | console.log('body', body); 61 | 62 | const userInfo = await getCnBlogsMetaInfo(body); 63 | console.log('userInfo', userInfo); 64 | return userInfo; 65 | } 66 | return null; 67 | } 68 | 69 | export const cnblogsMetaInfo = { 70 | getMediaInfo, 71 | } -------------------------------------------------------------------------------- /src/contents/services/message.services.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 { getReaderData } from "~media/parser" 17 | 18 | import { state } from "../components/postbot.data" 19 | 20 | import { BASE_URL } from '~config/config'; 21 | 22 | import { handleMetaMessage } from "./meta.services"; 23 | 24 | export const handleMessage = (request, sender, sendResponse) => { 25 | const data = getReaderData(); 26 | const { content, contentImages } = data; 27 | 28 | let mesage = {}; 29 | let userInfo = {}; 30 | switch (request.action) { 31 | case 'doLogin': 32 | window.open(`${BASE_URL}/exmay/postbot/media/publish`, '_blank'); 33 | sendResponse({}); 34 | break; 35 | case 'getImages': 36 | message = { contentImages: contentImages }; 37 | sendResponse(message); 38 | break; 39 | case 'getContent': 40 | message = { content: content }; 41 | sendResponse(message); 42 | break; 43 | case 'previewContent': 44 | message = { content: content }; 45 | // pageContent('test'); 46 | state.rangType = 'content'; 47 | state.isModalVisible = true; 48 | sendResponse(message); 49 | break; 50 | case 'selectionContent': 51 | message = { content: content }; 52 | // pageContent('test'); 53 | state.rangType = 'selection'; 54 | state.isModalVisible = true; 55 | sendResponse(message); 56 | break; 57 | case 'setFlowButton': 58 | state.showFlowButton = request?.showFlowButton; 59 | sendResponse({}); 60 | break; 61 | default: 62 | handleMetaMessage(request, sender, sendResponse); 63 | break; 64 | } 65 | } -------------------------------------------------------------------------------- /src/components/sidepanel/UserCard.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 | 38 | 39 | 64 | 65 | -------------------------------------------------------------------------------- /src/utils/image.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 | const BAIDU_DOWNLOAD_URL = 'https://image.baidu.com/search/down?url='; 18 | const WORD_PRESS_IMAGE_DOMAIN = 'i0.wp.com'; 19 | 20 | export const getImageDownloadUrl = (imageUrl) => { 21 | return BAIDU_DOWNLOAD_URL + imageUrl; 22 | } 23 | 24 | export const getImageUrl = (url) => { 25 | const regex = /^(https?:\/\/)([^\/]+)(\/.*)$/; 26 | const match = url.match(regex); 27 | if (match) { 28 | const protocol = match[1]; 29 | const domain = match[2]; 30 | const path = match[3]; 31 | return `${protocol}${WORD_PRESS_IMAGE_DOMAIN}/${domain}${path}`; 32 | } 33 | return url; 34 | } 35 | 36 | export const imageToBase64 = async (imageUrl) => { 37 | let imageBase64 = null; 38 | try { 39 | // 发送请求获取图片数据 40 | const response = await fetch(imageUrl); 41 | if (!response.ok) { 42 | throw new Error('网络响应失败'); 43 | } 44 | 45 | // 获取图片的 Blob 46 | const imageBlob = await response.blob(); 47 | 48 | // 使用 FileReader 将 Blob 转换为 base64 编码(仍是异步的) 49 | const reader = new FileReader(); 50 | 51 | // 返回一个 Promise 来包裹异步操作 52 | const base64Data = await new Promise((resolve, reject) => { 53 | reader.onloadend = () => resolve(reader.result); // 读取成功,返回 base64 编码 54 | reader.onerror = reject; // 读取失败,拒绝 Promise 55 | reader.readAsDataURL(imageBlob); // 异步读取 Blob 56 | }); 57 | 58 | // 将转换后的 base64 数据存储在 imageBase64 中 59 | console.log('base64Data', base64Data); 60 | imageBase64 = base64Data; 61 | } catch (error) { 62 | console.error('获取图片失败:', error); 63 | } 64 | return imageBase64; 65 | } 66 | 67 | export const imageDownloadToBase64 = async (imageUrl) => { 68 | const url = getImageDownloadUrl(imageUrl); 69 | return await imageToBase64(url); 70 | } -------------------------------------------------------------------------------- /src/media/meta/weixinChannels.meta.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 { platformMetas } from "~media/platform"; 17 | 18 | const Api = { 19 | MediaInfo: platformMetas.weixin_channels.mediaInfoUrl, 20 | }; 21 | 22 | const getMediaInfo = async () => { 23 | 24 | const body = { 25 | "timestamp": Date.now(), 26 | "_log_finder_uin": "", 27 | "_log_finder_id": "", 28 | "rawKeyBuff": null, 29 | "pluginSessionId": null, 30 | "scene": 7, 31 | "reqScene": 7 32 | }; 33 | 34 | const getTimestamp = () => { 35 | const timestamp = Math.floor(Date.now() / 1000); 36 | // console.debug(timestamp.toString(16)); 37 | return timestamp.toString(16); 38 | }; 39 | 40 | const getRandomHex = () => { 41 | const randomHex = [...Array(8)] 42 | .map(() => Math.floor(16 * Math.random()).toString(16)) 43 | .join(''); 44 | // console.debug(randomHex); 45 | return randomHex; 46 | }; 47 | 48 | const url = `${Api.MediaInfo}?_aid=&_rid=${getTimestamp()}-${getRandomHex()}&_pageUrl=https:%2F%2Fchannels.weixin.qq.com%2Fplatform`; 49 | 50 | const response = await fetch(url, { 51 | method: 'POST', 52 | headers: { 53 | 'Content-Type': 'application/json', 54 | 'Sec-Fetch-Site': 'same-origin', 55 | 'X-Wechat-Uin': '0000000000', 56 | }, 57 | body: JSON.stringify(body), 58 | }); 59 | if (response.ok) { 60 | const body = await response.json(); 61 | console.log('body', body); 62 | if (body.errCode === 0) { 63 | const data = body?.data; 64 | const finderUser = data?.finderUser; 65 | const { uniqId, nickname, headImgUrl, adminNickname, finderUsername } = finderUser; 66 | return { 67 | userId: uniqId, 68 | name: nickname, 69 | avatarUrl: headImgUrl, 70 | } 71 | } 72 | return null; 73 | } 74 | return null; 75 | } 76 | 77 | export const weixinChannelsMetaInfo = { 78 | getMediaInfo, 79 | } -------------------------------------------------------------------------------- /src/plugins/types.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 | // 插件类型枚举 18 | export enum PluginType { 19 | PUBLISHER = 'publisher', // 发布器插件(如掘金、微信等) 20 | AI = 'ai', // AI插件 21 | TOOL = 'tool', // 工具插件 22 | EXTENSION = 'extension' // 扩展插件 23 | } 24 | 25 | // 插件基础信息 26 | export interface PluginBase { 27 | code: string; 28 | name: string; 29 | version: string; 30 | type: PluginType; 31 | description?: string; 32 | author?: string; 33 | } 34 | 35 | // 发布器插件特有的元数据 36 | export interface PublisherMeta { 37 | platformName: string; 38 | site: string; 39 | homepage: string; 40 | mediaInfoUrl: string; 41 | faviconUrl: string; 42 | icon: string; 43 | tags: string[]; 44 | sort: number; 45 | status: 'enabled' | 'disabled'; 46 | } 47 | 48 | // 发布器插件配置(插件级别,不包含具体平台元数据) 49 | export interface PublisherConfig { 50 | types: ('article' | 'moment' | 'video' | 'podcast')[]; 51 | // 注意:具体的平台元数据(platformName, site等)在各平台的配置文件中定义 52 | // publishUrls 等具体配置也在平台级别定义 53 | } 54 | 55 | // AI插件配置 56 | export interface AIConfig { 57 | apiEndpoint: string; 58 | models: string[]; 59 | capabilities: string[]; 60 | } 61 | 62 | // 插件配置联合类型 63 | export type PluginConfig = PublisherConfig | AIConfig; 64 | 65 | // 插件实现接口 66 | export interface PluginImplementation { 67 | // 通用方法 68 | initialize?(): Promise; 69 | destroy?(): Promise; 70 | 71 | // 发布器插件特有方法 72 | getMediaInfo?(): Promise; 73 | getPublishUrl?(type?: string): string; 74 | publisher?(data: any): Promise; 75 | 76 | // AI插件特有方法 77 | generateContent?(prompt: string): Promise; 78 | analyzeContent?(content: string): Promise; 79 | 80 | // 插件级别的通用方法 81 | getSupportedPlatforms?(): string[]; 82 | } 83 | 84 | // 已注册插件 85 | export interface RegisteredPlugin { 86 | base: PluginBase; 87 | config: PluginConfig; 88 | implementation: PluginImplementation; 89 | modules?: { 90 | platform?: any; 91 | meta?: any; 92 | publisher?: any; 93 | }; 94 | } -------------------------------------------------------------------------------- /src/plugins/it/media/meta/segmentfault.meta.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 { platformMetas } from "../platform"; 18 | 19 | import { getImageUrl } from "~utils/image"; 20 | 21 | const Api = { 22 | MediaInfo: platformMetas.segmentfault.mediaInfoUrl, 23 | }; 24 | 25 | export const getSegmentFaultMetaInfo = (html) => { 26 | let userInfo = null; 27 | try { 28 | const jsonMatch = html.match(/ 104 | 105 | -------------------------------------------------------------------------------- /src/tabs/index.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 { ref, reactive } from 'vue'; 17 | 18 | import { executeScriptsToTabs } from '~media/publisher/publisher.script'; 19 | 20 | export const tabsState = reactive({ 21 | tabs: [], 22 | publishedTabs: [], 23 | }); 24 | 25 | const getTabIds = () => { 26 | const tabIds = tabsState.publishedTabs.map((tab) => tab.tab.id).filter((id): id is number => id !== undefined); 27 | return tabIds; 28 | } 29 | 30 | export const createTab = async (url) => { 31 | return await chrome.tabs.create({ url }); 32 | } 33 | 34 | export const updateTab = async (tabId, value) => { 35 | await chrome.tabs.update(tabId, value); 36 | } 37 | 38 | export const createTabGroup = async (tabIds) => { 39 | return await chrome.tabs.group({ 40 | tabIds: tabIds, 41 | }); 42 | } 43 | 44 | export const updateTabGroup = async (tabGroupId) => { 45 | await chrome.tabGroups.update(tabGroupId, 46 | { 47 | color: 'purple', 48 | title: `PostBot 内容同步助手`, 49 | } 50 | ); 51 | } 52 | 53 | export const addTabToTabGroup = async (tabIds, groupId) => { 54 | await chrome.tabs.group({ 55 | tabIds: tabIds, 56 | groupId, 57 | }); 58 | } 59 | 60 | export const handleTabClick = (tabId) => { 61 | 62 | } 63 | 64 | export const handleReloadClick = async (tabId) => { 65 | 66 | } 67 | 68 | export const setPublishedTabs = async (tabId) => { 69 | 70 | } 71 | 72 | export const waitOnUpdatedTab = async (tab) => { 73 | await new Promise((resolve) => { 74 | chrome.tabs.onUpdated.addListener(function listener(tabId, info) { 75 | if (tabId === tab!.id && info.status === 'complete') { 76 | chrome.tabs.onUpdated.removeListener(listener); 77 | resolve(); 78 | } 79 | }); 80 | }); 81 | } 82 | 83 | export const createTabsForPlatforms = async (data) => { 84 | let tabs = []; 85 | let tabGroupId; 86 | 87 | for (const platform of data?.platforms) { 88 | if (!platform) { 89 | return; 90 | } 91 | let tab = null; 92 | 93 | let publishUrls = platform?.publishUrls; 94 | if (!publishUrls) { 95 | const publishUrl = platform?.publishUrl; 96 | let url = null; 97 | if (publishUrl instanceof Function) { 98 | url = publishUrl(); 99 | } else { 100 | url = publishUrl; 101 | } 102 | publishUrls = [url]; 103 | } 104 | 105 | for (const publishUrl of publishUrls) { 106 | 107 | tab = await createTab(publishUrl); 108 | 109 | executeScriptsToTabs([{ tab, platform }], data); 110 | 111 | await updateTab(tab.id!, { active: true }); 112 | 113 | tabs.push({ 114 | tab, 115 | platform, 116 | }); 117 | 118 | if (!tabGroupId) { 119 | tabGroupId = await createTabGroup([tab.id!]); 120 | console.log('tabGroupId', tabGroupId); 121 | await updateTabGroup(tabGroupId); 122 | } else { 123 | await addTabToTabGroup([tab.id!], tabGroupId); 124 | } 125 | 126 | await waitOnUpdatedTab(tab); 127 | 128 | } 129 | 130 | }; 131 | } 132 | 133 | -------------------------------------------------------------------------------- /src/media/meta/weixin.meta.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 { platformMetas } from "~media/platform"; 17 | import { getDocument } from "~utils/html"; 18 | 19 | const Api = { 20 | HomePage: platformMetas.weixin.homepage, 21 | MediaInfo: platformMetas.weixin.mediaInfoUrl, 22 | }; 23 | 24 | const getMatchData = (match) => { 25 | return match ? match[1] : ''; 26 | } 27 | 28 | const parserUsrInfo = (script) => { 29 | const match = script.match(/window\.wx\.commonData\s*=\s*\{([\s\S]*?)\};/); 30 | 31 | console.log('match', match); 32 | if (!match) { 33 | throw new Error('获取登录信息失败'); 34 | } 35 | 36 | const tokenMatch = match[1].match(/t:\s*["'](\d+)["']/); 37 | const aliasMatch = match[1].match(/alias:\s*["']([^"']+)["']/); 38 | const usernameMatch = match[1].match(/user_name:\s*["']([^"']+)["']/); 39 | const nickenameMatch = match[1].match(/nick_name:\s*["']([^"']+)["']/); 40 | const ticketMatch = match[1].match(/ticket:\s*["']([^"']+)["']/); 41 | const avatarMatch = match[1].match(/head_img:\s*["']([^"']+)["']/); 42 | 43 | if (!tokenMatch) { 44 | throw new Error('暂无登录信息'); 45 | } 46 | 47 | const userInfo = { 48 | userId: getMatchData(aliasMatch), 49 | username: getMatchData(usernameMatch), 50 | name: getMatchData(nickenameMatch), 51 | token: getMatchData(tokenMatch), 52 | ticket: getMatchData(ticketMatch), 53 | avatarUrl: getMatchData(avatarMatch), 54 | }; 55 | console.log('userInfo', userInfo); 56 | return userInfo; 57 | } 58 | 59 | export const getWeixinMetaInfo = (html) => { 60 | const doc = getDocument(html); 61 | const scripts = doc.querySelectorAll('script'); 62 | const script = scripts[0].text; 63 | const code = script.substring(script.indexOf('window.wx.commonData')); 64 | console.debug('code', code); 65 | 66 | const userInfo = parserUsrInfo(code); 67 | return userInfo; 68 | } 69 | 70 | const getMetaInfo = (body) => { 71 | return new Promise((resolve, reject) => { 72 | chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { 73 | if (tabs.length > 0) { 74 | const activeTab = tabs[0]; // 获取首个(唯一)激活标签页的ID 75 | console.log("Active Tab:", activeTab); 76 | // }); 77 | 78 | // chrome.windows.getCurrent({ populate: true }, function (window) { 79 | // if (window.tabs.length > 0) { 80 | // const activeTab = window.tabs.find(tab => tab.active); 81 | // console.log('activeTab', activeTab); 82 | if (activeTab) { 83 | chrome.tabs.sendMessage(activeTab.id, { 84 | type: 'request', 85 | action: 'getWeixinMetaInfo', 86 | data: { 87 | html: body 88 | } 89 | }, (data) => { 90 | console.log('data', data); 91 | resolve(data); 92 | }) 93 | } else { 94 | reject('获取失败') 95 | } 96 | } else { 97 | reject('获取失败'); 98 | } 99 | }); 100 | }); 101 | } 102 | 103 | const getMediaInfo = async () => { 104 | const response = await fetch(Api.MediaInfo, { 105 | method: 'GET', 106 | }); 107 | if (response.ok) { 108 | const body = await response.text(); 109 | console.log('body', body); 110 | 111 | const userInfo = await getMetaInfo(body); 112 | console.log('userInfo', userInfo); 113 | return userInfo; 114 | } 115 | return null; 116 | } 117 | 118 | export const weixinMetaInfo = { 119 | getMediaInfo, 120 | } -------------------------------------------------------------------------------- /src/plugins/it/media/publisher/platform/article/juejin.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 | export const juejinArticlePublisher = async (data) => { 18 | console.log('juejinArticlePublisher data', data); 19 | 20 | // const { contentData, processedData } = data; 21 | 22 | const contentData = data?.data; 23 | const processedData = data?.data; 24 | 25 | const sleep = async (time) => { 26 | console.log('sleep', time); 27 | return new Promise((resolve) => setTimeout(resolve, time)); 28 | } 29 | 30 | const pasteEvent = (): ClipboardEvent => { 31 | console.log('pasteEvent'); 32 | return new ClipboardEvent('paste', { 33 | bubbles: true, 34 | cancelable: true, 35 | clipboardData: new DataTransfer(), 36 | }); 37 | } 38 | 39 | const observeElement = (selector, timeout = 10000) => { 40 | console.log('observeElement', selector); 41 | return new Promise((resolve, reject) => { 42 | // const checkElement = () => document.querySelector(selector); 43 | 44 | let checkElement = null; 45 | if (selector instanceof Function) { 46 | checkElement = selector; 47 | } else { 48 | checkElement = () => document.querySelector(selector); 49 | } 50 | 51 | // 立即检查元素 52 | let element = checkElement(); 53 | console.log('element', element); 54 | if (element) { 55 | resolve(element); 56 | return; 57 | } 58 | 59 | // 创建 MutationObserver 进行监听 60 | const observer = new MutationObserver(() => { 61 | element = checkElement(); 62 | console.log('element', element); 63 | if (element) { 64 | resolve(element); 65 | observer.disconnect(); 66 | } 67 | }); 68 | 69 | // 启动观察 70 | observer.observe(document.body, { 71 | childList: true, 72 | subtree: true, 73 | }); 74 | 75 | // 如果超时,拒绝 Promise,并返回中文错误提示 76 | setTimeout(() => { 77 | observer.disconnect(); 78 | reject(new Error(`未能在 ${timeout} 毫秒内找到选择器为 "${selector}" 的元素`)); 79 | }, timeout); 80 | }); 81 | }; 82 | 83 | const formElement = { 84 | title: '.editor-header input[placeholder="输入文章标题..."]', 85 | editor: '.bytemd-editor div.CodeMirror-code[role="presentation"]', 86 | submitButtons: 'button.xitu-btn', 87 | submitButtonText: '发布', 88 | } 89 | 90 | const fromRule = { 91 | title: { 92 | min: 2, 93 | max: 100, 94 | } 95 | } 96 | 97 | const autoFillContent = async (contentData) => { 98 | console.log('autoFillContent'); 99 | const titleInput = await observeElement(formElement.title) as HTMLElement; 100 | console.log('titleInput', titleInput); 101 | if (!titleInput) { 102 | console.log('未找到标题输入框'); 103 | return; 104 | } 105 | (titleInput as HTMLTextAreaElement).value = contentData?.title?.slice(0, fromRule.title.max) || ''; 106 | titleInput.dispatchEvent(new Event('input', { bubbles: true })); 107 | titleInput.dispatchEvent(new Event('change', { bubbles: true })); 108 | 109 | const editor = await observeElement(formElement.editor) as HTMLElement; 110 | console.log('editor', editor); 111 | if (!editor) { 112 | console.log('未找到编辑器'); 113 | return; 114 | } 115 | editor.focus(); 116 | const editorPasteEvent = pasteEvent(); 117 | editorPasteEvent.clipboardData.setData('text/html', contentData?.content); 118 | editor.dispatchEvent(editorPasteEvent); 119 | editor.dispatchEvent(new Event('input', { bubbles: true })); 120 | editor.dispatchEvent(new Event('change', { bubbles: true })); 121 | }; 122 | 123 | const autoPublish = () => { 124 | const submitButtons = document.querySelectorAll(formElement.submitButtons); 125 | console.log('submitButtons', submitButtons); 126 | if (!submitButtons) { 127 | return; 128 | } 129 | const submitButton = Array.from(submitButtons).find(button => button.textContent?.includes(formElement.submitButtonText)); 130 | console.log('submitButton', submitButton); 131 | if (!submitButton) { 132 | return; 133 | } 134 | (submitButton as HTMLElement).click(); 135 | } 136 | 137 | await autoFillContent(contentData); 138 | await sleep(5000); 139 | 140 | if (contentData.isAutoPublish) { 141 | autoPublish(); 142 | } 143 | 144 | } -------------------------------------------------------------------------------- /src/plugins/it/media/publisher/platform/article/51cto.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 $51ctoArticlePublisher = async (data) => { 20 | console.log('$51ctoArticlePublisher 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: '#title', 87 | editor: '#container', 88 | submitButtons: 'button.edit-submit', 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 | const editorPasteEvent = pasteEvent(); 120 | editorPasteEvent.clipboardData.setData('text/html', contentData?.content); 121 | editor.dispatchEvent(editorPasteEvent); 122 | editor.dispatchEvent(new Event('input', { bubbles: true })); 123 | editor.dispatchEvent(new Event('change', { bubbles: true })); 124 | }; 125 | 126 | const autoPublish = () => { 127 | const submitButtons = document.querySelectorAll(formElement.submitButtons); 128 | console.log('submitButtons', submitButtons); 129 | if (!submitButtons) { 130 | return; 131 | } 132 | const submitButton = Array.from(submitButtons).find(button => button.textContent?.includes(formElement.submitButtonText)); 133 | console.log('submitButton', submitButton); 134 | if (!submitButton) { 135 | return; 136 | } 137 | (submitButton as HTMLElement).click(); 138 | } 139 | 140 | await autoFillContent(contentData); 141 | await sleep(5000); 142 | 143 | if (contentData.isAutoPublish) { 144 | autoPublish(); 145 | } 146 | 147 | } -------------------------------------------------------------------------------- /src/plugins/it/media/publisher/platform/article/segmentfault.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 segmentfaultArticlePublisher = async (data) => { 20 | console.log('segmentfaultArticlePublisher 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: '#title', 87 | editor: '.sf-editor div.CodeMirror-code[role="presentation"]', 88 | submitButtons: 'div.d-none button.btn-primary', 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 | const editorPasteEvent = pasteEvent(); 120 | editorPasteEvent.clipboardData.setData('text/html', contentData?.content); 121 | editor.dispatchEvent(editorPasteEvent); 122 | editor.dispatchEvent(new Event('input', { bubbles: true })); 123 | editor.dispatchEvent(new Event('change', { bubbles: true })); 124 | }; 125 | 126 | const autoPublish = () => { 127 | const submitButtons = document.querySelectorAll(formElement.submitButtons); 128 | console.log('submitButtons', submitButtons); 129 | if (!submitButtons) { 130 | return; 131 | } 132 | const submitButton = Array.from(submitButtons).find(button => button.textContent?.includes(formElement.submitButtonText)); 133 | console.log('submitButton', submitButton); 134 | if (!submitButton) { 135 | return; 136 | } 137 | (submitButton as HTMLElement).click(); 138 | } 139 | 140 | await autoFillContent(contentData); 141 | await sleep(5000); 142 | 143 | if (contentData.isAutoPublish) { 144 | autoPublish(); 145 | } 146 | 147 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | logo 3 |

4 |

PostBot 内容同步助手 v1.1.7

5 |

6 | 一款开源的多平台内容同步分发生产力工具。 7 |
8 | 支持将文章、笔记、动态、图片、视频、音频等内容,一键同步发布至主流媒体平台。 9 |

10 |

11 | 12 | 13 | 14 | 15 | Ask DeepWiki 16 | 17 | zread 18 |

19 |

20 | 正在持续迭代中... 21 |

22 | 23 | ## PostBot 内容同步助手 介绍 24 | ![postbot-homepage](https://postbot.exmay.com/docs/images/postbot_homepage.png) 25 |
26 | ![postbot-homepage](https://postbot.exmay.com/docs/images/postbot_homepage_dark.png) 27 |
28 | ![postbot-extension](https://postbot.exmay.com/docs/images/postbot_extension.png) 29 |
30 | ![postbot-extension](https://postbot.exmay.com/docs/images/postbot_extension_dark.png) 31 |
32 | ![postbot-modal](https://postbot.exmay.com/docs/images/postbot_modal.png) 33 |
34 | ![postbot-modal](https://postbot.exmay.com/docs/images/postbot_modal_dark.png) 35 |
36 | ![postbot-publisher](https://postbot.exmay.com/docs/images/postbot_publisher.png?v=1.1.7) 37 |
38 | ![postbot-editor](https://postbot.exmay.com/docs/images/postbot_editor.png?v=1.1.7) 39 |
40 | ![postbot-editor](https://postbot.exmay.com/docs/images/postbot_moment.png?v=1.1.7) 41 |
42 | ![postbot-editor](https://postbot.exmay.com/docs/images/postbot_video.png?v=1.1.7) 43 |
44 | ![postbot-editor](https://postbot.exmay.com/docs/images/postbot_audio.png?v=1.1.7) 45 |
46 | ![postbot-sidebar](https://postbot.exmay.com/docs/images/postbot_sidebar.png) 47 | 48 | ## 🌟 核心特性 49 | ### 📝 专注内容 50 | - 支持文章、笔记、动态、图片、视频、音频等多内容。 51 | - 文本内容支持 HTML、Markdown 格式。 52 | ### [🌍 多平台](https://postbot.exmay.com/docs/media) 53 | - 覆盖微信/微博/今日头条/小红书/知乎/百家号/企鹅号/视频号/抖音/快手/哔哩哔哩(B站)等国内主流媒体平台,可轻松扩展兼容 X(Twitter)、Facebook、Instagram、TikTok、YouTube、LinkedIn 等国际媒体平台。 54 | ### 🚀 一键发布 55 | - 一次编辑排版、一键同步至多平台自动发布。 56 | - 可同时将内容自动同步发布至所有已登录的媒体平台。 57 | ### 🔐 安全 58 | - 区别于其他工具的核心在于本地化操作机制,直接复用浏览器本地登录状态,规避云端存储账号信息的隐私风险,安全安心。 59 | ### 📖 智能阅读器 60 | - 内置智能网页阅读器,网页内容智能提取。 61 | ### 🎯 平台适配 62 | - 自动优化内容适配,以满足各平台特性要求。 63 | ### ✨ AI 64 | - 可扩展集成 AI,内容更个性化、流程更智能。 65 | ### [🧩 扩展](https://postbot.exmay.com/docs/extension) 66 | - 通过插件、SDK、API接口、MCP、AI智能体及开放平台与应用市场,可扩展与链接各类行业平台或企业应用服务。 67 | ### 💡 生态 68 | - 提供内容创作者效率提升、开发者业务集成生态支持。 69 | 70 | ## 👀 使用体验 71 | - [PostBot 内容同步助手 云端体验](https://postbot.exmay.com/exmay/postbot/center/home) 72 | 73 | ## 📚 技术文档 74 | - [官网文档](https://postbot.exmay.com/docs) 75 | - [用户指南](https://postbot.exmay.com/docs/README) 76 | - [快速开始](https://postbot.exmay.com/docs/quickstart) 77 | 78 | ## 💻 代码托管 79 | - GitHub:[https://github.com/gitcoffee-os/postbot](https://github.com/gitcoffee-os/postbot) 80 | - Gitee:[https://gitee.com/gitcoffee-os/postbot](https://gitee.com/gitcoffee-os/postbot) 81 | 82 | ## 🙌 贡献指南 83 | 84 | PostBot 只有通过开源协作才能蓬勃发展。秉持这一精神,我们欢迎来自社区的各种贡献。如果您有意参与其中,请查阅我们的 [贡献者指南](https://postbot.exmay.com/docs/contributing) 。 85 | 86 | ## ⚖️ License 87 | 88 | 本仓库遵循 [GitCoffee Open Source License](https://github.com/gitcoffee-os/postbot/blob/main/LICENSE) 开源协议,该许可证本质上是 [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0),但有一些额外的限制。 89 | 90 | ## 🤝 商务合作 91 | 92 | - [合作咨询](https://postbot.exmay.com/docs/partner) 93 | 94 | ## 👥 加入社区 95 | PostBot 内容同步助手 微信 交流群: 96 | 97 | 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 | 93 | 94 | 179 | 180 | -------------------------------------------------------------------------------- /src/contents/components/PostbotFloatButton.tsx: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Copyright (c) 2025-2099 GitCoffee All Rights Reserved. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | import { defineComponent, h, ref, onMounted, onBeforeUnmount, computed } from 'vue' 18 | import { Button, FloatButtonGroup, FloatButton, Avatar } from 'ant-design-vue' 19 | import { SyncOutlined, QuestionCircleOutlined } from '@ant-design/icons-vue'; 20 | 21 | import iconUrl from "~assets/icon.png"; 22 | 23 | export default defineComponent({ 24 | name: 'PostbotFloatButton', 25 | setup(props, { emit }) { 26 | // 拖动相关状态 27 | const isMouseDown = ref(false); // 鼠标是否按下 28 | const isDragging = ref(false); 29 | const hasDragged = ref(false); // 标记是否发生过拖动 30 | const dragStartX = ref(0); 31 | const dragStartY = ref(0); 32 | const originalPosition = ref({ x: 0, y: 0 }); // 记录初始位置 33 | // 初始化默认位置(同步),确保有合理的默认值 34 | const getDefaultPosition = () => { 35 | const buttonWidth = 50; 36 | return { 37 | x: Math.max(0, (document.documentElement?.clientWidth || 1200) - buttonWidth), 38 | y: 200 39 | }; 40 | }; 41 | 42 | const position = ref(getDefaultPosition()); 43 | const positionLoaded = ref(true); // 初始就设置为已加载 44 | 45 | // 从 chrome.storage 加载保存的位置 46 | const loadPosition = async () => { 47 | try { 48 | const result = await chrome.storage.local.get(['postbot-float-button-position']); 49 | if (result['postbot-float-button-position']) { 50 | const savedPosition = result['postbot-float-button-position']; 51 | // 确保位置在合理范围内,确保按钮完全可见 52 | const buttonWidth = 50; 53 | const buttonHeight = 50; 54 | position.value = { 55 | x: Math.max(0, Math.min(savedPosition.x, document.documentElement.clientWidth - buttonWidth)), 56 | y: Math.max(0, Math.min(savedPosition.y, document.documentElement.clientHeight - buttonHeight)) 57 | }; 58 | } 59 | // 位置已加载(或保持默认位置) 60 | } catch (error) { 61 | console.warn('Failed to load float button position:', error); 62 | // 出错时保持默认位置 63 | } 64 | }; 65 | 66 | // 保存位置到 chrome.storage 67 | const savePosition = async () => { 68 | try { 69 | await chrome.storage.local.set({ 70 | 'postbot-float-button-position': position.value 71 | }); 72 | } catch (error) { 73 | console.warn('Failed to save float button position:', error); 74 | } 75 | }; 76 | 77 | // 鼠标按下事件 78 | const handleMouseDown = (event: MouseEvent) => { 79 | isMouseDown.value = true; 80 | isDragging.value = false; // 先不设置为 true,等待确认是否是拖动 81 | hasDragged.value = false; 82 | originalPosition.value = { ...position.value }; 83 | dragStartX.value = event.clientX - position.value.x; 84 | dragStartY.value = event.clientY - position.value.y; 85 | event.preventDefault(); 86 | event.stopPropagation(); 87 | }; 88 | 89 | // 鼠标移动事件 90 | const handleMouseMove = (event: MouseEvent) => { 91 | if (!isMouseDown.value) return; // 只有在鼠标按下时才处理 92 | 93 | // 检查是否移动了足够距离,认为是拖动操作 94 | const deltaX = Math.abs(event.clientX - (originalPosition.value.x + dragStartX.value)); 95 | const deltaY = Math.abs(event.clientY - (originalPosition.value.y + dragStartY.value)); 96 | 97 | if (deltaX > 5 || deltaY > 5) { 98 | if (!isDragging.value) { 99 | isDragging.value = true; 100 | hasDragged.value = true; 101 | } 102 | 103 | if (isDragging.value) { 104 | position.value.x = event.clientX - dragStartX.value; 105 | position.value.y = event.clientY - dragStartY.value; 106 | 107 | // 限制在视窗范围内,确保按钮完全可见 108 | // 估算按钮尺寸:Avatar(18px) + padding + border ≈ 50px 109 | const buttonWidth = 50; 110 | const buttonHeight = 50; 111 | const maxX = document.documentElement.clientWidth - buttonWidth; 112 | const maxY = document.documentElement.clientHeight - buttonHeight; 113 | 114 | position.value.x = Math.max(0, Math.min(position.value.x, maxX)); 115 | position.value.y = Math.max(0, Math.min(position.value.y, maxY)); 116 | } 117 | } 118 | }; 119 | 120 | // 鼠标释放事件 121 | const handleMouseUp = () => { 122 | if (isDragging.value && hasDragged.value) { 123 | savePosition(); // 异步保存 124 | } 125 | // 重置状态,但保留 hasDragged 用于阻止点击事件 126 | isMouseDown.value = false; 127 | isDragging.value = false; 128 | }; 129 | 130 | // 点击按钮的处理逻辑 131 | const handleClick = () => { 132 | if (hasDragged.value) { 133 | // 如果刚刚进行了拖动,重置标志并阻止点击事件 134 | hasDragged.value = false; 135 | return; 136 | } 137 | emit('click'); // 触发父组件传递的 show 事件 138 | }; 139 | 140 | // 计算属性:控制 tooltip 是否显示 141 | const showTooltip = computed(() => !isDragging.value); 142 | 143 | // 处理 chrome.storage 的变化 144 | const handleStorageChange = (changes: { [key: string]: chrome.storage.StorageChange }) => { 145 | if (changes['postbot-float-button-position'] && changes['postbot-float-button-position'].newValue) { 146 | position.value = changes['postbot-float-button-position'].newValue; 147 | } 148 | }; 149 | 150 | // 组件挂载时添加全局事件监听器并加载位置 151 | onMounted(async () => { 152 | await loadPosition(); 153 | document.addEventListener('mousemove', handleMouseMove); 154 | document.addEventListener('mouseup', handleMouseUp); 155 | chrome.storage.onChanged.addListener(handleStorageChange); 156 | }); 157 | 158 | // 组件卸载时移除事件监听器 159 | onBeforeUnmount(() => { 160 | document.removeEventListener('mousemove', handleMouseMove); 161 | document.removeEventListener('mouseup', handleMouseUp); 162 | chrome.storage.onChanged.removeListener(handleStorageChange); 163 | }); 164 | 165 | return () => 166 | h( 167 | FloatButtonGroup, 168 | { 169 | // shape: 'circle', 170 | shape: 'square', 171 | style: { 172 | position: 'fixed', 173 | left: `${position.value.x}px`, 174 | top: `${position.value.y}px`, 175 | bottom: 'auto', 176 | right: 'auto', 177 | // border: '1px solid #1AAD19', 178 | border: '1px solid #bd34fe', 179 | width: 'auto', 180 | zIndex: 999999, 181 | // boxShadow: '0 1px 2px -2px #8bc34a, 0 3px 6px 0 #8bc34a, 0 5px 12px 4px #8bc34a', 182 | boxShadow: isDragging.value 183 | ? '0 4px 8px -2px rgba(189, 52, 254, 0.5), 0 6px 12px 0 rgba(71, 202, 255, 0.5), 0 8px 16px 4px rgba(189, 52, 254, 0.5)' 184 | : '0 1px 2px -2px #bd34fe, 0 3px 6px 0 #47caff, 0 5px 12px 4px #bd34fe', 185 | background: '#ffffff', 186 | cursor: isDragging.value ? 'grabbing' : 'grab', 187 | userSelect: 'none', 188 | transition: !isDragging.value ? 'left 0.3s ease, top 0.3s ease' : 'none', // 拖动时无过渡 189 | }, 190 | onMousedown: handleMouseDown, 191 | }, 192 | { 193 | default: () => [ 194 | h( 195 | FloatButton, 196 | { 197 | shape: 'square', 198 | // badge: { count: 5, color: 'red' }, 199 | // style: { 200 | // right: '50px', 201 | // }, 202 | onClick: handleClick, 203 | }, 204 | { 205 | description: () => h('div', { 206 | style: { 207 | // color: '#1AAD19', 208 | color: '#bd34fe', 209 | fontWeight: 'bold', 210 | } 211 | }, 212 | // '内容同步' 213 | '同步' 214 | ), 215 | ...(showTooltip.value ? { tooltip: () => h('div', { 216 | style: { 217 | // color: '#bd34fe !important', 218 | // fontSize: '14px !important', 219 | // fontWeight: '500 !important', 220 | // padding: '8px 12px !important', 221 | // backgroundColor: '#ffffff !important', 222 | // border: '1px solid #bd34fe !important', 223 | // borderRadius: '6px !important', 224 | // boxShadow: '0 2px 8px rgba(189, 52, 254, 0.15) !important', 225 | // margin: '0 !important', 226 | // textAlign: 'left !important', 227 | } 228 | }, 'PostBot内容同步助手') } : {}), 229 | // icon: () => h(SyncOutlined) 230 | icon: () => 231 | h(Avatar, { 232 | src: iconUrl, 233 | size: 18, 234 | // size: 26, 235 | }) 236 | } 237 | ) 238 | ] 239 | } 240 | ); 241 | } 242 | }) -------------------------------------------------------------------------------- /src/media/publisher/platform/moment/zsxq.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 getGroupTopicPublishUrl = (groupId) => { 20 | const groups = state.metaInfoList?.zsxq?.groups; 21 | if (!groups) { 22 | return null; 23 | } 24 | let group = groups[0]; 25 | if (groupId) { 26 | group = groups.find(group => group.groupId === groupId); 27 | } 28 | const publishUrl = `https://wx.zsxq.com/group/${group.groupId}`; 29 | console.log('publishUrl', publishUrl); 30 | return publishUrl; 31 | } 32 | 33 | export const zsxqMonmentPublisher = async (data) => { 34 | console.log('zsxqMonmentPublisher data', data); 35 | 36 | const contentData = data?.data; 37 | const processedData = data?.data; 38 | 39 | const sleep = async (time) => { 40 | console.log('sleep', time); 41 | return new Promise((resolve) => setTimeout(resolve, time)); 42 | } 43 | 44 | const pasteEvent = (): ClipboardEvent => { 45 | console.log('pasteEvent'); 46 | return new ClipboardEvent('paste', { 47 | bubbles: true, 48 | cancelable: true, 49 | clipboardData: new DataTransfer(), 50 | }); 51 | } 52 | 53 | const observeElement = (selector, timeout = 10000) => { 54 | console.log('observeElement', selector); 55 | return new Promise((resolve, reject) => { 56 | // const checkElement = () => document.querySelector(selector); 57 | 58 | let checkElement = null; 59 | if (selector instanceof Function) { 60 | checkElement = selector; 61 | } else { 62 | checkElement = () => document.querySelector(selector); 63 | } 64 | 65 | // 立即检查元素 66 | let element = checkElement(); 67 | console.log('element', element); 68 | if (element) { 69 | resolve(element); 70 | return; 71 | } 72 | 73 | // 创建 MutationObserver 进行监听 74 | const observer = new MutationObserver(() => { 75 | element = checkElement(); 76 | console.log('element', element); 77 | if (element) { 78 | resolve(element); 79 | observer.disconnect(); 80 | } 81 | }); 82 | 83 | // 启动观察 84 | observer.observe(document.body, { 85 | childList: true, 86 | subtree: true, 87 | }); 88 | 89 | // 如果超时,拒绝 Promise,并返回中文错误提示 90 | setTimeout(() => { 91 | observer.disconnect(); 92 | reject(new Error(`未能在 ${timeout} 毫秒内找到选择器为 "${selector}" 的元素`)); 93 | }, timeout); 94 | }); 95 | }; 96 | 97 | const formElement = { 98 | topicAdd: 'div.post-topic-head', 99 | topicEditor: 'div.ql-editor', 100 | imageUpload: 'input[type="file"]', 101 | submitButtons: 'div.submit-btn', 102 | submitButtonText: '发布', 103 | }; 104 | 105 | const base64ToBinary = (base64) => { 106 | const binaryString = atob(base64); // 解码Base64 107 | const byteArray = new Uint8Array(binaryString.length); 108 | 109 | for (let i = 0; i < binaryString.length; i++) { 110 | byteArray[i] = binaryString.charCodeAt(i); 111 | } 112 | 113 | return byteArray; 114 | } 115 | 116 | const fetchImage = async (imageUrl) => { 117 | return new Promise((resolve, reject) => { 118 | // 发送消息到背景脚本,要求获取图片内容 119 | chrome.runtime.sendMessage({ 120 | type: 'request', 121 | action: 'fetchImage', 122 | data: { 123 | imageUrl: imageUrl 124 | } 125 | }, (response) => { 126 | console.log('response', response); 127 | const base64data = response.base64data; 128 | if (base64data) { 129 | const dataPairs = base64data.split(','); 130 | const fileType = dataPairs[0].replace('data:', '').split(';')[0]; 131 | const base64 = dataPairs[1]; 132 | const imageData = { 133 | type: fileType || 'image/jpg', 134 | bits: base64ToBinary(base64), 135 | overwrite: true, 136 | src: imageUrl, 137 | fileName: response.imageName 138 | } 139 | console.log('imageData', imageData); 140 | console.log('获取图片成功'); 141 | resolve(imageData); 142 | } else { 143 | console.log('获取图片失败'); 144 | reject('获取图片失败'); 145 | } 146 | }); 147 | }); 148 | } 149 | 150 | const getFileName = (fileName, url) => { 151 | let newFileName = fileName; 152 | console.log('fileName', fileName); 153 | if (!fileName) { 154 | const name = url.substring(url.lastIndexOf('/') + 1); 155 | if (name.indexOf('.') !== -1) { 156 | newFileName = name; 157 | } 158 | } 159 | 160 | if (!fileName) { 161 | newFileName = `${Date.now()}.jpg`; 162 | } 163 | console.log('newFileName', newFileName); 164 | return newFileName; 165 | } 166 | 167 | const uploadImages = async (images) => { 168 | console.log('images', images); 169 | const imageUpload = await observeElement(formElement.imageUpload); 170 | if (!imageUpload) { 171 | throw new Error('未找到图片上传元素'); 172 | } 173 | 174 | console.log('imageUpload', imageUpload); 175 | 176 | const dataTransfer = new DataTransfer(); 177 | 178 | for (const image of images) { 179 | if (image.objectUrl) { 180 | const response = await fetch(image.objectUrl); 181 | const blob = await response.blob(); 182 | 183 | const file = new File([blob], image.name, { type: image.type }); 184 | dataTransfer.items.add(file); 185 | } else { 186 | const url = image?.url || image?.src; 187 | const imageData = await fetchImage(url); 188 | 189 | let fileName = imageData.fileName; 190 | if (!fileName) { 191 | fileName = getFileName(fileName, url); 192 | } 193 | 194 | const blob = new Blob([imageData.bits], { type: imageData.type }); 195 | const file = new File([blob], fileName, { type: imageData.type }); 196 | dataTransfer.items.add(file); 197 | } 198 | } 199 | 200 | if (dataTransfer.files.length === 0) { 201 | console.error('上传文件失败'); 202 | return; 203 | } 204 | 205 | imageUpload.files = dataTransfer.files; 206 | imageUpload.dispatchEvent(new Event('change', { bubbles: true })); 207 | await sleep(2000); 208 | console.log('图片上传成功'); 209 | } 210 | 211 | const autoFillContent = async() => { 212 | const topicAdd = await observeElement(formElement.topicAdd) as HTMLElement; 213 | console.log('topicAdd', topicAdd); 214 | if (!topicAdd) { 215 | return; 216 | } 217 | topicAdd.click(); 218 | topicAdd.dispatchEvent(new Event('click', { bubbles: true })); 219 | await sleep(2000) 220 | 221 | const editor = await observeElement(formElement.topicEditor) as HTMLElement; 222 | console.log('editor', editor); 223 | if (!editor) { 224 | return; 225 | } 226 | editor.innerHTML = ''; 227 | editor.focus(); 228 | const editorPasteEvent = pasteEvent(); 229 | editorPasteEvent.clipboardData.setData('text/html', contentData?.content); 230 | editor.dispatchEvent(editorPasteEvent); 231 | editor.dispatchEvent(new Event('input', { bubbles: true })); 232 | editor.dispatchEvent(new Event('change', { bubbles: true })); 233 | } 234 | 235 | const autoPublish = () => { 236 | const submitButtons = document.querySelectorAll(formElement.submitButtons); 237 | console.log('submitButtons', submitButtons); 238 | if (!submitButtons) { 239 | return; 240 | } 241 | const submitButton = Array.from(submitButtons).find(button => button.textContent?.includes(formElement.submitButtonText)); 242 | console.log('submitButton', submitButton); 243 | if (!submitButton) { 244 | return; 245 | } 246 | (submitButton as HTMLElement).click(); 247 | } 248 | 249 | await autoFillContent(); 250 | await sleep(1000); 251 | 252 | await uploadImages(contentData?.images || contentData?.contentImages); 253 | 254 | if (contentData.isAutoPublish) { 255 | await sleep(5000); 256 | autoPublish(); 257 | } 258 | 259 | } --------------------------------------------------------------------------------