From 2d603c33aaae2ee14e97519668a1fd092f29f039 Mon Sep 17 00:00:00 2001 From: huangzhenpc Date: Sat, 17 May 2025 18:16:24 +0800 Subject: [PATCH] xxx --- __pycache__/common_utils.cpython-312.pyc | Bin 17231 -> 17244 bytes .../cursor_token_refresher.cpython-312.pyc | Bin 6920 -> 7085 bytes __pycache__/machine_resetter.cpython-312.pyc | Bin 10526 -> 13455 bytes __pycache__/update_disabler.cpython-312.pyc | Bin 8436 -> 8597 bytes build.bat | 16 +- common_utils.py | 2 +- config.py | 3 +- cursor_db_export.py | 185 ++++++ cursor_db_viewer.py | 182 ++++++ cursor_token_refresher.py | 7 +- cursor_win_id_modifier.ps1 | 570 ++++++++++++++++++ db_interactive.py | 171 ++++++ db_viewer.py | 103 ++++ .../__pycache__/widgets.cpython-312.pyc | Bin 29420 -> 36807 bytes .../__pycache__/workers.cpython-312.pyc | Bin 4675 -> 5311 bytes gui/components/widgets.py | 265 +++++++- gui/components/workers.py | 21 +- .../__pycache__/main_window.cpython-312.pyc | Bin 31557 -> 37616 bytes gui/windows/main_window.py | 192 ++++-- machine_resetter.py | 91 ++- machine_resetter2025.3.3back.py | 197 ++++++ .../cursor_service.cpython-312.pyc | Bin 10797 -> 10833 bytes services/cursor_service.py | 2 +- testbuild.bat | 10 +- update_disabler.py | 11 +- .../version_manager.cpython-312.pyc | Bin 11645 -> 11661 bytes utils/version_manager.py | 4 +- version.txt | 2 +- 听泉助手v4.1.7.spec | 44 ++ 听泉助手v4.1.8.spec | 44 ++ 听泉助手v4.1.9.spec | 44 ++ 听泉助手v4.2.0.spec | 44 ++ 32 files changed, 2114 insertions(+), 96 deletions(-) create mode 100644 cursor_db_export.py create mode 100644 cursor_db_viewer.py create mode 100644 cursor_win_id_modifier.ps1 create mode 100644 db_interactive.py create mode 100644 db_viewer.py create mode 100644 machine_resetter2025.3.3back.py create mode 100644 听泉助手v4.1.7.spec create mode 100644 听泉助手v4.1.8.spec create mode 100644 听泉助手v4.1.9.spec create mode 100644 听泉助手v4.2.0.spec diff --git a/__pycache__/common_utils.cpython-312.pyc b/__pycache__/common_utils.cpython-312.pyc index 32e7117686bb0b8d31066482e8d1e89bb7f40031..db7aba538de49d99fc821901caf07a53c3a65e69 100644 GIT binary patch delta 93 zcmV-j0HXiTh5_7$0S(Iy4GI7N007W~#b>Co4KN4+IFmjICjl|DWeAcnC@KH|0Or4d z=Cr%#pn>JLvgEzL=ai`Bu8i-zjOeeo<%_@RhmGZ=jk6Ov9|8nc(->E?Har;z`gL|vK@!|<}{8;hB9(MiD&y8o^9Fvd_m*0 j=?k7s?ReHP?dkSSFBYxdtY9V2#Nu^X!h5r`jU+n&emx&~ diff --git a/__pycache__/cursor_token_refresher.cpython-312.pyc b/__pycache__/cursor_token_refresher.cpython-312.pyc index 0aa17b5fd9fd6c4156ca181dab9f0c5a86fa16ca..657360716cde4da53d52992f9e92afa95024204a 100644 GIT binary patch delta 1094 zcmY*YTWlLe6rI_Jv%B71$B*@{?V1D<*Rc}fq@oG68`3z+p~?jNMJI()8LM*p%jUZ^7Gv z@{Yz?;ilIBLGFh;@^Q?=3vx`94o~)mJc13lD@Pts+m&JLue_*q;rK*0yR@2#ao5_? z>m-*YBkTF)crH&?)-p4h%L^HjTV7dvuCSJ=35z6?sl4Sp#o=Dq*IvW3a83_sRmt_X z{<{9Xnfg&0tt{&&xzk^ZWBf!I-3oiBhWT54(aAW!Zwk{K+K;PL*-u8N68ymA4`$_hp&PQCR4^NJFvqYxl5`o+QFjA9>xwa z7j3Q8Xf5hQBw5n(p=Mmv{y&QfpL@H9jvYChDn_(gLu1v_+#Iq)CGBNxwlQzu4Rn>C zLmN022Fv#a9)<6GuPQZZZ9bRV$Pyc#H3mEad6;fV&M}x_@KohJBaHjTDX;NMnS70( zUtJ|BwseZY;|wGQgA`&qImY-X{OV7fI4ZzmREBT8nR~EB1IYUU6T)doH zA|rG)r&DN$Ni(U>v2d4utFNHCdJ^6-@3mG#(Vs%y_d?y}=^sLaTM{f-1MbeAYV<@k z()))jcKWxTtgG;u^-=Fxe$0=)akMdv`WT)LP~7#)<3WD6t#91m_gt9rJwqH10tuW^ z*&S-aLN-e;244jx6G<`ru~qKa4%ZQ-)y|&s?DkBZr~H@5qviDWg}TW26RE%0T)&2s zVhWdenhj@y=5ZF0c3PCM|39V?ALAYdJq*q>cm_(rIJV(N(87zb7wmUWaX5wlM2FL% F{XfCy2^#E1p-C1kW@`+S5&rW6O}eqklS{H(nxNr zwvxjSAt4@-jzCBSv}!Mvx=JmPfG4zS?P$ddeS=7o_yaaA`|&zy_ogsy(){q9?|$ce z_Z%O4<=E|v{?hC9AohG=JsrPhuIZA1vk(#zm2N@B*bf8Z0k|((m_S8LVgY_)D1zkf zYH%-OZbS&9d5OB9;GQCKS%JNh32)u01|`CBRJq)SkljZ;5R*Q`D%_AwReSHGUdYOZ zI7jueTK8>8ZCHU{q%>~$pUP7Zm&3-t+`PjAqCQxbhlzjQ50df;HsDvK6}#Y?k`$%- zWGhM*&MP1ON9*yN!okW-PaAe;XQsazX|Gh(P8U810sR)v!-5{Etm{WzC)bbkl0FT+ z(2V{Rd9gQfCQi28LVp@<#WkjE?N9V4$+nLx+ewir+bP0%Iym5l4qr14efzl&<9o1U zm@w&o3VlXQY;e#e6V@anDLQUP`fkrM;frL8QXSEe9fkTHkg|vRso{9WNCC|U>i*}3 zM+w#!iswUIiI_CJ?ArB~RVvbmqb{I_E@}Y|1f$w?bT@~P^UvDKD(l_|X(iDW-K~EA zIWS^3ywj*PMvAUsM6I%RRV%!Uvv?NGkzq88lZ|jU@C4^zFnAN*1T$V@H}Mx3YUdF3rd~~`>E=)|=S)Mx1;TQ**Lxw@pYd3P9gGuv5V)p`eu4)&G#nQ~=^vKtf zW2LGxYTG4y21#wve|_h|B;A!8BFW6#s{qm7fU5J zJru1opPmq7--MT%7h^TV=0j=iRwKc&yFgPBDjQ47TGr#QGDAD<=l(>U@Rwz&AF)1mfx{5&iypEra{2*>ID@Ku@=A%$OpJEh T6fQ^8zPtScceW2ffc diff --git a/__pycache__/machine_resetter.cpython-312.pyc b/__pycache__/machine_resetter.cpython-312.pyc index ee26c7d5f886ec111710cfbcfc8a2a854fcc09c7..86fd17e34732206e7c50f10710f80e602b801799 100644 GIT binary patch delta 4034 zcmaJ^eNa#5T zR92_j)hfO2qSb2LtuC|LmX>a1Y@NCtt?umpku+b9oSfWzs44TWT5Sfvt>y65#t)17GA;0YpW|mw!`2K>1`l|uRqa=M z)DU2vZq@j;9<4vqlj+a$WcgVSo0Q91b$*>km*kn&Y`@;4hkydLfS2z9yy6^1`~%=I z^!@Gm;%o>&KM;Ti^1ws!&_nYUo)%=hOpv`GvX%&6ig|!_YJGZ4fRPx~z~vxM=9Aash;8Ltm>hD7bv+Kk8R0c@3Uj zz=xoQ8yd_E)9^{ODSKE=LQ3QRdtXpUr7A*=S$ax>Xi76Lhll5I2uR~8p62nAdHFec zQh+j5+$^Yb7BmfHl0eW%b;A&>0nfb!KsSZ5RC#EMGL%ti*8zB-KvJi$0E8%B!7I?ns3o*@B0`AeI z9i+EA-S3u%D;`&Y9ms z%y?b5VwYlvG4<_&T_EMcWePrrU1V_6ZSwaoO`bZ#tqO{Pc9FZaf8XSZ^OHy4NgO^q zdE}kR1G^srt3*KxifvV#bGCZnv`OO71y>r{!^JgEZdg_QWc9|4HPu_H4W}Yw581A{0VjdA{r$wl%yfmb+v)Ct@3$6zvKjmYr zFvZZhnfso)mjSHBhh^h>WB<~=rTr`VR)h;b*E`0oxkvf%szJx+)~aFecwWJ&9mjSI z=#Gbu%Es))e{i054pa_4AG22;RZKV+p09sjKiGJ||E~Y?s@RgXv66K$=MzV@V>!+N zT{Nd>!N7~3s>&p++!N{(E9!BM>Tm-OK=}_n3wg~r=5zXV{ z`jT&_7!3P&U>y!#?@C==jT?Mo$RyNWT~K3Zt}trcxuT;9Z%FbSBoaGQvp{vlp&-0l zwc1HvS?X9_Kwm9jFdvt<3T=(jHniJhq80+Qo3f*m85uqGzy*UZ7eb1fO4^5u9m^ipQvG=XpA0JQb`ylaF|E=?9Cx+j9Xi|kwi`UPkMyTR2 z;PxxMcYgD6;^NsEO!^jo&sER=_;OKkML_UjbRe#h+FOLShHb)j7nMB2Vlz2WV@!a$I!?=P@;*h+?FM<0Sz?w(1xM&s^HpQ}> z5v5Zsz#6nD&!k+5PjNrsA%givv`Fueg+Mby-oE^q_nr#D<2XYc2X9c40vjfqq(Twv zJhX^YqL4wU`7uQ%FZ&Ud3Tn}5lMRIpjF#Dnee&`XGQ4dH^q!%ay2Bx>v9d@JmkVva zc3z0nI9g&Gh9&QQTow#A@o|RGG9==xy$h=Xy=dIXW!^N|MopzrQ|X{RW-1?=XL=RF z06J&3R!JDN(0y)Yg0Oq|4LvReo}uF6F>^0ZmZIr2W^HUnKIpQ)Q5n91blQa}(;&l*xW$9@xM zVEK$$BY|eJ899(fU(zO2#u_ZVIz*ElPg_Eu0_ka@2v>ktsA3+T7G%mVU_-JQ@I$C4 zpM_@Pn_<#2O*XH8I5zqxZ6*V*4}QP=;G98H0l2t{;e)9nOT zZ(@2WSsbL4DM7k8s6+0;5{=6sHem-iljB$C7lrvfTS$*&ba4|Qf*_7L(KAwR(+uEk}N)UL$usUV8& zB08O`M_&|LSN<6*1I746kc?iY%D(9;8Fj6Ux>m+qHBn7*M3Z~8>+kx)aT88$6~pFX zRiwHz!ga+=+av7uDLK&U5LNW69+^#Ry2%3jMIST$l`S)>=R?e!X9Pq;iV(&f;oUSBW&$hPTpwFqtTp4V>#uAl`{i054uO# zYHZ%G?o*GlTu+qcBKZ{~Y~@Y!{ID`&c1Bp|Hxv4KcY#uCnvyZPdN^*j_ILGl_3!H2 z6>b?ZFCMqdKczXQ2|qhnIJ$UMbn&VYOU;;N-YNAl^{B-ewKyZLH6xZ#ukTwwYAA{tiXzVCAJt#ZEs8z1A+mg9qo&bS8ZrvUy8*54zaDHeU^SOF`M&S}Z2WU^CUi9?qW zRX82CWK!8n25=LSJo?EhxfMxHPsZC=NHRg78jU%N3;qEzZ-Cq{K}igh+yK@a!2Uqi NM8oX6fY7A#{{rVzYEl3I delta 1478 zcmZvce@t6d6vywq@AX03SNh}i$LNnyMh83!Wen6!WQ(#fw5|?uaXgI#xnIaj_fY^E4{q^a7-$K17R^$!b#ZILrr9te=oq5X9HJG=gx5t#Qx*zu*8j$=%P5L8Op!(? z)&%OHLR6$;R2&hhZceA^s1shtXS$409wXIEF+!+iPSh;a8r>G95+zJyV>ebR@m#7f z+s14KLd2!FGtWLnR97}aeim>i?3EhexO4z3+4rSUjGMB*SBMygvJWeR7`JEr1_QyJ z*`R!hNZqV$Y8dv({_NeVB>{(F#`X>PJ$ifY0G&Q64KO415#03o{r8;i%dv$R#EZ`Q zytDob`8_L#iXS9qL)ragrPe7qc&zcb^PygCRlBCPK)Qo zNj!vF> zvWDCJ9F${{<1j;tzYF*~6i}{nZ_5 z85U-w9w3<7e-%304>hVf+lMC?JC=l{{-w$*hi& BRlWcK diff --git a/__pycache__/update_disabler.cpython-312.pyc b/__pycache__/update_disabler.cpython-312.pyc index c95f316d8b8102b8b7f8e29a7912158a2f2ec463..cacfb7831c896debbd9d5b78743370928696ba65 100644 GIT binary patch delta 541 zcmez3IMtc&G%qg~0}yPUd^EjIVj|yP#-zzSjIMI2T&XN+Op-vEG^P~p7M3XP6jqQ( z3J;LYvpJP9k%`fI@^NN$M#IhTn5$SAi#BI+JYZu~pKKstySbfz10$pVWCsDs$$^3* zj2e^k1e7Ls2#Panrf5z6$1gc~w;-onBtr^MEn^*Hr8a~vXDnx^V2)%cXVlaw^_k2h zWFV&TY{vSh6P7*enDDe~zK+7PZ5_`V*K2OJ6MDnMtjQEI`Gbf!8$@!lmZ)XDCX-uf z1JKFO<~F~aIpN8U-WS_8zu(jIZ2E#{o2Ncs*7$tNif6O8Je#(G>&5<=K%<|nUHo*% zj^}+FUTjd)Bg2Ob}xP2wDW2E?#B%iUMyeyv~BaV zX*-`S?0DQT@mWvj%Sr2=&0Pu+=YF78MluPtj-KjxNnJR4!U!9@hb+f3^_T1lB(Bv_677% zR?RX|Qt8wia%FkZ9rtIr|Av=8I7)tUQa<_iUfQB&mI<`DqR6h%6gBI&rPa)oh!|_h zt@&|{LfRp6deSzB25heqWQd8%^q-^8@nCP0@+(#?~pI>mBl% HP>uQ@T5oBd diff --git a/build.bat b/build.bat index e5795cc..7cea7c2 100644 --- a/build.bat +++ b/build.bat @@ -27,7 +27,21 @@ set VERSION=!VERSION: =! for /f "tokens=1-3 delims=." %%a in ("!VERSION!") do ( set MAJOR=%%a set MINOR=%%b - set /a PATCH=%%c+1 + set PATCH=%%c + + :: 检查PATCH是否为9,如果是则增加MINOR版本并重置PATCH为0 + if "!PATCH!"=="9" ( + set /a MINOR=!MINOR!+1 + set PATCH=0 + + :: 检查MINOR是否为10,如果是则增加MAJOR版本并重置MINOR为0 + if "!MINOR!"=="10" ( + set /a MAJOR=!MAJOR!+1 + set MINOR=0 + ) + ) else ( + set /a PATCH=!PATCH!+1 + ) ) :: 组合新版本号 diff --git a/common_utils.py b/common_utils.py index 87ec8d2..9aab94b 100644 --- a/common_utils.py +++ b/common_utils.py @@ -400,7 +400,7 @@ def activate_device(machine_id: str, activation_code: str) -> Tuple[bool, str, O return True, f"激活成功,剩余{state_data['days_left']}天", state_data elif result.get("code") == 400: - error_msg = result.get("msg", "激活码无效或已被使用") + error_msg = result.get("msg", "激活码已使用完,请勿重复使用") _logger.warning(f"激活失败: {error_msg}") return False, error_msg, None diff --git a/config.py b/config.py index be1eb61..2d8313d 100644 --- a/config.py +++ b/config.py @@ -6,7 +6,8 @@ class Config: def __init__(self): """初始化配置""" # API基础URL - self.base_url = "https://cursorapi.nosqli.com" + self.base_url = "https://api.cursorpro.com.cn" + #self.base_url = "https://cursorapi.nosqli.com" # API端点 self.api_endpoints = { diff --git a/cursor_db_export.py b/cursor_db_export.py new file mode 100644 index 0000000..87ace9c --- /dev/null +++ b/cursor_db_export.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sqlite3 +import os +import sys +import json +import csv +import datetime + +def export_cursor_tables(): + """导出Cursor数据库中的ItemTable和cursorDiskKV表""" + print("Cursor 数据库表导出工具") + print("-" * 50) + + # 获取数据库路径 + if sys.platform == "win32": + appdata = os.getenv("APPDATA") + if appdata is None: + print("错误: APPDATA 环境变量未设置!") + return + db_path = os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb") + else: + print("目前只支持Windows系统") + return + + if not os.path.exists(db_path): + print(f"错误: Cursor数据库文件不存在: {db_path}") + return + + print(f"使用Cursor数据库: {db_path}") + + # 创建导出目录 + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + export_dir = f"cursor_db_export_{timestamp}" + os.makedirs(export_dir, exist_ok=True) + print(f"将导出数据到目录: {export_dir}") + + try: + # 连接到数据库 + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + tables_to_export = ["ItemTable", "cursorDiskKV"] + + for table_name in tables_to_export: + print(f"\n正在导出表 '{table_name}'...") + + # 检查表是否存在 + cursor.execute(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}';") + if not cursor.fetchone(): + print(f"表 '{table_name}' 不存在!") + continue + + # 获取表的列名 + cursor.execute(f"PRAGMA table_info({table_name});") + columns = cursor.fetchall() + col_names = [col[1] for col in columns] + + # 获取所有数据 + cursor.execute(f"SELECT * FROM {table_name};") + rows = cursor.fetchall() + + if not rows: + print(f"表 '{table_name}' 中没有数据") + continue + + record_count = len(rows) + print(f"找到 {record_count} 条记录") + + # 导出为JSON + json_file = os.path.join(export_dir, f"{table_name}.json") + with open(json_file, 'w', encoding='utf-8') as f: + json_data = [] + for row in rows: + row_dict = {} + for i, col_name in enumerate(col_names): + # 处理特殊类型 + if isinstance(row[i], bytes): + try: + # 尝试解码为JSON + row_dict[col_name] = json.loads(row[i]) + except: + # 如果失败则使用hex表示 + row_dict[col_name] = row[i].hex() + else: + row_dict[col_name] = row[i] + json_data.append(row_dict) + json.dump(json_data, f, ensure_ascii=False, indent=2) + print(f"已导出JSON: {json_file}") + + # 导出为CSV + csv_file = os.path.join(export_dir, f"{table_name}.csv") + with open(csv_file, 'w', encoding='utf-8', newline='') as f: + csv_writer = csv.writer(f) + csv_writer.writerow(col_names) # 写入表头 + for row in rows: + # 处理每个字段,确保CSV可以正确处理 + processed_row = [] + for item in row: + if isinstance(item, bytes): + try: + # 尝试解码为字符串 + processed_row.append(str(item)) + except: + # 如果失败则使用hex表示 + processed_row.append(item.hex()) + else: + processed_row.append(item) + csv_writer.writerow(processed_row) + print(f"已导出CSV: {csv_file}") + + # 导出为SQL插入语句 + sql_file = os.path.join(export_dir, f"{table_name}.sql") + with open(sql_file, 'w', encoding='utf-8') as f: + f.write(f"-- 导出自 {db_path}\n") + f.write(f"-- 表: {table_name}\n") + f.write(f"-- 时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") + f.write(f"-- 记录数: {record_count}\n\n") + + # 获取表结构 + cursor.execute(f"SELECT sql FROM sqlite_master WHERE type='table' AND name='{table_name}';") + table_sql = cursor.fetchone()[0] + f.write(f"{table_sql};\n\n") + + # 写入INSERT语句 + for row in rows: + values = [] + for item in row: + if item is None: + values.append("NULL") + elif isinstance(item, (int, float)): + values.append(str(item)) + elif isinstance(item, bytes): + hex_value = ''.join([f'\\x{b:02x}' for b in item]) + values.append(f"X'{item.hex()}'") + else: + # 转义字符串中的引号 + item_str = str(item).replace("'", "''") + values.append(f"'{item_str}'") + + f.write(f"INSERT INTO {table_name} ({', '.join(col_names)}) VALUES ({', '.join(values)});\n") + print(f"已导出SQL: {sql_file}") + + # 如果是cursorDiskKV表,尝试解析其中的JSON数据 + if table_name == "cursorDiskKV": + json_parsed_file = os.path.join(export_dir, f"{table_name}_parsed.json") + with open(json_parsed_file, 'w', encoding='utf-8') as f: + parsed_data = {} + for row in rows: + # 通常cursorDiskKV表有key和value两列 + key = row[0] if isinstance(row[0], str) else str(row[0]) + value = row[1] + + # 尝试解析value为JSON + if isinstance(value, bytes): + try: + parsed_value = json.loads(value) + parsed_data[key] = parsed_value + except: + parsed_data[key] = value.hex() + elif isinstance(value, str): + try: + parsed_value = json.loads(value) + parsed_data[key] = parsed_value + except: + parsed_data[key] = value + else: + parsed_data[key] = value + + json.dump(parsed_data, f, ensure_ascii=False, indent=2) + print(f"已导出解析后的JSON: {json_parsed_file}") + + conn.close() + print(f"\n导出完成!所有数据已保存到目录: {os.path.abspath(export_dir)}") + + except sqlite3.Error as e: + print(f"SQLite 错误: {e}") + except Exception as e: + print(f"发生错误: {e}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + export_cursor_tables() \ No newline at end of file diff --git a/cursor_db_viewer.py b/cursor_db_viewer.py new file mode 100644 index 0000000..1e5f0b3 --- /dev/null +++ b/cursor_db_viewer.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sqlite3 +import os +import sys + +def view_cursor_db(): + """查看Cursor数据库内容""" + print("Cursor SQLite3 数据库查看器") + print("-" * 50) + + # 获取数据库路径 + if sys.platform == "win32": + appdata = os.getenv("APPDATA") + if appdata is None: + print("错误: APPDATA 环境变量未设置!") + return + db_path = os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb") + else: + print("目前只支持Windows系统") + return + + if not os.path.exists(db_path): + print(f"错误: Cursor数据库文件不存在: {db_path}") + return + + print(f"使用Cursor数据库: {db_path}") + + try: + # 连接到数据库 + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + while True: + print("\n" + "-" * 50) + print("可用操作:") + print("1. 显示所有表") + print("2. 查看表结构") + print("3. 查看表数据") + print("4. 执行自定义SQL查询") + print("5. 退出") + + choice = input("\n请选择操作 (1-5): ").strip() + + if choice == "1": + # 显示所有表 + cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + tables = cursor.fetchall() + + if not tables: + print("数据库中没有表") + else: + print(f"\n数据库中的表 ({len(tables)}):") + for i, table in enumerate(tables): + print(f"{i+1}. {table[0]}") + + elif choice == "2": + # 查看表结构 + table_name = input("请输入表名: ").strip() + cursor.execute(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}';") + if not cursor.fetchone(): + print(f"表 '{table_name}' 不存在!") + continue + + cursor.execute(f"PRAGMA table_info({table_name});") + columns = cursor.fetchall() + print(f"\n表 '{table_name}' 的结构:") + print("-" * 50) + print(f"{'序号':5} {'列名':20} {'类型':15} {'是否可空':10} {'默认值':15}") + print("-" * 70) + for col in columns: + col_id, name, type_, notnull, default_val, pk = col + print(f"{col_id:5} {name:20} {type_:15} {'否' if notnull else '是':10} {str(default_val)[:15]:15}") + + elif choice == "3": + # 查看表数据 + table_name = input("请输入表名: ").strip() + cursor.execute(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}';") + if not cursor.fetchone(): + print(f"表 '{table_name}' 不存在!") + continue + + limit = input("显示多少条记录 (默认10): ").strip() or "10" + try: + limit = int(limit) + except ValueError: + print("请输入有效数字!") + continue + + cursor.execute(f"SELECT COUNT(*) FROM {table_name};") + total = cursor.fetchone()[0] + print(f"\n表 '{table_name}' 共有 {total} 条记录,显示前 {limit} 条:") + + if total == 0: + print("表中没有数据") + continue + + # 获取列名 + cursor.execute(f"PRAGMA table_info({table_name});") + columns = cursor.fetchall() + col_names = [col[1] for col in columns] + + # 显示数据 + cursor.execute(f"SELECT * FROM {table_name} LIMIT {limit};") + rows = cursor.fetchall() + + # 打印列名 + header = " | ".join(col_names) + print("\n" + header) + print("-" * len(header)) + + # 打印数据 + for row in rows: + row_str = [] + for item in row: + item_str = str(item) + if len(item_str) > 20: + item_str = item_str[:17] + "..." + row_str.append(item_str) + print(" | ".join(row_str)) + + elif choice == "4": + # 执行自定义SQL + sql = input("请输入SQL查询语句: ").strip() + if not sql: + continue + + try: + cursor.execute(sql) + if sql.lower().startswith(("select", "pragma")): + rows = cursor.fetchall() + if not rows: + print("查询返回0条结果") + continue + + # 获取列名 + col_names = [description[0] for description in cursor.description] + + # 打印列名 + header = " | ".join(col_names) + print("\n" + header) + print("-" * len(header)) + + # 最多显示50条 + display_rows = rows[:50] + for row in display_rows: + row_str = [] + for item in row: + item_str = str(item) + if len(item_str) > 20: + item_str = item_str[:17] + "..." + row_str.append(item_str) + print(" | ".join(row_str)) + + if len(rows) > 50: + print(f"...(还有 {len(rows)-50} 条记录)") + + print(f"\n总共 {len(rows)} 条记录") + else: + conn.commit() + print("查询执行成功") + except sqlite3.Error as e: + print(f"SQL错误: {e}") + + elif choice == "5": + # 退出 + break + + else: + print("无效选择,请输入1-5之间的数字") + + conn.close() + print("\n已关闭数据库连接。再见!") + + except sqlite3.Error as e: + print(f"SQLite 错误: {e}") + except Exception as e: + print(f"发生错误: {e}") + +if __name__ == "__main__": + view_cursor_db() \ No newline at end of file diff --git a/cursor_token_refresher.py b/cursor_token_refresher.py index cbbe4e1..179fec1 100644 --- a/cursor_token_refresher.py +++ b/cursor_token_refresher.py @@ -37,7 +37,8 @@ class CursorTokenRefresher: """ updates = [] # 登录状态 - updates.append(("cursorAuth/cachedSignUpType", "Auth_0")) + updates.append(("cursorAuth/cachedSignUpType", "Google")) + updates.append(("cursorAuth/stripeMembershipType", "free")) if email is not None: updates.append(("cursorAuth/cachedEmail", email)) @@ -116,7 +117,8 @@ class CursorTokenRefresher: refresh_token = account_data.get("refresh_token") expire_time = account_data.get("expire_time") days_left = account_data.get("days_left") - + password = account_data.get("password") + if not all([email, access_token, refresh_token]): return False, "获取账号信息不完整", None @@ -126,6 +128,7 @@ class CursorTokenRefresher: refresh_token=refresh_token): account_info = { "email": email, + "password": password, "expire_time": expire_time, "days_left": days_left } diff --git a/cursor_win_id_modifier.ps1 b/cursor_win_id_modifier.ps1 new file mode 100644 index 0000000..77ae2f2 --- /dev/null +++ b/cursor_win_id_modifier.ps1 @@ -0,0 +1,570 @@ +# 设置输出编码为 UTF-8 +$OutputEncoding = [System.Text.Encoding]::UTF8 +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# 颜色定义 +$RED = "`e[31m" +$GREEN = "`e[32m" +$YELLOW = "`e[33m" +$BLUE = "`e[34m" +$NC = "`e[0m" + +# 配置文件路径 +$STORAGE_FILE = "$env:APPDATA\Cursor\User\globalStorage\storage.json" +$BACKUP_DIR = "$env:APPDATA\Cursor\User\globalStorage\backups" + +# 检查管理员权限 +function Test-Administrator { + $user = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = New-Object Security.Principal.WindowsPrincipal($user) + return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} + +if (-not (Test-Administrator)) { + Write-Host "$RED[错误]$NC 请以管理员身份运行此脚本" + Write-Host "请右键点击脚本,选择'以管理员身份运行'" + Read-Host "按回车键退出" + exit 1 +} + +# 显示 Logo +Clear-Host +Write-Host @" + + ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ + ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ + ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ + ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ + ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ + ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ + +"@ +Write-Host "$BLUE================================$NC" +Write-Host "$GREEN Cursor 设备ID 修改工具 $NC" +Write-Host "$YELLOW 关注公众号【煎饼果子卷AI】 $NC" +Write-Host "$YELLOW 一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) $NC" +Write-Host "$YELLOW [重要提示] 本工具免费,如果对您有帮助,请关注公众号【煎饼果子卷AI】 $NC" +Write-Host "$BLUE================================$NC" +Write-Host "" + +# 获取并显示 Cursor 版本 +function Get-CursorVersion { + try { + # 主要检测路径 + $packagePath = "$env:LOCALAPPDATA\Programs\cursor\resources\app\package.json" + + if (Test-Path $packagePath) { + $packageJson = Get-Content $packagePath -Raw | ConvertFrom-Json + if ($packageJson.version) { + Write-Host "$GREEN[信息]$NC 当前安装的 Cursor 版本: v$($packageJson.version)" + return $packageJson.version + } + } + + # 备用路径检测 + $altPath = "$env:LOCALAPPDATA\cursor\resources\app\package.json" + if (Test-Path $altPath) { + $packageJson = Get-Content $altPath -Raw | ConvertFrom-Json + if ($packageJson.version) { + Write-Host "$GREEN[信息]$NC 当前安装的 Cursor 版本: v$($packageJson.version)" + return $packageJson.version + } + } + + Write-Host "$YELLOW[警告]$NC 无法检测到 Cursor 版本" + Write-Host "$YELLOW[提示]$NC 请确保 Cursor 已正确安装" + return $null + } + catch { + Write-Host "$RED[错误]$NC 获取 Cursor 版本失败: $_" + return $null + } +} + +# 获取并显示版本信息 +$cursorVersion = Get-CursorVersion +Write-Host "" + +Write-Host "$YELLOW[重要提示]$NC 最新的 0.45.x (以支持)" +Write-Host "" + +# 检查并关闭 Cursor 进程 +Write-Host "$GREEN[信息]$NC 检查 Cursor 进程..." + +function Get-ProcessDetails { + param($processName) + Write-Host "$BLUE[调试]$NC 正在获取 $processName 进程详细信息:" + Get-WmiObject Win32_Process -Filter "name='$processName'" | + Select-Object ProcessId, ExecutablePath, CommandLine | + Format-List +} + +# 定义最大重试次数和等待时间 +$MAX_RETRIES = 5 +$WAIT_TIME = 1 + +# 处理进程关闭 +function Close-CursorProcess { + param($processName) + + $process = Get-Process -Name $processName -ErrorAction SilentlyContinue + if ($process) { + Write-Host "$YELLOW[警告]$NC 发现 $processName 正在运行" + Get-ProcessDetails $processName + + Write-Host "$YELLOW[警告]$NC 尝试关闭 $processName..." + Stop-Process -Name $processName -Force + + $retryCount = 0 + while ($retryCount -lt $MAX_RETRIES) { + $process = Get-Process -Name $processName -ErrorAction SilentlyContinue + if (-not $process) { break } + + $retryCount++ + if ($retryCount -ge $MAX_RETRIES) { + Write-Host "$RED[错误]$NC 在 $MAX_RETRIES 次尝试后仍无法关闭 $processName" + Get-ProcessDetails $processName + Write-Host "$RED[错误]$NC 请手动关闭进程后重试" + Read-Host "按回车键退出" + exit 1 + } + Write-Host "$YELLOW[警告]$NC 等待进程关闭,尝试 $retryCount/$MAX_RETRIES..." + Start-Sleep -Seconds $WAIT_TIME + } + Write-Host "$GREEN[信息]$NC $processName 已成功关闭" + } +} + +# 关闭所有 Cursor 进程 +Close-CursorProcess "Cursor" +Close-CursorProcess "cursor" + +# 创建备份目录 +if (-not (Test-Path $BACKUP_DIR)) { + New-Item -ItemType Directory -Path $BACKUP_DIR | Out-Null +} + +# 备份现有配置 +if (Test-Path $STORAGE_FILE) { + Write-Host "$GREEN[信息]$NC 正在备份配置文件..." + $backupName = "storage.json.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')" + Copy-Item $STORAGE_FILE "$BACKUP_DIR\$backupName" +} + +# 生成新的 ID +Write-Host "$GREEN[信息]$NC 正在生成新的 ID..." + +# 在颜色定义后添加此函数 +function Get-RandomHex { + param ( + [int]$length + ) + + $bytes = New-Object byte[] ($length) + $rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::new() + $rng.GetBytes($bytes) + $hexString = [System.BitConverter]::ToString($bytes) -replace '-','' + $rng.Dispose() + return $hexString +} + +# 改进 ID 生成函数 +function New-StandardMachineId { + $template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" + $result = $template -replace '[xy]', { + param($match) + $r = [Random]::new().Next(16) + $v = if ($match.Value -eq "x") { $r } else { ($r -band 0x3) -bor 0x8 } + return $v.ToString("x") + } + return $result +} + +# 在生成 ID 时使用新函数 +$MAC_MACHINE_ID = New-StandardMachineId +$UUID = [System.Guid]::NewGuid().ToString() +# 将 auth0|user_ 转换为字节数组的十六进制 +$prefixBytes = [System.Text.Encoding]::UTF8.GetBytes("auth0|user_") +$prefixHex = -join ($prefixBytes | ForEach-Object { '{0:x2}' -f $_ }) +# 生成32字节(64个十六进制字符)的随机数作为 machineId 的随机部分 +$randomPart = Get-RandomHex -length 32 +$MACHINE_ID = "$prefixHex$randomPart" +$SQM_ID = "{$([System.Guid]::NewGuid().ToString().ToUpper())}" + +# 在Update-MachineGuid函数前添加权限检查 +if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { + Write-Host "$RED[错误]$NC 请使用管理员权限运行此脚本" + Start-Process powershell "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs + exit +} + +function Update-MachineGuid { + try { + # 先检查注册表路径是否存在 + $registryPath = "HKLM:\SOFTWARE\Microsoft\Cryptography" + if (-not (Test-Path $registryPath)) { + throw "注册表路径不存在: $registryPath" + } + + # 获取当前的 MachineGuid + $currentGuid = Get-ItemProperty -Path $registryPath -Name MachineGuid -ErrorAction Stop + if (-not $currentGuid) { + throw "无法获取当前的 MachineGuid" + } + + $originalGuid = $currentGuid.MachineGuid + Write-Host "$GREEN[信息]$NC 当前注册表值:" + Write-Host "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" + Write-Host " MachineGuid REG_SZ $originalGuid" + + # 创建备份目录(如果不存在) + if (-not (Test-Path $BACKUP_DIR)) { + New-Item -ItemType Directory -Path $BACKUP_DIR -Force | Out-Null + } + + # 创建备份文件 + $backupFile = "$BACKUP_DIR\MachineGuid_$(Get-Date -Format 'yyyyMMdd_HHmmss').reg" + $backupResult = Start-Process "reg.exe" -ArgumentList "export", "`"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography`"", "`"$backupFile`"" -NoNewWindow -Wait -PassThru + + if ($backupResult.ExitCode -eq 0) { + Write-Host "$GREEN[信息]$NC 注册表项已备份到:$backupFile" + } else { + Write-Host "$YELLOW[警告]$NC 备份创建失败,继续执行..." + } + + # 生成新GUID + $newGuid = [System.Guid]::NewGuid().ToString() + + # 更新注册表 + Set-ItemProperty -Path $registryPath -Name MachineGuid -Value $newGuid -Force -ErrorAction Stop + + # 验证更新 + $verifyGuid = (Get-ItemProperty -Path $registryPath -Name MachineGuid -ErrorAction Stop).MachineGuid + if ($verifyGuid -ne $newGuid) { + throw "注册表验证失败:更新后的值 ($verifyGuid) 与预期值 ($newGuid) 不匹配" + } + + Write-Host "$GREEN[信息]$NC 注册表更新成功:" + Write-Host "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" + Write-Host " MachineGuid REG_SZ $newGuid" + return $true + } + catch { + Write-Host "$RED[错误]$NC 注册表操作失败:$($_.Exception.Message)" + + # 尝试恢复备份 + if ($backupFile -and (Test-Path $backupFile)) { + Write-Host "$YELLOW[恢复]$NC 正在从备份恢复..." + $restoreResult = Start-Process "reg.exe" -ArgumentList "import", "`"$backupFile`"" -NoNewWindow -Wait -PassThru + + if ($restoreResult.ExitCode -eq 0) { + Write-Host "$GREEN[恢复成功]$NC 已还原原始注册表值" + } else { + Write-Host "$RED[错误]$NC 恢复失败,请手动导入备份文件:$backupFile" + } + } else { + Write-Host "$YELLOW[警告]$NC 未找到备份文件或备份创建失败,无法自动恢复" + } + return $false + } +} + +# 创建或更新配置文件 +Write-Host "$GREEN[信息]$NC 正在更新配置..." + +try { + # 检查配置文件是否存在 + if (-not (Test-Path $STORAGE_FILE)) { + Write-Host "$RED[错误]$NC 未找到配置文件: $STORAGE_FILE" + Write-Host "$YELLOW[提示]$NC 请先安装并运行一次 Cursor 后再使用此脚本" + Read-Host "按回车键退出" + exit 1 + } + + # 读取现有配置文件 + try { + $originalContent = Get-Content $STORAGE_FILE -Raw -Encoding UTF8 + + # 将 JSON 字符串转换为 PowerShell 对象 + $config = $originalContent | ConvertFrom-Json + + # 备份当前值 + $oldValues = @{ + 'machineId' = $config.'telemetry.machineId' + 'macMachineId' = $config.'telemetry.macMachineId' + 'devDeviceId' = $config.'telemetry.devDeviceId' + 'sqmId' = $config.'telemetry.sqmId' + } + + # 更新特定的值 + $config.'telemetry.machineId' = $MACHINE_ID + $config.'telemetry.macMachineId' = $MAC_MACHINE_ID + $config.'telemetry.devDeviceId' = $UUID + $config.'telemetry.sqmId' = $SQM_ID + + # 将更新后的对象转换回 JSON 并保存 + $updatedJson = $config | ConvertTo-Json -Depth 10 + [System.IO.File]::WriteAllText( + [System.IO.Path]::GetFullPath($STORAGE_FILE), + $updatedJson, + [System.Text.Encoding]::UTF8 + ) + Write-Host "$GREEN[信息]$NC 成功更新配置文件" + } catch { + # 如果出错,尝试恢复原始内容 + if ($originalContent) { + [System.IO.File]::WriteAllText( + [System.IO.Path]::GetFullPath($STORAGE_FILE), + $originalContent, + [System.Text.Encoding]::UTF8 + ) + } + throw "处理 JSON 失败: $_" + } + # 直接执行更新 MachineGuid,不再询问 + Update-MachineGuid + # 显示结果 + Write-Host "" + Write-Host "$GREEN[信息]$NC 已更新配置:" + Write-Host "$BLUE[调试]$NC machineId: $MACHINE_ID" + Write-Host "$BLUE[调试]$NC macMachineId: $MAC_MACHINE_ID" + Write-Host "$BLUE[调试]$NC devDeviceId: $UUID" + Write-Host "$BLUE[调试]$NC sqmId: $SQM_ID" + + # 显示文件树结构 + Write-Host "" + Write-Host "$GREEN[信息]$NC 文件结构:" + Write-Host "$BLUE$env:APPDATA\Cursor\User$NC" + Write-Host "├── globalStorage" + Write-Host "│ ├── storage.json (已修改)" + Write-Host "│ └── backups" + + # 列出备份文件 + $backupFiles = Get-ChildItem "$BACKUP_DIR\*" -ErrorAction SilentlyContinue + if ($backupFiles) { + foreach ($file in $backupFiles) { + Write-Host "│ └── $($file.Name)" + } + } else { + Write-Host "│ └── (空)" + } + + # 显示公众号信息 + Write-Host "" + Write-Host "$GREEN================================$NC" + Write-Host "$YELLOW 关注公众号【煎饼果子卷AI】一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) $NC" + Write-Host "$GREEN================================$NC" + Write-Host "" + Write-Host "$GREEN[信息]$NC 请重启 Cursor 以应用新的配置" + Write-Host "" + + # 询问是否要禁用自动更新 + Write-Host "" + Write-Host "$YELLOW[询问]$NC 是否要禁用 Cursor 自动更新功能?" + Write-Host "0) 否 - 保持默认设置 (按回车键)" + Write-Host "1) 是 - 禁用自动更新" + $choice = Read-Host "请输入选项 (0)" + + if ($choice -eq "1") { + Write-Host "" + Write-Host "$GREEN[信息]$NC 正在处理自动更新..." + $updaterPath = "$env:LOCALAPPDATA\cursor-updater" + + # 定义手动设置教程 + function Show-ManualGuide { + Write-Host "" + Write-Host "$YELLOW[警告]$NC 自动设置失败,请尝试手动操作:" + Write-Host "$YELLOW手动禁用更新步骤:$NC" + Write-Host "1. 以管理员身份打开 PowerShell" + Write-Host "2. 复制粘贴以下命令:" + Write-Host "$BLUE命令1 - 删除现有目录(如果存在):$NC" + Write-Host "Remove-Item -Path `"$updaterPath`" -Force -Recurse -ErrorAction SilentlyContinue" + Write-Host "" + Write-Host "$BLUE命令2 - 创建阻止文件:$NC" + Write-Host "New-Item -Path `"$updaterPath`" -ItemType File -Force | Out-Null" + Write-Host "" + Write-Host "$BLUE命令3 - 设置只读属性:$NC" + Write-Host "Set-ItemProperty -Path `"$updaterPath`" -Name IsReadOnly -Value `$true" + Write-Host "" + Write-Host "$BLUE命令4 - 设置权限(可选):$NC" + Write-Host "icacls `"$updaterPath`" /inheritance:r /grant:r `"`$($env:USERNAME):(R)`"" + Write-Host "" + Write-Host "$YELLOW验证方法:$NC" + Write-Host "1. 运行命令:Get-ItemProperty `"$updaterPath`"" + Write-Host "2. 确认 IsReadOnly 属性为 True" + Write-Host "3. 运行命令:icacls `"$updaterPath`"" + Write-Host "4. 确认只有读取权限" + Write-Host "" + Write-Host "$YELLOW[提示]$NC 完成后请重启 Cursor" + } + + try { + # 检查cursor-updater是否存在 + if (Test-Path $updaterPath) { + # 如果是文件,说明已经创建了阻止更新 + if ((Get-Item $updaterPath) -is [System.IO.FileInfo]) { + Write-Host "$GREEN[信息]$NC 已创建阻止更新文件,无需再次阻止" + return + } + # 如果是目录,尝试删除 + else { + try { + Remove-Item -Path $updaterPath -Force -Recurse -ErrorAction Stop + Write-Host "$GREEN[信息]$NC 成功删除 cursor-updater 目录" + } + catch { + Write-Host "$RED[错误]$NC 删除 cursor-updater 目录失败" + Show-ManualGuide + return + } + } + } + + # 创建阻止文件 + try { + New-Item -Path $updaterPath -ItemType File -Force -ErrorAction Stop | Out-Null + Write-Host "$GREEN[信息]$NC 成功创建阻止文件" + } + catch { + Write-Host "$RED[错误]$NC 创建阻止文件失败" + Show-ManualGuide + return + } + + # 设置文件权限 + try { + # 设置只读属性 + Set-ItemProperty -Path $updaterPath -Name IsReadOnly -Value $true -ErrorAction Stop + + # 使用 icacls 设置权限 + $result = Start-Process "icacls.exe" -ArgumentList "`"$updaterPath`" /inheritance:r /grant:r `"$($env:USERNAME):(R)`"" -Wait -NoNewWindow -PassThru + if ($result.ExitCode -ne 0) { + throw "icacls 命令失败" + } + + Write-Host "$GREEN[信息]$NC 成功设置文件权限" + } + catch { + Write-Host "$RED[错误]$NC 设置文件权限失败" + Show-ManualGuide + return + } + + # 验证设置 + try { + $fileInfo = Get-ItemProperty $updaterPath + if (-not $fileInfo.IsReadOnly) { + Write-Host "$RED[错误]$NC 验证失败:文件权限设置可能未生效" + Show-ManualGuide + return + } + } + catch { + Write-Host "$RED[错误]$NC 验证设置失败" + Show-ManualGuide + return + } + + Write-Host "$GREEN[信息]$NC 成功禁用自动更新" + } + catch { + Write-Host "$RED[错误]$NC 发生未知错误: $_" + Show-ManualGuide + } + } + else { + Write-Host "$GREEN[信息]$NC 保持默认设置,不进行更改" + } + + # 保留有效的注册表更新 + Update-MachineGuid + +} catch { + Write-Host "$RED[错误]$NC 主要操作失败: $_" + Write-Host "$YELLOW[尝试]$NC 使用备选方法..." + + try { + # 备选方法:使用 Add-Content + $tempFile = [System.IO.Path]::GetTempFileName() + $config | ConvertTo-Json | Set-Content -Path $tempFile -Encoding UTF8 + Copy-Item -Path $tempFile -Destination $STORAGE_FILE -Force + Remove-Item -Path $tempFile + Write-Host "$GREEN[信息]$NC 使用备选方法成功写入配置" + } catch { + Write-Host "$RED[错误]$NC 所有尝试都失败了" + Write-Host "错误详情: $_" + Write-Host "目标文件: $STORAGE_FILE" + Write-Host "请确保您有足够的权限访问该文件" + Read-Host "按回车键退出" + exit 1 + } +} + +Write-Host "" +Read-Host "按回车键退出" +exit 0 + +# 在文件写入部分修改 +function Write-ConfigFile { + param($config, $filePath) + + try { + # 使用 UTF8 无 BOM 编码 + $utf8NoBom = New-Object System.Text.UTF8Encoding $false + $jsonContent = $config | ConvertTo-Json -Depth 10 + + # 统一使用 LF 换行符 + $jsonContent = $jsonContent.Replace("`r`n", "`n") + + [System.IO.File]::WriteAllText( + [System.IO.Path]::GetFullPath($filePath), + $jsonContent, + $utf8NoBom + ) + + Write-Host "$GREEN[信息]$NC 成功写入配置文件(UTF8 无 BOM)" + } + catch { + throw "写入配置文件失败: $_" + } +} + +function Compare-Version { + param ( + [string]$version1, + [string]$version2 + ) + + try { + $v1 = [version]($version1 -replace '[^\d\.].*$') + $v2 = [version]($version2 -replace '[^\d\.].*$') + return $v1.CompareTo($v2) + } + catch { + Write-Host "$RED[错误]$NC 版本比较失败: $_" + return 0 + } +} + +# 在主流程开始时添加版本检查 +Write-Host "$GREEN[信息]$NC 正在检查 Cursor 版本..." +$cursorVersion = Get-CursorVersion + +if ($cursorVersion) { + $compareResult = Compare-Version $cursorVersion "0.45.0" + if ($compareResult -ge 0) { + Write-Host "$RED[错误]$NC 当前版本 ($cursorVersion) 暂不支持" + Write-Host "$YELLOW[建议]$NC 请使用 v0.44.11 及以下版本" + Write-Host "$YELLOW[建议]$NC 可以从以下地址下载支持的版本:" + Write-Host "Windows: https://download.todesktop.com/230313mzl4w4u92/Cursor%20Setup%200.44.11%20-%20Build%20250103fqxdt5u9z-x64.exe" + Write-Host "Mac ARM64: https://dl.todesktop.com/230313mzl4w4u92/versions/0.44.11/mac/zip/arm64" + Read-Host "按回车键退出" + exit 1 + } + else { + Write-Host "$GREEN[信息]$NC 当前版本 ($cursorVersion) 支持重置功能" + } +} +else { + Write-Host "$YELLOW[警告]$NC 无法检测版本,将继续执行..." +} \ No newline at end of file diff --git a/db_interactive.py b/db_interactive.py new file mode 100644 index 0000000..c4f2306 --- /dev/null +++ b/db_interactive.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sqlite3 +import os + +def main(): + print("SQLite3 数据库查看器") + print("-" * 40) + + # 获取数据库路径 + db_path = input("请输入数据库文件的完整路径: ").strip() + + if not os.path.exists(db_path): + print(f"错误: 文件 '{db_path}' 不存在!") + return + + try: + # 连接到数据库 + print(f"连接到数据库 '{db_path}'...") + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + while True: + print("\n" + "-" * 40) + print("可用操作:") + print("1. 显示所有表") + print("2. 查看表结构") + print("3. 查看表数据") + print("4. 执行自定义SQL查询") + print("5. 退出") + + choice = input("\n请选择操作 (1-5): ").strip() + + if choice == "1": + # 显示所有表 + cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + tables = cursor.fetchall() + + if not tables: + print("数据库中没有表") + else: + print(f"\n数据库中的表 ({len(tables)}):") + for i, table in enumerate(tables): + print(f"{i+1}. {table[0]}") + + elif choice == "2": + # 查看表结构 + table_name = input("请输入表名: ").strip() + cursor.execute(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}';") + if not cursor.fetchone(): + print(f"表 '{table_name}' 不存在!") + continue + + cursor.execute(f"PRAGMA table_info({table_name});") + columns = cursor.fetchall() + print(f"\n表 '{table_name}' 的结构:") + print("-" * 40) + print(f"{'序号':5} {'列名':20} {'类型':15} {'是否可空':10} {'默认值':15}") + print("-" * 70) + for col in columns: + col_id, name, type_, notnull, default_val, pk = col + print(f"{col_id:5} {name:20} {type_:15} {'否' if notnull else '是':10} {str(default_val)[:15]:15}") + + elif choice == "3": + # 查看表数据 + table_name = input("请输入表名: ").strip() + cursor.execute(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}';") + if not cursor.fetchone(): + print(f"表 '{table_name}' 不存在!") + continue + + limit = input("显示多少条记录 (默认10): ").strip() or "10" + try: + limit = int(limit) + except ValueError: + print("请输入有效数字!") + continue + + cursor.execute(f"SELECT COUNT(*) FROM {table_name};") + total = cursor.fetchone()[0] + print(f"\n表 '{table_name}' 共有 {total} 条记录,显示前 {limit} 条:") + + if total == 0: + print("表中没有数据") + continue + + # 获取列名 + cursor.execute(f"PRAGMA table_info({table_name});") + columns = cursor.fetchall() + col_names = [col[1] for col in columns] + + # 显示数据 + cursor.execute(f"SELECT * FROM {table_name} LIMIT {limit};") + rows = cursor.fetchall() + + # 打印列名 + header = " | ".join(col_names) + print("\n" + header) + print("-" * len(header)) + + # 打印数据 + for row in rows: + row_str = [] + for item in row: + item_str = str(item) + if len(item_str) > 20: + item_str = item_str[:17] + "..." + row_str.append(item_str) + print(" | ".join(row_str)) + + elif choice == "4": + # 执行自定义SQL + sql = input("请输入SQL查询语句: ").strip() + if not sql: + continue + + try: + cursor.execute(sql) + if sql.lower().startswith(("select", "pragma")): + rows = cursor.fetchall() + if not rows: + print("查询返回0条结果") + continue + + # 获取列名 + col_names = [description[0] for description in cursor.description] + + # 打印列名 + header = " | ".join(col_names) + print("\n" + header) + print("-" * len(header)) + + # 最多显示50条 + display_rows = rows[:50] + for row in display_rows: + row_str = [] + for item in row: + item_str = str(item) + if len(item_str) > 20: + item_str = item_str[:17] + "..." + row_str.append(item_str) + print(" | ".join(row_str)) + + if len(rows) > 50: + print(f"...(还有 {len(rows)-50} 条记录)") + + print(f"\n总共 {len(rows)} 条记录") + else: + conn.commit() + print("查询执行成功") + except sqlite3.Error as e: + print(f"SQL错误: {e}") + + elif choice == "5": + # 退出 + break + + else: + print("无效选择,请输入1-5之间的数字") + + conn.close() + print("\n已关闭数据库连接。再见!") + + except sqlite3.Error as e: + print(f"SQLite 错误: {e}") + except Exception as e: + print(f"发生错误: {e}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/db_viewer.py b/db_viewer.py new file mode 100644 index 0000000..62c51f9 --- /dev/null +++ b/db_viewer.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sqlite3 +import sys +import os + +def print_separator(char='-', length=80): + """打印分隔线""" + print(char * length) + +def show_database_info(db_path): + """显示数据库信息""" + if not os.path.exists(db_path): + print(f"错误:数据库文件 '{db_path}' 不存在") + return + + try: + # 连接到数据库 + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # 获取所有表名 + cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + tables = cursor.fetchall() + + if not tables: + print("数据库中没有表") + conn.close() + return + + print(f"在数据库 '{db_path}' 中找到 {len(tables)} 个表:") + print_separator() + + # 显示每个表的结构和数据 + for table in tables: + table_name = table[0] + print(f"\n表名: {table_name}") + print_separator() + + # 获取表结构 + cursor.execute(f"PRAGMA table_info({table_name});") + columns = cursor.fetchall() + + # 打印列名 + col_names = [col[1] for col in columns] + col_types = [col[2] for col in columns] + print("表结构:") + for i, (name, type_) in enumerate(zip(col_names, col_types)): + print(f" {i+1}. {name} ({type_})") + + # 获取记录数 + cursor.execute(f"SELECT COUNT(*) FROM {table_name};") + count = cursor.fetchone()[0] + print(f"\n记录数: {count}") + + # 获取表数据 (最多显示10条) + if count > 0: + cursor.execute(f"SELECT * FROM {table_name} LIMIT 10;") + rows = cursor.fetchall() + + print("\n数据预览 (最多10条):") + print_separator() + + # 打印列名 + for col in col_names: + print(f"{col:15}", end="") + print("\n" + "-" * (15 * len(col_names))) + + # 打印数据 + for row in rows: + for item in row: + # 对值进行截断,防止过长的数据破坏格式 + item_str = str(item) + if len(item_str) > 13: + item_str = item_str[:10] + "..." + print(f"{item_str:15}", end="") + print() + + if count > 10: + print(f"...(还有 {count-10} 条记录)") + + print_separator("=") + + conn.close() + print("\n查询完成!") + + except sqlite3.Error as e: + print(f"SQLite 错误: {e}") + except Exception as e: + print(f"发生错误: {e}") + +def show_usage(): + """显示使用帮助""" + print(f"使用方法: python {os.path.basename(__file__)} <数据库文件路径>") + print("示例: python db_viewer.py C:\\path\\to\\your\\database.db") + +if __name__ == "__main__": + if len(sys.argv) != 2: + show_usage() + else: + db_path = sys.argv[1] + show_database_info(db_path) \ No newline at end of file diff --git a/gui/components/__pycache__/widgets.cpython-312.pyc b/gui/components/__pycache__/widgets.cpython-312.pyc index f28855500609f978ba83d0b76deabcaebff7efcb..2fa552ea5e4959d4f6524f337b38aca896103157 100644 GIT binary patch delta 9672 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 diff --git a/gui/components/__pycache__/workers.cpython-312.pyc b/gui/components/__pycache__/workers.cpython-312.pyc index 67f2b9c368de5c951af9c0029b7839605d390544..f7b5b1a093592572610c5a6b759abd90d9bcb05d 100644 GIT binary patch delta 1222 zcma)+Z%h+s9LJyM+H0>Zl$OaqnY7Z53Qibnvc|#?g;v7eI5)+_H&m}-RcOr}bqT4V z+f>RFC3%d1$efCfWMdX;w2&H>Y#Ng#O9{zfWLcKTUE3uKN9>ktul78>C7TN`_PqE! zzkBZYe1G5HbGheh>q52u+x&bjz@y`7wkupXqc>4-AF8Enus(K(>Y^yO0=-tS6|E?r z(N97&_YL}r?t(`!ccOPxM;k-*2_xD5;$ZckLwRK!umuzJ8IAu<)(}#k2E39rl9{Vv z3(?0aYuo=2c!>zGTh1u_I<_dJ9tRT?7NptYkW!9A;6x#klr~s`epd~`(pXq+g8dro zKr7rkpZfFzr+Jk*<>C4F0~{OtCAU5>pSl#a*E+-tcf^a6V&rN%eD3rqe`5`# z4~e7q()aHQmmg+EzRcVj6(R$|g#}^qn)t;{A@WeX8kOH_v5IhgN`6S)|16c57ltnh zm!p{jqqE}ltznS&=eO|VYadDhKV6L|Z+fXM{ zgf^IR*Lor%PJD!m;#SfRV(W=Ptt{;{2i@cV(hFm_s5pHao54M#XKt=1F5+b2(TsqG zWMgQK*{*muSVvT28;lbgf5M+N3?ynl)L64BP+~>9wSV@*Cd-PYZrNgAwAiEVOO`#! z%4P{D_reB=R+TkwC_s@(Dh8!Hr+kYgj=1w_*f zyQ*B(;BmFaZB{&f-Rdq^JSnH$W|YvCK_~iNSCOy7{htLtC~KZmZE!Yk2=>?E74Bt% zR|$3zR1j>#s8(@$64w*#Ah2VsDp^mjhue)qUWH-K4bDK`J2CnS4tjZSZ%2ST>E&v< zp#M0@d=G=1AFZ+eSZqOo$o`e>JC;$NzPF-*>}(_;Ka?!gL?QwY`b1v}n~)~&jo7dH zPI!DXpY&!vWrsA@_^&}@tB>~_>-PR*u?J`IEC#WtMo~jKEvZ1h5q)Pk>e$R*3(n;T zPLiGE9wFyi`J7^CtK3El>NIxm%Jq>;ezCz-rm-i$2D`mY+*^1vxik1y^oOysrX51- e=+-PCbM^?uK(+I(bY5S9PH z$Dk6-cp*6ELQw1l#kh%HlTYx58n`IAs4^T;<#d%{KBA}MD$aaVoE6CSTgh4^3bdt2 z3`B^72ni5jIoX#_jtAto7KWQFTA!KBH&^iWGFt0_BxOK^Hi*yx5pp0x7D#Ba74d=C z1|UKML>K{yTP!JwC5c5AlN|&^#4LdVRs2Dz#i=FX`9;~OMS4Z0c|nu21p3UaKpLz; zgdm6z01>t@A^^xJhB-&|GZP!D3PhRhBB2tcd? zyG+m}vp6v+Cl%qg7?9E=AOQ+;R!ETNcevk>R=O^2cv0H$vb1rhPlx-2;2TUl0+T(2 zqp|wK31~nOh=>9i2lIy#l0Td#-xAK@2KyJR*m`oHhy<4xP&qKV8H$}I7mFxqSTd^2 gkov*^q`s6faxkh)(E7pvq`ruN*j`^jY;dRm072%I!2kdN diff --git a/gui/components/widgets.py b/gui/components/widgets.py index 662d4bc..7ef6fb5 100644 --- a/gui/components/widgets.py +++ b/gui/components/widgets.py @@ -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'剩余天数:{days_left}天') - - status_text.append("
") - # 设备信息 - 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('
') + status_text.append('公告:
') + status_text.append(f'{announcement}') + status_text.append('
') # 更新文本 self.status_text.setHtml("
".join(status_text)) @@ -496,6 +701,16 @@ class MemberStatusWidget(QFrame): # 如果发生异常,显示错误信息 error_text = f'状态更新失败: {str(e)}' 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): """自定义加载动画组件""" diff --git a/gui/components/workers.py b/gui/components/workers.py index 08c8f37..00351a1 100644 --- a/gui/components/workers.py +++ b/gui/components/workers.py @@ -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)) \ No newline at end of file diff --git a/gui/windows/__pycache__/main_window.cpython-312.pyc b/gui/windows/__pycache__/main_window.cpython-312.pyc index efd07fb9a2320234a460141b1f4b265b0b2c77ab..bb1d39ea91e42c80064336a93c80df9e183acecb 100644 GIT binary patch delta 11163 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 delta 6789 zcmaJm4R};VlJ8AsCNr5g`JKssCX*kMOh^V2{xyP_2u4g40bxgR80I~af%ysj-T;YV z6O>yL0SVilpn~GLxUeXq<0{I!>Unp&d+T>5OT6e4|8|*xe%=}t{dV`h<*uq9*(Z-k+LVc>~XJ2RM-v`5@=PUtN$7 z={!0f`$@s1klv#Y89e)2>UtPTAIuD8d9p$_kBxmBg7#3hCp(nm$qD6pav9DDG zR4^J#u%|Lq<*5p}Jg!i+ry96*yA`k65Qni<%|m5_x1RIAtP5Bg-;F9m~MFNm21SSIFrO^gdk5#-UktZq$` ziw;=dyR`;KY5};C*yN-h5sV+V!|CkRB;br)1kWhhir3o;1acmA z8uImf)5sj^&dcNX(6Q8`_1#HL+}a4o-KFEW?fUHo#Q>zNb?YDHL}Rz%QSNZk3Dg?e zc4N1Z)~&G9bsmcjy8Jdf{oG}to6>U9j0qy1>o#t*(#`1w^OB*Lrm;?iqL~R#VZ!y3 ze0w(QxlRt`eVu-1mfod{nt|)W(@&p2-8=fs(daBHX3Wi;f_`NWqLC@+5eeJ@7$ACK%gvOt`cl5DCqX&+R z#{N0}?8_IQJse$xbKU!LeAk&j^$x^OK6c@OQ;ks~N61X~>^bgjpFTVg?|bo2z5BoT z)t(DGUyKht@ae9-I0c(%wC#bu(ZlzFbOEKGdFA5ClcRL+`Bx8JDfhxtJ75u-+zb7E zqlcazee?y8qRBn~+TqbXJ1)NZ?D;b>gKnY5!K~|&=nYP#} z^2jQ{%XnCv<4yC(ne@2LnS47A4&#Yy%`jx7^clWR7tg~uUq}D!Xtc1Hi}voa)4?J;e_!nOVn5H%p>>sc^lj%d_XZei1!HuglvE!?!Yw{U z(o`oCS~@ikmFZ0-PW~7TlsH+)_49Q3Y8!pBWN^L@`Sr3A=^!EOrodUaBOnLX2PKlr z#x_R-%J(Ej>4{P&{|tSrbcrTWM{j$;Mr+FCSiR67zNixM z`r9S{rty5C8YD*g{fyBM%w4uAo-rZ!Ra!Hx7F@$K&BnK^2QpHE6Jdz(h9p_`!N#D0 zHA`4KNW>3Vwj?>>hC;_1hguyGXv|0eyQVg=i`eK|OTs&5a7(iQfkBbC+s z@2Re8I(li9s^j-k^K@cchvWj&(Hrd$VS9;*?wg*ImW>0+kq#-miUdgx{pIvqOz2`R zBdMe2nt3J6dv*h+%RWWzNs|r6d^HU`U6^M!J`WYhE{;CekYA!;&a#1HHoxt6L#&Zf5FsvyU7+E{P(@%Eg)4x{P@J|r+T^7iud^gV$WxkP=^>Z83 z=#TmebxIbUmS4_;h)XF|w$XS=SSj`i8;zy0^0%M%7TW2| zIVtlMR|1=m3EX(xS-T21x3QWoz7rI$rOWf|WfSUNmE4Ww^A*fXtWB8<;}2_BWv)l& z>`AjFn713c4a{g>kF^X_2y3D#vDcu;gsEY#=F%tfviSK3>GFA5*?rswUNrBVAm_OR z5k1?MMc&Mkp8aL7p?;E6>4KL2@s z1wFMOSkR7$jR9w~+|-Ey?P^Gp?m{ zR#oC9LQMy0jhLx?LG3!i3Q(3!S9JLNkhrvBbQ=c60LvMDJF1cVp!#K{r&b zAt|(8B3@bXDN&i=xll94b9}%N2`4zPC{qQvE>7Hqh=pb>wHX^=0P2b9OKW?wrX~>Y z`lzBnRq`l~7b189!4UweO(TS?BEoXVGY+N{r07D5RKKQSVtTBTNi~jGHM!oPuQL)= zWUQOku^O2)P2N?f7H42Cc2E5?P|D?yc%c0H*d&aY3WiJt2gAd)*PpGuVX&rgu&`;^ zbYrZ(d4Nwzn=V}9(#+N|Cw=SYH6>$C&YV4BDjqTwAJLCE>xP_l!>0O6My|MeuzJZ^ z$5Oig=DM0mrYopt4LN5Gn`VD(N*yuf4w-Tf77d#!>HjP{kjxxNE0AJIEzLadr7f*R z^{+5BwKSaAmsb&o1=EdT9~3(x`86QOYXGn&p@&+h8JYG+=xphU++i!ha zZ)gOfZ)n3xPtPOJTj@$y4ILx^{=@d;Ur^2&0IHCv?7ZuhF!>$gy@=18z;6vG?OuNb zY6?uiWC?;JNRqtON5Zg^s>L6ad<1UC9Z`imiQ`x-s3t!NC;_+M7jzl4O@529$x%O6 zIppU^`3nFtdbqZmv+3ehv-#bzEvxn=oBjjPtxHINTGu-GQ?zDnIsZIezSe2M6_Vc| zI77SFI#`WKcHlQ!Ow(e*R8us@kfxfT%4#FHfa~ZV*1Gwll)pP~X(=*d!cM#nyoJ6WH^Ijhbq|~(qIeabyy;ZTcehms(Iv9(z+x=iv>WWtc3v3x$@$aI zxZUnnkUy~nY7#uDBiloyGKcZdzT#Ct`zp5#(rup~p*ZAiR3@mx4Z zNv2;`71G~rOxJgFP-OY;ETW&aWYf7J8(o(s&GRoLJ0G@@m>9pE{_!Td~qDgW92UxhKuL!S5B|`x5nS<-)tUiXzdsJk_WOrv1FbTaz=!*A))L@ z)3K^kjqeEa&m*RINPxI;r2SOVurM1g-e&8Fsbt7hGHfb)*Cgyq>rWdol@6IokIWx7 z)xfxrJ_50|D$71BRJ>bS(bx2jP&{Vfgklyj=rx;5_|NFRbu(yjsg)kwywQLLPhken zCq-5fw#JJG2^*bDs`iK=8x z=J!{Ik6*D>@);xl5Y6aXz#pK?y88INvEi=1Bn#S+!~k>^(#N~^EQBORDDN5Epk ze(XJnU^fDM%+Wl31bY~@RlNwW=HwiHUqtXBf{zg3{wyM>Ho1(T1wku-sgKKR5y=1! zAPalU%DSXWG30*bpkM8qnS7hhQ2v#ur>=ooambdZ#CU^Lh_F!N=$)oq|FXHp* z!}|++3XqVMa7Ea2ASg!QL{I`i&2EcAevgDYB4PYr3-0l3NlKBT3_&@P2|yBpqB5!k zf-;$g_zJ|^8N$5<5-dEkdqci3zK4)XBycq##QKw2<+xUNGv`!I4WWn_4N41%0I>=F z{step_title}:{step_content}') + # 最后一个标题标红,其他保持原来的青色 + title_color = "#e0a800" if i == len(steps)-1 else "#17a2b8" + step_label.setText(f'{step_title}:{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() \ No newline at end of file + event.accept() + + def open_purchase_link(self): + """打开购买链接""" + self.activation_widget.show_activation_required() diff --git a/machine_resetter.py b/machine_resetter.py index 9c5734b..91f0cf0 100644 --- a/machine_resetter.py +++ b/machine_resetter.py @@ -8,6 +8,7 @@ import winreg import ctypes import shutil import json +import secrets from datetime import datetime from typing import Dict, Tuple, Any, Optional from logger import logger @@ -86,6 +87,42 @@ class MachineResetter: self.logger.info(f"配置已备份到: {backup_path}") return backup_path + def check_cursor_version(self) -> Tuple[bool, str]: + """ + 检查 Cursor 版本是否支持 + :return: (是否支持, 版本号) + """ + 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) + version = data.get('version', '') + # 检查版本号是否低于 0.44.0 + version_parts = list(map(int, version.split('.'))) + if version_parts[0] == 0 and version_parts[1] < 44: + return False, version + return True, version + + return True, "未知版本" + except Exception as e: + self.logger.warning(f"版本检测失败: {str(e)}") + return True, "版本检测失败" + + def generate_secure_random(self, length: int) -> str: + """ + 生成安全的随机十六进制字符串 + :param length: 字节长度 + :return: 十六进制字符串 + """ + return secrets.token_hex(length) + def generate_new_ids(self) -> Dict[str, str]: """ 生成新的机器码系列 @@ -93,8 +130,8 @@ class MachineResetter: """ # 生成 auth0|user_ 前缀的十六进制 prefix = "auth0|user_".encode('utf-8').hex() - # 生成32字节(64个十六进制字符)的随机数 - random_part = uuid.uuid4().hex + uuid.uuid4().hex + # 使用安全的随机数生成方法 + random_part = self.generate_secure_random(32) # 生成64个十六进制字符 return { "machineId": f"{prefix}{random_part}", @@ -159,6 +196,11 @@ class MachineResetter: :return: (新ID字典, 新GUID) """ try: + # 检查 Cursor 版本 + version_supported, version = self.check_cursor_version() + if not version_supported: + raise Exception(f"当前 Cursor 版本 ({version}) 不支持重置,请使用 v0.44.0 及以上版本") + # 检查管理员权限 self._update_progress("checking", "检查管理员权限...") if not self.is_admin(): @@ -174,23 +216,34 @@ class MachineResetter: backup_path = self.backup_file() self.logger.info(f"配置已备份到: {backup_path}") - # 生成新机器码 - self._update_progress("generating", "生成新的机器码...") - new_ids = self.generate_new_ids() - - # 更新配置文件 - self._update_progress("updating", "更新配置文件...") - self.update_config(new_ids) - - # 更新注册表 - self._update_progress("registry", "更新注册表...") - new_guid = self.update_machine_guid() - - self._update_progress("complete", "重置完成") - self.logger.info("机器码重置成功") - - return new_ids, new_guid - + try: + # 生成新机器码 + self._update_progress("generating", "生成新的机器码...") + new_ids = self.generate_new_ids() + + # 更新配置文件 + self._update_progress("updating", "更新配置文件...") + self.update_config(new_ids) + + # 更新注册表 + self._update_progress("registry", "更新注册表...") + new_guid = self.update_machine_guid() + + self._update_progress("complete", "重置完成") + self.logger.info("机器码重置成功") + + return new_ids, new_guid + + except Exception as e: + # 如果出错,尝试恢复备份 + try: + if os.path.exists(backup_path): + shutil.copy2(backup_path, self.storage_file) + self.logger.info("已恢复配置文件备份") + except Exception as restore_error: + self.logger.error(f"恢复备份失败: {str(restore_error)}") + raise + except Exception as e: self.logger.error(f"重置失败: {str(e)}") self._update_progress("error", f"操作失败: {str(e)}") diff --git a/machine_resetter2025.3.3back.py b/machine_resetter2025.3.3back.py new file mode 100644 index 0000000..9c5734b --- /dev/null +++ b/machine_resetter2025.3.3back.py @@ -0,0 +1,197 @@ +import os +import sys +import sqlite3 +import requests +import urllib3 +import uuid +import winreg +import ctypes +import shutil +import json +from datetime import datetime +from typing import Dict, Tuple, Any, Optional +from logger import logger +from exit_cursor import ExitCursor + +class MachineResetter: + """机器码重置核心类""" + + def __init__(self, storage_file: str = None, backup_dir: str = None): + """ + 初始化机器码重置器 + :param storage_file: 可选,storage.json文件路径 + :param backup_dir: 可选,备份目录路径 + """ + # 设置基础路径 + appdata = os.getenv('APPDATA') + if not appdata: + raise EnvironmentError("APPDATA 环境变量未设置") + + self.storage_file = storage_file or os.path.join(appdata, 'Cursor', 'User', 'globalStorage', 'storage.json') + self.backup_dir = backup_dir or os.path.join(os.path.dirname(self.storage_file), 'backups') + + # 确保备份目录存在 + os.makedirs(self.backup_dir, exist_ok=True) + + # 获取日志记录器 + self.logger = logger.get_logger("MachineReset") + + # 进度回调 + self._callback = None + + def set_progress_callback(self, callback) -> None: + """设置进度回调函数""" + self._callback = callback + + def _update_progress(self, status: str, message: str) -> None: + """更新进度信息""" + if self._callback: + self._callback({"status": status, "message": message}) + + @staticmethod + def is_admin() -> bool: + """检查是否具有管理员权限""" + try: + return ctypes.windll.shell32.IsUserAnAdmin() + except: + return False + + def _get_storage_content(self) -> Dict[str, Any]: + """读取storage.json的内容""" + try: + with open(self.storage_file, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception as e: + raise Exception(f"读取配置文件失败: {str(e)}") + + def _save_storage_content(self, content: Dict[str, Any]) -> None: + """保存内容到storage.json""" + try: + with open(self.storage_file, 'w', encoding='utf-8') as f: + json.dump(content, f, indent=2, ensure_ascii=False) + except Exception as e: + raise Exception(f"保存配置文件失败: {str(e)}") + + def backup_file(self) -> str: + """ + 备份配置文件 + :return: 备份文件路径 + """ + if not os.path.exists(self.storage_file): + raise FileNotFoundError(f"配置文件不存在:{self.storage_file}") + + backup_name = f"storage.json.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + backup_path = os.path.join(self.backup_dir, backup_name) + shutil.copy2(self.storage_file, backup_path) + self.logger.info(f"配置已备份到: {backup_path}") + return backup_path + + def generate_new_ids(self) -> Dict[str, str]: + """ + 生成新的机器码系列 + :return: 包含新ID的字典 + """ + # 生成 auth0|user_ 前缀的十六进制 + prefix = "auth0|user_".encode('utf-8').hex() + # 生成32字节(64个十六进制字符)的随机数 + random_part = uuid.uuid4().hex + uuid.uuid4().hex + + return { + "machineId": f"{prefix}{random_part}", + "macMachineId": str(uuid.uuid4()), + "devDeviceId": str(uuid.uuid4()), + "sqmId": "{" + str(uuid.uuid4()).upper() + "}" + } + + def update_config(self, new_ids: Dict[str, str]) -> None: + """ + 更新配置文件 + :param new_ids: 新的ID字典 + """ + config_content = self._get_storage_content() + + # 更新配置 + config_content['telemetry.machineId'] = new_ids['machineId'] + config_content['telemetry.macMachineId'] = new_ids['macMachineId'] + config_content['telemetry.devDeviceId'] = new_ids['devDeviceId'] + config_content['telemetry.sqmId'] = new_ids['sqmId'] + + # 保存更新后的配置 + self._save_storage_content(config_content) + self.logger.info("配置文件更新成功") + + def update_machine_guid(self) -> str: + """ + 更新注册表中的MachineGuid + :return: 新的GUID + """ + reg_path = r"SOFTWARE\Microsoft\Cryptography" + try: + # 打开注册表键 + reg_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, reg_path, 0, + winreg.KEY_WRITE | winreg.KEY_READ) + + # 获取当前GUID用于备份 + current_guid, _ = winreg.QueryValueEx(reg_key, "MachineGuid") + self.logger.info(f"当前MachineGuid: {current_guid}") + + # 生成新GUID + new_guid = str(uuid.uuid4()) + + # 更新注册表 + winreg.SetValueEx(reg_key, "MachineGuid", 0, winreg.REG_SZ, new_guid) + + # 验证更改 + updated_guid, _ = winreg.QueryValueEx(reg_key, "MachineGuid") + if updated_guid != new_guid: + raise Exception("注册表验证失败,值未成功更新") + + winreg.CloseKey(reg_key) + self.logger.info(f"新MachineGuid: {new_guid}") + return new_guid + + except Exception as e: + raise Exception(f"更新MachineGuid失败: {str(e)}") + + def run(self) -> Tuple[Dict[str, str], str]: + """ + 执行重置操作 + :return: (新ID字典, 新GUID) + """ + try: + # 检查管理员权限 + self._update_progress("checking", "检查管理员权限...") + if not self.is_admin(): + raise PermissionError("必须以管理员权限运行该程序") + + # 先退出 Cursor 进程 + self._update_progress("exiting", "正在关闭 Cursor 进程...") + if not ExitCursor(): + raise Exception("无法完全关闭 Cursor 进程,请手动关闭后重试") + + # 备份配置文件 + self._update_progress("backup", "备份配置文件...") + backup_path = self.backup_file() + self.logger.info(f"配置已备份到: {backup_path}") + + # 生成新机器码 + self._update_progress("generating", "生成新的机器码...") + new_ids = self.generate_new_ids() + + # 更新配置文件 + self._update_progress("updating", "更新配置文件...") + self.update_config(new_ids) + + # 更新注册表 + self._update_progress("registry", "更新注册表...") + new_guid = self.update_machine_guid() + + self._update_progress("complete", "重置完成") + self.logger.info("机器码重置成功") + + return new_ids, new_guid + + except Exception as e: + self.logger.error(f"重置失败: {str(e)}") + self._update_progress("error", f"操作失败: {str(e)}") + raise \ No newline at end of file diff --git a/services/__pycache__/cursor_service.cpython-312.pyc b/services/__pycache__/cursor_service.cpython-312.pyc index f42a2d8901bf00e98c988aa03b90354ba60805c4..7733398a00a112dae0c0918119b34500b9d24c78 100644 GIT binary patch delta 115 zcmZ1*axsMWG%qg~0}z;W98KS~k#`O=qx0ld%<7DWn=djylrhu=O1*6FeZF_yvpKt- z&Rp?)LF2QI2~WG`zgWK=DE@T9`sC80;{2lLd#1hEHxZ~{@*#zpj5U*E6m9q}NP66m TR=Odh@{yIld)=YouUoT47nT9N;hOwKC&`vZ9bvc$pHXZFddWt diff --git a/services/cursor_service.py b/services/cursor_service.py index cb6a196..8d71d9f 100644 --- a/services/cursor_service.py +++ b/services/cursor_service.py @@ -200,7 +200,7 @@ class CursorService: try: new_ids, new_guid = self.machine_resetter.run() - return True, "重置成功", { + return True, "重置机器码成功请重启cursor编辑器", { "new_ids": new_ids, "new_guid": new_guid } diff --git a/testbuild.bat b/testbuild.bat index d19f49f..bebe3e8 100644 --- a/testbuild.bat +++ b/testbuild.bat @@ -58,9 +58,15 @@ del /s /q *.pyc >nul 2>&1 del /s /q *.pyo >nul 2>&1 :: 清理旧的打包文件 -echo 清理旧文件... -if exist "build" rd /s /q "build" +:: 保留当前版本的 .spec 文件 +set CURRENT_SPEC=dist\听泉cursor助手_test.spec +if exist "!CURRENT_SPEC!" ( + move "!CURRENT_SPEC!" "!CURRENT_SPEC!.bak" +) if exist "*.spec" del /f /q "*.spec" +if exist "!CURRENT_SPEC!.bak" ( + move "!CURRENT_SPEC!.bak" "!CURRENT_SPEC!" +) :: 使用优化选项进行打包 echo 开始打包... diff --git a/update_disabler.py b/update_disabler.py index dbc83f1..c91655a 100644 --- a/update_disabler.py +++ b/update_disabler.py @@ -44,10 +44,10 @@ class UpdateDisabler: if not os.path.exists(parent_dir): os.makedirs(parent_dir, exist_ok=True) - def disable(self) -> bool: + def disable(self) -> Tuple[bool, str]: """ 禁用自动更新 - :return: 是否成功 + :return: (是否成功, 消息) """ try: self._update_progress("start", "开始禁用自动更新...") @@ -123,13 +123,14 @@ class UpdateDisabler: self._update_progress("complete", "禁用自动更新完成") self.logger.info("成功禁用自动更新") - return True + return True, "禁用自动更新成功" except Exception as e: self.logger.error(f"禁用自动更新失败: {str(e)}") self._update_progress("error", f"操作失败: {str(e)}") - self.show_manual_guide() - return False + + # 返回权限不足的错误,触发统一的解决方案提示 + return False, "权限不足,无法禁用更新\n这是正常现象,不影响使用\n您可以点击【解决方案】按钮来解决此问题" def show_manual_guide(self) -> str: """ diff --git a/utils/__pycache__/version_manager.cpython-312.pyc b/utils/__pycache__/version_manager.cpython-312.pyc index 761dfe17a43812676d02ed8a464b425236aa67c3..fabe1d3997e8a5d23dd89d65cee257dd86f72492 100644 GIT binary patch delta 204 zcmewx)f>%wnwOW00SM$5A5GWW$h(|{(Q)!t7Ij9$&39NVr6g~0m8BLHXXfX{=jGqx zPDw3JF3Kz@0rDrS$)|EB05ujRf{3Ka)8s7}Q#YTNU(P6ei>tICC9xzmKDRi%2%=_l zlj1){F`LUu4img@DC%BUG`y&2xIy=T&}BuJ&C`@`Gc%@4E>e$R%$IWb*;EZ8>Lx4etY*xdd|oG-v2n7tZV&)%Ekln0 delta 188 zcmeB;{u{-6nwOW00SNM>wx?@tltHZx=Lj(N&BU0zIag~DBV+Dl0UZhM ca*%t<8 diff --git a/utils/version_manager.py b/utils/version_manager.py index 3b0b29a..a810790 100644 --- a/utils/version_manager.py +++ b/utils/version_manager.py @@ -147,8 +147,8 @@ class VersionManager: if update_info.get("has_update"): # 有更新可用 version_info = update_info.get("version_info", {}) - new_version = version_info.get("version") - update_msg = version_info.get("update_msg", "") + new_version = version_info.get("version_no") + update_msg = version_info.get("description", "") download_url = version_info.get("download_url", "") is_force = update_info.get("is_force", 0) diff --git a/version.txt b/version.txt index 7636e75..6aba2b2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.0.5 +4.2.0 diff --git a/听泉助手v4.1.7.spec b/听泉助手v4.1.7.spec new file mode 100644 index 0000000..ecf7183 --- /dev/null +++ b/听泉助手v4.1.7.spec @@ -0,0 +1,44 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_submodules + +hiddenimports = ['json', 'sqlite3', 'winreg', 'ctypes', 'platform', 'uuid', 'hashlib', 'datetime', 'urllib3', 'requests', 'PyQt5', 'PyQt5.sip', 'psutil', 'psutil._psutil_windows', 'psutil._pswindows'] +hiddenimports += collect_submodules('psutil') + + +a = Analysis( + ['tingquan_assistant.py'], + pathex=[], + binaries=[], + datas=[('version.txt', '.'), ('requirements.txt', '.'), ('two.ico', '.'), ('config.py', '.'), ('logger.py', '.'), ('common_utils.py', '.'), ('cursor_token_refresher.py', '.'), ('machine_resetter.py', '.'), ('update_disabler.py', '.'), ('exit_cursor.py', '.'), ('gui', 'gui'), ('services', 'services'), ('utils', 'utils')], + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=['_tkinter', 'tkinter', 'PIL.ImageTk', 'PIL.ImageWin', 'numpy', 'pandas', 'matplotlib', '__pycache__', '*.pyc', '*.pyo', '*.pyd'], + noarchive=False, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='听泉助手v4.1.7', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + version='file_version_info.txt', + uac_admin=True, + icon=['two.ico'], +) diff --git a/听泉助手v4.1.8.spec b/听泉助手v4.1.8.spec new file mode 100644 index 0000000..706c034 --- /dev/null +++ b/听泉助手v4.1.8.spec @@ -0,0 +1,44 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_submodules + +hiddenimports = ['json', 'sqlite3', 'winreg', 'ctypes', 'platform', 'uuid', 'hashlib', 'datetime', 'urllib3', 'requests', 'PyQt5', 'PyQt5.sip', 'psutil', 'psutil._psutil_windows', 'psutil._pswindows'] +hiddenimports += collect_submodules('psutil') + + +a = Analysis( + ['tingquan_assistant.py'], + pathex=[], + binaries=[], + datas=[('version.txt', '.'), ('requirements.txt', '.'), ('two.ico', '.'), ('config.py', '.'), ('logger.py', '.'), ('common_utils.py', '.'), ('cursor_token_refresher.py', '.'), ('machine_resetter.py', '.'), ('update_disabler.py', '.'), ('exit_cursor.py', '.'), ('gui', 'gui'), ('services', 'services'), ('utils', 'utils')], + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=['_tkinter', 'tkinter', 'PIL.ImageTk', 'PIL.ImageWin', 'numpy', 'pandas', 'matplotlib', '__pycache__', '*.pyc', '*.pyo', '*.pyd'], + noarchive=False, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='听泉助手v4.1.8', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + version='file_version_info.txt', + uac_admin=True, + icon=['two.ico'], +) diff --git a/听泉助手v4.1.9.spec b/听泉助手v4.1.9.spec new file mode 100644 index 0000000..25d28f9 --- /dev/null +++ b/听泉助手v4.1.9.spec @@ -0,0 +1,44 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_submodules + +hiddenimports = ['json', 'sqlite3', 'winreg', 'ctypes', 'platform', 'uuid', 'hashlib', 'datetime', 'urllib3', 'requests', 'PyQt5', 'PyQt5.sip', 'psutil', 'psutil._psutil_windows', 'psutil._pswindows'] +hiddenimports += collect_submodules('psutil') + + +a = Analysis( + ['tingquan_assistant.py'], + pathex=[], + binaries=[], + datas=[('version.txt', '.'), ('requirements.txt', '.'), ('two.ico', '.'), ('config.py', '.'), ('logger.py', '.'), ('common_utils.py', '.'), ('cursor_token_refresher.py', '.'), ('machine_resetter.py', '.'), ('update_disabler.py', '.'), ('exit_cursor.py', '.'), ('gui', 'gui'), ('services', 'services'), ('utils', 'utils')], + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=['_tkinter', 'tkinter', 'PIL.ImageTk', 'PIL.ImageWin', 'numpy', 'pandas', 'matplotlib', '__pycache__', '*.pyc', '*.pyo', '*.pyd'], + noarchive=False, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='听泉助手v4.1.9', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + version='file_version_info.txt', + uac_admin=True, + icon=['two.ico'], +) diff --git a/听泉助手v4.2.0.spec b/听泉助手v4.2.0.spec new file mode 100644 index 0000000..476220b --- /dev/null +++ b/听泉助手v4.2.0.spec @@ -0,0 +1,44 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_submodules + +hiddenimports = ['json', 'sqlite3', 'winreg', 'ctypes', 'platform', 'uuid', 'hashlib', 'datetime', 'urllib3', 'requests', 'PyQt5', 'PyQt5.sip', 'psutil', 'psutil._psutil_windows', 'psutil._pswindows'] +hiddenimports += collect_submodules('psutil') + + +a = Analysis( + ['tingquan_assistant.py'], + pathex=[], + binaries=[], + datas=[('version.txt', '.'), ('requirements.txt', '.'), ('two.ico', '.'), ('config.py', '.'), ('logger.py', '.'), ('common_utils.py', '.'), ('cursor_token_refresher.py', '.'), ('machine_resetter.py', '.'), ('update_disabler.py', '.'), ('exit_cursor.py', '.'), ('gui', 'gui'), ('services', 'services'), ('utils', 'utils')], + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=['_tkinter', 'tkinter', 'PIL.ImageTk', 'PIL.ImageWin', 'numpy', 'pandas', 'matplotlib', '__pycache__', '*.pyc', '*.pyo', '*.pyd'], + noarchive=False, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='听泉助手v4.2.0', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + upx_exclude=[], + runtime_tmpdir=None, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + version='file_version_info.txt', + uac_admin=True, + icon=['two.ico'], +)