# -*- coding: utf-8 -*- from playwright.async_api import Page import asyncio from auto_media_publisher.uploaders.uploader_base import UploaderBase from datetime import datetime,date import re CONTENT_UPLOAD_URL="https://mp.weixin.qq.com/" PUBLISH_SECCESSFULL_URL = "https://mp.weixin.qq.com/cgi-bin/home?t=home/index" class UploaderGongzhonghao(UploaderBase): async def wai_for_upload(self,new_page,selector): await new_page.evaluate(f'''() => {{ const container = document.querySelector('{selector}'); if (container) {{ const input = container.querySelector('input[type="file"]'); // 从父元素开始查找 if (input) {{ input.style.display = 'block'; // 改变 display 为 block,使其可见 input.style.visibility = 'visible'; // 使其可见 input.style.opacity = '1'; // 确保它完全透明 }} }} }}''') async def set_schedule_time(self, new_page): try: save_publish= await new_page.wait_for_selector("#js_send button", timeout=60000) await save_publish.click(force=True) await asyncio.sleep(2) send_option_div = await new_page.wait_for_selector(".weui-desktop-dialog__ft", timeout=60000) if self.scheduled_time > datetime.now() : await new_page.locator(".mass-send__td-setting > .publish_container > .weui-desktop-form__controls > .mass-send__timer-wrp > .weui-desktop-switch > .weui-desktop-switch__box").click() delta_days = (self.scheduled_time.date() - date.today()).days if delta_days > 0: await new_page.locator("dt").filter(has_text="今天 今天").click() option_text = "明天" if delta_days==1 else f"月{self.scheduled_time.day}日" #day_selector = new_page.locator(f"li.weui-desktop-dropdown__list-ele_text").nth(delta_days) #await day_selector.click() await new_page.get_by_text(option_text).first.click() await new_page.get_by_role("textbox", name="请选择时间").click() await new_page.get_by_role("textbox", name="请选择时间").press("ControlOrMeta+a") await new_page.get_by_role("textbox", name="请选择时间").fill(self.scheduled_time.strftime("%H:%M")) await new_page.get_by_role("heading",name="发表").click() send_option_send = await send_option_div.wait_for_selector('text="发表"',state="attached", timeout=600000) await send_option_send.click(force=True) await asyncio.sleep(3) except Exception as e: self.logger.error(f'[ERROR ] {self.user} , {self.platform} ,设置 发布时间 发生错误:{e}') async def set_cover_image(self,new_page): try: # 等待并定位到 #js_cover_area 元素 cover_area = await new_page.wait_for_selector('#js_cover_area') # 悬停鼠标在 #js_cover_area 上 await cover_area.hover() await new_page.get_by_role("link", name="从图片库选择").click() image_upload_container = await new_page.wait_for_selector(".weui-desktop-dialog__bd", timeout=60000) await self.wai_for_upload(new_page,".weui-desktop-dialog__bd") image_input = await image_upload_container.wait_for_selector('input[type="file"]', timeout=60000) await image_input.set_input_files(self.path_cover) library_next_div = await new_page.wait_for_selector(".weui-desktop-dialog__ft", timeout=60000) await library_next_div.evaluate('''(div) => { return new Promise(resolve => { const btn = Array.from(div.querySelectorAll('button')).find(el => el.innerText.trim() === '下一步'); if (!btn) return; // 创建 MutationObserver 监听 class 变化 const observer = new MutationObserver(() => { if (!btn.classList.contains('weui-desktop-btn_disabled')) { observer.disconnect(); // 停止监听 resolve(true); } }); observer.observe(btn, { attributes: true, attributeFilter: ['class'] }); // 如果初始状态就满足条件,直接 resolve if (!btn.classList.contains('weui-desktop-btn_disabled')) { observer.disconnect(); resolve(true); } }); }''') # 找到按钮并点击 next_btn = await library_next_div.wait_for_selector('text="下一步"', state="attached", timeout=60000) await next_btn.click(force=True) # whole div has been changed library_next_div = await new_page.wait_for_selector(".weui-desktop-dialog__ft", timeout=60000) confirm_btn = await library_next_div.wait_for_selector('text="确认"',state="attached", timeout=600000) await confirm_btn.scroll_into_view_if_needed() # 确保按钮滚动到视图 await confirm_btn.click(force=True) #wait for setting cover image: preview_div_selector = 'div.js_cover_preview_new.select-cover__preview.first_appmsg_cover' while True: # 使用 evaluate 来检查 background-image 样式属性 has_background_image = await new_page.evaluate('''(selector) => { const div = document.querySelector(selector); if (div) { const bgImage = window.getComputedStyle(div).getPropertyValue('background-image'); return bgImage.includes('https'); } return false; }''', preview_div_selector) if has_background_image: print("背景图已加载,继续执行操作") break # 背景图片已加载,跳出循环 else: print("背景图未加载,继续等待") await asyncio.sleep(1) # 每秒检查一次 await asyncio.sleep(1) except Exception as e: self.logger.error(f'[ERROR ] {self.user} , {self.platform} ,设置 封面 发生错误:{e}') async def upload_media_and_wait(self,new_page): try: await new_page.get_by_role("listitem", name="文档导入").click() import_container = await new_page.wait_for_selector("#js_import_file_container", timeout=60000) await self.wai_for_upload(new_page,"#js_import_file_container") file_input = await import_container.wait_for_selector('input[type="file"]', timeout=60000) await file_input.set_input_files(self.path_media) # 替换为你的文件路径 await new_page.wait_for_selector("#js_import_file_container", state="hidden", timeout=60000) self.logger.info(f"{self.user},{self.path_media} 文件导入完成!") await asyncio.sleep(1) except Exception as e: self.logger.error(f'[ERROR ] {self.user} , {self.platform} ,设置 封面 发生错误:{e}') async def set_title_and_description(self,new_page): try: # 设置标题和作者,要在导入之后,否则会被修改掉 if self.title and len(self.title) >1: title_textarea = await new_page.wait_for_selector("#title", timeout=60000) await title_textarea.scroll_into_view_if_needed() await title_textarea.click() await new_page.keyboard.press('Control+A') # 对于 Mac,可以使用 'Meta+A' await new_page.keyboard.press('Backspace') await title_textarea.type(self.title, delay=50) # delay 可以控制输入的速度 author_input = await new_page.wait_for_selector("#author", timeout=60000) await author_input.fill(self.author) description_input = await new_page.wait_for_selector("#js_description", timeout=60000) await description_input.fill(self.description) await asyncio.sleep(1) except Exception as e: self.logger.error(f'[ERROR ] {self.user} , {self.platform} ,设置 标题和描述 发生错误:{e}') async def set_tags(self,new_page): try: await new_page.locator("#js_article_tags_area").get_by_text("未添加").click() await new_page.locator('input[placeholder="请选择合集"]').click() await asyncio.sleep(2) # 只选第一个 await new_page.locator('li.select-opt-li').nth(0).click() ''' for tag in self.tags: try: await new_page.get_by_placeholder("输入后按回车分割").fill(tag) await new_page.get_by_placeholder("输入后按回车分割").press("Enter") except Exception as e: print(f"添加标签 '{tag}' 失败: {str(e)}") ''' await new_page.get_by_role("button", name="确认").click() await asyncio.sleep(3) except Exception as e: self.logger.error(f'[ERROR ] {self.user} , {self.platform} ,设置 tag 发生错误:{e}') async def _upload_core(self) -> bool: try: # 创建一个新的页面 page = await self.context.new_page() # 访问指定的 URL await page.goto(CONTENT_UPLOAD_URL, timeout=120000) self.logger.info(f'[ gongzhognhao ] {self.user} , {self.scheduled_time} 正在打开-文章上传页面') # 点击“文章” await page.locator(".new-creation__menu-content").first.click() # 等待新窗口出现并切换 new_page = await page.context.wait_for_event("page") await new_page.wait_for_load_state() await self.upload_media_and_wait(new_page) await self.set_title_and_description(new_page) await self.set_cover_image(new_page) if self.tags: await self.set_tags(new_page) # 定时发布 if self.scheduled_time: await self.set_schedule_time(new_page) # 判断文章是否发布成功 await self.context.storage_state(path=self.cookie_path) # 保存cookie self.logger.info(f'{self.user},{self.platform} cookie更新完毕!') await asyncio.sleep(1) # 这里延迟是为了方便眼睛直观的观看 await new_page.get_by_role("button", name="继续发表").click() await asyncio.sleep(3) max_attempts = 10 for attempt in range(max_attempts): try: await new_page.wait_for_url(lambda url: url.startswith(PUBLISH_SECCESSFULL_URL), timeout=50000) self.logger.info(f"{self.user},{self.path_media}文章发布成功,页面已跳转到首页!(尝试次数: {attempt + 1})") return True except TimeoutError: print(f"尝试 {attempt + 1}/{max_attempts}:未跳转到目标页面,继续等待...") if attempt == max_attempts - 1: print("达到最大尝试次数,文章发布失败!") return False except Exception as e: self.logger.error(f"{self.user} upload docx {self.path_media} fails, error is :{e}") return False