mirror of
https://github.com/yeongpin/cursor-free-vip.git
synced 2025-08-03 04:57:36 +08:00
Update version to 1.7.19, add Cursor Account Info feature, and enhance configuration handling for updater paths. Update CHANGELOG.md with new features and fixes.
This commit is contained in:
parent
bd9e10056f
commit
4587fd9373
@ -1,5 +1,11 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## v1.7.19
|
||||||
|
1. Add: Cursor Account Info | 增加 Cursor 賬號信息
|
||||||
|
2. Fix: Disable Auto Update | 修復禁用自動更新
|
||||||
|
3. Add: 0.48.x Version Support | 增加 0.48.x 版本支持
|
||||||
|
4. Fix: Some Issues | 修復一些問題
|
||||||
|
|
||||||
## v1.7.18
|
## v1.7.18
|
||||||
1. Fix: No Write Permission | 修復沒有寫入權限
|
1. Fix: No Write Permission | 修復沒有寫入權限
|
||||||
2. Fix: Improve Linux path detection and config handling | 修正 linux 路徑和config寫入讀取
|
2. Fix: Improve Linux path detection and config handling | 修正 linux 路徑和config寫入讀取
|
||||||
|
@ -49,7 +49,8 @@ def setup_config(translator=None):
|
|||||||
'sqlite_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb"),
|
'sqlite_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb"),
|
||||||
'machine_id_path': os.path.join(appdata, "Cursor", "machineId"),
|
'machine_id_path': os.path.join(appdata, "Cursor", "machineId"),
|
||||||
'cursor_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app"),
|
'cursor_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app"),
|
||||||
'updater_path': os.path.join(localappdata, "cursor-updater")
|
'updater_path': os.path.join(localappdata, "cursor-updater"),
|
||||||
|
'update_yml_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app", "update.yml")
|
||||||
}
|
}
|
||||||
elif sys.platform == "darwin":
|
elif sys.platform == "darwin":
|
||||||
default_config['MacPaths'] = {
|
default_config['MacPaths'] = {
|
||||||
@ -57,7 +58,8 @@ def setup_config(translator=None):
|
|||||||
'sqlite_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/state.vscdb")),
|
'sqlite_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/state.vscdb")),
|
||||||
'machine_id_path': os.path.expanduser("~/Library/Application Support/Cursor/machineId"),
|
'machine_id_path': os.path.expanduser("~/Library/Application Support/Cursor/machineId"),
|
||||||
'cursor_path': "/Applications/Cursor.app/Contents/Resources/app",
|
'cursor_path': "/Applications/Cursor.app/Contents/Resources/app",
|
||||||
'updater_path': os.path.expanduser("~/Library/Application Support/cursor-updater")
|
'updater_path': os.path.expanduser("~/Library/Application Support/cursor-updater"),
|
||||||
|
'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml"
|
||||||
}
|
}
|
||||||
elif sys.platform == "linux":
|
elif sys.platform == "linux":
|
||||||
sudo_user = os.environ.get('SUDO_USER')
|
sudo_user = os.environ.get('SUDO_USER')
|
||||||
@ -68,7 +70,8 @@ def setup_config(translator=None):
|
|||||||
'sqlite_path': os.path.abspath(os.path.join(actual_home, ".config/cursor/User/globalStorage/state.vscdb")),
|
'sqlite_path': os.path.abspath(os.path.join(actual_home, ".config/cursor/User/globalStorage/state.vscdb")),
|
||||||
'machine_id_path': os.path.expanduser("~/.config/cursor/machineid"),
|
'machine_id_path': os.path.expanduser("~/.config/cursor/machineid"),
|
||||||
'cursor_path': get_linux_cursor_path(),
|
'cursor_path': get_linux_cursor_path(),
|
||||||
'updater_path': os.path.expanduser("~/.config/cursor-updater")
|
'updater_path': os.path.expanduser("~/.config/cursor-updater"),
|
||||||
|
'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Read existing configuration and merge
|
# Read existing configuration and merge
|
||||||
|
469
cursor_acc_info.py
Normal file
469
cursor_acc_info.py
Normal file
@ -0,0 +1,469 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import sqlite3
|
||||||
|
from typing import Dict, Optional
|
||||||
|
import platform
|
||||||
|
from colorama import Fore, Style, init
|
||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
# Initialize colorama
|
||||||
|
init()
|
||||||
|
|
||||||
|
# Setup logger
|
||||||
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Define emoji constants
|
||||||
|
EMOJI = {
|
||||||
|
"USER": "👤",
|
||||||
|
"USAGE": "📊",
|
||||||
|
"PREMIUM": "⭐",
|
||||||
|
"BASIC": "📝",
|
||||||
|
"SUBSCRIPTION": "💳",
|
||||||
|
"INFO": "ℹ️",
|
||||||
|
"ERROR": "❌",
|
||||||
|
"SUCCESS": "✅",
|
||||||
|
"WARNING": "⚠️",
|
||||||
|
"TIME": "🕒"
|
||||||
|
}
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
"""Config"""
|
||||||
|
NAME_LOWER = "cursor"
|
||||||
|
NAME_CAPITALIZE = "Cursor"
|
||||||
|
BASE_HEADERS = {
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
|
||||||
|
"Accept": "application/json",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
class UsageManager:
|
||||||
|
"""Usage Manager"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_proxy():
|
||||||
|
"""get proxy"""
|
||||||
|
# from config import get_config
|
||||||
|
proxy = os.environ.get("HTTP_PROXY") or os.environ.get("HTTPS_PROXY")
|
||||||
|
if proxy:
|
||||||
|
return {"http": proxy, "https": proxy}
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_usage(token: str) -> Optional[Dict]:
|
||||||
|
"""get usage"""
|
||||||
|
url = f"https://www.{Config.NAME_LOWER}.com/api/usage"
|
||||||
|
headers = Config.BASE_HEADERS.copy()
|
||||||
|
headers.update({"Cookie": f"Workos{Config.NAME_CAPITALIZE}SessionToken=user_01OOOOOOOOOOOOOOOOOOOOOOOO%3A%3A{token}"})
|
||||||
|
try:
|
||||||
|
proxies = UsageManager.get_proxy()
|
||||||
|
response = requests.get(url, headers=headers, timeout=10, proxies=proxies)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
# 获取 GPT-4 使用量和限制
|
||||||
|
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"
|
||||||
|
gpt35_data = data.get("gpt-3.5-turbo", {})
|
||||||
|
basic_usage = gpt35_data.get("numRequestsTotal", 0)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'premium_usage': premium_usage,
|
||||||
|
'max_premium_usage': max_premium_usage,
|
||||||
|
'basic_usage': basic_usage,
|
||||||
|
'max_basic_usage': "No Limit" # 将 GPT-3.5 的限制设为 "No Limit"
|
||||||
|
}
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"获取使用量失败: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_stripe_profile(token: str) -> Optional[Dict]:
|
||||||
|
"""get user subscription info"""
|
||||||
|
url = f"https://api2.{Config.NAME_LOWER}.sh/auth/full_stripe_profile"
|
||||||
|
headers = Config.BASE_HEADERS.copy()
|
||||||
|
headers.update({"Authorization": f"Bearer {token}"})
|
||||||
|
try:
|
||||||
|
proxies = UsageManager.get_proxy()
|
||||||
|
response = requests.get(url, headers=headers, timeout=10, proxies=proxies)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
except requests.RequestException as e:
|
||||||
|
logger.error(f"获取订阅信息失败: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_token_from_config():
|
||||||
|
"""get path info from config"""
|
||||||
|
try:
|
||||||
|
from config import get_config
|
||||||
|
config = get_config()
|
||||||
|
if not config:
|
||||||
|
return None
|
||||||
|
|
||||||
|
system = platform.system()
|
||||||
|
if system == "Windows" and config.has_section('WindowsPaths'):
|
||||||
|
return {
|
||||||
|
'storage_path': config.get('WindowsPaths', 'storage_path'),
|
||||||
|
'sqlite_path': config.get('WindowsPaths', 'sqlite_path'),
|
||||||
|
'session_path': os.path.join(os.getenv("APPDATA"), "Cursor", "Session Storage")
|
||||||
|
}
|
||||||
|
elif system == "Darwin" and config.has_section('MacPaths'): # macOS
|
||||||
|
return {
|
||||||
|
'storage_path': config.get('MacPaths', 'storage_path'),
|
||||||
|
'sqlite_path': config.get('MacPaths', 'sqlite_path'),
|
||||||
|
'session_path': os.path.expanduser("~/Library/Application Support/Cursor/Session Storage")
|
||||||
|
}
|
||||||
|
elif system == "Linux" and config.has_section('LinuxPaths'):
|
||||||
|
return {
|
||||||
|
'storage_path': config.get('LinuxPaths', 'storage_path'),
|
||||||
|
'sqlite_path': config.get('LinuxPaths', 'sqlite_path'),
|
||||||
|
'session_path': os.path.expanduser("~/.config/Cursor/Session Storage")
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取配置路径失败: {str(e)}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_token_from_storage(storage_path):
|
||||||
|
"""get token from storage.json"""
|
||||||
|
if not os.path.exists(storage_path):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(storage_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
# try to get accessToken
|
||||||
|
if 'cursorAuth/accessToken' in data:
|
||||||
|
return data['cursorAuth/accessToken']
|
||||||
|
|
||||||
|
# try other possible keys
|
||||||
|
for key in data:
|
||||||
|
if 'token' in key.lower() and isinstance(data[key], str) and len(data[key]) > 20:
|
||||||
|
return data[key]
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"get token from storage.json failed: {str(e)}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_token_from_sqlite(sqlite_path):
|
||||||
|
"""get token from sqlite"""
|
||||||
|
if not os.path.exists(sqlite_path):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(sqlite_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT value FROM ItemTable WHERE key LIKE '%token%'")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
try:
|
||||||
|
value = row[0]
|
||||||
|
if isinstance(value, str) and len(value) > 20:
|
||||||
|
return value
|
||||||
|
# try to parse JSON
|
||||||
|
data = json.loads(value)
|
||||||
|
if isinstance(data, dict) and 'token' in data:
|
||||||
|
return data['token']
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"get token from sqlite failed: {str(e)}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_token_from_session(session_path):
|
||||||
|
"""get token from session"""
|
||||||
|
if not os.path.exists(session_path):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# try to find all possible session files
|
||||||
|
for file in os.listdir(session_path):
|
||||||
|
if file.endswith('.log'):
|
||||||
|
file_path = os.path.join(session_path, file)
|
||||||
|
try:
|
||||||
|
with open(file_path, 'rb') as f:
|
||||||
|
content = f.read().decode('utf-8', errors='ignore')
|
||||||
|
# find token pattern
|
||||||
|
token_match = re.search(r'"token":"([^"]+)"', content)
|
||||||
|
if token_match:
|
||||||
|
return token_match.group(1)
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"get token from session failed: {str(e)}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_token():
|
||||||
|
"""get Cursor token"""
|
||||||
|
# get path from config
|
||||||
|
paths = get_token_from_config()
|
||||||
|
if not paths:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# try to get token from different locations
|
||||||
|
token = get_token_from_storage(paths['storage_path'])
|
||||||
|
if token:
|
||||||
|
return token
|
||||||
|
|
||||||
|
token = get_token_from_sqlite(paths['sqlite_path'])
|
||||||
|
if token:
|
||||||
|
return token
|
||||||
|
|
||||||
|
token = get_token_from_session(paths['session_path'])
|
||||||
|
if token:
|
||||||
|
return token
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def format_subscription_type(subscription_data: Dict) -> str:
|
||||||
|
"""format subscription type"""
|
||||||
|
if not subscription_data:
|
||||||
|
return "Free"
|
||||||
|
|
||||||
|
# handle new API response format
|
||||||
|
if "membershipType" in subscription_data:
|
||||||
|
membership_type = subscription_data.get("membershipType", "").lower()
|
||||||
|
subscription_status = subscription_data.get("subscriptionStatus", "").lower()
|
||||||
|
|
||||||
|
if subscription_status == "active":
|
||||||
|
if membership_type == "pro":
|
||||||
|
return "Pro"
|
||||||
|
elif membership_type == "pro_trial":
|
||||||
|
return "Pro Trial"
|
||||||
|
elif membership_type == "team":
|
||||||
|
return "Team"
|
||||||
|
elif membership_type == "enterprise":
|
||||||
|
return "Enterprise"
|
||||||
|
elif membership_type:
|
||||||
|
return membership_type.capitalize()
|
||||||
|
else:
|
||||||
|
return "Active Subscription"
|
||||||
|
elif subscription_status:
|
||||||
|
return f"{membership_type.capitalize()} ({subscription_status})"
|
||||||
|
|
||||||
|
# compatible with old API response format
|
||||||
|
subscription = subscription_data.get("subscription")
|
||||||
|
if subscription:
|
||||||
|
plan = subscription.get("plan", {}).get("nickname", "Unknown")
|
||||||
|
status = subscription.get("status", "unknown")
|
||||||
|
|
||||||
|
if status == "active":
|
||||||
|
if "pro" in plan.lower():
|
||||||
|
return "Pro"
|
||||||
|
elif "pro_trial" in plan.lower():
|
||||||
|
return "Pro Trial"
|
||||||
|
elif "team" in plan.lower():
|
||||||
|
return "Team"
|
||||||
|
elif "enterprise" in plan.lower():
|
||||||
|
return "Enterprise"
|
||||||
|
else:
|
||||||
|
return plan
|
||||||
|
else:
|
||||||
|
return f"{plan} ({status})"
|
||||||
|
|
||||||
|
return "Free"
|
||||||
|
|
||||||
|
def get_email_from_storage(storage_path):
|
||||||
|
"""get email from storage.json"""
|
||||||
|
if not os.path.exists(storage_path):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(storage_path, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
# try to get email
|
||||||
|
if 'cursorAuth/cachedEmail' in data:
|
||||||
|
return data['cursorAuth/cachedEmail']
|
||||||
|
|
||||||
|
# try other possible keys
|
||||||
|
for key in data:
|
||||||
|
if 'email' in key.lower() and isinstance(data[key], str) and '@' in data[key]:
|
||||||
|
return data[key]
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"get email from storage.json failed: {str(e)}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_email_from_sqlite(sqlite_path):
|
||||||
|
"""get email from sqlite"""
|
||||||
|
if not os.path.exists(sqlite_path):
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(sqlite_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
# try to query records containing email
|
||||||
|
cursor.execute("SELECT value FROM ItemTable WHERE key LIKE '%email%' OR key LIKE '%cursorAuth%'")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
for row in rows:
|
||||||
|
try:
|
||||||
|
value = row[0]
|
||||||
|
# if it's a string and contains @, it might be an email
|
||||||
|
if isinstance(value, str) and '@' in value:
|
||||||
|
return value
|
||||||
|
|
||||||
|
# try to parse JSON
|
||||||
|
try:
|
||||||
|
data = json.loads(value)
|
||||||
|
if isinstance(data, dict):
|
||||||
|
# check if there's an email field
|
||||||
|
if 'email' in data:
|
||||||
|
return data['email']
|
||||||
|
# check if there's a cachedEmail field
|
||||||
|
if 'cachedEmail' in data:
|
||||||
|
return data['cachedEmail']
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"get email from sqlite failed: {str(e)}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def display_account_info(translator=None):
|
||||||
|
"""display account info"""
|
||||||
|
print(f"\n{Fore.CYAN}{'─' * 40}{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}")
|
||||||
|
|
||||||
|
# get token
|
||||||
|
token = get_token()
|
||||||
|
if not token:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_info.token_not_found') if translator else 'Token not found. Please login to Cursor first.'}{Style.RESET_ALL}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# get path info
|
||||||
|
paths = get_token_from_config()
|
||||||
|
if not paths:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_info.config_not_found') if translator else 'Configuration not found.'}{Style.RESET_ALL}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# get email info - try multiple sources
|
||||||
|
email = get_email_from_storage(paths['storage_path'])
|
||||||
|
|
||||||
|
# if not found in storage, try from sqlite
|
||||||
|
if not email:
|
||||||
|
email = get_email_from_sqlite(paths['sqlite_path'])
|
||||||
|
|
||||||
|
# get subscription info
|
||||||
|
subscription_info = UsageManager.get_stripe_profile(token)
|
||||||
|
|
||||||
|
# if not found in storage and sqlite, try from subscription info
|
||||||
|
if not email and subscription_info:
|
||||||
|
# try to get email from subscription info
|
||||||
|
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)
|
||||||
|
|
||||||
|
# display 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}")
|
||||||
|
else:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.email_not_found') if translator else 'Email not found'}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# display 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}")
|
||||||
|
|
||||||
|
# display 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}")
|
||||||
|
else:
|
||||||
|
print(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
|
||||||
|
if usage_info:
|
||||||
|
print(f"\n{Fore.GREEN}{EMOJI['USAGE']} {translator.get('account_info.usage') if translator else 'Usage Statistics'}:{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# GPT-4 usage
|
||||||
|
premium_usage = usage_info.get('premium_usage', 0)
|
||||||
|
max_premium_usage = usage_info.get('max_premium_usage', "No Limit")
|
||||||
|
|
||||||
|
# make sure the value is not None
|
||||||
|
if premium_usage is None:
|
||||||
|
premium_usage = 0
|
||||||
|
|
||||||
|
# handle "No Limit" case
|
||||||
|
if isinstance(max_premium_usage, str) and max_premium_usage == "No Limit":
|
||||||
|
premium_color = Fore.GREEN # when there is no limit, use green
|
||||||
|
premium_display = f"{premium_usage}/{max_premium_usage}"
|
||||||
|
else:
|
||||||
|
# calculate percentage when the value is a number
|
||||||
|
if max_premium_usage is None or max_premium_usage == 0:
|
||||||
|
max_premium_usage = 999
|
||||||
|
premium_percentage = 0
|
||||||
|
else:
|
||||||
|
premium_percentage = (premium_usage / max_premium_usage) * 100
|
||||||
|
|
||||||
|
# select color based on usage percentage
|
||||||
|
premium_color = Fore.GREEN
|
||||||
|
if premium_percentage > 70:
|
||||||
|
premium_color = Fore.YELLOW
|
||||||
|
if premium_percentage > 90:
|
||||||
|
premium_color = Fore.RED
|
||||||
|
|
||||||
|
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}")
|
||||||
|
|
||||||
|
# Slow Response
|
||||||
|
basic_usage = usage_info.get('basic_usage', 0)
|
||||||
|
max_basic_usage = usage_info.get('max_basic_usage', "No Limit")
|
||||||
|
|
||||||
|
# make sure the value is not None
|
||||||
|
if basic_usage is None:
|
||||||
|
basic_usage = 0
|
||||||
|
|
||||||
|
# handle "No Limit" case
|
||||||
|
if isinstance(max_basic_usage, str) and max_basic_usage == "No Limit":
|
||||||
|
basic_color = Fore.GREEN # when there is no limit, use green
|
||||||
|
basic_display = f"{basic_usage}/{max_basic_usage}"
|
||||||
|
else:
|
||||||
|
# calculate percentage when the value is a number
|
||||||
|
if max_basic_usage is None or max_basic_usage == 0:
|
||||||
|
max_basic_usage = 999
|
||||||
|
basic_percentage = 0
|
||||||
|
else:
|
||||||
|
basic_percentage = (basic_usage / max_basic_usage) * 100
|
||||||
|
|
||||||
|
# select color based on usage percentage
|
||||||
|
basic_color = Fore.GREEN
|
||||||
|
if basic_percentage > 70:
|
||||||
|
basic_color = Fore.YELLOW
|
||||||
|
if basic_percentage > 90:
|
||||||
|
basic_color = Fore.RED
|
||||||
|
|
||||||
|
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}")
|
||||||
|
else:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('account_info.usage_not_found') if translator else 'Usage information not found'}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
print(f"{Fore.CYAN}{'─' * 40}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
def main(translator=None):
|
||||||
|
"""main function"""
|
||||||
|
try:
|
||||||
|
display_account_info(translator)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_info.error') if translator else 'Error'}: {str(e)}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -31,10 +31,13 @@ class AutoUpdateDisabler:
|
|||||||
if config:
|
if config:
|
||||||
if self.system == "Windows":
|
if self.system == "Windows":
|
||||||
self.updater_path = config.get('WindowsPaths', 'updater_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"))
|
self.updater_path = config.get('WindowsPaths', 'updater_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"))
|
||||||
|
self.update_yml_path = config.get('WindowsPaths', 'update_yml_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "update.yml"))
|
||||||
elif self.system == "Darwin":
|
elif self.system == "Darwin":
|
||||||
self.updater_path = config.get('MacPaths', 'updater_path', fallback=os.path.expanduser("~/Library/Application Support/cursor-updater"))
|
self.updater_path = config.get('MacPaths', 'updater_path', fallback=os.path.expanduser("~/Library/Application Support/cursor-updater"))
|
||||||
|
self.update_yml_path = config.get('MacPaths', 'update_yml_path', fallback="/Applications/Cursor.app/Contents/Resources/app-update.yml")
|
||||||
elif self.system == "Linux":
|
elif self.system == "Linux":
|
||||||
self.updater_path = config.get('LinuxPaths', 'updater_path', fallback=os.path.expanduser("~/.config/cursor-updater"))
|
self.updater_path = config.get('LinuxPaths', 'updater_path', fallback=os.path.expanduser("~/.config/cursor-updater"))
|
||||||
|
self.update_yml_path = config.get('LinuxPaths', 'update_yml_path', fallback=os.path.expanduser("~/.config/cursor/resources/app-update.yml"))
|
||||||
else:
|
else:
|
||||||
# If configuration loading fails, use default paths
|
# If configuration loading fails, use default paths
|
||||||
self.updater_paths = {
|
self.updater_paths = {
|
||||||
@ -43,6 +46,13 @@ class AutoUpdateDisabler:
|
|||||||
"Linux": os.path.expanduser("~/.config/cursor-updater")
|
"Linux": os.path.expanduser("~/.config/cursor-updater")
|
||||||
}
|
}
|
||||||
self.updater_path = self.updater_paths.get(self.system)
|
self.updater_path = self.updater_paths.get(self.system)
|
||||||
|
|
||||||
|
self.update_yml_paths = {
|
||||||
|
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "update.yml"),
|
||||||
|
"Darwin": "/Applications/Cursor.app/Contents/Resources/app-update.yml",
|
||||||
|
"Linux": os.path.expanduser("~/.config/cursor/resources/app-update.yml")
|
||||||
|
}
|
||||||
|
self.update_yml_path = self.update_yml_paths.get(self.system)
|
||||||
|
|
||||||
def _kill_cursor_processes(self):
|
def _kill_cursor_processes(self):
|
||||||
"""End all Cursor processes"""
|
"""End all Cursor processes"""
|
||||||
@ -82,26 +92,68 @@ class AutoUpdateDisabler:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.remove_directory_failed', error=str(e)) if self.translator else f'删除目录失败: {e}'}{Style.RESET_ALL}")
|
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.remove_directory_failed', error=str(e)) if self.translator else f'删除目录失败: {e}'}{Style.RESET_ALL}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def _clear_update_yml_file(self):
|
||||||
|
"""Clear update.yml file"""
|
||||||
|
try:
|
||||||
|
update_yml_path = self.update_yml_path
|
||||||
|
if not update_yml_path:
|
||||||
|
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
|
||||||
|
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.clearing_update_yml') if self.translator else '正在清空更新配置文件...'}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
if os.path.exists(update_yml_path):
|
||||||
|
# 清空文件内容
|
||||||
|
with open(update_yml_path, 'w') as f:
|
||||||
|
f.write('')
|
||||||
|
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.update_yml_cleared') if self.translator else '更新配置文件已清空'}{Style.RESET_ALL}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.update_yml_not_found') if self.translator else '更新配置文件不存在'}{Style.RESET_ALL}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.clear_update_yml_failed', error=str(e)) if self.translator else f'清空更新配置文件失败: {e}'}{Style.RESET_ALL}")
|
||||||
|
return False
|
||||||
|
|
||||||
def _create_blocking_file(self):
|
def _create_blocking_file(self):
|
||||||
"""Create blocking file"""
|
"""Create blocking files"""
|
||||||
try:
|
try:
|
||||||
|
# 检查 updater_path
|
||||||
updater_path = self.updater_path
|
updater_path = self.updater_path
|
||||||
if not updater_path:
|
if not updater_path:
|
||||||
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
|
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
|
||||||
|
|
||||||
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.creating_block_file') if self.translator else '正在创建阻止文件...'}{Style.RESET_ALL}")
|
print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.creating_block_file') if self.translator else '正在创建阻止文件...'}{Style.RESET_ALL}")
|
||||||
|
|
||||||
# Create empty file
|
# 创建 updater_path 阻止文件
|
||||||
|
os.makedirs(os.path.dirname(updater_path), exist_ok=True)
|
||||||
open(updater_path, 'w').close()
|
open(updater_path, 'w').close()
|
||||||
|
|
||||||
# Set read-only attribute
|
# 设置 updater_path 为只读
|
||||||
if self.system == "Windows":
|
if self.system == "Windows":
|
||||||
os.system(f'attrib +r "{updater_path}"')
|
os.system(f'attrib +r "{updater_path}"')
|
||||||
else:
|
else:
|
||||||
os.chmod(updater_path, 0o444) # Set to read-only
|
os.chmod(updater_path, 0o444) # 设置为只读
|
||||||
|
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.block_file_created') if self.translator else '阻止文件已创建'}: {updater_path}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# 检查 update_yml_path
|
||||||
|
update_yml_path = self.update_yml_path
|
||||||
|
if update_yml_path and os.path.exists(os.path.dirname(update_yml_path)):
|
||||||
|
# 创建 update_yml_path 阻止文件
|
||||||
|
with open(update_yml_path, 'w') as f:
|
||||||
|
f.write('# This file is locked to prevent auto-updates\nversion: 0.0.0\n')
|
||||||
|
|
||||||
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.block_file_created') if self.translator else '阻止文件已创建'}{Style.RESET_ALL}")
|
# 设置 update_yml_path 为只读
|
||||||
|
if self.system == "Windows":
|
||||||
|
os.system(f'attrib +r "{update_yml_path}"')
|
||||||
|
else:
|
||||||
|
os.chmod(update_yml_path, 0o444) # 设置为只读
|
||||||
|
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.yml_locked') if self.translator else '更新配置文件已锁定'}: {update_yml_path}{Style.RESET_ALL}")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -121,7 +173,11 @@ class AutoUpdateDisabler:
|
|||||||
if not self._remove_updater_directory():
|
if not self._remove_updater_directory():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 3. Create blocking file
|
# 3. Clear update.yml file
|
||||||
|
if not self._clear_update_yml_file():
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 4. Create blocking file
|
||||||
if not self._create_blocking_file():
|
if not self._create_blocking_file():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -287,7 +287,12 @@
|
|||||||
"removing_directory": "Removing Directory",
|
"removing_directory": "Removing Directory",
|
||||||
"directory_removed": "Directory Removed",
|
"directory_removed": "Directory Removed",
|
||||||
"creating_block_file": "Creating Block File",
|
"creating_block_file": "Creating Block File",
|
||||||
"block_file_created": "Block File Created"
|
"block_file_created": "Block File Created",
|
||||||
|
"clearing_update_yml": "Clearing update.yml file",
|
||||||
|
"update_yml_cleared": "update.yml file cleared",
|
||||||
|
"update_yml_not_found": "update.yml file not found",
|
||||||
|
"clear_update_yml_failed": "Failed to clear update.yml file: {error}",
|
||||||
|
"unsupported_os": "Unsupported OS: {system}"
|
||||||
},
|
},
|
||||||
"updater": {
|
"updater": {
|
||||||
"checking": "Checking for updates...",
|
"checking": "Checking for updates...",
|
||||||
@ -440,5 +445,36 @@
|
|||||||
"completed_successfully": "GitHub + Cursor registration completed successfully!",
|
"completed_successfully": "GitHub + Cursor registration completed successfully!",
|
||||||
"registration_encountered_issues": "GitHub + Cursor registration encountered issues.",
|
"registration_encountered_issues": "GitHub + Cursor registration encountered issues.",
|
||||||
"check_browser_windows_for_manual_intervention_or_try_again_later": "Check browser windows for manual intervention or try again later."
|
"check_browser_windows_for_manual_intervention_or_try_again_later": "Check browser windows for manual intervention or try again later."
|
||||||
}
|
},
|
||||||
|
"account_info": {
|
||||||
|
"subscription": "Subscription",
|
||||||
|
"trial_remaining": "Remaining Pro Trial",
|
||||||
|
"days": "days",
|
||||||
|
"subscription_not_found": "Subscription information not found",
|
||||||
|
"email_not_found": "Email not found",
|
||||||
|
"failed_to_get_account": "Failed to get account information",
|
||||||
|
"config_not_found": "Configuration not found.",
|
||||||
|
"failed_to_get_usage": "Failed to get usage information",
|
||||||
|
"failed_to_get_subscription": "Failed to get subscription information",
|
||||||
|
"failed_to_get_email": "Failed to get email address",
|
||||||
|
"failed_to_get_token": "Failed to get token",
|
||||||
|
"failed_to_get_account_info": "Failed to get account information",
|
||||||
|
"title": "Account Information",
|
||||||
|
"email": "Email",
|
||||||
|
"token": "Token",
|
||||||
|
"usage": "Usage",
|
||||||
|
"subscription_type": "Subscription Type",
|
||||||
|
"remaining_trial": "Remaining Trial",
|
||||||
|
"days_remaining": "Days Remaining",
|
||||||
|
"premium": "Premium",
|
||||||
|
"pro": "Pro",
|
||||||
|
"pro_trial": "Pro Trial",
|
||||||
|
"team": "Team",
|
||||||
|
"enterprise": "Enterprise",
|
||||||
|
"free": "Free",
|
||||||
|
"active": "Active",
|
||||||
|
"inactive": "Inactive",
|
||||||
|
"premium_usage": "Premium Usage",
|
||||||
|
"basic_usage": "Basic Usage"
|
||||||
|
}
|
||||||
}
|
}
|
@ -282,7 +282,12 @@
|
|||||||
"removing_directory": "删除目录",
|
"removing_directory": "删除目录",
|
||||||
"directory_removed": "目录已删除",
|
"directory_removed": "目录已删除",
|
||||||
"creating_block_file": "创建阻止文件",
|
"creating_block_file": "创建阻止文件",
|
||||||
"block_file_created": "阻止文件已创建"
|
"block_file_created": "阻止文件已创建",
|
||||||
|
"clearing_update_yml": "清空 update.yml 文件",
|
||||||
|
"update_yml_cleared": "update.yml 文件已清空",
|
||||||
|
"update_yml_not_found": "update.yml 文件未找到",
|
||||||
|
"clear_update_yml_failed": "清空 update.yml 文件失败: {error}",
|
||||||
|
"unsupported_os": "不支持的操作系统: {system}"
|
||||||
},
|
},
|
||||||
"updater": {
|
"updater": {
|
||||||
"checking": "检查更新...",
|
"checking": "检查更新...",
|
||||||
@ -435,5 +440,37 @@
|
|||||||
"completed_successfully": "GitHub + Cursor 注册成功",
|
"completed_successfully": "GitHub + Cursor 注册成功",
|
||||||
"registration_encountered_issues": "GitHub + Cursor 注册遇到问题",
|
"registration_encountered_issues": "GitHub + Cursor 注册遇到问题",
|
||||||
"check_browser_windows_for_manual_intervention_or_try_again_later": "检查浏览器窗口进行手动干预或稍后再试"
|
"check_browser_windows_for_manual_intervention_or_try_again_later": "检查浏览器窗口进行手动干预或稍后再试"
|
||||||
|
},
|
||||||
|
"account_info": {
|
||||||
|
"subscription": "订阅",
|
||||||
|
"trial_remaining": "剩余试用",
|
||||||
|
"days": "天",
|
||||||
|
"subscription_not_found": "订阅信息未找到",
|
||||||
|
"email_not_found": "邮箱未找到",
|
||||||
|
"failed_to_get_account": "获取账户信息失败",
|
||||||
|
"config_not_found": "配置未找到。",
|
||||||
|
"failed_to_get_usage": "获取使用信息失败",
|
||||||
|
"failed_to_get_subscription": "获取订阅信息失败",
|
||||||
|
"failed_to_get_email": "获取邮箱地址失败",
|
||||||
|
"failed_to_get_token": "获取 token 失败",
|
||||||
|
"failed_to_get_account_info": "获取账户信息失败",
|
||||||
|
"title": "账户信息",
|
||||||
|
"email": "邮箱",
|
||||||
|
"token": "Token",
|
||||||
|
"usage": "使用量",
|
||||||
|
"subscription_type": "订阅类型",
|
||||||
|
"remaining_trial": "剩余试用",
|
||||||
|
"days_remaining": "剩余天数",
|
||||||
|
"premium": "高级",
|
||||||
|
"pro": "专业",
|
||||||
|
"pro_trial": "专业试用",
|
||||||
|
"team": "团队",
|
||||||
|
"enterprise": "企业",
|
||||||
|
"free": "免费",
|
||||||
|
"active": "活跃",
|
||||||
|
"inactive": "非活跃",
|
||||||
|
"premium_usage": "高级使用量",
|
||||||
|
"basic_usage": "基础使用量"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -261,7 +261,12 @@
|
|||||||
"removing_directory": "刪除目錄",
|
"removing_directory": "刪除目錄",
|
||||||
"directory_removed": "目錄已刪除",
|
"directory_removed": "目錄已刪除",
|
||||||
"creating_block_file": "創建阻止文件",
|
"creating_block_file": "創建阻止文件",
|
||||||
"block_file_created": "阻止文件已創建"
|
"block_file_created": "阻止文件已創建",
|
||||||
|
"clearing_update_yml": "清空 update.yml 文件",
|
||||||
|
"update_yml_cleared": "update.yml 文件已清空",
|
||||||
|
"update_yml_not_found": "update.yml 文件未找到",
|
||||||
|
"clear_update_yml_failed": "清空 update.yml 文件失败: {error}",
|
||||||
|
"unsupported_os": "不支持的操作系统: {system}"
|
||||||
},
|
},
|
||||||
"updater": {
|
"updater": {
|
||||||
"checking": "檢查更新...",
|
"checking": "檢查更新...",
|
||||||
@ -414,5 +419,36 @@
|
|||||||
"completed_successfully": "GitHub + Cursor 註冊成功",
|
"completed_successfully": "GitHub + Cursor 註冊成功",
|
||||||
"registration_encountered_issues": "GitHub + Cursor 註冊遇到問題",
|
"registration_encountered_issues": "GitHub + Cursor 註冊遇到問題",
|
||||||
"check_browser_windows_for_manual_intervention_or_try_again_later": "檢查瀏覽器視窗進行手動干預或稍後再試"
|
"check_browser_windows_for_manual_intervention_or_try_again_later": "檢查瀏覽器視窗進行手動干預或稍後再試"
|
||||||
|
},
|
||||||
|
"account_info": {
|
||||||
|
"subscription": "訂閱",
|
||||||
|
"trial_remaining": "剩餘試用",
|
||||||
|
"days": "天",
|
||||||
|
"subscription_not_found": "訂閱信息未找到",
|
||||||
|
"email_not_found": "郵箱未找到",
|
||||||
|
"failed_to_get_account": "獲取帳戶信息失敗",
|
||||||
|
"config_not_found": "配置未找到。",
|
||||||
|
"failed_to_get_usage": "獲取使用信息失敗",
|
||||||
|
"failed_to_get_subscription": "獲取訂閱信息失敗",
|
||||||
|
"failed_to_get_email": "獲取郵箱地址失敗",
|
||||||
|
"failed_to_get_token": "獲取 token 失敗",
|
||||||
|
"failed_to_get_account_info": "獲取帳戶信息失敗",
|
||||||
|
"title": "帳戶信息",
|
||||||
|
"email": "郵箱",
|
||||||
|
"token": "Token",
|
||||||
|
"usage": "使用量",
|
||||||
|
"subscription_type": "訂閱類型",
|
||||||
|
"remaining_trial": "剩餘試用",
|
||||||
|
"days_remaining": "剩餘天數",
|
||||||
|
"premium": "高級",
|
||||||
|
"pro": "專業",
|
||||||
|
"pro_trial": "專業試用",
|
||||||
|
"team": "團隊",
|
||||||
|
"enterprise": "企業",
|
||||||
|
"free": "免費",
|
||||||
|
"active": "活躍",
|
||||||
|
"inactive": "非活躍",
|
||||||
|
"premium_usage": "高級使用量",
|
||||||
|
"basic_usage": "基礎使用量"
|
||||||
}
|
}
|
||||||
}
|
}
|
6
main.py
6
main.py
@ -242,6 +242,12 @@ translator = Translator()
|
|||||||
|
|
||||||
def print_menu():
|
def print_menu():
|
||||||
"""Print menu options"""
|
"""Print menu options"""
|
||||||
|
try:
|
||||||
|
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"\n{Fore.CYAN}{EMOJI['MENU']} {translator.get('menu.title')}:{Style.RESET_ALL}")
|
||||||
print(f"{Fore.YELLOW}{'─' * 40}{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}0{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.exit')}")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user