From 166506977e6bbb07ad4e0f129307c78c7ff9cdbd Mon Sep 17 00:00:00 2001 From: taiyi Date: Sat, 29 Nov 2025 14:37:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E9=AA=8C=E8=AF=81=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ArticleReplace.py | 25 ++- ai_studio.py | 68 ++++--- auth_config.json | 2 +- auth_validator.py | 497 ++++++++++++++++++++++++++++++++++++++++----- get_web_content.py | 86 +++++++- main_process.py | 47 ++++- utils.py | 4 +- 7 files changed, 632 insertions(+), 97 deletions(-) diff --git a/ArticleReplace.py b/ArticleReplace.py index 7c0d049..c9bf680 100644 --- a/ArticleReplace.py +++ b/ArticleReplace.py @@ -1352,7 +1352,16 @@ class ArticleReplaceApp(ctk.CTk): # 计算处理结果 total_links = len(results) - success_links = sum(1 for _, success, _ in results if success) + success_links = 0 + + # 正确计算成功链接数 + for result in results: + if isinstance(result, tuple) and len(result) >= 2: + _, success, _ = result + if success: + success_links += 1 + else: + logger.warning(f"意外的结果格式: {result}") # 记录结束时间和总耗时 end_time = time.time() @@ -1364,8 +1373,15 @@ class ArticleReplaceApp(ctk.CTk): logger.info(f"总耗时: {elapsed_time:.2f} 秒") # 在主线程中显示处理结果 - self.after(0, lambda: messagebox.showinfo("处理完成", - f"共处理 {total_links} 个链接\n成功: {success_links} 个\n失败: {total_links - success_links} 个\n总耗时: {elapsed_time:.2f} 秒")) + def show_result(): + try: + messagebox.showinfo("处理完成", + f"共处理 {total_links} 个链接\n成功: {success_links} 个\n失败: {total_links - success_links} 个\n总耗时: {elapsed_time:.2f} 秒") + except Exception as e: + logger.error(f"显示处理结果时出错: {e}") + messagebox.showerror("错误", f"显示处理结果时出错: {e}") + + self.after(0, show_result) except Exception as e: logger.error(f"处理任务出错: {e}") error_msg = str(e) @@ -1440,8 +1456,7 @@ def main(): software_id="ArticleReplace", api_url="http://km.taisan.online/api/v1", gui_mode=True, - secret_key="taiyi1224", - restore_tkinter=True + secret_key="taiyi1224" ) # 执行验证 diff --git a/ai_studio.py b/ai_studio.py index 1a12cf4..f657d3f 100644 --- a/ai_studio.py +++ b/ai_studio.py @@ -84,7 +84,6 @@ def call_coze_article_workflow(parameters): 调用 Coze 工作流的函数 :param parameters: 传递给工作流的输入参数(字典格式) - :param is_async: 是否异步执行(默认 False) :return: 工作流的执行结果 """ @@ -105,20 +104,29 @@ def call_coze_article_workflow(parameters): response = requests.post(url, json=data, headers=headers) if response.status_code == 200: - # data = json.loads(response.text)['data'] - # print("data:",data['output']) - import ast + try: + # 解析响应 + result_dict = json.loads(response.text) + print(result_dict) - # 直接解析整个result字符串 - result_dict = ast.literal_eval(response.text) + # 获取data字段 + data_str = result_dict['data'] + + # 如果data是字符串,尝试解析它 + if isinstance(data_str, str): + data_dict = json.loads(data_str) + else: + data_dict = data_str - # 解析data字段 - data_dict = ast.literal_eval(result_dict['data']) + # 获取output的值 + output_value = data_dict.get('output', '') - # 获取output的值 - output_value = data_dict['output'] - - return output_value + return output_value + except Exception as e: + return { + "error": f"解析响应时出错:{str(e)}", + "detail": response.text + } else: return { "error": f"请求失败,状态码:{response.status_code}", @@ -136,7 +144,7 @@ def call_coze_all_article_workflow(parameters,is_async=False): """ workflow_id = CONFIG['Coze']['workflow_id'] access_token = CONFIG['Coze']['access_token'] - is_async = CONFIG['Coze']['is_async'].lower() == 'False' + is_async = CONFIG['Coze']['is_async'].lower() == 'true' # 修复异步参数逻辑 url = "https://api.coze.cn/v1/workflow/run" headers = { "Authorization": f"Bearer {access_token}", @@ -151,21 +159,29 @@ def call_coze_all_article_workflow(parameters,is_async=False): response = requests.post(url, json=data, headers=headers) if response.status_code == 200: - # data = json.loads(response.text)['data'] - # print("data:",data['output']) - import ast + try: + # 解析响应 + result_dict = json.loads(response.text) + print(result_dict) - # 直接解析整个result字符串 - result_dict = ast.literal_eval(response.text) - print(result_dict) + # 获取data字段 + data_str = result_dict['data'] + + # 如果data是字符串,尝试解析它 + if isinstance(data_str, str): + data_dict = json.loads(data_str) + else: + data_dict = data_str - # 解析data字段 - data_dict = ast.literal_eval(result_dict['data']) - - # 获取output的值 - title = data_dict['title'] - article = data_dict['article'] - return title, article + # 获取output的值 + title = data_dict.get('title', '') + article = data_dict.get('article', '') + return title, article + except Exception as e: + return { + "error": f"解析响应时出错:{str(e)}", + "detail": response.text + } else: return { "error": f"请求失败,状态码:{response.status_code}", diff --git a/auth_config.json b/auth_config.json index 9264173..bdf4ac9 100644 --- a/auth_config.json +++ b/auth_config.json @@ -1,3 +1,3 @@ { - "last_license_key": "LGCXVKAH-665RMFLI-WFDH6OJT-CZBCTATA" + "last_license_key": "THD0RP4X-RU8BCYNB-SEIVKENY-UYB1943X" } \ No newline at end of file diff --git a/auth_validator.py b/auth_validator.py index 6bc6c06..739db0d 100644 --- a/auth_validator.py +++ b/auth_validator.py @@ -7,7 +7,6 @@ Python软件授权验证器 (现代化UI版) 3. 现代化深色主题 UI 4. 机器码一键复制 5. 防止后台禁用卡密后仍能使用 -6. 验证通过后自动恢复tkinter原始缩放比例 使用方法 (完全兼容旧版): from auth_validator import AuthValidator @@ -21,7 +20,7 @@ Python软件授权验证器 (现代化UI版) sys.exit() """ -import sys +import sys # 加在文件开头,比如其他 import 语句后面 import os import json @@ -34,6 +33,17 @@ import uuid from datetime import datetime, timedelta from typing import Optional, Tuple, Dict, Any +# PyInstaller打包环境下的SSL证书处理 +# 尝试导入certifi来解决打包后SSL证书问题 +try: + import certifi + + # 设置requests使用certifi的证书 + os.environ['REQUESTS_CA_BUNDLE'] = certifi.where() + os.environ['SSL_CERT_FILE'] = certifi.where() +except ImportError: + pass + # 尝试导入现代化UI库,如果未安装则提示 try: import customtkinter as ctk @@ -202,8 +212,19 @@ class AuthCore: # 发送POST请求 verify_url = f"{self.api_url}/auth/verify" + + # 添加调试信息 + debug_info = f"请求URL: {verify_url}\n请求数据: {request_data}" + try: - resp = requests.post(verify_url, json=request_data, timeout=self.timeout) + # 在PyInstaller打包环境中可能需要特殊处理SSL验证 + # 如果是打包环境,尝试禁用SSL验证(仅用于测试) + is_frozen = getattr(sys, 'frozen', False) + if is_frozen: + # 打包环境,可能需要特殊处理 + resp = requests.post(verify_url, json=request_data, timeout=self.timeout, verify=False) + else: + resp = requests.post(verify_url, json=request_data, timeout=self.timeout) except requests.exceptions.Timeout: return False, f"连接超时({self.timeout}秒),请检查网络连接或服务器地址: {self.api_url}", {} except requests.exceptions.ConnectionError as e: @@ -216,7 +237,9 @@ class AuthCore: elif "No route to host" in error_detail: return False, f"无法到达服务器,请检查网络连接: {self.api_url}", {} else: - return False, f"无法连接到服务器 ({self.api_url}),请检查网络连接和服务器状态", {} + return False, f"无法连接到服务器 ({self.api_url}),请检查网络连接和服务器状态\n详细错误: {error_detail}", {} + except Exception as e: + return False, f"网络请求异常: {str(e)}\n{debug_info}", {} # 检查HTTP状态码 if resp.status_code != 200: @@ -238,7 +261,10 @@ class AuthCore: return False, error_msg, {} # 解析响应 - result = resp.json() + try: + result = resp.json() + except Exception as e: + return False, f"服务器响应格式错误: {str(e)}\n响应内容: {resp.text}", {} # 检查验证结果 if not result.get('success', False): @@ -279,6 +305,185 @@ class AuthCore: except: pass + def unbind_license(self, license_key: str) -> Tuple[bool, str]: + """解绑卡密与机器码的绑定""" + try: + # 生成时间戳 + timestamp = int(time.time()) + + # 生成签名数据 + signature_data = f"{self.software_id}{license_key}{self.machine_code}{timestamp}" + + # 生成签名 + combined = f"{signature_data}{self.secret_key}".encode('utf-8') + signature = hashlib.sha256(combined).hexdigest() + + # 构建请求数据 + request_data = { + "software_id": self.software_id, + "license_key": license_key, + "machine_code": self.machine_code, + "timestamp": timestamp, + "signature": signature + } + + # 发送POST请求到解绑接口 + unbind_url = f"{self.api_url}/auth/unbind" + + try: + # 在PyInstaller打包环境中可能需要特殊处理SSL验证 + is_frozen = getattr(sys, 'frozen', False) + if is_frozen: + # 打包环境,可能需要特殊处理 + resp = requests.post(unbind_url, json=request_data, timeout=self.timeout, verify=False) + else: + resp = requests.post(unbind_url, json=request_data, timeout=self.timeout) + except requests.exceptions.Timeout: + return False, f"连接超时({self.timeout}秒),请检查网络连接或服务器地址: {self.api_url}" + except requests.exceptions.ConnectionError as e: + # 提供更详细的连接错误信息 + error_detail = str(e) + if "Name or service not known" in error_detail or "nodename nor servname provided" in error_detail: + return False, f"无法解析服务器地址,请检查API地址是否正确: {self.api_url}" + elif "Connection refused" in error_detail: + return False, f"服务器拒绝连接,请确认服务器是否运行在: {self.api_url}" + elif "No route to host" in error_detail: + return False, f"无法到达服务器,请检查网络连接: {self.api_url}" + else: + return False, f"无法连接到服务器 ({self.api_url}),请检查网络连接和服务器状态\n详细错误: {error_detail}" + except Exception as e: + return False, f"网络请求异常: {str(e)}" + + # 检查HTTP状态码 + if resp.status_code != 200: + # 处理特定的HTTP状态码 + if resp.status_code == 503: + return False, "服务器暂时不可用,请稍后重试" + elif resp.status_code == 500: + return False, "服务器内部错误,请联系管理员" + elif resp.status_code == 404: + return False, "API接口不存在,请检查API地址" + elif resp.status_code == 401: + return False, "签名验证失败,请检查密钥配置" + else: + try: + error_data = resp.json() + error_msg = error_data.get('message', f'服务器返回错误: {resp.status_code}') + except: + error_msg = f'服务器返回错误: {resp.status_code}' + return False, error_msg + + # 解析响应 + try: + result = resp.json() + except Exception as e: + return False, f"服务器响应格式错误: {str(e)}\n响应内容: {resp.text}" + + # 检查解绑结果 + if not result.get('success', False): + error_msg = result.get('message', '解绑失败') + return False, error_msg + + return True, result.get('message', '解绑成功') + + except requests.exceptions.Timeout: + return False, "连接超时,请检查网络连接" + except requests.exceptions.ConnectionError as e: + return False, f"无法连接到服务器: {str(e)},请检查网络连接和服务器地址" + except requests.exceptions.RequestException as e: + return False, f"网络请求失败: {str(e)}" + except Exception as e: + return False, f"解绑过程出错: {str(e)}" + + def unbind_device(self) -> Tuple[bool, str]: + """解绑当前设备与卡密的绑定""" + try: + # 生成时间戳 + timestamp = int(time.time()) + + # 生成签名数据 + signature_data = f"{self.software_id}{self.machine_code}{timestamp}" + + # 生成签名 + combined = f"{signature_data}{self.secret_key}".encode('utf-8') + signature = hashlib.sha256(combined).hexdigest() + + # 构建请求数据 + request_data = { + "software_id": self.software_id, + "machine_code": self.machine_code, + "timestamp": timestamp, + "signature": signature + } + + # 发送POST请求到解绑设备接口 + unbind_url = f"{self.api_url}/auth/unbind_device" + + try: + # 在PyInstaller打包环境中可能需要特殊处理SSL验证 + is_frozen = getattr(sys, 'frozen', False) + if is_frozen: + # 打包环境,可能需要特殊处理 + resp = requests.post(unbind_url, json=request_data, timeout=self.timeout, verify=False) + else: + resp = requests.post(unbind_url, json=request_data, timeout=self.timeout) + except requests.exceptions.Timeout: + return False, f"连接超时({self.timeout}秒),请检查网络连接或服务器地址: {self.api_url}" + except requests.exceptions.ConnectionError as e: + # 提供更详细的连接错误信息 + error_detail = str(e) + if "Name or service not known" in error_detail or "nodename nor servname provided" in error_detail: + return False, f"无法解析服务器地址,请检查API地址是否正确: {self.api_url}" + elif "Connection refused" in error_detail: + return False, f"服务器拒绝连接,请确认服务器是否运行在: {self.api_url}" + elif "No route to host" in error_detail: + return False, f"无法到达服务器,请检查网络连接: {self.api_url}" + else: + return False, f"无法连接到服务器 ({self.api_url}),请检查网络连接和服务器状态\n详细错误: {error_detail}" + except Exception as e: + return False, f"网络请求异常: {str(e)}" + + # 检查HTTP状态码 + if resp.status_code != 200: + # 处理特定的HTTP状态码 + if resp.status_code == 503: + return False, "服务器暂时不可用,请稍后重试" + elif resp.status_code == 500: + return False, "服务器内部错误,请联系管理员" + elif resp.status_code == 404: + return False, "API接口不存在,请检查API地址" + elif resp.status_code == 401: + return False, "签名验证失败,请检查密钥配置" + else: + try: + error_data = resp.json() + error_msg = error_data.get('message', f'服务器返回错误: {resp.status_code}') + except: + error_msg = f'服务器返回错误: {resp.status_code}' + return False, error_msg + + # 解析响应 + try: + result = resp.json() + except Exception as e: + return False, f"服务器响应格式错误: {str(e)}\n响应内容: {resp.text}" + + # 检查解绑结果 + if not result.get('success', False): + error_msg = result.get('message', '解绑失败') + return False, error_msg + + return True, result.get('message', '设备解绑成功') + + except requests.exceptions.Timeout: + return False, "连接超时,请检查网络连接" + except requests.exceptions.ConnectionError as e: + return False, f"无法连接到服务器: {str(e)},请检查网络连接和服务器地址" + except requests.exceptions.RequestException as e: + return False, f"网络请求失败: {str(e)}" + except Exception as e: + return False, f"解绑过程出错: {str(e)}" + # ========================================== # 3. 现代化 UI 层 (View) @@ -298,14 +503,22 @@ class AuthWindow(ctk.CTk): # 窗口基础设置 self.title("软件授权验证(有问题联系V:taiyi1224)") - self.geometry("300x350") # 增加高度以容纳服务器地址显示 - self.resizable(False, False) + self.geometry("400x600") # 增加高度以容纳新按钮 + self.minsize(300, 500) # 更新最小窗口大小 + self.resizable(True, True) # 启用窗口大小调整 ctk.set_appearance_mode("Dark") ctk.set_default_color_theme("blue") # 绑定窗口关闭事件 self.protocol("WM_DELETE_WINDOW", self._on_closing) + # 绑定窗口大小变化事件 + self.bind("", self._on_window_resize) + + # 初始化布局参数 + self.window_width = 400 + self.window_height = 600 + # 居中显示 self._center_window() self._setup_ui() @@ -337,13 +550,59 @@ class AuthWindow(ctk.CTk): y = (self.winfo_screenheight() // 2) - (height // 2) self.geometry(f'{width}x{height}+{x}+{y}') + def _on_window_resize(self, event): + """窗口大小变化事件处理""" + # 只有当窗口确实是当前窗口且大小发生变化时才处理 + if event.widget == self and (self.window_width != event.width or self.window_height != event.height): + self.window_width = event.width + self.window_height = event.height + self._update_layout() + + def _update_layout(self): + """动态更新布局""" + # 更新各组件的尺寸和位置 + padding_x = max(20, int(self.window_width * 0.05)) + + # 更新头部区域 + self.header.pack_configure( + pady=(max(20, int(self.window_height * 0.04)), max(10, int(self.window_height * 0.02)))) + + # 动态调整标题字体大小 + title_font_size = max(16, min(22, int(self.window_width * 0.04))) + self.title_label.configure(font=("Microsoft YaHei UI", title_font_size, "bold")) + + # 更新机器码区域 + self.mc_frame.pack_configure(padx=padding_x, pady=max(5, int(self.window_height * 0.01))) + + # 更新输入区域 + self.input_frame.pack_configure(padx=padding_x, pady=max(5, int(self.window_height * 0.01))) + + # 更新服务器地址标签 + self.lbl_server.pack_configure(pady=(max(5, int(self.window_height * 0.01)), 0)) + + # 更新状态标签 + self.lbl_status.pack_configure( + pady=(max(5, int(self.window_height * 0.01)), max(2, int(self.window_height * 0.005)))) + + # 更新验证按钮 + self.btn_verify.pack_configure(padx=padding_x, pady=max(10, int(self.window_height * 0.02))) + + # 动态调整状态标签的换行宽度 + wrap_length = max(200, int(self.window_width * 0.8)) + self.lbl_status.configure(wraplength=wrap_length) + + # 动态调整底部标签的位置 + self.footer_label.pack_configure(pady=max(5, int(self.window_height * 0.01))) + def _setup_ui(self): # 1. 头部图标与标题 self.header = ctk.CTkFrame(self, fg_color="transparent") self.header.pack(pady=(40, 20)) - ctk.CTkLabel(self.header, text="🔐", font=("Segoe UI Emoji", 56)).pack() - ctk.CTkLabel(self.header, text="用户授权系统", font=("Microsoft YaHei UI", 22, "bold")).pack(pady=5) + self.icon_label = ctk.CTkLabel(self.header, text="🔐", font=("Segoe UI Emoji", 56)) + self.icon_label.pack() + self.title_label = ctk.CTkLabel(self.header, text="用户授权系统", font=("Microsoft YaHei UI", 22, "bold")) + self.title_label.pack(pady=5) # 2. 机器码显示区 self.mc_frame = ctk.CTkFrame(self, fg_color="#2B2B2B", corner_radius=8) @@ -379,7 +638,45 @@ class AuthWindow(ctk.CTk): ) self.entry_key.pack(fill="x") - # 4. 服务器地址显示(小字,灰色) + # 4. 操作按钮区 + self.button_frame = ctk.CTkFrame(self, fg_color="transparent") + self.button_frame.pack(padx=30, pady=10, fill="x") + + # 验证按钮 + self.btn_verify = ctk.CTkButton( + self.button_frame, + text="立即验证授权", + height=40, + font=("Microsoft YaHei UI", 14, "bold"), + command=self._handle_verify + ) + self.btn_verify.pack(fill="x", pady=(0, 10)) + + # 解绑卡密按钮 + self.btn_unbind_license = ctk.CTkButton( + self.button_frame, + text="解绑当前卡密", + height=40, + font=("Microsoft YaHei UI", 14), + fg_color="transparent", + border_width=2, + command=self._handle_unbind_license + ) + self.btn_unbind_license.pack(fill="x", pady=(0, 10)) + + # 解绑设备按钮 + self.btn_unbind_device = ctk.CTkButton( + self.button_frame, + text="解绑当前设备", + height=40, + font=("Microsoft YaHei UI", 14), + fg_color="transparent", + border_width=2, + command=self._handle_unbind_device + ) + self.btn_unbind_device.pack(fill="x") + + # 5. 服务器地址显示(小字,灰色) self.lbl_server = ctk.CTkLabel( self, text=f"服务器: {self.auth_core.api_url}", @@ -388,7 +685,7 @@ class AuthWindow(ctk.CTk): ) self.lbl_server.pack(pady=(10, 0)) - # 5. 状态提示信息(支持多行) + # 6. 状态提示信息(支持多行) self.lbl_status = ctk.CTkLabel( self, text="等待验证...", @@ -399,19 +696,9 @@ class AuthWindow(ctk.CTk): ) self.lbl_status.pack(pady=(10, 5)) - # 6. 验证按钮 - self.btn_verify = ctk.CTkButton( - self, - text="立即验证授权", - height=50, - font=("Microsoft YaHei UI", 16, "bold"), - command=self._handle_verify - ) - self.btn_verify.pack(padx=30, pady=20, fill="x") - # 底部版权 - ctk.CTkLabel(self, text="Powered by AuthValidator", font=("Arial", 10), text_color="#444").pack(side="bottom", - pady=10) + self.footer_label = ctk.CTkLabel(self, text="Powered by AuthValidator", font=("Arial", 10), text_color="#444") + self.footer_label.pack(side="bottom", pady=10) def _load_history(self): """读取历史卡密,如果有则自动验证""" @@ -537,6 +824,140 @@ class AuthWindow(ctk.CTk): self.pending_callbacks.clear() self.destroy() + def _handle_unbind_license(self): + """处理解绑卡密操作""" + key = self.entry_key.get().strip() + if not key: + self.lbl_status.configure(text="❌ 卡密不能为空", text_color="#F44336") + return + + # 如果正在验证中,忽略重复请求 + if self.verifying: + return + + # 标记为正在验证 + self.verifying = True + self.is_verified = False # 重置验证状态 + + # 锁定UI + self.btn_verify.configure(state="disabled", text="正在连接服务器...") + self.btn_unbind_license.configure(state="disabled") + self.btn_unbind_device.configure(state="disabled") + self.entry_key.configure(state="disabled") + self.lbl_status.configure(text="⏳ 正在解绑卡密中,请稍候...", text_color="#2196F3") + + # 开启线程进行解绑 + threading.Thread(target=self._unbind_license_thread, args=(key,), daemon=True).start() + + def _unbind_license_thread(self, key): + """后台解绑卡密逻辑""" + success, msg = self.auth_core.unbind_license(key) + # 回到主线程更新UI,使用安全的方式 + if not self.is_destroyed: + callback_id = self.after(0, lambda: self._on_unbind_license_result(success, msg)) + self.pending_callbacks.append(callback_id) + + def _on_unbind_license_result(self, success, msg): + # 如果窗口已销毁,直接返回 + if self.is_destroyed: + return + + # 标记验证完成 + self.verifying = False + + self.btn_verify.configure(state="normal", text="立即验证授权") + self.btn_unbind_license.configure(state="normal") + self.btn_unbind_device.configure(state="normal") + self.entry_key.configure(state="normal") + + if success: + # 解绑成功 - 必须先设置 is_verified 为 False,再关闭窗口 + self.is_verified = False + self.lbl_status.configure(text=f"✅ {msg}", text_color="#4CAF50") + + # 清除本地缓存 + self.auth_core.clear_token() + + # 延迟关闭窗口 + callback_id = self.after(800, self._safe_close_window) + self.pending_callbacks.append(callback_id) + else: + # 解绑失败,确保 is_verified 为 False + + # 格式化错误消息,如果是连接错误,显示API地址 + error_msg = msg + if "无法连接" in msg or "连接超时" in msg or "服务器" in msg: + # 在错误消息中已经包含了API地址,直接显示 + pass + elif len(msg) > 80: + # 如果错误消息太长,截断并添加提示 + error_msg = msg[:80] + "..." + + self.lbl_status.configure(text=f"❌ {error_msg}", text_color="#F44336") + + def _handle_unbind_device(self): + """处理解绑设备操作""" + # 如果正在验证中,忽略重复请求 + if self.verifying: + return + + # 标记为正在验证 + self.verifying = True + self.is_verified = False # 重置验证状态 + + # 锁定UI + self.btn_verify.configure(state="disabled", text="正在连接服务器...") + self.btn_unbind_license.configure(state="disabled") + self.btn_unbind_device.configure(state="disabled") + self.entry_key.configure(state="disabled") + self.lbl_status.configure(text="⏳ 正在解绑设备中,请稍候...", text_color="#2196F3") + + # 开启线程进行解绑 + threading.Thread(target=self._unbind_device_thread, daemon=True).start() + + def _unbind_device_thread(self): + """后台解绑设备逻辑""" + success, msg = self.auth_core.unbind_device() + # 回到主线程更新UI,使用安全的方式 + if not self.is_destroyed: + callback_id = self.after(0, lambda: self._on_unbind_device_result(success, msg)) + self.pending_callbacks.append(callback_id) + + def _on_unbind_device_result(self, success, msg): + # 如果窗口已销毁,直接返回 + if self.is_destroyed: + return + + # 标记验证完成 + self.verifying = False + + self.btn_verify.configure(state="normal", text="立即验证授权") + self.btn_unbind_license.configure(state="normal") + self.btn_unbind_device.configure(state="normal") + self.entry_key.configure(state="normal") + + if success: + # 解绑成功 + self.lbl_status.configure(text=f"✅ {msg}", text_color="#4CAF50") + + # 延迟2秒后重置状态 + callback_id = self.after(2000, lambda: self.lbl_status.configure(text="等待验证...", + text_color="gray") if not self.is_destroyed else None) + self.pending_callbacks.append(callback_id) + else: + # 解绑失败 + + # 格式化错误消息,如果是连接错误,显示API地址 + error_msg = msg + if "无法连接" in msg or "连接超时" in msg or "服务器" in msg: + # 在错误消息中已经包含了API地址,直接显示 + pass + elif len(msg) > 80: + # 如果错误消息太长,截断并添加提示 + error_msg = msg[:80] + "..." + + self.lbl_status.configure(text=f"❌ {error_msg}", text_color="#F44336") + # ========================================== # 4. 统一接口层 (Facade) @@ -550,8 +971,8 @@ class AuthValidator: def __init__(self, software_id: str, - api_url: str = "http://localhost:5000/api/v1", - secret_key: str = "default_secret", + api_url: str = "http://km.taisan.online/api/v1", + secret_key: str = "taiyi1224", **kwargs): """ 初始化验证器 @@ -569,7 +990,6 @@ class AuthValidator: 1. 读取保存的卡密(如果有) 2. 弹出现代化UI窗口并自动验证 3. 验证失败则要求用户重新输入 - 4. 验证成功后恢复tkinter的原始缩放比例 Returns: bool: 是否验证成功 """ @@ -579,32 +999,7 @@ class AuthValidator: # 运行主循环 (这会阻塞代码执行,直到窗口关闭) app.mainloop() - # 窗口关闭后,如果验证成功,重置 tkinter 的缩放比例 - if app.is_verified: - self._reset_tk_scaling() - # 窗口关闭后,检查是否验证成功 return app.is_verified - def _reset_tk_scaling(self): - """ - 重置 tkinter 的 DPI 缩放,恢复到系统默认值 - 因为 customtkinter 会修改全局的 tkinter 缩放设置 - 这样可以确保后续创建的 tkinter 窗口使用正常的尺寸 - """ - try: - # 创建一个临时的 tkinter 根窗口来重置缩放 - temp_root = tk.Tk() - temp_root.withdraw() # 隐藏窗口 - - # 重置缩放因子为系统默认值(通常是 1.0) - # 这会影响后续所有 tkinter 窗口的尺寸计算 - temp_root.tk.call('tk', 'scaling', 1.0) - - # 销毁临时窗口 - temp_root.destroy() - except Exception as e: - # 如果重置失败,静默处理,不影响主流程 - pass - # --- END OF FILE --- \ No newline at end of file diff --git a/get_web_content.py b/get_web_content.py index 1f513b2..44751fa 100644 --- a/get_web_content.py +++ b/get_web_content.py @@ -7,7 +7,10 @@ from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import requests - +from webdriver_manager.chrome import ChromeDriverManager +from selenium.webdriver.chrome.service import Service +import os +import logging def extract_images_from_html(html_content): soup = BeautifulSoup(html_content, 'html.parser') @@ -365,7 +368,10 @@ def get_webpage_source_selenium(url): """ 增强版的Selenium获取网页源代码函数 专门针对头条网站的动态加载特性进行优化 + 使用webdriver-manager自动管理ChromeDriver,并将驱动保存到项目目录 """ + import platform + chrome_options = Options() chrome_options.add_argument('--headless') chrome_options.add_argument('--disable-gpu') @@ -377,7 +383,78 @@ def get_webpage_source_selenium(url): chrome_options.add_argument( 'user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36') - driver = webdriver.Chrome(options=chrome_options) + # 使用webdriver-manager自动管理ChromeDriver,并保存到项目drivers目录 + try: + # 设置drivers目录路径 + project_root = os.getcwd() + drivers_path = os.path.join(project_root, "drivers") + + # 确保drivers目录存在 + os.makedirs(drivers_path, exist_ok=True) + + # 检查drivers目录下是否已有兼容的ChromeDriver + chromedriver_path = None + if os.path.exists(drivers_path): + for root, dirs, files in os.walk(drivers_path): + for file in files: + # 检查文件名是否包含ChromeDriver并且与当前系统架构匹配 + if file.lower().startswith("chromedriver"): + candidate_path = os.path.join(root, file) + # 检查文件是否可执行 + if os.access(candidate_path, os.X_OK) or file.lower().endswith('.exe'): + # 对于Windows系统,确保是.exe文件 + if platform.system() == 'Windows' and not file.lower().endswith('.exe'): + candidate_path += '.exe' + + # 检查文件是否存在且可执行 + if os.path.exists(candidate_path): + chromedriver_path = candidate_path + break + if chromedriver_path: + break + + # 如果drivers目录下没有兼容的ChromeDriver,则使用webdriver-manager下载并保存 + if not chromedriver_path: + try: + # 使用webdriver-manager下载适合当前系统的ChromeDriver到drivers目录 + from webdriver_manager.core.driver_cache import DriverCacheManager + + # 创建自定义缓存管理器,指定保存路径 + cache_manager = DriverCacheManager(root_dir=drivers_path) + + # 获取ChromeDriver路径 + chromedriver_path = ChromeDriverManager(cache_manager=cache_manager).install() + logging.info(f"ChromeDriver已下载到: {chromedriver_path}") + except Exception as download_error: + logging.warning(f"使用webdriver-manager下载ChromeDriver失败: {download_error}") + # 尝试使用系统PATH中的ChromeDriver + chromedriver_path = None + + # 使用找到的ChromeDriver路径 + if chromedriver_path and os.path.exists(chromedriver_path): + try: + service = Service(chromedriver_path) + driver = webdriver.Chrome(service=service, options=chrome_options) + except Exception as service_error: + logging.warning(f"使用本地ChromeDriver失败: {service_error}") + # 尝试使用webdriver-manager重新下载 + try: + chromedriver_path = ChromeDriverManager().install() + service = Service(chromedriver_path) + driver = webdriver.Chrome(service=service, options=chrome_options) + except Exception as fallback_error: + logging.warning(f"使用webdriver-manager重新下载也失败: {fallback_error}") + # 最后的备选方案 + driver = webdriver.Chrome(options=chrome_options) + else: + # 如果所有方法都失败了,尝试使用系统PATH中的ChromeDriver + logging.warning("无法找到兼容的ChromeDriver,尝试使用系统PATH中的ChromeDriver") + driver = webdriver.Chrome(options=chrome_options) + + except Exception as e: + # 最后的备选方案 + logging.warning(f"ChromeDriver初始化失败,使用默认方式: {e}") + driver = webdriver.Chrome(options=chrome_options) try: driver.get(url) @@ -415,5 +492,8 @@ def get_webpage_source_selenium(url): print(f"使用增强版Selenium获取网页源代码时出错: {e}") return None finally: - driver.quit() + try: + driver.quit() + except: + pass diff --git a/main_process.py b/main_process.py index 9ffded9..6899948 100644 --- a/main_process.py +++ b/main_process.py @@ -13,7 +13,13 @@ from config import * # ==============================主程序=========================== def process_link(link_info, ai_service, current_template=None,generation_type=None): - link, article_type = link_info # 解包链接和类型信息 + # 确保 link_info 是元组或列表 + if isinstance(link_info, (tuple, list)) and len(link_info) >= 2: + link, article_type = link_info # 解包链接和类型信息 + else: + # 如果不是元组或列表,假设它是单独的链接字符串 + link = link_info + article_type = generation_type or "未分类" try: if link.startswith("https://www.toutiao.com"): title_text, article_text, img_urls = toutiao_w_extract_content(link) @@ -99,7 +105,15 @@ def process_link(link_info, ai_service, current_template=None,generation_type=No print("原文中内容为:", article_text) input_data = {"title": title_text, "article": article_text} print("发送的请求数据为:", input_data) - title, message_content = call_coze_all_article_workflow(input_data) + try: + result = call_coze_all_article_workflow(input_data) + # 检查返回值是否为错误信息 + if isinstance(result, dict) and 'error' in result: + raise Exception(result['error']) + title, message_content = result + except Exception as e: + logger.error(f"调用 Coze 工作流时出错: {e}") + raise finally: if 'original_config' in locals(): CONFIG['Coze'].update(original_config) @@ -189,9 +203,19 @@ def link_to_text(num_threads=None, ai_service="dify", current_template=None, gen # 记录已处理的链接 with open(use_link_path, 'a+', encoding='utf-8') as f: - for link, success, _ in results: - if success: - f.write(link + "\n") + for result in results: + # 确保 result 是正确的格式 + if isinstance(result, tuple) and len(result) >= 2: + link_info, success, _ = result + # 如果 link_info 是元组,提取链接;否则直接使用 + if isinstance(link_info, (tuple, list)): + link = link_info[0] + else: + link = link_info + if success: + f.write(str(link) + "\n") + else: + logger.warning(f"意外的结果格式: {result}") return results @@ -206,17 +230,20 @@ def worker(ai_service, current_template=None,generation_type=None): while True: try: # 从队列中获取任务 - link = task_queue.get() - if link is None: # 结束信号 + link_info = task_queue.get() + if link_info is None: # 结束信号 break + # 解包链接和类型信息 + link, article_type = link_info + # 处理链接 try: logger.info(f"开始处理链接:{link}") - process_link(link, ai_service, current_template,generation_type) - result_queue.put((link, True, None)) # 成功 + process_link((link, article_type), ai_service, current_template,generation_type) + result_queue.put(((link, article_type), True, None)) # 成功 except Exception as e: - result_queue.put((link, False, str(e))) # 失败 + result_queue.put(((link, article_type), False, str(e))) # 失败 logger.error(f"处理链接 {link} 时出错: {e}") # 标记任务完成 diff --git a/utils.py b/utils.py index 11282f8..a75b6c4 100644 --- a/utils.py +++ b/utils.py @@ -24,7 +24,9 @@ def text_detection(text): content = str(response.text) data = json.loads(content) print(data) - conclusion = data['conclusion'] + + # 安全地获取 conclusion 字段,如果不存在则返回默认值 + conclusion = data.get('conclusion', '合规') return conclusion