diff --git a/.env.example b/.env.example index 11232e07..908350b8 100644 --- a/.env.example +++ b/.env.example @@ -2,4 +2,6 @@ API_KEY=默认使用通义千问,apikey通过百炼模型平台获取 COOKIES_STR=your_cookies_here MODEL_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 MODEL_NAME=qwen-max -TOGGLE_KEYWORDS=。 \ No newline at end of file +TOGGLE_KEYWORDS=。 +ENABLE_INTENT=1 +MAX_USER_HISTORY=5 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index c9a5eebc..5893e645 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,70 +1,38 @@ -FROM python:3.10-alpine AS builder - -WORKDIR /app - -# 只安装构建所需的依赖 -RUN apk add --no-cache --virtual .build-deps \ - gcc \ - musl-dev \ - libffi-dev \ - build-base \ - curl - -# 创建虚拟环境并安装依赖 -RUN python -m venv /opt/venv -ENV PATH="/opt/venv/bin:$PATH" - -# 复制依赖文件并安装 -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -# 第二阶段:最终镜像 -FROM python:3.10-alpine - -# 添加元数据标签 -LABEL maintainer="coderxiu" -LABEL description="闲鱼AI客服机器人" -LABEL version="1.0" - -# 设置时区和编码 -ENV TZ=Asia/Shanghai \ - PYTHONIOENCODING=utf-8 \ - LANG=C.UTF-8 \ - PATH="/opt/venv/bin:$PATH" \ - PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 - -# 只安装运行时必要的包 -RUN apk add --no-cache \ - tzdata \ - nodejs \ - npm \ - && npm install -g npm@10.2.4 \ - && npm cache clean --force \ - && ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ - && echo Asia/Shanghai > /etc/timezone \ - # 减小apk缓存 - && rm -rf /var/cache/apk/* - -# 设置工作目录 +FROM python:3.8 + +# --------------------------- +# 1. 设置国内Apt源,加速系统包安装 +# --------------------------- +RUN echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian stable main contrib non-free" > /etc/apt/sources.list && \ + echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian stable-updates main contrib non-free" >> /etc/apt/sources.list && \ + echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian-security stable-security main contrib non-free" >> /etc/apt/sources.list && \ + rm -rf /etc/apt/sources.list.d/* && \ + apt-get update && \ + apt-get install -y curl gnupg && \ +# --------------------------- +# 2. 安装Node.js 18(官方推荐方式) +# --------------------------- + curl -fsSL https://deb.nodesource.com/setup_18.x | bash - && \ + apt-get install -y nodejs && \ + node -v && npm -v && \ + apt-get clean && rm -rf /var/lib/apt/lists/* + +# --------------------------- +# 3. 用清华PyPI源加速pip,全局配置 +# --------------------------- +RUN mkdir -p /root/.pip && \ + echo '[global]\nindex-url = https://pypi.tuna.tsinghua.edu.cn/simple' > /root/.pip/pip.conf + +# 设置容器内工作目录 WORKDIR /app -# 从构建阶段复制虚拟环境 -COPY --from=builder /opt/venv /opt/venv - -# 创建必要的目录 -RUN mkdir -p data prompts - -# 复制示例提示词文件并重命名为正式文件 -COPY prompts/classify_prompt_example.txt prompts/classify_prompt.txt -COPY prompts/price_prompt_example.txt prompts/price_prompt.txt -COPY prompts/tech_prompt_example.txt prompts/tech_prompt.txt -COPY prompts/default_prompt_example.txt prompts/default_prompt.txt +# 复制所有项目文件到镜像 +COPY . . -# 只复制绝对必要的文件 -COPY main.py XianyuAgent.py XianyuApis.py context_manager.py ./ -COPY utils/ utils/ -COPY static/ static/ +# --------------------------- +# 4. 升级pip、安装Python依赖(走清华PyPI源) +# --------------------------- +RUN pip install --upgrade pip && pip install -r requirements.txt -# 容器启动时运行的命令 +# 启动主程序 CMD ["python", "main.py"] diff --git a/XianyuAgent.py b/XianyuAgent.py index 970db5c3..e05f98e4 100644 --- a/XianyuAgent.py +++ b/XianyuAgent.py @@ -3,10 +3,13 @@ import os from openai import OpenAI from loguru import logger - +import time class XianyuReplyBot: def __init__(self): + # 意图识别开关,取值见 .env: ENABLE_INTENT=False/True/0/1 + self.max_user_history = int(os.getenv("MAX_USER_HISTORY", "5")) + self.enable_intent = str(os.getenv("ENABLE_INTENT", "1")).lower() in ("1", "true", "yes") # 初始化OpenAI客户端 self.client = OpenAI( api_key=os.getenv("API_KEY"), @@ -17,11 +20,10 @@ def __init__(self): self.router = IntentRouter(self.agents['classify']) self.last_intent = None # 记录最后一次意图 - def _init_agents(self): """初始化各领域Agent""" self.agents = { - 'classify':ClassifyAgent(self.client, self.classify_prompt, self._safe_filter), + 'classify': ClassifyAgent(self.client, self.classify_prompt, self._safe_filter), 'price': PriceAgent(self.client, self.price_prompt, self._safe_filter), 'tech': TechAgent(self.client, self.tech_prompt, self._safe_filter), 'default': DefaultAgent(self.client, self.default_prompt, self._safe_filter), @@ -30,28 +32,19 @@ def _init_agents(self): def _init_system_prompts(self): """初始化各Agent专用提示词,直接从文件中加载""" prompt_dir = "prompts" - try: - # 加载分类提示词 with open(os.path.join(prompt_dir, "classify_prompt.txt"), "r", encoding="utf-8") as f: self.classify_prompt = f.read() logger.debug(f"已加载分类提示词,长度: {len(self.classify_prompt)} 字符") - - # 加载价格提示词 with open(os.path.join(prompt_dir, "price_prompt.txt"), "r", encoding="utf-8") as f: self.price_prompt = f.read() logger.debug(f"已加载价格提示词,长度: {len(self.price_prompt)} 字符") - - # 加载技术提示词 with open(os.path.join(prompt_dir, "tech_prompt.txt"), "r", encoding="utf-8") as f: self.tech_prompt = f.read() logger.debug(f"已加载技术提示词,长度: {len(self.tech_prompt)} 字符") - - # 加载默认提示词 with open(os.path.join(prompt_dir, "default_prompt.txt"), "r", encoding="utf-8") as f: self.default_prompt = f.read() logger.debug(f"已加载默认提示词,长度: {len(self.default_prompt)} 字符") - logger.info("成功加载所有提示词") except Exception as e: logger.error(f"加载提示词时出错: {e}") @@ -63,64 +56,73 @@ def _safe_filter(self, text: str) -> str: return "[安全提醒]请通过平台沟通" if any(p in text for p in blocked_phrases) else text def format_history(self, context: List[Dict]) -> str: - """格式化对话历史,返回完整的对话记录""" - # 过滤掉系统消息,只保留用户和助手的对话 + """ + 返回最近N轮用户对话,每轮包括用户和助手各一条。 + N由.env里的MAX_USER_HISTORY控制,默认5。 + """ user_assistant_msgs = [msg for msg in context if msg['role'] in ['user', 'assistant']] - return "\n".join([f"{msg['role']}: {msg['content']}" for msg in user_assistant_msgs]) - - def generate_reply(self, user_msg: str, item_desc: str, context: List[Dict]) -> str: - """生成回复主流程""" - # 记录用户消息 - # logger.debug(f'用户所发消息: {user_msg}') - - formatted_context = self.format_history(context) - # logger.debug(f'对话历史: {formatted_context}') - - # 1. 路由决策 - detected_intent = self.router.detect(user_msg, item_desc, formatted_context) + # 找出最近N条用户消息的位置 + user_indices = [i for i, msg in enumerate(user_assistant_msgs) if msg['role'] == 'user'] + last_n_user_indices = user_indices[-self.max_user_history:] if len( + user_indices) >= self.max_user_history else user_indices + selected_indices = [] + for idx in last_n_user_indices: + selected_indices.append(idx) + # 如果下一个消息是assistant,则一并加入 + if idx + 1 < len(user_assistant_msgs) and user_assistant_msgs[idx + 1]['role'] == 'assistant': + selected_indices.append(idx + 1) - # 2. 获取对应Agent + # 排序去重恢复原顺序 + selected_indices = sorted(set(selected_indices)) + selected_msgs = [user_assistant_msgs[i] for i in selected_indices] + return "\n".join([f"{msg['role']}: {msg['content']}" for msg in selected_msgs]) - internal_intents = {'classify'} # 定义不对外开放的Agent + def generate_reply(self, user_msg: str, item_desc: str, context: List[Dict]) -> str: + """生成回复主流程""" + formatted_context = self.format_history(context) - if detected_intent in self.agents and detected_intent not in internal_intents: - agent = self.agents[detected_intent] - logger.info(f'意图识别完成: {detected_intent}') - self.last_intent = detected_intent # 保存当前意图 - else: + if not self.enable_intent: + # 关闭意图识别:恒定使用 default,无议价 agent = self.agents['default'] - logger.info(f'意图识别完成: default') - self.last_intent = 'default' # 保存当前意图 - - # 3. 获取议价次数 - bargain_count = self._extract_bargain_count(context) - logger.info(f'议价次数: {bargain_count}') - - # 4. 生成回复 + bargain_count = 0 + logger.info('[意图识别已关闭] 使用default agent,无议价') + else: + # 开启意图识别流程 + detected_intent = self.router.detect(user_msg, item_desc, formatted_context) + internal_intents = {'classify'} # 内部agent不对外 + if detected_intent in self.agents and detected_intent not in internal_intents: + agent = self.agents[detected_intent] + logger.info(f'意图识别完成: {detected_intent}') + self.last_intent = detected_intent + else: + agent = self.agents['default'] + logger.info(f'意图识别完成: default') + self.last_intent = 'default' + bargain_count = self._extract_bargain_count(context) + logger.info(f'议价次数: {bargain_count}') + return agent.generate( user_msg=user_msg, item_desc=item_desc, context=formatted_context, bargain_count=bargain_count ) - + def _extract_bargain_count(self, context: List[Dict]) -> int: """ 从上下文中提取议价次数信息 - + Args: context: 对话历史 - + Returns: int: 议价次数,如果没有找到则返回0 """ - # 查找系统消息中的议价次数信息 for msg in context: if msg['role'] == 'system' and '议价次数' in msg['content']: try: - # 提取议价次数 match = re.search(r'议价次数[::]\s*(\d+)', msg['content']) if match: return int(match.group(1)) @@ -144,7 +146,7 @@ def __init__(self, classify_agent): 'tech': { # 技术类优先判定 'keywords': ['参数', '规格', '型号', '连接', '对比'], 'patterns': [ - r'和.+比' + r'和.+比' ] }, 'price': { @@ -157,31 +159,24 @@ def __init__(self, classify_agent): def detect(self, user_msg: str, item_desc, context) -> str: """三级路由策略(技术优先)""" text_clean = re.sub(r'[^\w\u4e00-\u9fa5]', '', user_msg) - + # 1. 技术类关键词优先检查 if any(kw in text_clean for kw in self.rules['tech']['keywords']): - # logger.debug(f"技术类关键词匹配: {[kw for kw in self.rules['tech']['keywords'] if kw in text_clean]}") return 'tech' - # 2. 技术类正则优先检查 for pattern in self.rules['tech']['patterns']: if re.search(pattern, text_clean): - # logger.debug(f"技术类正则匹配: {pattern}") return 'tech' # 3. 价格类检查 for intent in ['price']: if any(kw in text_clean for kw in self.rules[intent]['keywords']): - # logger.debug(f"价格类关键词匹配: {[kw for kw in self.rules[intent]['keywords'] if kw in text_clean]}") return intent - for pattern in self.rules[intent]['patterns']: if re.search(pattern, text_clean): - # logger.debug(f"价格类正则匹配: {pattern}") return intent - + # 4. 大模型兜底 - # logger.debug("使用大模型进行意图分类") return self.classify_agent.generate( user_msg=user_msg, item_desc=item_desc, @@ -211,7 +206,7 @@ def _build_messages(self, user_msg: str, item_desc: str, context: str) -> List[D ] def _call_llm(self, messages: List[Dict], temperature: float = 0.4) -> str: - """调用大模型""" + start = time.time() response = self.client.chat.completions.create( model=os.getenv("MODEL_NAME", "qwen-max"), messages=messages, @@ -219,13 +214,14 @@ def _call_llm(self, messages: List[Dict], temperature: float = 0.4) -> str: max_tokens=500, top_p=0.8 ) + logger.info(f"LLM调用耗时: {time.time() - start:.2f}秒") return response.choices[0].message.content class PriceAgent(BaseAgent): """议价处理Agent""" - def generate(self, user_msg: str, item_desc: str, context: str, bargain_count: int=0) -> str: + def generate(self, user_msg: str, item_desc: str, context: str, bargain_count: int = 0) -> str: """重写生成逻辑""" dynamic_temp = self._calc_temperature(bargain_count) messages = self._build_messages(user_msg, item_desc, context) @@ -247,17 +243,16 @@ def _calc_temperature(self, bargain_count: int) -> float: class TechAgent(BaseAgent): """技术咨询Agent""" - def generate(self, user_msg: str, item_desc: str, context: str, bargain_count: int=0) -> str: + def generate(self, user_msg: str, item_desc: str, context: str, bargain_count: int = 0) -> str: """重写生成逻辑""" messages = self._build_messages(user_msg, item_desc, context) - # messages[0]['content'] += "\n▲知识库:\n" + self._fetch_tech_specs() response = self.client.chat.completions.create( model=os.getenv("MODEL_NAME", "qwen-max"), messages=messages, - temperature=0.4, + temperature=0.8, max_tokens=500, - top_p=0.8, + top_p=1, extra_body={ "enable_search": True, } @@ -266,11 +261,6 @@ def generate(self, user_msg: str, item_desc: str, context: str, bargain_count: i return self.safety_filter(response.choices[0].message.content) - # def _fetch_tech_specs(self) -> str: - # """模拟获取技术参数(可连接数据库)""" - # return "功率:200W@8Ω\n接口:XLR+RCA\n频响:20Hz-20kHz" - - class ClassifyAgent(BaseAgent): """意图识别Agent""" @@ -285,4 +275,4 @@ class DefaultAgent(BaseAgent): def _call_llm(self, messages: List[Dict], *args) -> str: """限制默认回复长度""" response = super()._call_llm(messages, temperature=0.7) - return response \ No newline at end of file + return response diff --git a/hmd.txt b/hmd.txt new file mode 100644 index 00000000..06d5adad --- /dev/null +++ b/hmd.txt @@ -0,0 +1,41 @@ +加好友 +支付宝 +收款码 +暂时无法发消息 +去创建合约 +去支付 +去评价 +信息卡片 +退款成功 +我发起了退款申请 +记得及时发货 +我已付款等待你发货 +交易成功 +交易关闭 +等待买家付款 +等待卖家发货 +收货地址 +物流提醒 +订单号 +订单状态 +已完成 +未付款 +系统提示 +系统消息 +官方提示 +goofish.com +http +https +\[.*?\] +【.*?】 +\{.*?\} +[^a-zA-Z0-9\u4e00-\u9fa5]{6,} +^(命令|test|测试|假设|角色扮演|指令).{0,10}$ +你是谁 +你用什么模型 +你来自哪里 +你是什么 +你是ai +你的身份 +你是谁开发的 +你怎么来的 diff --git a/main.py b/main.py index 431ec21d..0de90b79 100644 --- a/main.py +++ b/main.py @@ -8,26 +8,24 @@ from dotenv import load_dotenv from XianyuApis import XianyuApis - from utils.xianyu_utils import generate_mid, generate_uuid, trans_cookies, generate_device_id, decrypt from XianyuAgent import XianyuReplyBot from context_manager import ChatContextManager - class XianyuLive: def __init__(self, cookies_str): self.xianyu = XianyuApis() self.base_url = 'wss://wss-goofish.dingtalk.com/' self.cookies_str = cookies_str self.cookies = trans_cookies(cookies_str) - self.xianyu.session.cookies.update(self.cookies) # 直接使用 session.cookies.update + self.xianyu.session.cookies.update(self.cookies) self.myid = self.cookies['unb'] self.device_id = generate_device_id(self.myid) self.context_manager = ChatContextManager() - - # 心跳相关配置 - self.heartbeat_interval = 15 # 心跳间隔15秒 - self.heartbeat_timeout = 5 # 心跳超时5秒 + + # 心跳等 + self.heartbeat_interval = 15 + self.heartbeat_timeout = 5 self.last_heartbeat_time = 0 self.last_heartbeat_response = 0 self.heartbeat_task = None @@ -42,6 +40,25 @@ def __init__(self, cookies_str): self.toggle_keywords = os.getenv("TOGGLE_KEYWORDS", "。") logger.info(f"人工接管切换关键词为: {self.toggle_keywords}") + def is_blacklisted(self, content: str) -> bool: + """ + 实时读取同级目录hmd.txt,每行一个黑名单词,命中任意即True + """ + try: + blacklist_keywords = [] + with open("hmd.txt", "r", encoding="utf-8") as f: + for line in f: + word = line.strip() + if word: + blacklist_keywords.append(word) + for word in blacklist_keywords: + if word.lower() in (content or "").lower(): + return True + except Exception as e: + # 没有hmd.txt则认为没有黑名单 + logger.debug(f"hmd.txt读取异常: {e}") + return False + async def send_msg(self, ws, cid, toid, text): text = { "contentType": 1, @@ -105,7 +122,6 @@ async def init(self, ws): } } await ws.send(json.dumps(msg)) - # 等待一段时间,确保连接注册完成 await asyncio.sleep(1) msg = {"lwp": "/r/SyncStatus/ackDiff", "headers": {"mid": "5701741704675979 0"}, "body": [ {"pipeline": "sync", "tooLong2Tag": "PNM,1", "channel": "sync", "topic": "sync", "highPts": 0, @@ -114,21 +130,19 @@ async def init(self, ws): logger.info('连接注册完成') def is_chat_message(self, message): - """判断是否为用户聊天消息""" try: return ( - isinstance(message, dict) - and "1" in message - and isinstance(message["1"], dict) # 确保是字典类型 + isinstance(message, dict) + and "1" in message + and isinstance(message["1"], dict) and "10" in message["1"] - and isinstance(message["1"]["10"], dict) # 确保是字典类型 + and isinstance(message["1"]["10"], dict) and "reminderContent" in message["1"]["10"] ) except Exception: return False def is_sync_package(self, message_data): - """判断是否为同步包消息""" try: return ( isinstance(message_data, dict) @@ -141,7 +155,6 @@ def is_sync_package(self, message_data): return False def is_typing_status(self, message): - """判断是否为用户正在输入状态消息""" try: return ( isinstance(message, dict) @@ -210,7 +223,6 @@ def toggle_manual_mode(self, chat_id): return "manual" async def handle_message(self, message_data, websocket): - """处理所有类型的消息""" try: try: @@ -232,28 +244,21 @@ async def handle_message(self, message_data, websocket): except Exception as e: pass - # 如果不是同步包消息,直接返回 if not self.is_sync_package(message_data): return - # 获取并解密数据 sync_data = message_data["body"]["syncPushPackage"]["data"][0] - - # 检查是否有必要的字段 if "data" not in sync_data: logger.debug("同步包中无data字段") return - # 解密数据 try: data = sync_data["data"] try: data = base64.b64decode(data).decode("utf-8") data = json.loads(data) - # logger.info(f"无需解密 message: {data}") return except Exception as e: - # logger.info(f'加密数据: {data}') decrypted_data = decrypt(data) message = json.loads(decrypted_data) except Exception as e: @@ -261,7 +266,6 @@ async def handle_message(self, message_data, websocket): return try: - # 判断是否为订单消息,需要自行编写付款后的逻辑 if message['3']['redReminder'] == '等待买家付款': user_id = message['1'].split('@')[0] user_url = f'https://www.goofish.com/personal?userId={user_id}' @@ -277,39 +281,39 @@ async def handle_message(self, message_data, websocket): user_url = f'https://www.goofish.com/personal?userId={user_id}' logger.info(f'交易成功 {user_url} 等待卖家发货') return - except: pass - # 判断消息类型 if self.is_typing_status(message): - logger.debug("用户正在输入") return elif not self.is_chat_message(message): - logger.debug("其他非聊天消息") - logger.debug(f"原始消息: {message}") return - # 处理聊天消息 create_time = int(message["1"]["5"]) send_user_name = message["1"]["10"]["reminderTitle"] send_user_id = message["1"]["10"]["senderUserId"] send_message = message["1"]["10"]["reminderContent"] - - # 时效性验证(过滤5分钟前消息) + + # ----------这里: 实时黑名单拦截---------- + if self.is_blacklisted(send_message): + logger.info(f"消息命中黑名单关键词,已忽略: {send_message}") + return + if (time.time() * 1000 - create_time) > 300000: logger.debug("过期消息丢弃") return - + # 获取商品ID和会话ID url_info = message["1"]["10"]["reminderUrl"] item_id = url_info.split("itemId=")[1].split("&")[0] if "itemId=" in url_info else None chat_id = message["1"]["2"].split('@')[0] + if not item_id: logger.warning("无法获取商品ID") return + # 检查是否为卖家(自己)发送的控制命令 if send_user_id == self.myid: logger.debug("检测到卖家消息,检查是否为控制命令") @@ -346,15 +350,15 @@ async def handle_message(self, message_data, websocket): api_result = self.xianyu.get_item_info(item_id) if 'data' in api_result and 'itemDO' in api_result['data']: item_info = api_result['data']['itemDO'] - # 保存商品信息到数据库 self.context_manager.save_item_info(item_id, item_info) else: logger.warning(f"获取商品信息失败: {api_result}") return else: logger.info(f"从数据库获取商品信息: {item_id}") - + item_description = f"{item_info['desc']};当前商品售卖价格为:{str(item_info['soldPrice'])}" + # 获取完整的对话上下文 context = self.context_manager.get_context_by_chat(chat_id) @@ -364,12 +368,17 @@ async def handle_message(self, message_data, websocket): item_description, context=context ) - - # 检查是否为价格意图,如果是则增加议价次数 + + # ---------大模型返回为空不过滤----------- + if not bot_reply or str(bot_reply).strip() == "": + logger.info("大模型返回结果为空,不回复") + return + if bot.last_intent == "price": self.context_manager.increment_bargain_count_by_chat(chat_id) bargain_count = self.context_manager.get_bargain_count_by_chat(chat_id) logger.info(f"用户 {send_user_name} 对商品 {item_id} 的议价次数: {bargain_count}") + # 添加机器人回复到上下文 self.context_manager.add_message_by_chat(chat_id, self.myid, item_id, "assistant", bot_reply) @@ -382,7 +391,6 @@ async def handle_message(self, message_data, websocket): logger.debug(f"原始消息: {message_data}") async def send_heartbeat(self, ws): - """发送心跳包并等待响应""" try: heartbeat_mid = generate_mid() heartbeat_msg = { @@ -400,27 +408,20 @@ async def send_heartbeat(self, ws): raise async def heartbeat_loop(self, ws): - """心跳维护循环""" while True: try: current_time = time.time() - - # 检查是否需要发送心跳 if current_time - self.last_heartbeat_time >= self.heartbeat_interval: await self.send_heartbeat(ws) - - # 检查上次心跳响应时间,如果超时则认为连接已断开 if (current_time - self.last_heartbeat_response) > (self.heartbeat_interval + self.heartbeat_timeout): logger.warning("心跳响应超时,可能连接已断开") break - await asyncio.sleep(1) except Exception as e: logger.error(f"心跳循环出错: {e}") break async def handle_heartbeat_response(self, message_data): - """处理心跳响应""" try: if ( isinstance(message_data, dict) @@ -454,23 +455,15 @@ async def main(self): async with websockets.connect(self.base_url, extra_headers=headers) as websocket: self.ws = websocket await self.init(websocket) - - # 初始化心跳时间 self.last_heartbeat_time = time.time() self.last_heartbeat_response = time.time() - - # 启动心跳任务 self.heartbeat_task = asyncio.create_task(self.heartbeat_loop(websocket)) - + async for message in websocket: try: message_data = json.loads(message) - - # 处理心跳响应 if await self.handle_heartbeat_response(message_data): continue - - # 发送通用ACK响应 if "headers" in message_data and "mid" in message_data["headers"]: ack = { "code": 200, @@ -479,15 +472,11 @@ async def main(self): "sid": message_data["headers"].get("sid", "") } } - # 复制其他可能的header字段 for key in ["app-key", "ua", "dt"]: if key in message_data["headers"]: ack["headers"][key] = message_data["headers"][key] await websocket.send(json.dumps(ack)) - - # 处理其他消息 await self.handle_message(message_data, websocket) - except json.JSONDecodeError: logger.error("消息解析失败") except Exception as e: @@ -502,8 +491,7 @@ async def main(self): await self.heartbeat_task except asyncio.CancelledError: pass - await asyncio.sleep(5) # 等待5秒后重连 - + await asyncio.sleep(5) except Exception as e: logger.error(f"连接发生错误: {e}") if self.heartbeat_task: @@ -512,14 +500,11 @@ async def main(self): await self.heartbeat_task except asyncio.CancelledError: pass - await asyncio.sleep(5) # 等待5秒后重连 - + await asyncio.sleep(5) if __name__ == '__main__': - #加载环境变量 cookie load_dotenv() cookies_str = os.getenv("COOKIES_STR") bot = XianyuReplyBot() xianyuLive = XianyuLive(cookies_str) - # 常驻进程 asyncio.run(xianyuLive.main()) diff --git a/main.spec b/main.spec new file mode 100644 index 00000000..2ba8dd94 --- /dev/null +++ b/main.spec @@ -0,0 +1,38 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['main.py'], + pathex=[], + binaries=[], + datas=[], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='main', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, +) diff --git a/static/xianyu_js_version_2.js b/static/xianyu_js_version_2.js index 42d114cb..074a5c91 100644 --- a/static/xianyu_js_version_2.js +++ b/static/xianyu_js_version_2.js @@ -1,3 +1,9 @@ +if (typeof atob === 'undefined') { + global.atob = function(str) { + return Buffer.from(str, 'base64').toString('binary'); + } +} + const crypto = require('crypto') const generate_mid = () => {