feat: 微信自动化客服(wechatauto-replica) 干净历史导入 - AI 自动回复/语音收发/朋友圈发布
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""微信自动回复接管脚本 v2(wechatauto-replica)
|
||||
|
||||
功能:监听微信新消息,当【真实好友】发来私聊消息时,自动回复。
|
||||
规则:
|
||||
- 只回复 wxid_ 开头的真实好友(公众号 gh_、群聊 @chatroom、
|
||||
文件传输助手 filehelper、特殊号 @openim/@placeholder/brandsessionholder 一律不碰)
|
||||
- 跳过自己发出去的消息(sender_id == 2)
|
||||
- 同一好友 COOLDOWN_SEC 秒内只回一次(防刷屏)
|
||||
- GUI 发送全局串行(多个好友同时来消息不会抢窗口)
|
||||
|
||||
用法(项目目录下,独立 venv):
|
||||
.venv\\Scripts\\python.exe wechat_daemon.py
|
||||
|
||||
前置:微信 4.x 已登录、桌面未锁屏、管理员权限(提密钥)。
|
||||
"""
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
|
||||
try:
|
||||
os.system("chcp 65001 >nul 2>&1")
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
from wechatauto.db import WeChatDB, Listener
|
||||
from wechatauto.guia import WeChatGUI
|
||||
|
||||
# ==================== 自动回复配置(改这里) ====================
|
||||
AUTO_REPLY = True # 总开关
|
||||
DEFAULT_REPLY = "【自动回复】已收到您的消息,稍后回复您。"
|
||||
|
||||
# 关键词规则:消息命中关键词时用对应话术回复;未命中则用 DEFAULT_REPLY
|
||||
KEYWORD_RULES = {
|
||||
# "你好": "你好呀,请问有什么可以帮您?",
|
||||
# "价格": "具体价格您可以看下我发给您的报价单。",
|
||||
}
|
||||
|
||||
# 只回复这些前缀的会话(真实好友)。留空 = 不限制前缀
|
||||
REPLY_ONLY_PREFIX = ("wxid_",)
|
||||
|
||||
# 防抖:同一会话内多少秒内不重复自动回复
|
||||
COOLDOWN_SEC = 30
|
||||
|
||||
# 黑名单会话(精确匹配 username,绝不自动回复)
|
||||
BLOCKLIST = {"filehelper"}
|
||||
# ==============================================================
|
||||
|
||||
_gui = None
|
||||
_gui_lock = threading.Lock()
|
||||
_last_reply = {} # username -> 上次回复时间戳
|
||||
_send_lock = threading.Lock()
|
||||
|
||||
|
||||
def get_gui():
|
||||
global _gui
|
||||
with _gui_lock:
|
||||
if _gui is None:
|
||||
_gui = WeChatGUI()
|
||||
return _gui
|
||||
|
||||
|
||||
def should_reply(username: str) -> bool:
|
||||
"""是否对某会话启用自动回复。"""
|
||||
if not AUTO_REPLY:
|
||||
return False
|
||||
if username in BLOCKLIST:
|
||||
return False
|
||||
if username.endswith("@chatroom"):
|
||||
return False
|
||||
if not REPLY_ONLY_PREFIX:
|
||||
return True
|
||||
return username.startswith(REPLY_ONLY_PREFIX)
|
||||
|
||||
|
||||
def pick_reply(content: str) -> str:
|
||||
for kw, reply in KEYWORD_RULES.items():
|
||||
if kw in content:
|
||||
return reply
|
||||
return DEFAULT_REPLY
|
||||
|
||||
|
||||
def on_message(msg: dict, lst: Listener):
|
||||
username = msg.get("username", "")
|
||||
sender_id = msg.get("sender_id", "")
|
||||
content = str(msg.get("content", ""))
|
||||
|
||||
# 跳过自己发出去的消息(避免回复自己的回复 → 死循环)
|
||||
if str(sender_id) == "2":
|
||||
return
|
||||
|
||||
if not should_reply(username):
|
||||
return
|
||||
|
||||
# 防抖
|
||||
now = time.time()
|
||||
last = _last_reply.get(username, 0)
|
||||
if now - last < COOLDOWN_SEC:
|
||||
return
|
||||
_last_reply[username] = now
|
||||
|
||||
reply = pick_reply(content)
|
||||
nick = _last_reply.get("_db") and _last_reply["_db"].get_nickname(username) or username
|
||||
# 全局串行发送,避免多个会话并发抢同一微信窗口
|
||||
with _send_lock:
|
||||
try:
|
||||
r = get_gui().send_msg(reply, who=username, verify=True)
|
||||
print(f"[自动回复] {username} ({nick}) <- {reply} | ok={r.is_success} {r['message']}",
|
||||
flush=True)
|
||||
except Exception as e: # noqa: BLE001
|
||||
print(f"[自动回复失败] {username}: {e!r}", flush=True)
|
||||
|
||||
|
||||
def main():
|
||||
db = WeChatDB()
|
||||
info = db.get_self_info()
|
||||
nick = info.get("nick_name") or info.get("username")
|
||||
_last_reply["_db"] = db
|
||||
print(f"已接管微信: {nick} ({info.get('username')})", flush=True)
|
||||
print(f"自动回复: {'开' if AUTO_REPLY else '关'} | 默认话术: {DEFAULT_REPLY} | "
|
||||
f"只回前缀: {REPLY_ONLY_PREFIX} | 防抖 {COOLDOWN_SEC}s", flush=True)
|
||||
|
||||
lst = Listener(db, interval=1.0)
|
||||
# add_all:监听所有已有会话 + 自动发现新会话
|
||||
lst.add_all(on_message)
|
||||
print("自动回复监听已启动(真实好友发消息即自动回复,公众号/群聊不碰),Ctrl+C 退出...",
|
||||
flush=True)
|
||||
|
||||
lst.start()
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
lst.stop()
|
||||
print("已停止监听。", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user