0
0
mirror of https://github.com/yeongpin/cursor-free-vip.git synced 2025-04-24 08:25:23 +00:00

Update version to 1.8.02 in .env and CHANGELOG.md, adding new features and fixes including disabling auto-update, configuration options, and contributors options.

This commit is contained in:
yeongpin 2025-03-29 21:49:57 +08:00
parent 350d781ce8
commit c15ea25cb3
12 changed files with 436 additions and 68 deletions

4
.env
View File

@ -1,2 +1,2 @@
version=1.8.01
VERSION=1.8.01
version=1.8.02
VERSION=1.8.02

View File

@ -1,5 +1,14 @@
# Change Log
## v1.8.02
1. Fix: Disable Auto Update | 修復禁用自動更新
2. Add: Config Options | 增加配置選項
3. Add: Contributors Options | 增加貢獻者選項
4. Optimize Row & Colume Options | 優化行與列選項
5. Add: Update Windows Machine ID | 增加更新 Windows 機器 ID
6. Fix: Too Many Free Trial On Some Machine | 修復某些機器上太多免費試用
7. Fix: Some Issues | 修復一些問題
## v1.8.01
1. Add: Cursor Account Info | 增加 Cursor 賬號信息
2. Fix: Disable Auto Update | 修復禁用自動更新

View File

@ -4,6 +4,18 @@ import configparser
from colorama import Fore, Style
from utils import get_user_documents_path, get_default_chrome_path, get_linux_cursor_path
EMOJI = {
"INFO": "",
"WARNING": "⚠️",
"ERROR": "",
"SUCCESS": "",
"ADMIN": "🔒",
"ARROW": "➡️",
"USER": "👤",
"KEY": "🔑",
"SETTINGS": "⚙️"
}
def setup_config(translator=None):
"""Setup configuration file and return config object"""
try:
@ -37,6 +49,10 @@ def setup_config(translator=None):
'failed_retry_time': '0.5-1',
'retry_interval': '8-12',
'max_timeout': '160'
},
'Utils': {
'enabled_update_check': 'True',
'enabled_account_info': 'True'
}
}
@ -115,6 +131,33 @@ def setup_config(translator=None):
print(f"{Fore.RED}❌ Error setting up config: {e}{Style.RESET_ALL}")
return None
def print_config(config, translator=None):
"""Print configuration in a readable format"""
if not config:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.config_not_available')}{Style.RESET_ALL}")
return
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.configuration')}:{Style.RESET_ALL}")
print(f"\n{Fore.CYAN}{'' * 70}{Style.RESET_ALL}")
for section in config.sections():
print(f"{Fore.GREEN}[{section}]{Style.RESET_ALL}")
for key, value in config.items(section):
# 对布尔值进行特殊处理,使其显示为彩色
if value.lower() in ('true', 'yes', 'on', '1'):
value_display = f"{Fore.GREEN}{translator.get('config.enabled')}{Style.RESET_ALL}"
elif value.lower() in ('false', 'no', 'off', '0'):
value_display = f"{Fore.RED}{translator.get('config.disabled')}{Style.RESET_ALL}"
else:
value_display = value
print(f" {key} = {value_display}")
print(f"\n{Fore.CYAN}{'' * 70}{Style.RESET_ALL}")
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip", "config.ini")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_directory')}: {config_dir}{Style.RESET_ALL}")
print()
def get_config(translator=None):
"""Get existing config or create new one"""
return setup_config(translator)

View File

@ -64,12 +64,12 @@ class UsageManager:
response.raise_for_status()
data = response.json()
# 获取 GPT-4 使用量和限制
# get Premium usage and limit
gpt4_data = data.get("gpt-4", {})
premium_usage = gpt4_data.get("numRequestsTotal", 0)
max_premium_usage = gpt4_data.get("maxRequestUsage", 999)
# 获取 GPT-3.5 使用量,但将限制设为 "No Limit"
# get Basic usage, but set limit to "No Limit"
gpt35_data = data.get("gpt-3.5-turbo", {})
basic_usage = gpt35_data.get("numRequestsTotal", 0)
@ -77,10 +77,15 @@ class UsageManager:
'premium_usage': premium_usage,
'max_premium_usage': max_premium_usage,
'basic_usage': basic_usage,
'max_basic_usage': "No Limit" # 将 GPT-3.5 的限制设为 "No Limit"
'max_basic_usage': "No Limit" # set Basic limit to "No Limit"
}
except requests.RequestException as e:
logger.error(f"获取使用量失败: {str(e)}")
# only log error
logger.error(f"Get usage info failed: {str(e)}")
return None
except Exception as e:
# catch all other exceptions
logger.error(f"Get usage info failed: {str(e)}")
return None
@staticmethod
@ -95,7 +100,7 @@ class UsageManager:
response.raise_for_status()
return response.json()
except requests.RequestException as e:
logger.error(f"获取订阅信息失败: {str(e)}")
logger.error(f"Get subscription info failed: {str(e)}")
return None
def get_token_from_config():
@ -126,7 +131,7 @@ def get_token_from_config():
'session_path': os.path.expanduser("~/.config/Cursor/Session Storage")
}
except Exception as e:
logger.error(f"获取配置路径失败: {str(e)}")
logger.error(f"Get config path failed: {str(e)}")
return None
@ -339,9 +344,9 @@ def get_email_from_sqlite(sqlite_path):
def display_account_info(translator=None):
"""display account info"""
print(f"\n{Fore.CYAN}{'' * 40}{Style.RESET_ALL}")
print(f"\n{Fore.CYAN}{'' * 70}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['USER']} {translator.get('account_info.title') if translator else 'Cursor Account Information'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'' * 40}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'' * 70}{Style.RESET_ALL}")
# get token
token = get_token()
@ -363,7 +368,11 @@ def display_account_info(translator=None):
email = get_email_from_sqlite(paths['sqlite_path'])
# get subscription info
subscription_info = UsageManager.get_stripe_profile(token)
try:
subscription_info = UsageManager.get_stripe_profile(token)
except Exception as e:
logger.error(f"Get subscription info failed: {str(e)}")
subscription_info = None
# if not found in storage and sqlite, try from subscription info
if not email and subscription_info:
@ -371,32 +380,43 @@ def display_account_info(translator=None):
if 'customer' in subscription_info and 'email' in subscription_info['customer']:
email = subscription_info['customer']['email']
# get usage info
usage_info = UsageManager.get_usage(token)
# get usage info - silently handle errors
try:
usage_info = UsageManager.get_usage(token)
except Exception as e:
logger.error(f"Get usage info failed: {str(e)}")
usage_info = None
# display account info
# Prepare left and right info
left_info = []
right_info = []
# Left side shows account info
if email:
print(f"{Fore.GREEN}{EMOJI['USER']} {translator.get('account_info.email') if translator else 'Email'}: {Fore.WHITE}{email}{Style.RESET_ALL}")
left_info.append(f"{Fore.GREEN}{EMOJI['USER']} {translator.get('account_info.email') if translator else 'Email'}: {Fore.WHITE}{email}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.email_not_found') if translator else 'Email not found'}{Style.RESET_ALL}")
left_info.append(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.email_not_found') if translator else 'Email not found'}{Style.RESET_ALL}")
# display subscription type
# Add an empty line
# left_info.append("")
# Show subscription type
if subscription_info:
subscription_type = format_subscription_type(subscription_info)
print(f"{Fore.GREEN}{EMOJI['SUBSCRIPTION']} {translator.get('account_info.subscription') if translator else 'Subscription'}: {Fore.WHITE}{subscription_type}{Style.RESET_ALL}")
left_info.append(f"{Fore.GREEN}{EMOJI['SUBSCRIPTION']} {translator.get('account_info.subscription') if translator else 'Subscription'}: {Fore.WHITE}{subscription_type}{Style.RESET_ALL}")
# display remaining trial days
# Show remaining trial days
days_remaining = subscription_info.get("daysRemainingOnTrial")
if days_remaining is not None and days_remaining > 0:
print(f"{Fore.GREEN}{EMOJI['TIME']} {translator.get('account_info.trial_remaining') if translator else 'Remaining Pro Trial'}: {Fore.WHITE}{days_remaining} {translator.get('account_info.days') if translator else 'days'}{Style.RESET_ALL}")
left_info.append(f"{Fore.GREEN}{EMOJI['TIME']} {translator.get('account_info.trial_remaining') if translator else 'Remaining Pro Trial'}: {Fore.WHITE}{days_remaining} {translator.get('account_info.days') if translator else 'days'}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.subscription_not_found') if translator else 'Subscription information not found'}{Style.RESET_ALL}")
left_info.append(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.subscription_not_found') if translator else 'Subscription information not found'}{Style.RESET_ALL}")
# display usage info
# Right side shows usage info - only if available
if usage_info:
print(f"\n{Fore.GREEN}{EMOJI['USAGE']} {translator.get('account_info.usage') if translator else 'Usage Statistics'}:{Style.RESET_ALL}")
right_info.append(f"{Fore.GREEN}{EMOJI['USAGE']} {translator.get('account_info.usage') if translator else 'Usage Statistics'}:{Style.RESET_ALL}")
# GPT-4 usage
# Premium usage
premium_usage = usage_info.get('premium_usage', 0)
max_premium_usage = usage_info.get('max_premium_usage', "No Limit")
@ -425,7 +445,7 @@ def display_account_info(translator=None):
premium_display = f"{premium_usage}/{max_premium_usage} ({premium_percentage:.1f}%)"
print(f"{Fore.YELLOW}{EMOJI['PREMIUM']} {translator.get('account_info.premium_usage') if translator else 'Fast Response'}: {premium_color}{premium_display}{Style.RESET_ALL}")
right_info.append(f"{Fore.YELLOW}{EMOJI['PREMIUM']} {translator.get('account_info.premium_usage') if translator else 'Fast Response'}: {premium_color}{premium_display}{Style.RESET_ALL}")
# Slow Response
basic_usage = usage_info.get('basic_usage', 0)
@ -456,11 +476,70 @@ def display_account_info(translator=None):
basic_display = f"{basic_usage}/{max_basic_usage} ({basic_percentage:.1f}%)"
print(f"{Fore.BLUE}{EMOJI['BASIC']} {translator.get('account_info.basic_usage') if translator else 'Slow Response'}: {basic_color}{basic_display}{Style.RESET_ALL}")
right_info.append(f"{Fore.BLUE}{EMOJI['BASIC']} {translator.get('account_info.basic_usage') if translator else 'Slow Response'}: {basic_color}{basic_display}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.usage_not_found') if translator else 'Usage information not found'}{Style.RESET_ALL}")
# if get usage info failed, only log in log, not show in interface
# you can choose to not show any usage info, or show a simple prompt
# right_info.append(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_info.usage_unavailable') if translator else 'Usage information unavailable'}{Style.RESET_ALL}")
pass # not show any usage info
print(f"{Fore.CYAN}{'' * 40}{Style.RESET_ALL}")
# Calculate the maximum display width of left info
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
def get_display_width(s):
"""Calculate the display width of a string, considering Chinese characters and emojis"""
# Remove ANSI color codes
clean_s = ansi_escape.sub('', s)
width = 0
for c in clean_s:
# Chinese characters and some emojis occupy two character widths
if ord(c) > 127:
width += 2
else:
width += 1
return width
max_left_width = 0
for item in left_info:
width = get_display_width(item)
max_left_width = max(max_left_width, width)
# Set the starting position of right info
fixed_spacing = 4 # Fixed spacing
right_start = max_left_width + fixed_spacing
# Calculate the number of spaces needed for right info
spaces_list = []
for i in range(len(left_info)):
if i < len(left_info):
left_item = left_info[i]
left_width = get_display_width(left_item)
spaces = right_start - left_width
spaces_list.append(spaces)
# Print info
max_rows = max(len(left_info), len(right_info))
for i in range(max_rows):
# Print left info
if i < len(left_info):
left_item = left_info[i]
print(left_item, end='')
# Use pre-calculated spaces
spaces = spaces_list[i]
else:
# If left side has no items, print only spaces
spaces = right_start
print('', end='')
# Print right info
if i < len(right_info):
print(' ' * spaces + right_info[i])
else:
print() # Change line
print(f"{Fore.CYAN}{'' * 70}{Style.RESET_ALL}")
def main(translator=None):
"""main function"""

View File

@ -5,6 +5,8 @@ import shutil
from colorama import Fore, Style, init
import subprocess
from config import get_config
import re
import tempfile
# Initialize colorama
init()
@ -54,6 +56,45 @@ class AutoUpdateDisabler:
}
self.update_yml_path = self.update_yml_paths.get(self.system)
def _change_main_js(self):
"""Change main.js"""
try:
main_path = get_config(self.translator).get('main_js_path', fallback=os.path.expanduser("~/.config/cursor/resources/app/main.js"))
original_stat = os.stat(main_path)
original_mode = original_stat.st_mode
original_uid = original_stat.st_uid
original_gid = original_stat.st_gid
with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file:
with open(main_path, "r", encoding="utf-8") as main_file:
content = main_file.read()
patterns = {
r"https://api2.cursor.sh/aiserver.v1.AuthService/DownloadUpdate": r"",
}
for pattern, replacement in patterns.items():
content = re.sub(pattern, replacement, content)
tmp_file.write(content)
tmp_path = tmp_file.name
shutil.copy2(main_path, main_path + ".old")
shutil.move(tmp_path, main_path)
os.chmod(main_path, original_mode)
if os.name != "nt":
os.chown(main_path, original_uid, original_gid)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.file_modified')}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}")
if "tmp_path" in locals():
os.unlink(tmp_path)
return False
def _kill_cursor_processes(self):
"""End all Cursor processes"""
try:
@ -181,6 +222,10 @@ class AutoUpdateDisabler:
if not self._create_blocking_file():
return False
# 5. Change main.js
if not self._change_main_js():
return False
print(f"{Fore.GREEN}{EMOJI['CHECK']} {self.translator.get('update.disable_success') if self.translator else '自动更新已禁用'}{Style.RESET_ALL}")
return True

View File

@ -22,7 +22,9 @@
"admin_required": "Running as executable, administrator privileges required.",
"admin_required_continue": "Continuing without administrator privileges.",
"coming_soon": "Coming Soon",
"fixed_soon": "Fixed Soon"
"fixed_soon": "Fixed Soon",
"contribute": "Contribute to the Project",
"config": "Show Config"
},
"languages": {
"en": "English",
@ -106,7 +108,10 @@
"version_too_low": "Cursor Version Too Low: {version} < 0.45.0",
"no_write_permission": "No Write Permission: {path}",
"path_not_found": "Path Not Found: {path}",
"modify_file_failed": "Modify File Failed: {error}"
"modify_file_failed": "Modify File Failed: {error}",
"windows_machine_id_updated": "Windows Machine ID Updated Successfully",
"update_windows_machine_id_failed": "Update Windows Machine ID Failed: {error}",
"update_windows_machine_guid_failed": "Update Windows Machine GUID Failed: {error}"
},
"register": {
"title": "Cursor Registration Tool",
@ -308,7 +313,8 @@
"update_skipped": "Skipping update.",
"invalid_choice": "Invalid choice. Please enter 'Y' or 'n'.",
"development_version": "Development Version {current} > {latest}",
"changelog_title": "Changelog"
"changelog_title": "Changelog",
"rate_limit_exceeded": "GitHub API rate limit exceeded. Skipping update check."
},
"totally_reset": {
"title": "Totally Reset Cursor",
@ -478,6 +484,15 @@
"active": "Active",
"inactive": "Inactive",
"premium_usage": "Premium Usage",
"basic_usage": "Basic Usage"
"basic_usage": "Basic Usage",
"usage_not_found": "Usage not found",
"lifetime_access_enabled": "Lifetime Access Enabled"
},
"config": {
"config_not_available": "Configuration not available",
"configuration": "Configuration",
"enabled": "Enabled",
"disabled": "Disabled",
"config_directory": "Config Directory"
}
}

View File

@ -22,7 +22,9 @@
"admin_required": "运行可执行文件,需要管理员权限",
"admin_required_continue": "继续使用当前版本...",
"coming_soon": "即将推出",
"fixed_soon": "即将修复"
"fixed_soon": "即将修复",
"contribute": "贡献项目",
"config": "显示配置"
},
"languages": {
"en": "英语",
@ -106,7 +108,10 @@
"version_too_low": "Cursor版本太低: {version} < 0.45.0",
"no_write_permission": "没有写入权限: {path}",
"path_not_found": "路径未找到: {path}",
"modify_file_failed": "修改文件失败: {error}"
"modify_file_failed": "修改文件失败: {error}",
"windows_machine_id_updated": "Windows机器ID更新成功",
"update_windows_machine_id_failed": "更新Windows机器ID失败: {error}",
"update_windows_machine_guid_failed": "更新Windows机器GUID失败: {error}"
},
"register": {
"title": "Cursor 注册工具",
@ -303,7 +308,8 @@
"update_skipped": "跳过更新。",
"invalid_choice": "选择无效。请输入 'Y' 或 'n'.",
"development_version": "开发版本 {current} > {latest}",
"changelog_title": "更新日志"
"changelog_title": "更新日志",
"rate_limit_exceeded": "GitHub API 速率限制超过。跳过更新检查。"
},
"totally_reset": {
"title": "完全重置 Cursor",
@ -473,7 +479,15 @@
"active": "活跃",
"inactive": "非活跃",
"premium_usage": "高级使用量",
"basic_usage": "基础使用量"
"basic_usage": "基础使用量",
"usage_not_found": "使用量未找到",
"lifetime_access_enabled": "永久访问已启用"
},
"config": {
"config_not_available": "配置未找到。",
"configuration": "配置",
"enabled": "已启用",
"disabled": "已禁用",
"config_directory": "配置目录"
}
}

View File

@ -20,7 +20,9 @@
"admin_required": "運行可執行文件,需要管理員權限",
"admin_required_continue": "繼續使用當前版本...",
"coming_soon": "即將推出",
"fixed_soon": "即將修復"
"fixed_soon": "即將修復",
"contribute": "貢獻項目",
"config": "顯示配置"
},
"languages": {
"en": "英文",
@ -104,7 +106,10 @@
"version_too_low": "Cursor版本太低: {version} < 0.45.0",
"no_write_permission": "沒有寫入權限: {path}",
"path_not_found": "路徑未找到: {path}",
"modify_file_failed": "修改文件失敗: {error}"
"modify_file_failed": "修改文件失敗: {error}",
"windows_machine_id_updated": "Windows機器ID更新成功",
"update_windows_machine_id_failed": "更新Windows機器ID失敗: {error}",
"update_windows_machine_guid_failed": "更新Windows機器GUID失敗: {error}"
},
"register": {
@ -282,7 +287,8 @@
"update_skipped": "跳過更新。",
"invalid_choice": "選擇無效。請輸入 'Y' 或 'n'.",
"development_version": "開發版本 {current} > {latest}",
"changelog_title": "更新日誌"
"changelog_title": "更新日誌",
"rate_limit_exceeded": "GitHub API 速率限制超過。跳過更新檢查。"
},
"totally_reset": {
"title": "完全重置 Cursor",
@ -452,6 +458,15 @@
"active": "活躍",
"inactive": "非活躍",
"premium_usage": "高級使用量",
"basic_usage": "基礎使用量"
"basic_usage": "基礎使用量",
"usage_not_found": "使用量未找到",
"lifetime_access_enabled": "永久訪問已啟用"
},
"config": {
"config_not_available": "配置未找到。",
"configuration": "配置",
"enabled": "已啟用",
"disabled": "已禁用",
"config_directory": "配置目錄"
}
}

View File

@ -94,7 +94,7 @@ CURSOR_OTHER_INFO = center_multiline_text(OTHER_INFO_TEXT, handle_chinese=True)
def print_logo():
print(CURSOR_LOGO)
print(CURSOR_DESCRIPTION)
print(CURSOR_CONTRIBUTORS)
# print(CURSOR_CONTRIBUTORS)
print(CURSOR_OTHER_INFO)
if __name__ == "__main__":

148
main.py
View File

@ -10,11 +10,13 @@ import platform
import requests
import subprocess
from config import get_config
import shutil
import re
# Only import windll on Windows systems
if platform.system() == 'Windows':
import ctypes
# 只在 Windows 上导入 windll
# Only import windll on Windows systems
from ctypes import windll
# Initialize colorama
@ -32,7 +34,13 @@ EMOJI = {
"ARROW": "",
"LANG": "🌐",
"UPDATE": "🔄",
"ADMIN": "🔐"
"ADMIN": "🔐",
"AIRDROP": "💰",
"ROCKET": "🚀",
"STAR": "",
"SUN": "🌟",
"CONTRIBUTE": "🤝",
"SETTINGS": "⚙️"
}
# Function to check if running as frozen executable
@ -243,27 +251,110 @@ translator = Translator()
def print_menu():
"""Print menu options"""
try:
import cursor_acc_info
cursor_acc_info.display_account_info(translator)
config = get_config()
if config.getboolean('Utils', 'enabled_account_info'):
import cursor_acc_info
cursor_acc_info.display_account_info(translator)
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.account_info_error', error=str(e))}{Style.RESET_ALL}")
print(f"\n{Fore.CYAN}{EMOJI['MENU']} {translator.get('menu.title')}:{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{'' * 40}{Style.RESET_ALL}")
print(f"{Fore.GREEN}0{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.exit')}")
print(f"{Fore.GREEN}1{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.reset')}")
print(f"{Fore.GREEN}2{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register')} ({Fore.RED}{translator.get('menu.outdate')}{Style.RESET_ALL})")
print(f"{Fore.GREEN}3{Style.RESET_ALL}. 🌟 {translator.get('menu.register_google')}")
print(f"{Fore.YELLOW} ┗━━ 🔥 {translator.get('menu.lifetime_access_enabled')} 🔥{Style.RESET_ALL}")
print(f"{Fore.GREEN}4{Style.RESET_ALL}. ⭐ {translator.get('menu.register_github')}")
print(f"{Fore.YELLOW} ┗━━ 🚀 {translator.get('menu.lifetime_access_enabled')} 🚀{Style.RESET_ALL}")
print(f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}")
print(f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.temp_github_register')}")
print(f"{Fore.GREEN}7{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}")
print(f"{Fore.GREEN}8{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}")
print(f"{Fore.GREEN}9{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}")
print(f"{Fore.GREEN}10{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.totally_reset')}")
print(f"{Fore.YELLOW}{'' * 40}{Style.RESET_ALL}")
if translator.current_language == 'zh_cn' or translator.current_language == 'zh_tw':
print(f"{Fore.YELLOW}{'' * 70}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{'' * 110}{Style.RESET_ALL}")
# Get terminal width
try:
terminal_width = shutil.get_terminal_size().columns
except:
terminal_width = 80 # Default width
# Define all menu items
menu_items = {
0: f"{Fore.GREEN}0{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.exit')}",
1: f"{Fore.GREEN}1{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.reset')}",
2: f"{Fore.GREEN}2{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register')} ({Fore.RED}{translator.get('menu.outdate')}{Style.RESET_ALL})",
3: f"{Fore.GREEN}3{Style.RESET_ALL}. {EMOJI['SUN']} {translator.get('menu.register_google')} {EMOJI['ROCKET']} ({Fore.YELLOW}{translator.get('menu.lifetime_access_enabled')}{Style.RESET_ALL})",
4: f"{Fore.GREEN}4{Style.RESET_ALL}. {EMOJI['STAR']} {translator.get('menu.register_github')} {EMOJI['ROCKET']} ({Fore.YELLOW}{translator.get('menu.lifetime_access_enabled')}{Style.RESET_ALL})",
5: f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}",
6: f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.temp_github_register')}",
7: f"{Fore.GREEN}7{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}",
8: f"{Fore.GREEN}8{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}",
9: f"{Fore.GREEN}9{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}",
10: f"{Fore.GREEN}10{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.totally_reset')}",
11: f"{Fore.GREEN}11{Style.RESET_ALL}. {EMOJI['CONTRIBUTE']} {translator.get('menu.contribute')}",
12: f"{Fore.GREEN}12{Style.RESET_ALL}. {EMOJI['SETTINGS']} {translator.get('menu.config')}"
}
# Automatically calculate the number of menu items in the left and right columns
total_items = len(menu_items)
left_column_count = (total_items + 1) // 2 # The number of options displayed on the left (rounded up)
# Build left and right columns of menus
sorted_indices = sorted(menu_items.keys())
left_menu = [menu_items[i] for i in sorted_indices[:left_column_count]]
right_menu = [menu_items[i] for i in sorted_indices[left_column_count:]]
# Calculate the maximum display width of left menu items
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
def get_display_width(s):
"""Calculate the display width of a string, considering Chinese characters and emojis"""
# Remove ANSI color codes
clean_s = ansi_escape.sub('', s)
width = 0
for c in clean_s:
# Chinese characters and some emojis occupy two character widths
if ord(c) > 127:
width += 2
else:
width += 1
return width
max_left_width = 0
for item in left_menu:
width = get_display_width(item)
max_left_width = max(max_left_width, width)
# Set the starting position of right menu
fixed_spacing = 4 # Fixed spacing
right_start = max_left_width + fixed_spacing
# Calculate the number of spaces needed for right menu items
spaces_list = []
for i in range(len(left_menu)):
if i < len(left_menu):
left_item = left_menu[i]
left_width = get_display_width(left_item)
spaces = right_start - left_width
spaces_list.append(spaces)
# Print menu items
max_rows = max(len(left_menu), len(right_menu))
for i in range(max_rows):
# Print left menu items
if i < len(left_menu):
left_item = left_menu[i]
print(left_item, end='')
# Use pre-calculated spaces
spaces = spaces_list[i]
else:
# If left side has no items, print only spaces
spaces = right_start
print('', end='')
# Print right menu items
if i < len(right_menu):
print(' ' * spaces + right_menu[i])
else:
print() # Change line
if translator.current_language == 'zh_cn' or translator.current_language == 'zh_tw':
print(f"{Fore.YELLOW}{'' * 70}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{'' * 110}{Style.RESET_ALL}")
def select_language():
"""Language selection menu"""
@ -303,6 +394,11 @@ def check_latest_version():
timeout=10
)
# Check if rate limit exceeded
if response.status_code == 403 and "rate limit exceeded" in response.text.lower():
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.rate_limit_exceeded', fallback='GitHub API rate limit exceeded. Skipping update check.')}{Style.RESET_ALL}")
return
# Check if response is successful
if response.status_code != 200:
raise Exception(f"GitHub API returned status code {response.status_code}")
@ -454,12 +550,14 @@ def main():
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.config_init_failed')}{Style.RESET_ALL}")
return
check_latest_version() # Add version check before showing menu
if config.getboolean('Utils', 'enabled_update_check'):
check_latest_version() # Add version check before showing menu
print_menu()
while True:
try:
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices='0-10')}: {Style.RESET_ALL}")
choice_num = 12
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices=f'0-{choice_num}')}: {Style.RESET_ALL}")
if choice == "0":
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.exit')}...{Style.RESET_ALL}")
@ -507,6 +605,14 @@ def main():
totally_reset_cursor.run(translator)
# print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.fixed_soon')}{Style.RESET_ALL}")
print_menu()
elif choice == "11":
import logo
print(logo.CURSOR_CONTRIBUTORS)
print_menu()
elif choice == "12":
from config import print_config
print_config(get_config(), translator)
print_menu()
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")
print_menu()

View File

@ -25,9 +25,10 @@ EMOJI = {
}
class OAuthHandler:
def __init__(self, translator=None):
def __init__(self, translator=None, auth_type=None):
self.translator = translator
self.config = get_config(translator)
self.auth_type = auth_type # make sure the auth_type is not None
os.environ['BROWSER_HEADLESS'] = 'False'
self.browser = None
@ -380,7 +381,7 @@ class OAuthHandler:
if self._delete_current_account():
# Start new authentication based on auth type
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new authentication process...{Style.RESET_ALL}")
if auth_type == "google":
if self.auth_type == "google":
return self.handle_google_auth()
else: # github
return self.handle_github_auth()
@ -839,7 +840,7 @@ def main(auth_type, translator=None):
auth_type (str): Type of authentication ('google' or 'github')
translator: Translator instance for internationalization
"""
handler = OAuthHandler(translator)
handler = OAuthHandler(translator, auth_type)
if auth_type.lower() == 'google':
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.google_start')}{Style.RESET_ALL}")

View File

@ -25,7 +25,8 @@ EMOJI = {
"SUCCESS": "",
"ERROR": "",
"INFO": "",
"RESET": "🔄",
"RESET": "<EFBFBD><EFBFBD>",
"WARNING": "⚠️",
}
def get_cursor_paths(translator=None) -> Tuple[str, str]:
@ -576,6 +577,7 @@ class MachineIDResetter:
if sys.platform.startswith("win"):
self._update_windows_machine_guid()
self._update_windows_machine_id()
elif sys.platform == "darwin":
self._update_macos_platform_uuid(new_ids)
@ -606,6 +608,45 @@ class MachineIDResetter:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_guid_failed', error=str(e))}{Style.RESET_ALL}")
raise
def _update_windows_machine_id(self):
"""Update Windows MachineId in SQMClient registry"""
try:
import winreg
# 1. Generate new GUID
new_guid = str(uuid.uuid4()).upper()
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('reset.new_machine_id')}: {new_guid}{Style.RESET_ALL}")
# 2. Open the registry key
try:
key = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\SQMClient",
0,
winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY
)
except FileNotFoundError:
# If the key does not exist, create it
key = winreg.CreateKey(
winreg.HKEY_LOCAL_MACHINE,
r"SOFTWARE\Microsoft\SQMClient"
)
# 3. Set MachineId value
winreg.SetValueEx(key, "MachineId", 0, winreg.REG_SZ, new_guid)
winreg.CloseKey(key)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.windows_machine_id_updated')}{Style.RESET_ALL}")
return True
except PermissionError:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.permission_denied')}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('reset.run_as_admin')}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.update_windows_machine_id_failed', error=str(e))}{Style.RESET_ALL}")
return False
def _update_macos_platform_uuid(self, new_ids):
"""Update macOS Platform UUID"""
try: