
1. 项目缘起一个被微信消息淹没的桌面不知道你有没有过这样的体验在Windows电脑上专注写代码、看文档或者处理一个复杂的表格时手机微信的提示音此起彼伏。每次拿起手机可能只是一个群里的闲聊或者一条无关紧要的公众号推送但你的工作流已经被打断了。更麻烦的是有时候手机放在一边充电或者开了静音结果错过了真正重要的消息。我之前就长期被这个问题困扰。作为一个重度依赖电脑工作的人我希望所有重要的信息都能汇聚到我的桌面操作中心——Windows通知中心。这样我既能及时看到消息预览又不用频繁切换设备。更重要的是如果能直接在通知上点击回复那就完美了可以省去解锁手机、打开微信、找到联系人、打字这一系列繁琐操作。市面上有一些现成的工具比如一些安卓模拟器的通知同步功能或者一些第三方商业软件。但它们要么太笨重开个模拟器就为了收通知要么有隐私顾虑你的聊天记录要经过别人的服务器要么功能不满足只能看不能回。所以我决定自己动手用Python造一个轮子一个轻量、本地化、支持快捷回复的微信消息转发到Windows通知中心的工具。这个项目的核心价值在于将移动端的即时通讯体验无缝整合到桌面工作流中实现消息的集中管理与高效处理同时完全掌控自己的数据。它非常适合开发者、远程工作者、多任务处理者或者任何希望提升桌面端信息处理效率的人。2. 技术栈选型与核心原理拆解要实现这个功能我们需要解决几个核心问题1. 如何从手机获取微信消息2. 如何发送到Windows通知中心3. 如何实现快捷回复并送回手机这涉及到移动端抓取、桌面端交互和网络通信三个层面。2.1 消息获取为什么选择“微信机器人”而非Hook获取微信消息最直接的想法可能是去逆向安卓微信的协议但这门槛高、风险大且极易因微信更新而失效。更可行的方案是使用“微信机器人”框架。这类框架通常基于无障碍服务AccessibilityService或设备控制协议如ADB模拟用户操作或监听系统通知栏来获取消息。我最终选择了itchat的继任者之一结合wxauto或uiautomator2的思路但进行大幅改造。为什么不直接用itchat因为itchat基于Web协议早已被微信官方封杀完全不可用。现在的可行路径是在电脑上运行一个安卓模拟器如夜神、MuMu在模拟器里登录微信然后通过Python脚本控制模拟器内的微信。这里的关键技术点是Android Debug Bridge (ADB)。ADB允许我们从电脑上对安卓设备或模拟器发送指令、获取屏幕内容、监听日志等。我们的Python脚本将通过ADB与模拟器内的微信交互。核心原理链如下监听通知通过ADB命令adb shell dumpsys notification可以获取模拟器当前的所有通知列表从中解析出来自微信的通知提取发送人、消息内容等信息。这是最稳定、对微信侵入性最低的方式因为它只是读取系统已经产生的通知。模拟点击与抓取备选方案如果通知内容不全比如只显示“一条新消息”则需要更深入的操作。我们可以用ADB模拟点击打开微信然后使用uiautomator2这个Python库来获取当前聊天窗口的UI控件树从中精准定位并提取消息文本和联系人。uiautomator2提供了比原生ADB更强大的UI自动化能力。消息去重需要维护一个简单的消息ID或时间戳缓存避免同一条消息被重复抓取并推送。注意直接解析通知栏是最优方案。模拟点击打开微信应用虽然能获取更完整的信息流但会干扰你实际使用模拟器内的微信并且更耗资源。优先尝试从通知栏提取足够的信息通常包含发送人和消息前一段内容。2.2 通知推送Windows原生接口 vs. 第三方库将消息推送到Windows通知中心同样有多个选择。最正统的方式是使用Windows Toast Notification (Windows.Runtime)API这是UWP应用的标准方式效果原生、体验好。但它的缺点是通常需要打包成UWP应用或者以特定的方式从Python调用配置较为复杂。为了快速实现和跨版本兼容兼容Win10和Win11我选择了win10toast和plyer库的组合并进行了功能增强。win10toast库简单易用但其默认的通知不支持按钮快捷回复。而plyer库提供了一个跨平台的抽象层在Windows后端它调用的是wintoast这个库支持添加按钮操作。核心代码逻辑from plyer import notification import json def send_windows_notification(title, message, conversation_id): # 构建通知参数 toast_args { title: title, message: message, app_name: 微信消息助手, timeout: 10, # 通知停留时间 toast_flags: 0, } # 添加快捷回复按钮关键步骤 # 我们需要将回复动作和对应的会话ID编码传递给回调处理函数 actions [] quick_replies [收到, 稍后回复, 好的] # 预定义的快捷回复词 for reply in quick_replies: # 将动作数据序列化通常包含回复文本和会话标识 action_data json.dumps({type: quick_reply, text: reply, cid: conversation_id}) # plyer/wintoast 允许通过 actions 参数添加按钮但需要正确设置回调 # 注意plyer的API可能不直接暴露此功能可能需要深入其Windows后端或使用winrt库直接操作 actions.append((reply, action_data)) # 这是一个概念性结构实际API调用需调整 # 实际上更直接的方式是使用 winrt 库来创建包含按钮的Toast # 以下是简化后的思路 notification.notify(**toast_args) # 真正的按钮绑定需要更底层的Windows API调用下文会详细展开。实际上plyer对高级Toast特性的支持有限。为了实现可点击的按钮并触发Python回调我们必须直接使用winrt库来调用Windows Runtime API。这是本项目的技术难点之一。2.3 快捷回复闭环交互的设计与实现快捷回复的难点在于“闭环”。Windows通知按钮被点击后系统需要知道执行什么代码并且这段代码需要将回复文本“送回”到模拟器内的微信中。实现闭环的步骤注册应用与COM组件为了让Windows能够激活我们的Python脚本我们需要将脚本“注册”为一个COM服务器或者打包成一个具有明确AppUserModelID (AUMID) 的快捷方式。这里我们采用一种更轻量级的方法在创建Toast通知时指定一个launch参数这个参数是一个协议URL例如wechatreply://reply?cidxxxtext预设文本。协议处理器我们在系统中注册一个自定义协议如wechatreply并将其关联到我们的Python脚本。当Toast按钮被点击系统会尝试打开这个URL从而启动我们的Python脚本并附带回复参数。脚本处理与回传Python脚本被启动后解析URL中的参数会话ID和回复文本然后通过ADB命令将回复文本输入到模拟器微信的对应聊天输入框中并模拟点击发送。定位输入框使用uiautomator2的定位功能找到当前聊天窗口的输入框控件通常其resource-id包含com.tencent.mm:id/bXX之类的模式。输入文本d(text输入框).set_text(回复内容)模拟发送找到发送按钮并点击d(resourceIdcom.tencent.mm:id/bXX).click()这个流程涉及桌面端、移动端和系统级的集成是项目中最精巧的部分。3. 手把手搭建开发与运行环境理论讲完了我们开始实战。请严格按照步骤操作避免环境问题导致后续失败。3.1 第一步安卓模拟器准备与ADB连接安装模拟器推荐使用夜神模拟器或MuMu模拟器。它们对ADB的支持比较友好。安装后在设置中开启“Root权限”部分操作需要和“允许ADB调试”。配置模拟器ADB端口夜神模拟器的默认ADB端口通常是62001MuMu是7555。你需要找到模拟器安装目录下的nox_adb.exe夜神或使用你自己的ADB工具连接指定端口。连接测试打开命令行使用以下命令连接# 如果你使用模拟器自带的adb cd D:\Program Files\Nox\bin # 切换到夜神安装目录的bin文件夹 nox_adb connect 127.0.0.1:62001 # 如果你使用系统全局的adb需要先安装Android SDK Platform-Tools adb connect 127.0.0.1:62001连接成功后使用adb devices命令应能看到类似127.0.0.1:62001 device的设备。安装微信在模拟器的应用市场里安装微信并登录你的账号建议使用小号避免安全风险。3.2 第二步Python环境与核心库安装建议使用Python 3.8及以上版本并创建虚拟环境。# 创建并激活虚拟环境可选但推荐 python -m venv wechat_notify_env wechat_notify_env\Scripts\activate # Windows # 安装核心库 pip install uiautomator2 # 用于安卓UI自动化 pip install pillow # 可能需要用于截图处理 pip install plyer # 跨平台通知基础功能 # 安装winrt库用于创建高级Windows Toast通知 pip install winrtuiautomator2安装后还需要在模拟器中初始化服务python -m uiautomator2 init执行此命令会自动向连接的模拟器设备安装一个守护应用atx-agent。3.3 第三步获取微信通知栏信息我们首先实现最核心的消息抓取功能。创建一个message_grabber.py文件。import subprocess import re import time from datetime import datetime def get_android_notifications(adb_pathadb): 通过ADB获取安卓设备的通知列表并解析出微信通知。 返回列表每个元素是包含package, title, text, time的字典。 try: # 执行adb shell dumpsys notification命令 result subprocess.run( [adb_path, shell, dumpsys, notification, --noredact], capture_outputTrue, textTrue, encodingutf-8, errorsignore, timeout5 ) output result.stdout except subprocess.TimeoutExpired: print(ADB命令执行超时) return [] except Exception as e: print(f执行ADB命令出错: {e}) return [] notifications [] current_pkg None current_title None current_text None # 这是一个简化的解析器。实际输出格式复杂需要更健壮的解析。 lines output.split(\n) i 0 while i len(lines): line lines[i] # 查找通知条目开始的标志 if NotificationRecord( in line and pkg in line: # 提取包名例如 pkgcom.tencent.mm pkg_match re.search(rpkg([^\s]), line) current_pkg pkg_match.group(1) if pkg_match else None # 向前查找title和text title, text None, None for j in range(i, min(i20, len(lines))): # 在附近行查找 if android.title in lines[j]: title_match re.search(randroid.title\s*([^]*), lines[j]) if title_match: title title_match.group(1) if android.text in lines[j]: text_match re.search(randroid.text\s*([^]*), lines[j]) if text_match: text text_match.group(1) if title and text: break if current_pkg com.tencent.mm and (title or text): # 微信包名 # 简单的去重如果上一条通知的标题和文本完全相同则跳过 if not notifications or not (notifications[-1][title] title and notifications[-1][text] text): notifications.append({ package: current_pkg, title: title, text: text, time: datetime.now().strftime(%H:%M:%S) }) i 1 return notifications if __name__ __main__: # 测试函数每隔5秒抓取一次 seen_messages set() while True: notifs get_android_notifications() for n in notifs: # 使用标题文本前50字符作为唯一标识防止重复 msg_key f{n[title]}|{n[text][:50]} if msg_key not in seen_messages: seen_messages.add(msg_key) print(f[{n[time]}] {n[title]}: {n[text]}) time.sleep(5)运行这个脚本你应该能在控制台看到模拟器微信的通知被打印出来。这证明了我们抓取链路的第一步是通的。实操心得dumpsys notification的输出格式随安卓版本变化上述解析规则可能不完美。如果发现抓不到或抓不全可以先将完整的output保存到文件里分析其结构调整正则表达式。另一个更稳定的方法是使用uiautomator2直接抓取通知栏UI但代码会更复杂。4. 创建支持按钮的Windows Toast通知这是项目的精华部分。我们将使用winrt库直接与Windows API交互。创建一个toast_sender.py文件。import asyncio from winrt.windows.ui.notifications import ToastNotification, ToastNotificationManager, ToastTemplateType from winrt.windows.data.xml.dom import XmlDocument import os import sys def create_toast_with_reply(title, message, conversation_id, quick_replies[收到, 稍后处理, OK]): 创建并显示一个带有快捷回复按钮的Windows Toast通知。 conversation_id: 用于标识回复目标会话的字符串。 quick_replies: 快捷回复文本列表。 # 1. 定义Toast的XML模板 # 我们使用ToastGeneric模板它支持自适应内容。 toast_xml f toast launchactionviewConversationamp;conversationId{conversation_id} visual binding templateToastGeneric text{title}/text text{message}/text image placementappLogoOverride srcfile:///{os.path.abspath(wechat.ico)} / /binding /visual actions input idquickReplyInput typetext placeHolderContent输入回复.../ !-- 预定义按钮 -- # 动态添加快捷回复按钮 for reply in quick_replies: # 每个按钮的activationType设为background这样点击后不会前台启动应用。 # arguments 包含了我们自定义的协议和参数。 toast_xml faction activationTypebackground content{reply} argumentswechatreply://reply?cid{conversation_id}amp;text{reply} /\n # 添加一个手动输入发送的按钮 toast_xml action activationTypebackground content发送 argumentswechatreply://send hint-inputIdquickReplyInput/ /actions /toast # 2. 解析XML并创建Toast对象 xml_doc XmlDocument() xml_doc.load_xml(toast_xml) # 3. 创建Toast通知 # 需要指定AppUserModelId (AUMID)Windows用它来分组通知。 # 你可以使用任何字符串但最好保持唯一性。这里我们使用一个虚构的ID。 app_id PythonWeChatNotifier toast_notifier ToastNotificationManager.create_toast_notifier(app_id) toast ToastNotification(xml_doc) # 4. 注册激活事件处理程序用于处理按钮点击 # 注意winrt的事件处理需要在异步上下文中且后台激活需要应用有后台任务声明。 # 对于我们的脚本一个更简单的方法是使用协议处理器见下一节而非直接在这里处理事件。 # 因此我们先不在这里处理事件而是依靠协议URL启动我们的回复处理器。 # 5. 显示通知 toast_notifier.show(toast) print(fToast通知已发送: {title}) if __name__ __main__: # 测试发送一个带按钮的通知 create_toast_with_reply( title测试联系人, message这是一条测试微信消息你可以尝试点击下方的快捷回复按钮。, conversation_idtest_conv_123, quick_replies[明白, 正在忙, 谢谢] )运行这段代码你应该能在Windows通知中心看到一个带有“明白”、“正在忙”、“谢谢”三个按钮的通知。点击它们暂时不会有反应因为我们还没有注册协议处理器。5. 注册协议处理器与回复闭环实现现在我们需要让系统在点击Toast按钮时能够启动我们的Python脚本并传递参数。5.1 创建协议处理器脚本创建一个reply_handler.py脚本它负责解析参数并执行回复。import sys import json import urllib.parse import subprocess import uiautomator2 as u2 def send_reply_to_wechat(conversation_id, reply_text): 核心函数通过uiautomator2将回复文本发送到模拟器微信。 这里假设conversation_id能映射到微信的某个聊天窗口例如聊天对象名。 实际实现中你可能需要维护一个映射关系或者通过其他方式定位会话。 本例采用简化方案假设需要回复的会话就是当前微信前台窗口。 print(f准备回复: 会话[{conversation_id}] - 内容[{reply_text}]) try: # 1. 连接设备假设只有一个设备即我们的模拟器 d u2.connect() # 默认连接adb devices列表中的第一个设备 # 2. 确保微信在前台。这里简化处理直接启动微信主界面。 # 更优做法根据conversation_id通过ADB Intent打开特定聊天窗口。 d.app_start(com.tencent.mm) # 3. 等待微信启动并找到输入框 # 微信的输入框resource-id经常变需要自行用weditor工具探查。 # 假设当前已经是目标聊天窗口我们直接定位输入框。 input_box d(resourceIdcom.tencent.mm:id/bXX) # 这个ID需要替换为实际值 if not input_box.exists(timeout5): print(未找到输入框可能不在聊天界面。尝试点击返回键回到主界面再进入...) # 这里可以添加更复杂的导航逻辑比如根据conversation_id从聊天列表进入。 # 作为示例我们简单处理直接向当前输入框发送文本如果存在。 return # 4. 清空原有文本如果有并输入回复内容 input_box.set_text(reply_text) # 5. 找到发送按钮并点击 send_button d(resourceIdcom.tencent.mm:id/bXX) # 这个ID也需要替换 if send_button.exists: send_button.click() print(回复发送成功) else: print(未找到发送按钮。) except Exception as e: print(f发送回复时发生错误: {e}) def main(): # 协议处理器会被系统调用参数通常是完整的URL。 if len(sys.argv) 1: url sys.argv[1] print(f接收到参数: {url}) # 解析URL提取参数 parsed urllib.parse.urlparse(url) if parsed.scheme wechatreply: query_params urllib.parse.parse_qs(parsed.query) cid query_params.get(cid, [])[0] text query_params.get(text, [])[0] if parsed.path /reply and cid and text: send_reply_to_wechat(cid, text) elif parsed.path /send: # 处理来自输入框的“发送”按钮这里需要获取输入框的文本 # 这需要更复杂的IPC机制本例暂不实现。 print(手动发送功能暂未实现) else: print(无法识别的操作或参数不全) else: print(未接收到URL参数。) if __name__ __main__: main()5.2 注册自定义协议到系统我们需要在Windows注册表中添加一个条目将wechatreply://协议关联到我们的Python解释器和脚本。重要警告修改注册表有风险请先备份。建议在虚拟机或测试机上操作。创建一个register_protocol.reg文件文本文件后缀改为.reg内容如下Windows Registry Editor Version 5.00 [HKEY_CLASSES_ROOT\wechatreply] URL:WeChat Reply Protocol URL Protocol [HKEY_CLASSES_ROOT\wechatreply\shell] [HKEY_CLASSES_ROOT\wechatreply\shell\open] [HKEY_CLASSES_ROOT\wechatreply\shell\open\command] \C:\\Path\\To\\Your\\Python\\python.exe\ \C:\\Path\\To\\Your\\Project\\reply_handler.py\ \%1\请务必将两处C:\\Path\\To\\Your\\替换成你实际的路径。第一处你的Python解释器的完整路径例如C:\\Users\\YourName\\wechat_notify_env\\Scripts\\python.exe。第二处你的reply_handler.py脚本的完整路径。保存后双击这个.reg文件将其导入注册表。这样当系统或任何程序尝试打开wechatreply://...这样的链接时就会启动你指定的Python脚本来处理。6. 整合与优化打造稳定可用的服务现在我们把所有模块串联起来并解决一些实际问题。6.1 主服务循环与消息去重创建一个main_service.py作为主程序。import time import hashlib from message_grabber import get_android_notifications from toast_sender import create_toast_with_reply class WeChatNotifyService: def __init__(self): self.message_history set() # 用于去重的消息历史集合 # 一个简单的映射从通知标题可能是联系人/群名到我们内部会话ID的映射。 # 实际应用中这个映射可能需要持久化存储。 self.conversation_map {} def extract_conversation_id(self, title): 从通知标题生成一个简单的会话ID。更复杂的系统可能需要维护一个全局ID表。 # 简单处理用标题的MD5前8位作为ID return hashlib.md5(title.encode(utf-8)).hexdigest()[:8] def process_new_notifications(self): notifs get_android_notifications() new_messages [] for notif in notifs: if notif[package] ! com.tencent.mm: continue # 构建消息的唯一指纹包名标题内容时间精确到分钟避免秒级抖动 msg_fingerprint f{notif[package]}|{notif[title]}|{notif[text]}|{notif[time][:5]} if msg_fingerprint not in self.message_history: self.message_history.add(msg_fingerprint) new_messages.append(notif) for msg in new_messages: print(f新消息 - {msg[title]}: {msg[text]}) cid self.extract_conversation_id(msg[title]) self.conversation_map[cid] msg[title] # 更新映射 # 发送带按钮的Toast通知 create_toast_with_reply( titlemsg[title], messagemsg[text][:100], # 通知内容不宜过长 conversation_idcid, quick_replies[好的, 收到, 稍等, 嗯] ) def run(self, interval_seconds3): print(微信消息转发服务已启动...) try: while True: self.process_new_notifications() time.sleep(interval_seconds) except KeyboardInterrupt: print(\n服务被用户中断。) if __name__ __main__: service WeChatNotifyService() service.run()6.2 提升稳定性的关键技巧ADB连接保活长时间运行后ADB连接可能断开。需要在主循环中加入重连机制。def check_adb_connection(self): result subprocess.run([adb, devices], capture_outputTrue, textTrue) if device not in result.stdout: print(ADB设备断开尝试重连...) subprocess.run([adb, connect, 127.0.0.1:62001])在process_new_notifications开始时调用此函数。微信界面状态判断在send_reply_to_wechat函数中直接假设当前前台是目标聊天窗口很不可靠。更好的做法是点击通知时传递的conversation_id可以包含聊天对象的昵称或备注。回复脚本先启动微信然后利用uiautomator2的d(text昵称).click()来打开对应的聊天窗口再进行输入和发送。这要求你能获取到完整的联系人列表或聊天列表实现起来更复杂但更精准。资源清理与防内存泄漏uiautomator2的连接对象和winrt的异步操作要注意正确释放。主循环中避免创建大量短期对象。错误处理与日志将所有操作收到通知、发送Toast、点击回复、发送消息都记录到日志文件中方便排查问题。7. 部署为后台服务与开机自启要让这个工具真正实用需要让它像服务一样在后台静默运行。7.1 使用Pythonw后台运行将主脚本main_service.py重命名为main_service.pyw.pyw扩展名在Windows上会默认用pythonw.exe运行不显示控制台窗口。然后你可以直接双击.pyw文件它会在后台运行。7.2 创建Windows计划任务实现开机自启这是更可靠的方式。按Win R输入taskschd.msc打开“任务计划程序”。点击右侧“创建任务”。常规选项卡名称微信消息助手勾选“不管用户是否登录都要运行”和“使用最高权限运行”。触发器选项卡点击“新建”选择“登录时”或“启动时”。操作选项卡点击“新建”。操作启动程序程序或脚本浏览到你的pythonw.exe路径在虚拟环境的Scripts文件夹里。添加参数你的main_service.pyw文件的完整路径。起始于你的项目根目录。条件选项卡可以取消“只有在计算机使用交流电源时才启动此任务”如果你用的是笔记本。设置选项卡建议勾选“如果任务失败按以下频率重新启动”设置每5分钟尝试一次最多3次。点击“确定”输入你的Windows登录密码。这样每次开机你的微信消息转发服务就会自动在后台启动了。整个项目从构思到实现涉及了移动端自动化、Windows桌面开发、系统集成等多个环节。虽然核心代码不算长但要把所有细节调通确实需要踩不少坑。最花时间的部分往往不是写代码而是调试ADB命令的稳定性、分析微信UI布局的变化、以及处理Windows Toast通知那些略显晦涩的API。但当你最终在电脑上点击一下通知消息就自动回复出去的那一刻那种流畅感和效率提升会让你觉得这一切都是值得的。这个项目不仅是一个工具更是一个理解端到端自动化如何落地的绝佳案例。你可以在此基础上继续扩展比如增加消息过滤规则、支持更多通讯软件、甚至加入简单的AI自动回复让它变得更加强大。