This commit is contained in:
huangzhenpc
2025-05-17 18:16:24 +08:00
parent 753ea21977
commit 2d603c33aa
32 changed files with 2114 additions and 96 deletions

View File

@@ -8,6 +8,10 @@ from PyQt5.QtCore import (
QPoint, QRect, QSize, pyqtProperty
)
from PyQt5.QtGui import QPainter, QColor, QPen
import os
from pathlib import Path
from urllib.parse import quote
import requests
class ActivationStatusWidget(QFrame):
"""激活状态显示组件"""
@@ -111,11 +115,18 @@ class ActivationWidget(QFrame):
layout.addWidget(header_frame)
# 添加提示文本
# info_text = QLabel(
# "获取会员激活码,请通过以下方式:\n\n"
# "官方自助网站cursorpro.com.cn\n"
# "代理听泉助手vx联系behikcigar\n"
# "天猫商店https://e.tb.cn/h.TC2gtKSiccfl5MD?tk=GZvHenPgE4o CZ193\n\n"
# "诚挚祝愿,欢迎加盟合作!"
# )
info_text = QLabel(
"获取会员激活码,请通过以下方式:\n\n"
"官方自助网站cursorpro.com.cn\n"
"微信客服号behikcigar\n"
"商店铺xxx\n\n"
"代理听泉助手vx联系behikcigar\n"
"购买链接https://www.houfaka.com/links/3BD4C127\n\n"
"诚挚祝愿,欢迎加盟合作!"
)
info_text.setStyleSheet("""
@@ -153,7 +164,7 @@ class ActivationWidget(QFrame):
# 复制微信按钮
copy_wx_btn = QPushButton("复制微信")
copy_wx_btn.setCursor(Qt.PointingHandCursor)
copy_wx_btn.clicked.connect(lambda: self._copy_to_clipboard("bshkcigar", "已复制微信号"))
copy_wx_btn.clicked.connect(lambda: self._copy_to_clipboard("behikcigar", "已复制微信号"))
copy_wx_btn.setStyleSheet("""
QPushButton {
background-color: #198754;
@@ -169,13 +180,31 @@ class ActivationWidget(QFrame):
""")
btn_layout.addWidget(copy_wx_btn)
# 确定按钮
ok_btn = QPushButton("确定")
ok_btn.setCursor(Qt.PointingHandCursor)
ok_btn.clicked.connect(msg.accept)
ok_btn.setStyleSheet("""
# 复制淘宝店铺按钮
# copy_tb_btn = QPushButton("复制天猫店铺")
# copy_tb_btn.setCursor(Qt.PointingHandCursor)
# copy_tb_btn.clicked.connect(lambda: self._copy_to_clipboard("https://e.tb.cn/h.TC2gtKSiccfl5MD?tk=GZvHenPgE4o CZ193", "已复制淘宝店铺"))
# copy_tb_btn.setStyleSheet("""
# QPushButton {
# background-color: #ff5100;
# color: white;
# border: none;
# padding: 6px 16px;
# border-radius: 3px;
# font-size: 13px;
# }
# QPushButton:hover {
# background-color: #c42b1c;
# }
# """)
# btn_layout.addWidget(copy_tb_btn)
copy_tb_btn = QPushButton("复制购买链接")
copy_tb_btn.setCursor(Qt.PointingHandCursor)
copy_tb_btn.clicked.connect(lambda: self._copy_to_clipboard("https://www.houfaka.com/links/3BD4C127", "已复制"))
copy_tb_btn.setStyleSheet("""
QPushButton {
background-color: #6c757d;
background-color: #ff5100;
color: white;
border: none;
padding: 6px 16px;
@@ -183,10 +212,29 @@ class ActivationWidget(QFrame):
font-size: 13px;
}
QPushButton:hover {
background-color: #5c636a;
background-color: #c42b1c;
}
""")
btn_layout.addWidget(ok_btn)
btn_layout.addWidget(copy_tb_btn)
# 确定按钮
# ok_btn = QPushButton("确定")
# ok_btn.setCursor(Qt.PointingHandCursor)
# ok_btn.clicked.connect(msg.accept)
# ok_btn.setStyleSheet("""
# QPushButton {
# background-color: #6c757d;
# color: white;
# border: none;
# padding: 6px 16px;
# border-radius: 3px;
# font-size: 13px;
# }
# QPushButton:hover {
# background-color: #5c636a;
# }
# """)
# btn_layout.addWidget(ok_btn)
layout.addLayout(btn_layout)
@@ -214,7 +262,7 @@ class ActivationWidget(QFrame):
# 标题
title = QLabel("激活(听泉)会员,多个激活码可叠加整体时长")
title.setStyleSheet("color: #28a745; font-size: 14px; font-weight: bold; border: none;") # 绿色标题
title.setStyleSheet("color: #28a745; font-size: 14px; line-height: 15px;min-height: 15px; font-weight: bold; border: none;") # 绿色标题
layout.addWidget(title)
# 激活码输入区域
@@ -227,6 +275,8 @@ class ActivationWidget(QFrame):
background-color: #f8f9fa;
border: 1px solid #ced4da;
border-radius: 4px;
min-height: 20px;
line-height: 20px;
padding: 8px;
color: #495057;
}
@@ -242,6 +292,8 @@ class ActivationWidget(QFrame):
color: white;
border: none;
padding: 8px 22px;
min-height: 20px;
line-height: 20px;
border-radius: 4px;
font-size: 13px;
}
@@ -371,7 +423,7 @@ class MemberStatusWidget(QFrame):
职责:
1. 显示会员状态信息
2. 显示设备信息
2. 显示公告信息
3. 根据不同状态显示不同样式
属性:
@@ -381,11 +433,23 @@ class MemberStatusWidget(QFrame):
- 会员状态(正常/未激活)
- 到期时间
- 剩余天数
- 设备信息系统、设备名、IP、地区
- 公告区域从API获取每2分钟更新一次
"""
def __init__(self, parent=None):
super().__init__(parent)
self.setup_ui()
# 保存最后一次状态更新的数据
self.last_status = {}
# 创建定时器每2分钟更新一次公告
self.announcement_timer = QTimer(self)
self.announcement_timer.setInterval(120000) # 2分钟 = 120000毫秒
self.announcement_timer.timeout.connect(self.refresh_announcement)
self.announcement_timer.start()
# 初始化后,延迟一秒检查并显示重要公告
QTimer.singleShot(1000, self.check_important_announcement)
def setup_ui(self):
layout = QVBoxLayout(self)
@@ -397,7 +461,7 @@ class MemberStatusWidget(QFrame):
container_layout = QVBoxLayout(container)
container_layout.setSpacing(5) # 减小组件间距
container_layout.setContentsMargins(0,0,0,0) # 减小内边距
container.setFixedHeight(220) # 根据需要调整这个值
container.setFixedHeight(240) # 根据需要调整这个值
# 状态信息
self.status_text = QTextEdit()
@@ -455,15 +519,157 @@ class MemberStatusWidget(QFrame):
""")
layout.addWidget(container)
def check_important_announcement(self):
"""检查并显示重要公告level=5"""
announcement_data = self.get_announcement(return_full_data=True)
if announcement_data and announcement_data.get("level") == 5:
self.show_announcement_dialog(announcement_data.get("txt", ""))
def show_announcement_dialog(self, content):
"""显示公告弹窗"""
msg = QDialog(self)
msg.setWindowTitle("重要公告")
msg.setMinimumWidth(400)
msg.setWindowFlags(msg.windowFlags() & ~Qt.WindowContextHelpButtonHint)
# 创建布局
layout = QVBoxLayout()
layout.setSpacing(12)
layout.setContentsMargins(20, 15, 20, 15)
# 创建顶部框
header_frame = QFrame()
header_frame.setStyleSheet("""
QFrame {
background-color: #e6f7ff;
border: 1px solid #91d5ff;
border-radius: 4px;
}
QLabel {
background: transparent;
border: none;
}
""")
header_layout = QHBoxLayout(header_frame)
header_layout.setContentsMargins(12, 10, 12, 10)
header_layout.setSpacing(10)
icon_label = QLabel()
icon_label.setPixmap(self.style().standardIcon(QStyle.SP_MessageBoxInformation).pixmap(20, 20))
header_layout.addWidget(icon_label)
text_label = QLabel("重要公告")
text_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #1890ff; background: transparent;")
header_layout.addWidget(text_label)
header_layout.addStretch()
layout.addWidget(header_frame)
# 公告内容
content_text = QTextEdit()
content_text.setReadOnly(True)
content_text.setHtml(content)
content_text.setStyleSheet("""
QTextEdit {
border: 1px solid #e8e8e8;
border-radius: 4px;
padding: 10px;
background-color: #fafafa;
min-height: 120px;
}
""")
layout.addWidget(content_text)
# 确认按钮
btn_layout = QHBoxLayout()
btn_layout.addStretch()
ok_btn = QPushButton("我知道了")
ok_btn.setCursor(Qt.PointingHandCursor)
ok_btn.clicked.connect(msg.accept)
ok_btn.setStyleSheet("""
QPushButton {
background-color: #1890ff;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-size: 13px;
}
QPushButton:hover {
background-color: #40a9ff;
}
""")
btn_layout.addWidget(ok_btn)
layout.addLayout(btn_layout)
# 设置对话框样式和布局
msg.setStyleSheet("""
QDialog {
background-color: white;
}
""")
msg.setLayout(layout)
# 显示对话框
msg.exec_()
def refresh_announcement(self):
"""定时刷新公告内容"""
if self.last_status:
# 使用最后一次的状态数据更新界面,只刷新公告部分
self.update_status(self.last_status, refresh_announcement_only=True)
def get_announcement(self, return_full_data=False):
"""从API获取公告内容
Args:
return_full_data: 是否返回完整的公告数据字典包含level等信息
"""
try:
response = requests.get("http://api.cursorpro.com.cn/admin/api.version/ad", timeout=5)
if response.status_code == 200:
try:
# 解析JSON响应
json_data = response.json()
# 检查code是否为0成功
if json_data.get("code") == 0:
# 获取公告数据
announcement_data = json_data.get("data", {})
# 根据参数决定返回完整数据还是仅文本
if return_full_data:
return announcement_data
else:
return announcement_data.get("txt", "")
else:
# 如果code不是0返回错误信息
error_msg = f"获取公告失败: {json_data.get('msg', '未知错误')}"
return {} if return_full_data else error_msg
except ValueError:
# JSON解析错误
error_msg = "解析公告数据失败"
return {} if return_full_data else error_msg
else:
error_msg = f"获取公告失败,状态码: {response.status_code}"
return {} if return_full_data else error_msg
except Exception as e:
error_msg = f"获取公告失败: {str(e)}"
return {} if return_full_data else error_msg
def update_status(self, status: Dict):
def update_status(self, status: Dict, refresh_announcement_only=False):
"""更新状态显示"""
try:
# 保存最后一次状态数据用于刷新公告
if not refresh_announcement_only:
self.last_status = status.copy()
# 获取状态信息
is_activated = status.get("is_activated", False)
expire_time = status.get("expire_time", "未知")
days_left = status.get("days_left", 0)
device_info = status.get("device_info", {})
# 构建状态文本
status_text = []
@@ -479,15 +685,14 @@ class MemberStatusWidget(QFrame):
days_color = "#10B981" if days_left > 30 else "#F59E0B" if days_left > 7 else "#EF4444"
status_text.append(f'剩余天数:<span style="color: {days_color};">{days_left}天</span>')
status_text.append("<br/>")
# 设备信息
status_text.append("\n设备信息:")
status_text.append(f"系统:{device_info.get('os', 'Windows')}")
status_text.append(f"设备名:{device_info.get('device_name', '未知')}")
status_text.append(f"IP地址{device_info.get('ip', '未知')}")
status_text.append(f"地区:{device_info.get('location', '未知')}")
# 公告区域
announcement = self.get_announcement()
status_text.append('<div style="margin-top: 2px; padding: 2px; background-color: #f0f9ff; border-left: 4px solid #3b82f6; border-radius: 4px; max-height: 80px; overflow-y: auto;">')
status_text.append('<span style="font-weight: bold; color: #3b82f6;">公告:</span><br/>')
status_text.append(f'{announcement}')
status_text.append('</div>')
# 更新文本
self.status_text.setHtml("<br>".join(status_text))
@@ -496,6 +701,16 @@ class MemberStatusWidget(QFrame):
# 如果发生异常,显示错误信息
error_text = f'<span style="color: #EF4444;">状态更新失败: {str(e)}</span>'
self.status_text.setHtml(error_text)
def hideEvent(self, event):
"""窗口隐藏时停止定时器"""
self.announcement_timer.stop()
super().hideEvent(event)
def showEvent(self, event):
"""窗口显示时启动定时器"""
self.announcement_timer.start()
super().showEvent(event)
class LoadingSpinner(QWidget):
"""自定义加载动画组件"""

View File

@@ -22,6 +22,8 @@ class RefreshTokenWorker(BaseWorker):
"""刷新 Cursor Token 工作线程"""
def run(self):
try:
# 以下是原有代码
service = CursorService()
machine_id = get_hardware_id()
@@ -64,11 +66,26 @@ class RefreshTokenWorker(BaseWorker):
result_msg = (
f"授权刷新成功\n"
f"账号: {account_info.get('email')}\n"
f"密码: {account_info.get('password')}\n"
f"出现3.7拥挤或者vpn等均不是账号问题切勿多次刷新账号\n"
f"现在账号紧缺后台防止盗号有限制共日常重度使用也是够的账号用干了在刷新。\n"
f"机器码重置成功\n"
f"请重新启动 Cursor 编辑器"
)
else:
result_msg = f"授权刷新成功,但机器码重置失败: {reset_msg}"
# 检查是否是权限错误
if "Permission denied" in reset_msg or "Errno 13" in reset_msg:
result_msg = (
f"授权刷新成功,但机器码重置失败(权限不足)\n"
f"账号: {account_info.get('email')}\n"
f"密码: {account_info.get('password')}\n"
f"这是正常现象,不影响使用\n"
f"请重新启动 Cursor 编辑器"
)
# 将结果标记为成功,因为这种情况下仍然可以正常使用
success = True
else:
result_msg = f"授权刷新成功,但机器码重置失败: {reset_msg}"
else:
result_msg = f"授权刷新失败: {msg}"
@@ -93,6 +110,8 @@ class DisableWorker(BaseWorker):
try:
service = CursorService()
success, msg = service.disable_update()
# 始终传递失败状态和错误消息,让主窗口处理解决方案
self.finished.emit(('disable', (success, msg)))
except Exception as e:
self.error.emit(str(e))

View File

@@ -1,5 +1,5 @@
import json
from typing import Dict
from typing import Dict, Tuple, Any
from PyQt5.QtWidgets import (
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QMessageBox, QLabel, QLineEdit, QPushButton,
@@ -11,6 +11,7 @@ from PyQt5.QtGui import QFont, QIcon, QDesktopServices
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication
from pathlib import Path
import os
from services.cursor_service import CursorService
from gui.components.widgets import (
@@ -90,26 +91,34 @@ class InstructionsWidget(QFrame):
# 步骤说明
steps = [
("第一步", "输入激活码点击【激活】按钮完成激活"),
("第二步", "点击【刷新Cursor编辑器授权】一般情况下刷新即可正常使用"),
("如果无法对话", "点击【实现Cursor0.45.x限制】然后重新刷新授权"),
("建议操作", "点击【禁用Cursor版本更新】保持软件稳定运行")
("初次使用", "输入激活码点击【激活】按钮完成激活(一个激活码只能用一次)"),
("日常使用说明", "点击【刷新Cursor编辑器授权】重启编辑器打开新的chat对话窗口 "),
("代理及对接号池", " 全球动态代理高品质号。不降智可使用3.7thinkingpro一样体验https://cursorpro.com.cn"),
# ("账号问题", "出现3.7拥挤或者vpn、ip等均不是账号问题 重启编辑器或者new新chat或者切换代理路线即可 切勿多次刷新账号\n"
# "现在账号紧缺后台防止盗号有限制供日常重度使用也是够的账号。\n"
# "如果账号问题可以联系我处理"),
# ("建议操作", "点击【禁用Cursor版本更新】个别机型因为权限无法禁用这个不影响使用")
]
for step_title, step_content in steps:
for i, (step_title, step_content) in enumerate(steps):
step_label = QLabel(f"{step_title}{step_content}")
step_label.setWordWrap(True)
label_color = "#181818" if i == len(steps)-1 else "#495057"
step_label.setStyleSheet("""
QLabel {
color: #495057;
color: {label_color};
margin: 3px 0;
min-height: 15px;
line-height: 15px;
}
""")
layout.addWidget(step_label)
# 给标题部分添加颜色
text = step_label.text()
step_label.setText(f'<span style="color: #17a2b8;">{step_title}</span>{step_content}')
# 最后一个标题标红,其他保持原来的青色
title_color = "#e0a800" if i == len(steps)-1 else "#17a2b8"
step_label.setText(f'<span style="color: {title_color};">{step_title}</span>{step_content}')
class MainWindow(QMainWindow):
"""主窗口"""
@@ -125,8 +134,7 @@ class MainWindow(QMainWindow):
self.setup_ui()
# 移除直接自检
# self.auto_check()
def setup_tray(self):
"""初始化托盘图标"""
@@ -237,7 +245,24 @@ class MainWindow(QMainWindow):
def setup_ui(self):
"""初始化UI"""
self.setWindowTitle(f"听泉助手 v{get_current_version()}")
# 获取 Cursor 版本
cursor_version = "未知"
try:
package_paths = [
os.path.join(os.getenv('LOCALAPPDATA'), 'Programs', 'cursor', 'resources', 'app', 'package.json'),
os.path.join(os.getenv('LOCALAPPDATA'), 'cursor', 'resources', 'app', 'package.json')
]
for path in package_paths:
if os.path.exists(path):
with open(path, 'r', encoding='utf-8') as f:
data = json.load(f)
cursor_version = data.get('version', '未知')
break
except Exception as e:
print(f"获取 Cursor 版本失败: {e}")
self.setWindowTitle(f"听泉助手 v{get_current_version()} (本机Cursor版本{cursor_version})")
self.setMinimumSize(600, 500)
# 设置窗口图标
@@ -291,9 +316,11 @@ class MainWindow(QMainWindow):
background-color: #007bff;
color: white;
border: none;
padding: 12px;
padding: 10px;
border-radius: 4px;
font-size: 14px;
font-size: 15px;
min-height: 20px;
line-height: 20px;
}
QPushButton:hover {
background-color: #0056b3;
@@ -323,8 +350,9 @@ class MainWindow(QMainWindow):
background-color: #ccc;
}
""")
button_layout.addWidget(self.limit_button)
# button_layout.addWidget(self.limit_button)
# 禁用更新按钮 - 红色
self.disable_button = QPushButton("禁用 Cursor 版本更新")
self.disable_button.clicked.connect(lambda: self.start_task('disable'))
@@ -333,9 +361,11 @@ class MainWindow(QMainWindow):
background-color: #dc3545;
color: white;
border: none;
padding: 12px;
padding: 10px;
border-radius: 4px;
font-size: 14px;
min-height: 20px;
line-height: 20px;
}
QPushButton:hover {
background-color: #c82333;
@@ -346,7 +376,33 @@ class MainWindow(QMainWindow):
""")
button_layout.addWidget(self.disable_button)
layout.addLayout(button_layout)
# 购买链接按钮
self.purchase_button = QPushButton("购买(代理产品)激活码")
self.purchase_button.clicked.connect(self.open_purchase_link)
self.purchase_button.setStyleSheet("""
QPushButton {
background-color: #ffc107;
color: white;
border: none;
padding: 10px;
border-radius: 4px;
font-size: 14px;
margin-bottom: 10px;
min-height: 20px;
line-height: 20px;
}
QPushButton:hover {
background-color: #e0a800;
}
QPushButton:disabled {
background-color: #ccc;
}
""")
button_layout.addWidget(self.purchase_button)
# 检查更新按钮 - 灰色
self.check_update_button = QPushButton("检查更新")
@@ -475,37 +531,99 @@ class MainWindow(QMainWindow):
"""更新进度信息"""
self.status_bar.set_status(info.get('message', '处理中...'))
def handle_result(self, result: tuple):
"""处理任务结果"""
task_type, data = result
success, msg = data if isinstance(data, tuple) else (False, str(data))
def show_solution_dialog(self) -> None:
"""显示通用解决方案对话框"""
solution_msg = (
"请按照以下步骤操作:\n\n"
"1. 以管理员身份运行 PowerShell\n"
"2. 复制并运行以下命令:\n\n"
"irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/maticarmy/cursor-nosqli-tools/refs/heads/main/scripts/run/cursor_win_id_modifier.ps1 | iex\n\n"
"是否复制此命令到剪贴板?"
)
if success:
QMessageBox.information(self, "成功", msg)
else:
QMessageBox.warning(self, "失败", msg)
reply = QMessageBox.question(
self,
"解决方案",
solution_msg,
QMessageBox.Yes | QMessageBox.No,
QMessageBox.Yes
)
# 重新启用按钮
self.update_member_status()
self.status_bar.set_status("就绪")
if reply == QMessageBox.Yes:
clipboard = QApplication.clipboard()
clipboard.setText("irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/maticarmy/cursor-nosqli-tools/refs/heads/main/scripts/run/cursor_win_id_modifier.ps1 | iex")
QMessageBox.information(self, "成功", "命令已复制到剪贴板")
def show_error_with_solution(self, title: str, message: str) -> None:
"""显示带有解决方案按钮的错误对话框
:param title: 对话框标题
:param message: 错误信息
"""
msg_box = QMessageBox(self)
msg_box.setIcon(QMessageBox.Warning)
msg_box.setWindowTitle(title)
msg_box.setText(message)
# 清理工作线程
if self.current_worker:
self.current_worker.stop()
self.current_worker = None
# 添加解决方案按钮
solution_button = msg_box.addButton("解决方案", QMessageBox.ActionRole)
msg_box.addButton("关闭", QMessageBox.RejectRole)
msg_box.exec_()
# 如果点击了解决方案按钮
if msg_box.clickedButton() == solution_button:
self.show_solution_dialog()
def handle_result(self, result: Tuple[str, Any]):
"""处理工作线程的结果"""
try:
self.hide_loading()
action, data = result
if action == 'reset':
success, msg, _ = data
if success:
self.status_bar.set_status("重置成功")
QMessageBox.information(self, "成功", msg)
else:
self.status_bar.set_status("重置失败")
self.show_error_with_solution("操作失败", msg)
elif action == 'disable':
success, msg = data
if success:
self.status_bar.set_status("禁用更新成功")
QMessageBox.information(self, "成功", msg)
else:
self.status_bar.set_status("禁用更新失败")
self.show_error_with_solution("操作失败", msg)
elif action == 'refresh':
success, msg = data
if success:
self.status_bar.set_status("刷新成功")
QMessageBox.information(self, "成功", msg)
else:
self.status_bar.set_status("刷新失败")
self.show_error_with_solution("操作失败", msg)
except Exception as e:
self.logger.error(f"处理结果时出错: {str(e)}")
self.status_bar.set_status("处理结果时出错")
finally:
# 恢复按钮状态
self.update_member_status() # 确保按钮状态恢复
def handle_error(self, error_msg: str):
"""处理错误"""
self.status_bar.set_status("发生错误")
QMessageBox.critical(self, "错误", f"操作失败:{error_msg}")
# 重新启用按钮
self.update_member_status()
self.update_member_status() # 确保按钮状态恢复
# 清理工作线程
if self.current_worker:
self.current_worker.stop()
self.current_worker = None
def check_update(self):
"""手动检查更新"""
@@ -664,4 +782,8 @@ class MainWindow(QMainWindow):
# 停止所有正在运行的任务
if self.current_worker:
self.current_worker.stop()
event.accept()
event.accept()
def open_purchase_link(self):
"""打开购买链接"""
self.activation_widget.show_activation_required()