功能简介:
这段代码实现了一个在网页版本微信上自动回复消息的工具。 首先这个工具会打开你的微信网页版。它能和一个外部的智能服务进行交互,当你在微信上收到新消息的时候,它会把这个消息传给外部服务,然后外部服务会给出一个回复。这个工具就会把这个回复发送到微信上,就好像有一个小助手在帮你自动回复消息一样。 如果在处理新消息的过程中出了问题,比如等得太久没反应,它会去点击文件传输助手,让微信保持活跃状态。要是遇到了错误,它还会尝试刷新页面或者重新启动浏览器驱动来解决问题。 你可以通过按特定的键来退出这个自动回复程序。总之,它能帮你在微信上自动回复消息,让你不用一直手动回复,省了不少事儿呢。
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.youkuaiyun.com/m0_60781580/article/details/143671330
这是3.0的版本
更新内容:
1、可选择预设文字回复和接入的AI回复!
2、在AI算力不足时,会以预设文字进行补充回复
下载链接:
https://download.youkuaiyun.com/download/m0_60781580/89986884?spm=1001.2014.3001.5503
视频演示:
import logging
from dataclasses import dataclass
from pathlib import Path
from typing import Optional, List, Dict, Union
import json
import sys
import msvcrt
import time
import random
import requests
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import (
NoSuchElementException,
TimeoutException,
WebDriverException
)
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('wechat_auto_reply.log'),
logging.StreamHandler()
]
)
@dataclass
class ReplyRule:
"""回复规则类"""
keywords: List[str] # 触发关键字列表
response_type: str # 回复类型: "text", "image", "file"
content: str # 回复内容: 文本消息/文件路径
@dataclass
class Config:
"""配置类,用于管理所有配置参数"""
chromedriver_path: str
extension_path: str
api_url: str
api_key: str
wait_timeout: int = 30
retry_interval: int = 1
reply_mode: str = "api" # "api" 或 "preset"
preset_replies: List[str] = None # 预设文本回复列表
reply_rules: List[Dict] = None # 关键字回复规则列表
@classmethod
def load_from_file(cls, config_path: str = 'config.json') -> 'Config':
"""从配置文件加载配置"""
try:
with open(config_path, 'r', encoding='utf-8') as f:
config_data = json.load(f)
# 确保preset_replies是列表类型
if 'preset_replies' in config_data and not isinstance(config_data['preset_replies'], list):
config_data['preset_replies'] = [config_data['preset_replies']]
elif 'preset_replies' not in config_data:
config_data['preset_replies'] = []
# 处理reply_rules
if 'reply_rules' not in config_data:
config_data['reply_rules'] = []
return cls(**config_data)
except FileNotFoundError:
logging.error(f"配置文件 {config_path} 不存在")
raise
except json.JSONDecodeError:
logging.error(f"配置文件 {config_path} 格式错误")
raise
class WeChatAutoReply:
def __init__(self, config: Config):
self.config = config
self.driver = None
self.wait = None
self.reply_rules = [
ReplyRule(**rule) for rule in config.reply_rules
] if config.reply_rules else []
self.setup_driver()
def setup_driver(self):
"""设置并初始化WebDriver"""
try:
service = Service(executable_path=self.config.chromedriver_path)
options = webdriver.ChromeOptions()
if Path(self.config.extension_path).exists():
options.add_argument(f'--load-extension={self.config.extension_path}')
self.driver = webdriver.Chrome(service=service, options=options)
self.wait = WebDriverWait(self.driver, self.config.wait_timeout)
self.driver.get('https://wx.qq.com/?target=t')
logging.info("WebDriver初始化成功")
except WebDriverException as e:
logging.error(f"WebDriver初始化失败: {e}")
raise
def get_api_response(self, message: str) -> Optional[str]:
"""获取API响应"""
try:
url = f'{self.config.api_url}?key={self.config.api_key}&appid=0&msg={message}'
response = requests.get(url, timeout=10)
response.raise_for_status()
content = response.json().get('content', '').replace('(br)', '')
return content
except requests.RequestException as e:
logging.error(f"API请求失败: {e}")
return None
def get_preset_response(self) -> Optional[str]:
"""从预设回复列表中随机选择一条回复"""
if self.config.preset_replies:
return random.choice(self.config.preset_replies)
return None
def check_keyword_rules(self, message: str) -> Optional[ReplyRule]:
"""检查消息是否匹配关键字规则"""
for rule in self.reply_rules:
if any(keyword.lower() in message.lower() for keyword in rule.keywords):
return rule
return None
def send_message(self, content: str):
"""发送文本消息"""
try:
message_input = self.wait.until(
EC.presence_of_element_located((By.ID, 'editArea'))
)
message_input.clear()
message_input.send_keys(content)
send_button = self.wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, 'a.btn.btn_send'))
)
send_button.click()
logging.info(f"成功发送消息: {content[:50]}...")
except (TimeoutException, NoSuchElementException) as e:
logging.error(f"发送消息失败: {e}")
raise
def send_file_or_image(self, file_path: str):
"""发送文件或图片"""
try:
# 点击文件上传按钮
upload_button = self.wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, '.web_wechat_upload'))
)
upload_button.click()
# 等待文件输入元素出现
file_input = self.wait.until(
EC.presence_of_element_located((By.CSS_SELECTOR, 'input[type="file"]'))
)
# 发送文件路径到input元素
file_input.send_keys(str(Path(file_path).absolute()))
# 等待发送按钮可点击并点击
send_button = self.wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, '.btn_send'))
)
send_button.click()
logging.info(f"成功发送文件: {file_path}")
except Exception as e:
logging.error(f"发送文件失败: {e}")
raise
def handle_new_message(self):
"""处理新消息"""
try:
# 检查新消息提示
new_message = self.wait.until(
EC.presence_of_element_located(
(By.XPATH, "//i[contains(@class, 'icon web_wechat_reddot_middle ng-binding ng-scope')]")
)
)
ActionChains(self.driver).move_to_element(new_message).click().perform()
# 获取消息内容
message_element = self.wait.until(
EC.presence_of_element_located(
(By.XPATH, "//pre[contains(@class, 'js_message_plain ng-binding')]")
)
)
message_content = message_element.text
# 首先检查关键字规则
rule = self.check_keyword_rules(message_content)
if rule:
if rule.response_type == "text":
self.send_message(rule.content)
else: # image or file
self.send_file_or_image(rule.content)
return
# 如果没有匹配的关键字规则,使用常规回复
response = None
if self.config.reply_mode == "preset":
response = self.get_preset_response()
if not response:
logging.warning("预设回复列表为空,尝试使用API回复")
response = self.get_api_response(message_content)
else: # api mode
response = self.get_api_response(message_content)
if not response and self.config.preset_replies:
logging.warning("API回复失败,使用预设回复作为备选")
response = self.get_preset_response()
if response:
self.send_message(response)
else:
logging.error("无法获取有效回复")
except TimeoutException:
self.click_file_helper()
except Exception as e:
logging.error(f"处理消息时出错: {e}")
self.handle_error()
def click_file_helper(self):
"""点击文件传输助手,保持会话活跃"""
try:
file_helper = self.wait.until(
EC.presence_of_element_located(
(By.XPATH, "//span[contains(@class, 'nickname_text ng-binding') and contains(text(),'文件传输助手')]")
)
)
file_helper.click()
except (TimeoutException, NoSuchElementException) as e:
logging.warning(f"点击文件传输助手失败: {e}")
def handle_error(self):
"""错误处理"""
try:
self.driver.refresh()
time.sleep(self.config.retry_interval)
except WebDriverException as e:
logging.error(f"页面刷新失败: {e}")
self.restart_driver()
def restart_driver(self):
"""重启WebDriver"""
try:
if self.driver:
self.driver.quit()
self.setup_driver()
except WebDriverException as e:
logging.error(f"重启WebDriver失败: {e}")
raise
def run(self):
"""运行主循环"""
try:
logging.info("自动回复程序启动")
while True:
if msvcrt.kbhit() and msvcrt.getch() == b'\x1b':
logging.info("收到退出信号,程序正在终止...")
break
self.handle_new_message()
except KeyboardInterrupt:
logging.info("程序被手动中断")
finally:
if self.driver:
self.driver.quit()
logging.info("程序已终止")
def main():
try:
config = Config.load_from_file()
auto_reply = WeChatAutoReply(config)
auto_reply.run()
except Exception as e:
logging.critical(f"程序发生致命错误: {e}")
sys.exit(1)
if __name__ == "__main__":
main()