更新验证器
This commit is contained in:
parent
fed6096314
commit
166506977e
@ -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"
|
||||
)
|
||||
|
||||
# 执行验证
|
||||
|
||||
68
ai_studio.py
68
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}",
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
{
|
||||
"last_license_key": "LGCXVKAH-665RMFLI-WFDH6OJT-CZBCTATA"
|
||||
"last_license_key": "THD0RP4X-RU8BCYNB-SEIVKENY-UYB1943X"
|
||||
}
|
||||
@ -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("<Configure>", 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 ---
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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}")
|
||||
|
||||
# 标记任务完成
|
||||
|
||||
Loading…
Reference in New Issue
Block a user