Pro',
131 |
132 | r'py-1">Auto-select': r'py-1">Bypass-Version-Pin',
133 |
134 | #
135 | r'async getEffectiveTokenLimit(e){const n=e.modelName;if(!n)return 2e5;':r'async getEffectiveTokenLimit(e){return 9000000;const n=e.modelName;if(!n)return 9e5;',
136 | # Pro
137 | r'var DWr=ne("
You are currently signed in with
.");': r'var DWr=ne("
You are currently signed in with .
Pro
");',
138 |
139 | # Toast 替换
140 | r'notifications-toasts': r'notifications-toasts hidden'
141 | }
142 |
143 | # 使用patterns进行替换
144 | for old_pattern, new_pattern in patterns.items():
145 | content = content.replace(old_pattern, new_pattern)
146 |
147 | # Write to temporary file
148 | tmp_file.write(content)
149 | tmp_path = tmp_file.name
150 |
151 | # Backup original file with timestamp
152 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
153 | backup_path = f"{file_path}.backup.{timestamp}"
154 | shutil.copy2(file_path, backup_path)
155 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.backup_created', path=backup_path)}{Style.RESET_ALL}")
156 |
157 | # Move temporary file to original position
158 | if os.path.exists(file_path):
159 | os.remove(file_path)
160 | shutil.move(tmp_path, file_path)
161 |
162 | # Restore original permissions
163 | os.chmod(file_path, original_mode)
164 | if os.name != "nt": # Not Windows
165 | os.chown(file_path, original_uid, original_gid)
166 |
167 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.file_modified')}{Style.RESET_ALL}")
168 | return True
169 |
170 | except Exception as e:
171 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}")
172 | if "tmp_path" in locals():
173 | try:
174 | os.unlink(tmp_path)
175 | except:
176 | pass
177 | return False
178 |
179 | def run(translator=None):
180 | config = get_config(translator)
181 | if not config:
182 | return False
183 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
184 | print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('bypass_token_limit.title')}{Style.RESET_ALL}")
185 | print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
186 |
187 | modify_workbench_js(get_workbench_cursor_path(translator), translator)
188 |
189 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
190 | input(f"{EMOJI['INFO']} {translator.get('bypass_token_limit.press_enter')}...")
191 |
192 | if __name__ == "__main__":
193 | from main import translator as main_translator
194 | run(main_translator)
--------------------------------------------------------------------------------
/bypass_version.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import shutil
4 | import platform
5 | import configparser
6 | import time
7 | from colorama import Fore, Style, init
8 | import sys
9 | import traceback
10 | from utils import get_user_documents_path
11 |
12 | # Initialize colorama
13 | init()
14 |
15 | # Define emoji constants
16 | EMOJI = {
17 | 'INFO': 'ℹ️',
18 | 'SUCCESS': '✅',
19 | 'ERROR': '❌',
20 | 'WARNING': '⚠️',
21 | 'FILE': '📄',
22 | 'BACKUP': '💾',
23 | 'RESET': '🔄',
24 | 'VERSION': '🏷️'
25 | }
26 |
27 | def get_product_json_path(translator=None):
28 | """Get Cursor product.json path"""
29 | system = platform.system()
30 |
31 | # Read configuration
32 | config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
33 | config_file = os.path.join(config_dir, "config.ini")
34 | config = configparser.ConfigParser()
35 |
36 | if os.path.exists(config_file):
37 | config.read(config_file)
38 |
39 | if system == "Windows":
40 | localappdata = os.environ.get("LOCALAPPDATA")
41 | if not localappdata:
42 | raise OSError(translator.get('bypass.localappdata_not_found') if translator else "LOCALAPPDATA environment variable not found")
43 |
44 | product_json_path = os.path.join(localappdata, "Programs", "Cursor", "resources", "app", "product.json")
45 |
46 | # Check if path exists in config
47 | if 'WindowsPaths' in config and 'cursor_path' in config['WindowsPaths']:
48 | cursor_path = config.get('WindowsPaths', 'cursor_path')
49 | product_json_path = os.path.join(cursor_path, "product.json")
50 |
51 | elif system == "Darwin": # macOS
52 | product_json_path = "/Applications/Cursor.app/Contents/Resources/app/product.json"
53 | if config.has_section('MacPaths') and config.has_option('MacPaths', 'product_json_path'):
54 | product_json_path = config.get('MacPaths', 'product_json_path')
55 |
56 | elif system == "Linux":
57 | # Try multiple common paths
58 | possible_paths = [
59 | "/opt/Cursor/resources/app/product.json",
60 | "/usr/share/cursor/resources/app/product.json",
61 | "/usr/lib/cursor/app/product.json"
62 | ]
63 |
64 | # Add extracted AppImage paths
65 | extracted_usr_paths = os.path.expanduser("~/squashfs-root/usr/share/cursor/resources/app/product.json")
66 | if os.path.exists(extracted_usr_paths):
67 | possible_paths.append(extracted_usr_paths)
68 |
69 | for path in possible_paths:
70 | if os.path.exists(path):
71 | product_json_path = path
72 | break
73 | else:
74 | raise OSError(translator.get('bypass.product_json_not_found') if translator else "product.json not found in common Linux paths")
75 |
76 | else:
77 | raise OSError(translator.get('bypass.unsupported_os', system=system) if translator else f"Unsupported operating system: {system}")
78 |
79 | if not os.path.exists(product_json_path):
80 | raise OSError(translator.get('bypass.file_not_found', path=product_json_path) if translator else f"File not found: {product_json_path}")
81 |
82 | return product_json_path
83 |
84 | def compare_versions(version1, version2):
85 | """Compare two version strings"""
86 | v1_parts = [int(x) for x in version1.split('.')]
87 | v2_parts = [int(x) for x in version2.split('.')]
88 |
89 | for i in range(max(len(v1_parts), len(v2_parts))):
90 | v1 = v1_parts[i] if i < len(v1_parts) else 0
91 | v2 = v2_parts[i] if i < len(v2_parts) else 0
92 | if v1 < v2:
93 | return -1
94 | elif v1 > v2:
95 | return 1
96 |
97 | return 0
98 |
99 | def bypass_version(translator=None):
100 | """Bypass Cursor version check by modifying product.json"""
101 | try:
102 | print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('bypass.starting') if translator else 'Starting Cursor version bypass...'}{Style.RESET_ALL}")
103 |
104 | # Get product.json path
105 | product_json_path = get_product_json_path(translator)
106 | print(f"{Fore.CYAN}{EMOJI['FILE']} {translator.get('bypass.found_product_json', path=product_json_path) if translator else f'Found product.json: {product_json_path}'}{Style.RESET_ALL}")
107 |
108 | # Check file permissions
109 | if not os.access(product_json_path, os.W_OK):
110 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.no_write_permission', path=product_json_path) if translator else f'No write permission for file: {product_json_path}'}{Style.RESET_ALL}")
111 | return False
112 |
113 | # Read product.json
114 | try:
115 | with open(product_json_path, "r", encoding="utf-8") as f:
116 | product_data = json.load(f)
117 | except Exception as e:
118 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.read_failed', error=str(e)) if translator else f'Failed to read product.json: {str(e)}'}{Style.RESET_ALL}")
119 | return False
120 |
121 | # Get current version
122 | current_version = product_data.get("version", "0.0.0")
123 | print(f"{Fore.CYAN}{EMOJI['VERSION']} {translator.get('bypass.current_version', version=current_version) if translator else f'Current version: {current_version}'}{Style.RESET_ALL}")
124 |
125 | # Check if version needs to be modified
126 | if compare_versions(current_version, "0.46.0") < 0:
127 | # Create backup
128 | timestamp = time.strftime("%Y%m%d%H%M%S")
129 | backup_path = f"{product_json_path}.{timestamp}"
130 | shutil.copy2(product_json_path, backup_path)
131 | print(f"{Fore.GREEN}{EMOJI['BACKUP']} {translator.get('bypass.backup_created', path=backup_path) if translator else f'Backup created: {backup_path}'}{Style.RESET_ALL}")
132 |
133 | # Modify version
134 | new_version = "0.48.7"
135 | product_data["version"] = new_version
136 |
137 | # Save modified product.json
138 | try:
139 | with open(product_json_path, "w", encoding="utf-8") as f:
140 | json.dump(product_data, f, indent=2)
141 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('bypass.version_updated', old=current_version, new=new_version) if translator else f'Version updated from {current_version} to {new_version}'}{Style.RESET_ALL}")
142 | return True
143 | except Exception as e:
144 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.write_failed', error=str(e)) if translator else f'Failed to write product.json: {str(e)}'}{Style.RESET_ALL}")
145 | return False
146 | else:
147 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('bypass.no_update_needed', version=current_version) if translator else f'No update needed. Current version {current_version} is already >= 0.46.0'}{Style.RESET_ALL}")
148 | return True
149 |
150 | except Exception as e:
151 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.bypass_failed', error=str(e)) if translator else f'Version bypass failed: {str(e)}'}{Style.RESET_ALL}")
152 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('bypass.stack_trace') if translator else 'Stack trace'}: {traceback.format_exc()}{Style.RESET_ALL}")
153 | return False
154 |
155 | def main(translator=None):
156 | """Main function"""
157 | return bypass_version(translator)
158 |
159 | if __name__ == "__main__":
160 | main()
--------------------------------------------------------------------------------
/check_user_authorized.py:
--------------------------------------------------------------------------------
1 | import os
2 | import requests
3 | import time
4 | import hashlib
5 | import base64
6 | import struct
7 | from colorama import Fore, Style, init
8 |
9 | # Initialize colorama
10 | init()
11 |
12 | # Define emoji constants
13 | EMOJI = {
14 | "SUCCESS": "✅",
15 | "ERROR": "❌",
16 | "INFO": "ℹ️",
17 | "WARNING": "⚠️",
18 | "KEY": "🔑",
19 | "CHECK": "🔍"
20 | }
21 |
22 | def generate_hashed64_hex(input_str: str, salt: str = '') -> str:
23 | """Generate a SHA-256 hash of input + salt and return as hex"""
24 | hash_obj = hashlib.sha256()
25 | hash_obj.update((input_str + salt).encode('utf-8'))
26 | return hash_obj.hexdigest()
27 |
28 | def obfuscate_bytes(byte_array: bytearray) -> bytearray:
29 | """Obfuscate bytes using the algorithm from utils.js"""
30 | t = 165
31 | for r in range(len(byte_array)):
32 | byte_array[r] = ((byte_array[r] ^ t) + (r % 256)) & 0xFF
33 | t = byte_array[r]
34 | return byte_array
35 |
36 | def generate_cursor_checksum(token: str, translator=None) -> str:
37 | """Generate Cursor checksum from token using the algorithm"""
38 | try:
39 | # Clean the token
40 | clean_token = token.strip()
41 |
42 | # Generate machineId and macMachineId
43 | machine_id = generate_hashed64_hex(clean_token, 'machineId')
44 | mac_machine_id = generate_hashed64_hex(clean_token, 'macMachineId')
45 |
46 | # Get timestamp and convert to byte array
47 | timestamp = int(time.time() * 1000) // 1000000
48 | byte_array = bytearray(struct.pack('>Q', timestamp)[-6:]) # Take last 6 bytes
49 |
50 | # Obfuscate bytes and encode as base64
51 | obfuscated_bytes = obfuscate_bytes(byte_array)
52 | encoded_checksum = base64.b64encode(obfuscated_bytes).decode('utf-8')
53 |
54 | # Combine final checksum
55 | return f"{encoded_checksum}{machine_id}/{mac_machine_id}"
56 | except Exception as e:
57 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.error_generating_checksum', error=str(e)) if translator else f'Error generating checksum: {str(e)}'}{Style.RESET_ALL}")
58 | return ""
59 |
60 | def check_user_authorized(token: str, translator=None) -> bool:
61 | """
62 | Check if the user is authorized with the given token
63 |
64 | Args:
65 | token (str): The authorization token
66 | translator: Optional translator for internationalization
67 |
68 | Returns:
69 | bool: True if authorized, False otherwise
70 | """
71 | try:
72 | print(f"{Fore.CYAN}{EMOJI['CHECK']} {translator.get('auth_check.checking_authorization') if translator else 'Checking authorization...'}{Style.RESET_ALL}")
73 |
74 | # Clean the token
75 | if token and '%3A%3A' in token:
76 | token = token.split('%3A%3A')[1]
77 | elif token and '::' in token:
78 | token = token.split('::')[1]
79 |
80 | # Remove any whitespace
81 | token = token.strip()
82 |
83 | if not token or len(token) < 10: # Add a basic validation for token length
84 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.invalid_token') if translator else 'Invalid token'}{Style.RESET_ALL}")
85 | return False
86 |
87 | print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.token_length', length=len(token)) if translator else f'Token length: {len(token)} characters'}{Style.RESET_ALL}")
88 |
89 | # Try to get usage info using the DashboardService API
90 | try:
91 | # Generate checksum
92 | checksum = generate_cursor_checksum(token, translator)
93 |
94 | # Create request headers
95 | headers = {
96 | 'accept-encoding': 'gzip',
97 | 'authorization': f'Bearer {token}',
98 | 'connect-protocol-version': '1',
99 | 'content-type': 'application/proto',
100 | 'user-agent': 'connect-es/1.6.1',
101 | 'x-cursor-checksum': checksum,
102 | 'x-cursor-client-version': '0.48.7',
103 | 'x-cursor-timezone': 'Asia/Shanghai',
104 | 'x-ghost-mode': 'false',
105 | 'Host': 'api2.cursor.sh'
106 | }
107 |
108 | print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.checking_usage_information') if translator else 'Checking usage information...'}{Style.RESET_ALL}")
109 |
110 | # Make the request - this endpoint doesn't need a request body
111 | usage_response = requests.post(
112 | 'https://api2.cursor.sh/aiserver.v1.DashboardService/GetUsageBasedPremiumRequests',
113 | headers=headers,
114 | data=b'', # Empty body
115 | timeout=10
116 | )
117 |
118 | print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.usage_response', response=usage_response.status_code) if translator else f'Usage response status: {usage_response.status_code}'}{Style.RESET_ALL}")
119 |
120 | if usage_response.status_code == 200:
121 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('auth_check.user_authorized') if translator else 'User is authorized'}{Style.RESET_ALL}")
122 | return True
123 | elif usage_response.status_code == 401 or usage_response.status_code == 403:
124 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.user_unauthorized') if translator else 'User is unauthorized'}{Style.RESET_ALL}")
125 | return False
126 | else:
127 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.unexpected_status_code', code=usage_response.status_code) if translator else f'Unexpected status code: {usage_response.status_code}'}{Style.RESET_ALL}")
128 |
129 | # If the token at least looks like a valid JWT, consider it valid
130 | if token.startswith('eyJ') and '.' in token and len(token) > 100:
131 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.jwt_token_warning') if translator else 'Token appears to be in JWT format, but API check returned an unexpected status code. The token might be valid but API access is restricted.'}{Style.RESET_ALL}")
132 | return True
133 |
134 | return False
135 | except Exception as e:
136 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} Error checking usage: {str(e)}{Style.RESET_ALL}")
137 |
138 | # If the token at least looks like a valid JWT, consider it valid even if the API check fails
139 | if token.startswith('eyJ') and '.' in token and len(token) > 100:
140 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.jwt_token_warning') if translator else 'Token appears to be in JWT format, but API check failed. The token might be valid but API access is restricted.'}{Style.RESET_ALL}")
141 | return True
142 |
143 | return False
144 |
145 | except requests.exceptions.Timeout:
146 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.request_timeout') if translator else 'Request timed out'}{Style.RESET_ALL}")
147 | return False
148 | except requests.exceptions.ConnectionError:
149 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.connection_error') if translator else 'Connection error'}{Style.RESET_ALL}")
150 | return False
151 | except Exception as e:
152 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.check_error', error=str(e)) if translator else f'Error checking authorization: {str(e)}'}{Style.RESET_ALL}")
153 | return False
154 |
155 | def run(translator=None):
156 | """Run function to be called from main.py"""
157 | try:
158 | # Ask user if they want to get token from database or input manually
159 | choice = input(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.token_source') if translator else 'Get token from database or input manually? (d/m, default: d): '}{Style.RESET_ALL}").strip().lower()
160 |
161 | token = None
162 |
163 | # If user chooses database or default
164 | if not choice or choice == 'd':
165 | print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.getting_token_from_db') if translator else 'Getting token from database...'}{Style.RESET_ALL}")
166 |
167 | try:
168 | # Import functions from cursor_acc_info.py
169 | from cursor_acc_info import get_token
170 |
171 | # Get token using the get_token function
172 | token = get_token()
173 |
174 | if token:
175 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('auth_check.token_found_in_db') if translator else 'Token found in database'}{Style.RESET_ALL}")
176 | else:
177 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.token_not_found_in_db') if translator else 'Token not found in database'}{Style.RESET_ALL}")
178 | except ImportError:
179 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.cursor_acc_info_not_found') if translator else 'cursor_acc_info.py not found'}{Style.RESET_ALL}")
180 | except Exception as e:
181 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.error_getting_token_from_db', error=str(e)) if translator else f'Error getting token from database: {str(e)}'}{Style.RESET_ALL}")
182 |
183 | # If token not found in database or user chooses manual input
184 | if not token:
185 | # Try to get token from environment
186 | token = os.environ.get('CURSOR_TOKEN')
187 |
188 | # If not in environment, ask user to input
189 | if not token:
190 | token = input(f"{Fore.CYAN}{EMOJI['KEY']} {translator.get('auth_check.enter_token') if translator else 'Enter your Cursor token: '}{Style.RESET_ALL}")
191 |
192 | # Check authorization
193 | is_authorized = check_user_authorized(token, translator)
194 |
195 | if is_authorized:
196 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('auth_check.authorization_successful') if translator else 'Authorization successful!'}{Style.RESET_ALL}")
197 | else:
198 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.authorization_failed') if translator else 'Authorization failed!'}{Style.RESET_ALL}")
199 |
200 | return is_authorized
201 |
202 | except KeyboardInterrupt:
203 | print(f"\n{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.operation_cancelled') if translator else 'Operation cancelled by user'}{Style.RESET_ALL}")
204 | return False
205 | except Exception as e:
206 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.unexpected_error', error=str(e)) if translator else f'Unexpected error: {str(e)}'}{Style.RESET_ALL}")
207 | return False
208 |
209 | def main(translator=None):
210 | """Main function to check user authorization"""
211 | return run(translator)
212 |
213 | if __name__ == "__main__":
214 | main()
--------------------------------------------------------------------------------
/cursor_auth.py:
--------------------------------------------------------------------------------
1 | import sqlite3
2 | import os
3 | import sys
4 | from colorama import Fore, Style, init
5 | from config import get_config
6 |
7 | # Initialize colorama
8 | init()
9 |
10 | # Define emoji and color constants
11 | EMOJI = {
12 | 'DB': '🗄️',
13 | 'UPDATE': '🔄',
14 | 'SUCCESS': '✅',
15 | 'ERROR': '❌',
16 | 'WARN': '⚠️',
17 | 'INFO': 'ℹ️',
18 | 'FILE': '📄',
19 | 'KEY': '🔐'
20 | }
21 |
22 | class CursorAuth:
23 | def __init__(self, translator=None):
24 | self.translator = translator
25 |
26 | # Get configuration
27 | config = get_config(translator)
28 | if not config:
29 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.config_error') if self.translator else 'Failed to load configuration'}{Style.RESET_ALL}")
30 | sys.exit(1)
31 |
32 | # Get path based on operating system
33 | try:
34 | if sys.platform == "win32": # Windows
35 | if not config.has_section('WindowsPaths'):
36 | raise ValueError("Windows paths not configured")
37 | self.db_path = config.get('WindowsPaths', 'sqlite_path')
38 |
39 | elif sys.platform == 'linux': # Linux
40 | if not config.has_section('LinuxPaths'):
41 | raise ValueError("Linux paths not configured")
42 | self.db_path = config.get('LinuxPaths', 'sqlite_path')
43 |
44 | elif sys.platform == 'darwin': # macOS
45 | if not config.has_section('MacPaths'):
46 | raise ValueError("macOS paths not configured")
47 | self.db_path = config.get('MacPaths', 'sqlite_path')
48 |
49 | else:
50 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.unsupported_platform') if self.translator else 'Unsupported platform'}{Style.RESET_ALL}")
51 | sys.exit(1)
52 |
53 | # Verify if the path exists
54 | if not os.path.exists(os.path.dirname(self.db_path)):
55 | raise FileNotFoundError(f"Database directory not found: {os.path.dirname(self.db_path)}")
56 |
57 | except Exception as e:
58 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.path_error', error=str(e)) if self.translator else f'Error getting database path: {str(e)}'}{Style.RESET_ALL}")
59 | sys.exit(1)
60 |
61 | # Check if the database file exists
62 | if not os.path.exists(self.db_path):
63 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_not_found', path=self.db_path)}{Style.RESET_ALL}")
64 | return
65 |
66 | # Check file permissions
67 | if not os.access(self.db_path, os.R_OK | os.W_OK):
68 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_permission_error')}{Style.RESET_ALL}")
69 | return
70 |
71 | try:
72 | self.conn = sqlite3.connect(self.db_path)
73 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('auth.connected_to_database')}{Style.RESET_ALL}")
74 | except sqlite3.Error as e:
75 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_connection_error', error=str(e))}{Style.RESET_ALL}")
76 | return
77 |
78 | def update_auth(self, email=None, access_token=None, refresh_token=None):
79 | conn = None
80 | try:
81 | # Ensure the directory exists and set the correct permissions
82 | db_dir = os.path.dirname(self.db_path)
83 | if not os.path.exists(db_dir):
84 | os.makedirs(db_dir, mode=0o755, exist_ok=True)
85 |
86 | # If the database file does not exist, create a new one
87 | if not os.path.exists(self.db_path):
88 | conn = sqlite3.connect(self.db_path)
89 | cursor = conn.cursor()
90 | cursor.execute('''
91 | CREATE TABLE IF NOT EXISTS ItemTable (
92 | key TEXT PRIMARY KEY,
93 | value TEXT
94 | )
95 | ''')
96 | conn.commit()
97 | if sys.platform != "win32":
98 | os.chmod(self.db_path, 0o644)
99 | conn.close()
100 |
101 | # Reconnect to the database
102 | conn = sqlite3.connect(self.db_path)
103 | print(f"{EMOJI['INFO']} {Fore.GREEN} {self.translator.get('auth.connected_to_database')}{Style.RESET_ALL}")
104 | cursor = conn.cursor()
105 |
106 | # Add timeout and other optimization settings
107 | conn.execute("PRAGMA busy_timeout = 5000")
108 | conn.execute("PRAGMA journal_mode = WAL")
109 | conn.execute("PRAGMA synchronous = NORMAL")
110 |
111 | # Set the key-value pairs to update
112 | updates = []
113 |
114 | updates.append(("cursorAuth/cachedSignUpType", "Auth_0"))
115 |
116 | if email is not None:
117 | updates.append(("cursorAuth/cachedEmail", email))
118 | if access_token is not None:
119 | updates.append(("cursorAuth/accessToken", access_token))
120 | if refresh_token is not None:
121 | updates.append(("cursorAuth/refreshToken", refresh_token))
122 |
123 |
124 | # Use transactions to ensure data integrity
125 | cursor.execute("BEGIN TRANSACTION")
126 | try:
127 | for key, value in updates:
128 | # Check if the key exists
129 | cursor.execute("SELECT COUNT(*) FROM ItemTable WHERE key = ?", (key,))
130 | if cursor.fetchone()[0] == 0:
131 | cursor.execute("""
132 | INSERT INTO ItemTable (key, value)
133 | VALUES (?, ?)
134 | """, (key, value))
135 | else:
136 | cursor.execute("""
137 | UPDATE ItemTable SET value = ?
138 | WHERE key = ?
139 | """, (value, key))
140 | print(f"{EMOJI['INFO']} {Fore.CYAN} {self.translator.get('auth.updating_pair')} {key.split('/')[-1]}...{Style.RESET_ALL}")
141 |
142 | cursor.execute("COMMIT")
143 | print(f"{EMOJI['SUCCESS']} {Fore.GREEN}{self.translator.get('auth.database_updated_successfully')}{Style.RESET_ALL}")
144 | return True
145 |
146 | except Exception as e:
147 | cursor.execute("ROLLBACK")
148 | raise e
149 |
150 | except sqlite3.Error as e:
151 | print(f"\n{EMOJI['ERROR']} {Fore.RED} {self.translator.get('auth.database_error', error=str(e))}{Style.RESET_ALL}")
152 | return False
153 | except Exception as e:
154 | print(f"\n{EMOJI['ERROR']} {Fore.RED} {self.translator.get('auth.an_error_occurred', error=str(e))}{Style.RESET_ALL}")
155 | return False
156 | finally:
157 | if conn:
158 | conn.close()
159 | print(f"{EMOJI['DB']} {Fore.CYAN} {self.translator.get('auth.database_connection_closed')}{Style.RESET_ALL}")
--------------------------------------------------------------------------------
/cursor_register_manual.py:
--------------------------------------------------------------------------------
1 | import os
2 | from colorama import Fore, Style, init
3 | import time
4 | import random
5 | from faker import Faker
6 | from cursor_auth import CursorAuth
7 | from reset_machine_manual import MachineIDResetter
8 | from get_user_token import get_token_from_cookie
9 |
10 | os.environ["PYTHONVERBOSE"] = "0"
11 | os.environ["PYINSTALLER_VERBOSE"] = "0"
12 |
13 | # Initialize colorama
14 | init()
15 |
16 | # Define emoji constants
17 | EMOJI = {
18 | 'START': '🚀',
19 | 'FORM': '📝',
20 | 'VERIFY': '🔄',
21 | 'PASSWORD': '🔑',
22 | 'CODE': '📱',
23 | 'DONE': '✨',
24 | 'ERROR': '❌',
25 | 'WAIT': '⏳',
26 | 'SUCCESS': '✅',
27 | 'MAIL': '📧',
28 | 'KEY': '🔐',
29 | 'UPDATE': '🔄',
30 | 'INFO': 'ℹ️'
31 | }
32 |
33 | class CursorRegistration:
34 | def __init__(self, translator=None):
35 | self.translator = translator
36 | # Set to display mode
37 | os.environ['BROWSER_HEADLESS'] = 'False'
38 | self.browser = None
39 | self.controller = None
40 | self.sign_up_url = "https://authenticator.cursor.sh/sign-up"
41 | self.settings_url = "https://www.cursor.com/settings"
42 | self.email_address = None
43 | self.signup_tab = None
44 | self.email_tab = None
45 |
46 | # initialize Faker instance
47 | self.faker = Faker()
48 |
49 | # generate account information
50 | self.password = self._generate_password()
51 | self.first_name = self.faker.first_name()
52 | self.last_name = self.faker.last_name()
53 |
54 | # modify the first letter of the first name(keep the original function)
55 | new_first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
56 | self.first_name = new_first_letter + self.first_name[1:]
57 |
58 | print(f"\n{Fore.CYAN}{EMOJI['PASSWORD']} {self.translator.get('register.password')}: {self.password} {Style.RESET_ALL}")
59 | print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.first_name')}: {self.first_name} {Style.RESET_ALL}")
60 | print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.last_name')}: {self.last_name} {Style.RESET_ALL}")
61 |
62 | def _generate_password(self, length=12):
63 | """Generate password"""
64 | return self.faker.password(length=length, special_chars=True, digits=True, upper_case=True, lower_case=True)
65 |
66 | def setup_email(self):
67 | """Setup Email"""
68 | try:
69 | print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.manual_email_input') if self.translator else 'Please enter your email address:'}")
70 | self.email_address = input().strip()
71 |
72 | if '@' not in self.email_address:
73 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_email') if self.translator else '无效的邮箱地址'}{Style.RESET_ALL}")
74 | return False
75 |
76 | print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('register.email_address')}: {self.email_address}" + "\n" + f"{Style.RESET_ALL}")
77 | return True
78 |
79 | except Exception as e:
80 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.email_setup_failed', error=str(e))}{Style.RESET_ALL}")
81 | return False
82 |
83 | def get_verification_code(self):
84 | """Manually Get Verification Code"""
85 | try:
86 | print(f"{Fore.CYAN}{EMOJI['CODE']} {self.translator.get('register.manual_code_input') if self.translator else 'Please enter the verification code:'}")
87 | code = input().strip()
88 |
89 | if not code.isdigit() or len(code) != 6:
90 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_code') if self.translator else '无效的验证码'}{Style.RESET_ALL}")
91 | return None
92 |
93 | return code
94 |
95 | except Exception as e:
96 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.code_input_failed', error=str(e))}{Style.RESET_ALL}")
97 | return None
98 |
99 | def register_cursor(self):
100 | """Register Cursor"""
101 | browser_tab = None
102 | try:
103 | print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.register_start')}...{Style.RESET_ALL}")
104 |
105 | # Use new_signup.py directly for registration
106 | from new_signup import main as new_signup_main
107 |
108 | # Execute new registration process, passing translator
109 | result, browser_tab = new_signup_main(
110 | email=self.email_address,
111 | password=self.password,
112 | first_name=self.first_name,
113 | last_name=self.last_name,
114 | email_tab=None, # No email tab needed
115 | controller=self, # Pass self instead of self.controller
116 | translator=self.translator
117 | )
118 |
119 | if result:
120 | # Use the returned browser instance to get account information
121 | self.signup_tab = browser_tab # Save browser instance
122 | success = self._get_account_info()
123 |
124 | # Close browser after getting information
125 | if browser_tab:
126 | try:
127 | browser_tab.quit()
128 | except:
129 | pass
130 |
131 | return success
132 |
133 | return False
134 |
135 | except Exception as e:
136 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.register_process_error', error=str(e))}{Style.RESET_ALL}")
137 | return False
138 | finally:
139 | # Ensure browser is closed in any case
140 | if browser_tab:
141 | try:
142 | browser_tab.quit()
143 | except:
144 | pass
145 |
146 | def _get_account_info(self):
147 | """Get Account Information and Token"""
148 | try:
149 | self.signup_tab.get(self.settings_url)
150 | time.sleep(2)
151 |
152 | usage_selector = (
153 | "css:div.col-span-2 > div > div > div > div > "
154 | "div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > "
155 | "span.font-mono.text-sm\\/\\[0\\.875rem\\]"
156 | )
157 | usage_ele = self.signup_tab.ele(usage_selector)
158 | total_usage = "未知"
159 | if usage_ele:
160 | total_usage = usage_ele.text.split("/")[-1].strip()
161 |
162 | print(f"Total Usage: {total_usage}\n")
163 | print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('register.get_token')}...{Style.RESET_ALL}")
164 | max_attempts = 30
165 | retry_interval = 2
166 | attempts = 0
167 |
168 | while attempts < max_attempts:
169 | try:
170 | cookies = self.signup_tab.cookies()
171 | for cookie in cookies:
172 | if cookie.get("name") == "WorkosCursorSessionToken":
173 | token = get_token_from_cookie(cookie["value"], self.translator)
174 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.token_success')}{Style.RESET_ALL}")
175 | self._save_account_info(token, total_usage)
176 | return True
177 |
178 | attempts += 1
179 | if attempts < max_attempts:
180 | print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}")
181 | time.sleep(retry_interval)
182 | else:
183 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_max_attempts', max=max_attempts)}{Style.RESET_ALL}")
184 |
185 | except Exception as e:
186 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_failed', error=str(e))}{Style.RESET_ALL}")
187 | attempts += 1
188 | if attempts < max_attempts:
189 | print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}")
190 | time.sleep(retry_interval)
191 |
192 | return False
193 |
194 | except Exception as e:
195 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.account_error', error=str(e))}{Style.RESET_ALL}")
196 | return False
197 |
198 | def _save_account_info(self, token, total_usage):
199 | """Save Account Information to File"""
200 | try:
201 | # Update authentication information first
202 | print(f"{Fore.CYAN}{EMOJI['KEY']} {self.translator.get('register.update_cursor_auth_info')}...{Style.RESET_ALL}")
203 | if self.update_cursor_auth(email=self.email_address, access_token=token, refresh_token=token):
204 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.cursor_auth_info_updated')}...{Style.RESET_ALL}")
205 | else:
206 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.cursor_auth_info_update_failed')}...{Style.RESET_ALL}")
207 |
208 | # Reset machine ID
209 | print(f"{Fore.CYAN}{EMOJI['UPDATE']} {self.translator.get('register.reset_machine_id')}...{Style.RESET_ALL}")
210 | resetter = MachineIDResetter(self.translator) # Create instance with translator
211 | if not resetter.reset_machine_ids(): # Call reset_machine_ids method directly
212 | raise Exception("Failed to reset machine ID")
213 |
214 | # Save account information to file
215 | with open('cursor_accounts.txt', 'a', encoding='utf-8') as f:
216 | f.write(f"\n{'='*50}\n")
217 | f.write(f"Email: {self.email_address}\n")
218 | f.write(f"Password: {self.password}\n")
219 | f.write(f"Token: {token}\n")
220 | f.write(f"Usage Limit: {total_usage}\n")
221 | f.write(f"{'='*50}\n")
222 |
223 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.account_info_saved')}...{Style.RESET_ALL}")
224 | return True
225 |
226 | except Exception as e:
227 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.save_account_info_failed', error=str(e))}{Style.RESET_ALL}")
228 | return False
229 |
230 | def start(self):
231 | """Start Registration Process"""
232 | try:
233 | if self.setup_email():
234 | if self.register_cursor():
235 | print(f"\n{Fore.GREEN}{EMOJI['DONE']} {self.translator.get('register.cursor_registration_completed')}...{Style.RESET_ALL}")
236 | return True
237 | return False
238 | finally:
239 | # Close email tab
240 | if hasattr(self, 'temp_email'):
241 | try:
242 | self.temp_email.close()
243 | except:
244 | pass
245 |
246 | def update_cursor_auth(self, email=None, access_token=None, refresh_token=None):
247 | """Convenient function to update Cursor authentication information"""
248 | auth_manager = CursorAuth(translator=self.translator)
249 | return auth_manager.update_auth(email, access_token, refresh_token)
250 |
251 | def main(translator=None):
252 | """Main function to be called from main.py"""
253 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
254 | print(f"{Fore.CYAN}{EMOJI['START']} {translator.get('register.title')}{Style.RESET_ALL}")
255 | print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
256 |
257 | registration = CursorRegistration(translator)
258 | registration.start()
259 |
260 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
261 | input(f"{EMOJI['INFO']} {translator.get('register.press_enter')}...")
262 |
263 | if __name__ == "__main__":
264 | from main import translator as main_translator
265 | main(main_translator)
--------------------------------------------------------------------------------
/delete_cursor_google.py:
--------------------------------------------------------------------------------
1 | from oauth_auth import OAuthHandler
2 | import time
3 | from colorama import Fore, Style, init
4 | import sys
5 |
6 | # Initialize colorama
7 | init()
8 |
9 | # Define emoji constants
10 | EMOJI = {
11 | 'START': '🚀',
12 | 'DELETE': '🗑️',
13 | 'SUCCESS': '✅',
14 | 'ERROR': '❌',
15 | 'WAIT': '⏳',
16 | 'INFO': 'ℹ️',
17 | 'WARNING': '⚠️'
18 | }
19 |
20 | class CursorGoogleAccountDeleter(OAuthHandler):
21 | def __init__(self, translator=None):
22 | super().__init__(translator, auth_type='google')
23 |
24 | def delete_google_account(self):
25 | """Delete Cursor account using Google OAuth"""
26 | try:
27 | # Setup browser and select profile
28 | if not self.setup_browser():
29 | return False
30 |
31 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.starting_process') if self.translator else 'Starting account deletion process...'}{Style.RESET_ALL}")
32 |
33 | # Navigate to Cursor auth page - using the same URL as in registration
34 | self.browser.get("https://authenticator.cursor.sh/sign-up")
35 | time.sleep(2)
36 |
37 | # Click Google auth button using same selectors as in registration
38 | selectors = [
39 | "//a[contains(@href,'GoogleOAuth')]",
40 | "//a[contains(@class,'auth-method-button') and contains(@href,'GoogleOAuth')]",
41 | "(//a[contains(@class,'auth-method-button')])[1]" # First auth button as fallback
42 | ]
43 |
44 | auth_btn = None
45 | for selector in selectors:
46 | try:
47 | auth_btn = self.browser.ele(f"xpath:{selector}", timeout=2)
48 | if auth_btn:
49 | break
50 | except:
51 | continue
52 |
53 | if not auth_btn:
54 | raise Exception(self.translator.get('account_delete.google_button_not_found') if self.translator else "Google login button not found")
55 |
56 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.logging_in') if self.translator else 'Logging in with Google...'}{Style.RESET_ALL}")
57 | auth_btn.click()
58 |
59 | # Wait for authentication to complete using a more robust method
60 | print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('account_delete.waiting_for_auth', fallback='Waiting for Google authentication...')}{Style.RESET_ALL}")
61 |
62 | # Dynamic wait for authentication
63 | max_wait_time = 120 # Increase maximum wait time to 120 seconds
64 | start_time = time.time()
65 | check_interval = 3 # Check every 3 seconds
66 | google_account_alert_shown = False # Track if we've shown the alert already
67 |
68 | while time.time() - start_time < max_wait_time:
69 | current_url = self.browser.url
70 |
71 | # If we're already on the settings or dashboard page, we're successful
72 | if "/dashboard" in current_url or "/settings" in current_url or "cursor.com" in current_url:
73 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.login_successful') if self.translator else 'Login successful'}{Style.RESET_ALL}")
74 | break
75 |
76 | # If we're on Google accounts page or accounts.google.com, wait for user selection
77 | if "accounts.google.com" in current_url:
78 | # Only show the alert once to avoid spamming
79 | if not google_account_alert_shown:
80 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.select_google_account', fallback='Please select your Google account...')}{Style.RESET_ALL}")
81 | # Alert to indicate user action needed
82 | try:
83 | self.browser.run_js("""
84 | alert('Please select your Google account to continue with Cursor authentication');
85 | """)
86 | google_account_alert_shown = True # Mark that we've shown the alert
87 | except:
88 | pass # Alert is optional
89 |
90 | # Sleep before checking again
91 | time.sleep(check_interval)
92 | else:
93 | # If the loop completed without breaking, it means we hit the timeout
94 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.auth_timeout', fallback='Authentication timeout, continuing anyway...')}{Style.RESET_ALL}")
95 |
96 | # Check current URL to determine next steps
97 | current_url = self.browser.url
98 |
99 | # If we're already on the settings page, no need to navigate
100 | if "/settings" in current_url and "cursor.com" in current_url:
101 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.already_on_settings', fallback='Already on settings page')}{Style.RESET_ALL}")
102 | # If we're on the dashboard or any Cursor page but not settings, navigate to settings
103 | elif "cursor.com" in current_url or "authenticator.cursor.sh" in current_url:
104 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.navigating_to_settings', fallback='Navigating to settings page...')}{Style.RESET_ALL}")
105 | self.browser.get("https://www.cursor.com/settings")
106 | # If we're still on Google auth or somewhere else, try directly going to settings
107 | else:
108 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.login_redirect_failed', fallback='Login redirection failed, trying direct navigation...')}{Style.RESET_ALL}")
109 | self.browser.get("https://www.cursor.com/settings")
110 |
111 | # Wait for the settings page to load
112 | time.sleep(3) # Reduced from 5 seconds
113 |
114 | # First look for the email element to confirm we're logged in
115 | try:
116 | email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)")
117 | if email_element:
118 | email = email_element.text
119 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.found_email', email=email, fallback=f'Found email: {email}')}{Style.RESET_ALL}")
120 | except Exception as e:
121 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.email_not_found', error=str(e), fallback=f'Email not found: {str(e)}')}{Style.RESET_ALL}")
122 |
123 | # Click on "Advanced" tab or dropdown - keep only the successful approach
124 | advanced_found = False
125 |
126 | # Direct JavaScript querySelector approach that worked according to logs
127 | try:
128 | advanced_element_js = self.browser.run_js("""
129 | // Try to find the Advanced dropdown using querySelector with the exact classes
130 | let advancedElement = document.querySelector('div.mb-0.flex.cursor-pointer.items-center.text-xs:not([style*="display: none"])');
131 |
132 | // If not found, try a more general approach
133 | if (!advancedElement) {
134 | const allDivs = document.querySelectorAll('div');
135 | for (const div of allDivs) {
136 | if (div.textContent.includes('Advanced') &&
137 | div.className.includes('mb-0') &&
138 | div.className.includes('flex') &&
139 | div.className.includes('cursor-pointer')) {
140 | advancedElement = div;
141 | break;
142 | }
143 | }
144 | }
145 |
146 | // Click the element if found
147 | if (advancedElement) {
148 | advancedElement.click();
149 | return true;
150 | }
151 |
152 | return false;
153 | """)
154 |
155 | if advanced_element_js:
156 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.advanced_tab_clicked', fallback='Found and clicked Advanced using direct JavaScript selector')}{Style.RESET_ALL}")
157 | advanced_found = True
158 | time.sleep(1) # Reduced from 2 seconds
159 | except Exception as e:
160 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.advanced_tab_error', error=str(e), fallback='JavaScript querySelector approach failed: {str(e)}')}{Style.RESET_ALL}")
161 |
162 | if not advanced_found:
163 | # Fallback to direct URL navigation which is faster and more reliable
164 | try:
165 | self.browser.get("https://www.cursor.com/settings?tab=advanced")
166 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('account_delete.direct_advanced_navigation', fallback='Trying direct navigation to advanced tab')}{Style.RESET_ALL}")
167 | advanced_found = True
168 | except:
169 | raise Exception(self.translator.get('account_delete.advanced_tab_not_found') if self.translator else "Advanced option not found after multiple attempts")
170 |
171 | # Wait for dropdown/tab content to load
172 | time.sleep(2) # Reduced from 4 seconds
173 |
174 | # Find and click the "Delete Account" button
175 | delete_button_found = False
176 |
177 | # Simplified approach for delete button based on what worked
178 | delete_button_selectors = [
179 | 'xpath://button[contains(., "Delete Account")]',
180 | 'xpath://button[text()="Delete Account"]',
181 | 'xpath://div[contains(text(), "Delete Account")]',
182 | 'xpath://button[contains(text(), "Delete") and contains(text(), "Account")]'
183 | ]
184 |
185 | for selector in delete_button_selectors:
186 | try:
187 | delete_button = self.browser.ele(selector, timeout=2)
188 | if delete_button:
189 | delete_button.click()
190 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.delete_button_clicked') if self.translator else 'Clicked on Delete Account button'}{Style.RESET_ALL}")
191 | delete_button_found = True
192 | break
193 | except:
194 | continue
195 |
196 | if not delete_button_found:
197 | raise Exception(self.translator.get('account_delete.delete_button_not_found') if self.translator else "Delete Account button not found")
198 |
199 | # Wait for confirmation dialog to appear
200 | time.sleep(2)
201 |
202 | # Check if we need to input "Delete" at all - some modals might not require it
203 | input_required = True
204 | try:
205 | # Try detecting if the DELETE button is already enabled
206 | delete_button_enabled = self.browser.run_js("""
207 | const buttons = Array.from(document.querySelectorAll('button'));
208 | const deleteButtons = buttons.filter(btn =>
209 | btn.textContent.trim() === 'DELETE' ||
210 | btn.textContent.trim() === 'Delete'
211 | );
212 |
213 | if (deleteButtons.length > 0) {
214 | return !deleteButtons.some(btn => btn.disabled);
215 | }
216 | return false;
217 | """)
218 |
219 | if delete_button_enabled:
220 | print(f"{Fore.CYAN}{EMOJI['INFO']} DELETE button appears to be enabled already. Input may not be required.{Style.RESET_ALL}")
221 | input_required = False
222 | except:
223 | pass
224 |
225 | # Type "Delete" in the confirmation input - only if required
226 | delete_input_found = False
227 |
228 | if input_required:
229 | # Try common selectors for the input field
230 | delete_input_selectors = [
231 | 'xpath://input[@placeholder="Delete"]',
232 | 'xpath://div[contains(@class, "modal")]//input',
233 | 'xpath://input',
234 | 'css:input'
235 | ]
236 |
237 | for selector in delete_input_selectors:
238 | try:
239 | delete_input = self.browser.ele(selector, timeout=3)
240 | if delete_input:
241 | delete_input.clear()
242 | delete_input.input("Delete")
243 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.typed_delete', fallback='Typed \"Delete\" in confirmation box')}{Style.RESET_ALL}")
244 | delete_input_found = True
245 | time.sleep(2)
246 | break
247 | except:
248 | # Try direct JavaScript input as fallback
249 | try:
250 | self.browser.run_js(r"""
251 | arguments[0].value = "Delete";
252 | const event = new Event('input', { bubbles: true });
253 | arguments[0].dispatchEvent(event);
254 | const changeEvent = new Event('change', { bubbles: true });
255 | arguments[0].dispatchEvent(changeEvent);
256 | """, delete_input)
257 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.typed_delete_js', fallback='Typed \"Delete\" using JavaScript')}{Style.RESET_ALL}")
258 | delete_input_found = True
259 | time.sleep(2)
260 | break
261 | except:
262 | continue
263 |
264 | if not delete_input_found:
265 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.delete_input_not_found', fallback='Delete confirmation input not found, continuing anyway')}{Style.RESET_ALL}")
266 | time.sleep(2)
267 |
268 | # Wait before clicking the final DELETE button
269 | time.sleep(2)
270 |
271 | # Click on the final DELETE button
272 | confirm_button_found = False
273 |
274 | # Use JavaScript approach for the DELETE button
275 | try:
276 | delete_button_js = self.browser.run_js("""
277 | // Try to find the DELETE button by exact text content
278 | const buttons = Array.from(document.querySelectorAll('button'));
279 | const deleteButton = buttons.find(btn =>
280 | btn.textContent.trim() === 'DELETE' ||
281 | btn.textContent.trim() === 'Delete'
282 | );
283 |
284 | if (deleteButton) {
285 | console.log("Found DELETE button with JavaScript");
286 | deleteButton.click();
287 | return true;
288 | }
289 |
290 | // If not found by text, try to find right-most button in the modal
291 | const modalButtons = Array.from(document.querySelectorAll('.relative button, [role="dialog"] button, .modal button, [aria-modal="true"] button'));
292 |
293 | if (modalButtons.length > 1) {
294 | modalButtons.sort((a, b) => {
295 | const rectA = a.getBoundingClientRect();
296 | const rectB = b.getBoundingClientRect();
297 | return rectB.right - rectA.right;
298 | });
299 |
300 | console.log("Clicking right-most button in modal");
301 | modalButtons[0].click();
302 | return true;
303 | } else if (modalButtons.length === 1) {
304 | console.log("Clicking single button found in modal");
305 | modalButtons[0].click();
306 | return true;
307 | }
308 |
309 | return false;
310 | """)
311 |
312 | if delete_button_js:
313 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.delete_button_clicked', fallback='Clicked DELETE button')}{Style.RESET_ALL}")
314 | confirm_button_found = True
315 | except:
316 | pass
317 |
318 | if not confirm_button_found:
319 | # Fallback to simple selectors
320 | delete_button_selectors = [
321 | 'xpath://button[text()="DELETE"]',
322 | 'xpath://div[contains(@class, "modal")]//button[last()]'
323 | ]
324 |
325 | for selector in delete_button_selectors:
326 | try:
327 | delete_button = self.browser.ele(selector, timeout=2)
328 | if delete_button:
329 | delete_button.click()
330 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.delete_button_clicked', fallback='Account deleted successfully!')}{Style.RESET_ALL}")
331 | confirm_button_found = True
332 | break
333 | except:
334 | continue
335 |
336 | if not confirm_button_found:
337 | raise Exception(self.translator.get('account_delete.confirm_button_not_found') if self.translator else "Confirm button not found")
338 |
339 | # Wait a moment to see the confirmation
340 | time.sleep(2)
341 |
342 | return True
343 |
344 | except Exception as e:
345 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('account_delete.error', error=str(e)) if self.translator else f'Error during account deletion: {str(e)}'}{Style.RESET_ALL}")
346 | return False
347 | finally:
348 | # Clean up browser
349 | if self.browser:
350 | try:
351 | self.browser.quit()
352 | except:
353 | pass
354 |
355 | def main(translator=None):
356 | """Main function to handle Google account deletion"""
357 | print(f"\n{Fore.CYAN}{EMOJI['START']} {translator.get('account_delete.title') if translator else 'Cursor Google Account Deletion Tool'}{Style.RESET_ALL}")
358 | print(f"{Fore.YELLOW}{'─' * 50}{Style.RESET_ALL}")
359 |
360 | deleter = CursorGoogleAccountDeleter(translator)
361 |
362 | try:
363 | # Ask for confirmation
364 | print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('account_delete.warning') if translator else 'WARNING: This will permanently delete your Cursor account. This action cannot be undone.'}{Style.RESET_ALL}")
365 | confirm = input(f"{Fore.RED} {translator.get('account_delete.confirm_prompt') if translator else 'Are you sure you want to proceed? (y/N): '}{Style.RESET_ALL}").lower()
366 |
367 | if confirm != 'y':
368 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_delete.cancelled') if translator else 'Account deletion cancelled.'}{Style.RESET_ALL}")
369 | return
370 |
371 | success = deleter.delete_google_account()
372 |
373 | if success:
374 | print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('account_delete.success') if translator else 'Your Cursor account has been successfully deleted!'}{Style.RESET_ALL}")
375 | else:
376 | print(f"\n{Fore.RED}{EMOJI['ERROR']} {translator.get('account_delete.failed') if translator else 'Account deletion process failed or was cancelled.'}{Style.RESET_ALL}")
377 |
378 | except KeyboardInterrupt:
379 | print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_delete.interrupted') if translator else 'Account deletion process interrupted by user.'}{Style.RESET_ALL}")
380 | except Exception as e:
381 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_delete.unexpected_error', error=str(e)) if translator else f'Unexpected error: {str(e)}'}{Style.RESET_ALL}")
382 | finally:
383 | print(f"{Fore.YELLOW}{'─' * 50}{Style.RESET_ALL}")
384 |
385 | if __name__ == "__main__":
386 | main()
--------------------------------------------------------------------------------
/disable_auto_update.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import platform
4 | import shutil
5 | from colorama import Fore, Style, init
6 | import subprocess
7 | from config import get_config
8 | import re
9 | import tempfile
10 |
11 | # Initialize colorama
12 | init()
13 |
14 | # Define emoji constants
15 | EMOJI = {
16 | "PROCESS": "🔄",
17 | "SUCCESS": "✅",
18 | "ERROR": "❌",
19 | "INFO": "ℹ️",
20 | "FOLDER": "📁",
21 | "FILE": "📄",
22 | "STOP": "🛑",
23 | "CHECK": "✔️"
24 | }
25 |
26 | class AutoUpdateDisabler:
27 | def __init__(self, translator=None):
28 | self.translator = translator
29 | self.system = platform.system()
30 |
31 | # Get path from configuration file
32 | config = get_config(translator)
33 | if config:
34 | if self.system == "Windows":
35 | self.updater_path = config.get('WindowsPaths', 'updater_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"))
36 | self.update_yml_path = config.get('WindowsPaths', 'update_yml_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "update.yml"))
37 | self.product_json_path = config.get('WindowsPaths', 'product_json_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "product.json"))
38 | elif self.system == "Darwin":
39 | self.updater_path = config.get('MacPaths', 'updater_path', fallback=os.path.expanduser("~/Library/Application Support/cursor-updater"))
40 | self.update_yml_path = config.get('MacPaths', 'update_yml_path', fallback="/Applications/Cursor.app/Contents/Resources/app-update.yml")
41 | self.product_json_path = config.get('MacPaths', 'product_json_path', fallback="/Applications/Cursor.app/Contents/Resources/app/product.json")
42 | elif self.system == "Linux":
43 | self.updater_path = config.get('LinuxPaths', 'updater_path', fallback=os.path.expanduser("~/.config/cursor-updater"))
44 | self.update_yml_path = config.get('LinuxPaths', 'update_yml_path', fallback=os.path.expanduser("~/.config/cursor/resources/app-update.yml"))
45 | self.product_json_path = config.get('LinuxPaths', 'product_json_path', fallback=os.path.expanduser("~/.config/cursor/resources/app/product.json"))
46 | else:
47 | # If configuration loading fails, use default paths
48 | self.updater_paths = {
49 | "Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"),
50 | "Darwin": os.path.expanduser("~/Library/Application Support/cursor-updater"),
51 | "Linux": os.path.expanduser("~/.config/cursor-updater")
52 | }
53 | self.updater_path = self.updater_paths.get(self.system)
54 |
55 | self.update_yml_paths = {
56 | "Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "update.yml"),
57 | "Darwin": "/Applications/Cursor.app/Contents/Resources/app-update.yml",
58 | "Linux": os.path.expanduser("~/.config/cursor/resources/app-update.yml")
59 | }
60 | self.update_yml_path = self.update_yml_paths.get(self.system)
61 |
62 | self.product_json_paths = {
63 | "Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "product.json"),
64 | "Darwin": "/Applications/Cursor.app/Contents/Resources/app/product.json",
65 | "Linux": os.path.expanduser("~/.config/cursor/resources/app/product.json")
66 | }
67 | self.product_json_path = self.product_json_paths.get(self.system)
68 |
69 | def _remove_update_url(self):
70 | """Remove update URL"""
71 | try:
72 | original_stat = os.stat(self.product_json_path)
73 | original_mode = original_stat.st_mode
74 | original_uid = original_stat.st_uid
75 | original_gid = original_stat.st_gid
76 |
77 | with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file:
78 | with open(self.product_json_path, "r", encoding="utf-8") as product_json_file:
79 | content = product_json_file.read()
80 |
81 | patterns = {
82 | r"https://api2.cursor.sh/aiserver.v1.AuthService/DownloadUpdate": r"",
83 | r"https://api2.cursor.sh/updates": r"",
84 | r"http://cursorapi.com/updates": r"",
85 | }
86 |
87 | for pattern, replacement in patterns.items():
88 | content = re.sub(pattern, replacement, content)
89 |
90 | tmp_file.write(content)
91 | tmp_path = tmp_file.name
92 |
93 | shutil.copy2(self.product_json_path, self.product_json_path + ".old")
94 | shutil.move(tmp_path, self.product_json_path)
95 |
96 | os.chmod(self.product_json_path, original_mode)
97 | if os.name != "nt":
98 | os.chown(self.product_json_path, original_uid, original_gid)
99 |
100 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.file_modified')}{Style.RESET_ALL}")
101 | return True
102 |
103 | except Exception as e:
104 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}")
105 | if "tmp_path" in locals():
106 | os.unlink(tmp_path)
107 | return False
108 |
109 | def _kill_cursor_processes(self):
110 | """End all Cursor processes"""
111 | try:
112 | print(f"{Fore.CYAN}{EMOJI['PROCESS']} {self.translator.get('update.killing_processes') if self.translator else '正在结束 Cursor 进程...'}{Style.RESET_ALL}")
113 |
114 | if self.system == "Windows":
115 | subprocess.run(['taskkill', '/F', '/IM', 'Cursor.exe', '/T'], capture_output=True)
116 | else:
117 | subprocess.run(['pkill', '-f', 'Cursor'], capture_output=True)
118 |
119 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.processes_killed') if self.translator else 'Cursor 进程已结束'}{Style.RESET_ALL}")
120 | return True
121 |
122 | except Exception as e:
123 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.kill_process_failed', error=str(e)) if self.translator else f'结束进程失败: {e}'}{Style.RESET_ALL}")
124 | return False
125 |
126 | def _remove_updater_directory(self):
127 | """Delete updater directory"""
128 | try:
129 | updater_path = self.updater_path
130 | if not updater_path:
131 | raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
132 |
133 | print(f"{Fore.CYAN}{EMOJI['FOLDER']} {self.translator.get('update.removing_directory') if self.translator else '正在删除更新程序目录...'}{Style.RESET_ALL}")
134 |
135 | if os.path.exists(updater_path):
136 | try:
137 | if os.path.isdir(updater_path):
138 | shutil.rmtree(updater_path)
139 | else:
140 | os.remove(updater_path)
141 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.directory_removed') if self.translator else '更新程序目录已删除'}{Style.RESET_ALL}")
142 | except PermissionError:
143 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.directory_locked', path=updater_path) if self.translator else f'更新程序目录已被锁定,跳过删除: {updater_path}'}{Style.RESET_ALL}")
144 | return True
145 |
146 | except Exception as e:
147 | 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}")
148 | return True
149 |
150 | def _clear_update_yml_file(self):
151 | """Clear update.yml file"""
152 | try:
153 | update_yml_path = self.update_yml_path
154 | if not update_yml_path:
155 | raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
156 |
157 | print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.clearing_update_yml') if self.translator else '正在清空更新配置文件...'}{Style.RESET_ALL}")
158 |
159 | if os.path.exists(update_yml_path):
160 | try:
161 | with open(update_yml_path, 'w') as f:
162 | f.write('')
163 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.update_yml_cleared') if self.translator else '更新配置文件已清空'}{Style.RESET_ALL}")
164 | except PermissionError:
165 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.yml_locked') if self.translator else '更新配置文件已被锁定,跳过清空'}{Style.RESET_ALL}")
166 | else:
167 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.update_yml_not_found') if self.translator else '更新配置文件不存在'}{Style.RESET_ALL}")
168 | return True
169 |
170 | except Exception as e:
171 | 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}")
172 | return False
173 |
174 | def _create_blocking_file(self):
175 | """Create blocking files"""
176 | try:
177 | # 检查 updater_path
178 | updater_path = self.updater_path
179 | if not updater_path:
180 | raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
181 |
182 | print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.creating_block_file') if self.translator else '正在创建阻止文件...'}{Style.RESET_ALL}")
183 |
184 | # 创建 updater_path 阻止文件
185 | try:
186 | os.makedirs(os.path.dirname(updater_path), exist_ok=True)
187 | open(updater_path, 'w').close()
188 |
189 | # 设置 updater_path 为只读
190 | if self.system == "Windows":
191 | os.system(f'attrib +r "{updater_path}"')
192 | else:
193 | os.chmod(updater_path, 0o444) # 设置为只读
194 |
195 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.block_file_created') if self.translator else '阻止文件已创建'}: {updater_path}{Style.RESET_ALL}")
196 | except PermissionError:
197 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.block_file_locked') if self.translator else '阻止文件已被锁定,跳过创建'}{Style.RESET_ALL}")
198 |
199 | # 检查 update_yml_path
200 | update_yml_path = self.update_yml_path
201 | if update_yml_path and os.path.exists(os.path.dirname(update_yml_path)):
202 | try:
203 | # 创建 update_yml_path 阻止文件
204 | with open(update_yml_path, 'w') as f:
205 | f.write('# This file is locked to prevent auto-updates\nversion: 0.0.0\n')
206 |
207 | # 设置 update_yml_path 为只读
208 | if self.system == "Windows":
209 | os.system(f'attrib +r "{update_yml_path}"')
210 | else:
211 | os.chmod(update_yml_path, 0o444) # 设置为只读
212 |
213 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.yml_locked') if self.translator else '更新配置文件已锁定'}: {update_yml_path}{Style.RESET_ALL}")
214 | except PermissionError:
215 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.yml_already_locked') if self.translator else '更新配置文件已被锁定,跳过修改'}{Style.RESET_ALL}")
216 |
217 | return True
218 |
219 | except Exception as e:
220 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.create_block_file_failed', error=str(e)) if self.translator else f'创建阻止文件失败: {e}'}{Style.RESET_ALL}")
221 | return True # 返回 True 以继续执行后续步骤
222 |
223 | def disable_auto_update(self):
224 | """Disable auto update"""
225 | try:
226 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('update.start_disable') if self.translator else '开始禁用自动更新...'}{Style.RESET_ALL}")
227 |
228 | # 1. End processes
229 | if not self._kill_cursor_processes():
230 | return False
231 |
232 | # 2. Delete directory - 即使失败也继续执行
233 | self._remove_updater_directory()
234 |
235 | # 3. Clear update.yml file
236 | if not self._clear_update_yml_file():
237 | return False
238 |
239 | # 4. Create blocking file
240 | if not self._create_blocking_file():
241 | return False
242 |
243 | # 5. Remove update URL from product.json
244 | if not self._remove_update_url():
245 | return False
246 |
247 | print(f"{Fore.GREEN}{EMOJI['CHECK']} {self.translator.get('update.disable_success') if self.translator else '自动更新已禁用'}{Style.RESET_ALL}")
248 | return True
249 |
250 | except Exception as e:
251 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.disable_failed', error=str(e)) if self.translator else f'禁用自动更新失败: {e}'}{Style.RESET_ALL}")
252 | return False
253 |
254 | def run(translator=None):
255 | """Convenient function for directly calling the disable function"""
256 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
257 | print(f"{Fore.CYAN}{EMOJI['STOP']} {translator.get('update.title') if translator else 'Disable Cursor Auto Update'}{Style.RESET_ALL}")
258 | print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
259 |
260 | disabler = AutoUpdateDisabler(translator)
261 | disabler.disable_auto_update()
262 |
263 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
264 | input(f"{EMOJI['INFO']} {translator.get('update.press_enter') if translator else 'Press Enter to Continue...'}")
265 |
266 | if __name__ == "__main__":
267 | from main import translator as main_translator
268 | run(main_translator)
--------------------------------------------------------------------------------
/get_user_token.py:
--------------------------------------------------------------------------------
1 | import requests
2 | import json
3 | import time
4 | from colorama import Fore, Style
5 | import os
6 | from config import get_config
7 |
8 | # Define emoji constants
9 | EMOJI = {
10 | 'START': '🚀',
11 | 'OAUTH': '🔑',
12 | 'SUCCESS': '✅',
13 | 'ERROR': '❌',
14 | 'WAIT': '⏳',
15 | 'INFO': 'ℹ️',
16 | 'WARNING': '⚠️'
17 | }
18 |
19 | def refresh_token(token, translator=None):
20 | """Refresh the token using the Chinese server API
21 |
22 | Args:
23 | token (str): The full WorkosCursorSessionToken cookie value
24 | translator: Optional translator object
25 |
26 | Returns:
27 | str: The refreshed access token or original token if refresh fails
28 | """
29 | try:
30 | config = get_config(translator)
31 | # Get refresh_server URL from config or use default
32 | refresh_server = config.get('Token', 'refresh_server', fallback='https://token.cursorpro.com.cn')
33 |
34 | # Ensure the token is URL encoded properly
35 | if '%3A%3A' not in token and '::' in token:
36 | # Replace :: with URL encoded version if needed
37 | token = token.replace('::', '%3A%3A')
38 |
39 | # Make the request to the refresh server
40 | url = f"{refresh_server}/reftoken?token={token}"
41 |
42 | print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('token.refreshing') if translator else 'Refreshing token...'}{Style.RESET_ALL}")
43 |
44 | response = requests.get(url, timeout=30)
45 |
46 | if response.status_code == 200:
47 | try:
48 | data = response.json()
49 |
50 | if data.get('code') == 0 and data.get('msg') == "获取成功":
51 | access_token = data.get('data', {}).get('accessToken')
52 | days_left = data.get('data', {}).get('days_left', 0)
53 | expire_time = data.get('data', {}).get('expire_time', 'Unknown')
54 |
55 | if access_token:
56 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('token.refresh_success', days=days_left, expire=expire_time) if translator else f'Token refreshed successfully! Valid for {days_left} days (expires: {expire_time})'}{Style.RESET_ALL}")
57 | return access_token
58 | else:
59 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('token.no_access_token') if translator else 'No access token in response'}{Style.RESET_ALL}")
60 | else:
61 | error_msg = data.get('msg', 'Unknown error')
62 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.refresh_failed', error=error_msg) if translator else f'Token refresh failed: {error_msg}'}{Style.RESET_ALL}")
63 | except json.JSONDecodeError:
64 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.invalid_response') if translator else 'Invalid JSON response from refresh server'}{Style.RESET_ALL}")
65 | else:
66 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.server_error', status=response.status_code) if translator else f'Refresh server error: HTTP {response.status_code}'}{Style.RESET_ALL}")
67 |
68 | except requests.exceptions.Timeout:
69 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.request_timeout') if translator else 'Request to refresh server timed out'}{Style.RESET_ALL}")
70 | except requests.exceptions.ConnectionError:
71 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.connection_error') if translator else 'Connection error to refresh server'}{Style.RESET_ALL}")
72 | except Exception as e:
73 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.unexpected_error', error=str(e)) if translator else f'Unexpected error during token refresh: {str(e)}'}{Style.RESET_ALL}")
74 |
75 | # Return original token if refresh fails
76 | return token.split('%3A%3A')[-1] if '%3A%3A' in token else token.split('::')[-1] if '::' in token else token
77 |
78 | def get_token_from_cookie(cookie_value, translator=None):
79 | """Extract and process token from cookie value
80 |
81 | Args:
82 | cookie_value (str): The WorkosCursorSessionToken cookie value
83 | translator: Optional translator object
84 |
85 | Returns:
86 | str: The processed token
87 | """
88 | try:
89 | # Try to refresh the token with the API first
90 | refreshed_token = refresh_token(cookie_value, translator)
91 |
92 | # If refresh succeeded and returned a different token, use it
93 | if refreshed_token and refreshed_token != cookie_value:
94 | return refreshed_token
95 |
96 | # If refresh failed or returned same token, use traditional extraction method
97 | if '%3A%3A' in cookie_value:
98 | return cookie_value.split('%3A%3A')[-1]
99 | elif '::' in cookie_value:
100 | return cookie_value.split('::')[-1]
101 | else:
102 | return cookie_value
103 |
104 | except Exception as e:
105 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.extraction_error', error=str(e)) if translator else f'Error extracting token: {str(e)}'}{Style.RESET_ALL}")
106 | # Fall back to original behavior
107 | if '%3A%3A' in cookie_value:
108 | return cookie_value.split('%3A%3A')[-1]
109 | elif '::' in cookie_value:
110 | return cookie_value.split('::')[-1]
111 | else:
112 | return cookie_value
--------------------------------------------------------------------------------
/images/cloudflare_2025-02-12_13-43-21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/cloudflare_2025-02-12_13-43-21.png
--------------------------------------------------------------------------------
/images/fix_2025-01-14_21-30-43.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/fix_2025-01-14_21-30-43.png
--------------------------------------------------------------------------------
/images/free_2025-01-14_14-59-15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/free_2025-01-14_14-59-15.png
--------------------------------------------------------------------------------
/images/locale_2025-01-15_13-40-08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/locale_2025-01-15_13-40-08.png
--------------------------------------------------------------------------------
/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/logo.png
--------------------------------------------------------------------------------
/images/new107_2025-01-15_13-53-56.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/new107_2025-01-15_13-53-56.png
--------------------------------------------------------------------------------
/images/new_2025-02-27_10-42-44.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/new_2025-02-27_10-42-44.png
--------------------------------------------------------------------------------
/images/new_2025-03-19_00-19-09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/new_2025-03-19_00-19-09.png
--------------------------------------------------------------------------------
/images/new_2025-03-22_19-53-10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/new_2025-03-22_19-53-10.png
--------------------------------------------------------------------------------
/images/pass_2025-02-08_21-48-36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pass_2025-02-08_21-48-36.png
--------------------------------------------------------------------------------
/images/paypal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/paypal.png
--------------------------------------------------------------------------------
/images/pro_2025-01-11_00-50-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-01-11_00-50-40.png
--------------------------------------------------------------------------------
/images/pro_2025-01-11_00-51-07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-01-11_00-51-07.png
--------------------------------------------------------------------------------
/images/pro_2025-01-11_16-24-03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-01-11_16-24-03.png
--------------------------------------------------------------------------------
/images/pro_2025-01-11_22-33-09.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-01-11_22-33-09.gif
--------------------------------------------------------------------------------
/images/pro_2025-01-13_13-49-55.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-01-13_13-49-55.png
--------------------------------------------------------------------------------
/images/pro_2025-01-14_14-40-37.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-01-14_14-40-37.png
--------------------------------------------------------------------------------
/images/pro_2025-04-05_18-47-56.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-04-05_18-47-56.png
--------------------------------------------------------------------------------
/images/product_2025-04-16_10-40-21.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/product_2025-04-16_10-40-21.png
--------------------------------------------------------------------------------
/images/pronew_2025-02-13_15-01-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pronew_2025-02-13_15-01-32.png
--------------------------------------------------------------------------------
/images/provi-code.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/provi-code.jpg
--------------------------------------------------------------------------------
/images/what_2025-01-13_13-32-54.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/what_2025-01-13_13-32-54.png
--------------------------------------------------------------------------------
/locales/it.json:
--------------------------------------------------------------------------------
1 | {
2 | "menu": {
3 | "title": "Opzioni Disponibili",
4 | "exit": "Esci dal Programma",
5 | "reset": "Reimposta ID Macchina",
6 | "register": "Registra Nuovo Account Cursor",
7 | "register_google": "Registrati con il Tuo Account Google",
8 | "register_github": "Registrati con il Tuo Account GitHub",
9 | "register_manual": "Registra Cursor con Email Personalizzata",
10 | "quit": "Chiudi Applicazione Cursor",
11 | "select_language": "Cambia Lingua",
12 | "select_chrome_profile": "Seleziona Profilo Chrome",
13 | "input_choice": "Inserisci la tua scelta ({choices})",
14 | "invalid_choice": "Selezione non valida. Inserisci un numero da {choices}",
15 | "program_terminated": "Programma terminato dall'utente",
16 | "error_occurred": "Si è verificato un errore: {error}. Riprova",
17 | "press_enter": "Premi Invio per Uscire",
18 | "disable_auto_update": "Disabilita Aggiornamento Automatico di Cursor",
19 | "lifetime_access_enabled": "ACCESSO A VITA ABILITATO",
20 | "totally_reset": "Reimposta Completamente Cursor",
21 | "outdate": "Obsoleto",
22 | "temp_github_register": "Registrazione GitHub Temporanea",
23 | "admin_required": "Esecuzione come file eseguibile, richiesti privilegi di amministratore.",
24 | "admin_required_continue": "Continua senza privilegi di amministratore.",
25 | "coming_soon": "Prossimamente",
26 | "fixed_soon": "Corretto Presto",
27 | "contribute": "Contribuisci al Progetto",
28 | "config": "Mostra Configurazione",
29 | "delete_google_account": "Elimina Account Google di Cursor",
30 | "continue_prompt": "Continuare? (y/N): ",
31 | "operation_cancelled_by_user": "Operazione annullata dall'utente",
32 | "exiting": "Uscita in corso...",
33 | "bypass_version_check": "Ignora Controllo Versione Cursor",
34 | "check_user_authorized": "Verifica Autorizzazione Utente",
35 | "bypass_token_limit": "Ignora Limite Token",
36 | "language_config_saved": "Configurazione lingua salvata con successo",
37 | "lang_invalid_choice": "Scelta non valida. Inserisci una delle seguenti opzioni: ({lang_choices})",
38 | "restore_machine_id": "Ripristina ID Macchina dal Backup"
39 | },
40 | "languages": {
41 | "ar": "Arabo",
42 | "en": "Inglese",
43 | "zh_cn": "Cinese Semplificato",
44 | "zh_tw": "Cinese Tradizionale",
45 | "vi": "Vietnamita",
46 | "nl": "Olandese",
47 | "de": "Tedesco",
48 | "fr": "Francese",
49 | "pt": "Portoghese",
50 | "ru": "Russo",
51 | "tr": "Turco",
52 | "bg": "Bulgaro",
53 | "es": "Spagnolo",
54 | "ja": "Giapponese",
55 | "it": "Italiano"
56 | }
57 | }
--------------------------------------------------------------------------------
/locales/ja.json:
--------------------------------------------------------------------------------
1 | {
2 | "menu": {
3 | "title": "利用可能なオプション",
4 | "exit": "プログラムを終了",
5 | "reset": "マシンIDをリセット",
6 | "register": "新しいCursorアカウントを登録",
7 | "register_google": "Googleアカウントで登録",
8 | "register_github": "GitHubアカウントで登録",
9 | "register_manual": "カスタムメールでCursorを登録",
10 | "quit": "Cursorアプリケーションを閉じる",
11 | "select_language": "言語を変更",
12 | "select_chrome_profile": "Chromeプロファイルを選択",
13 | "input_choice": "選択肢を入力してください ({choices})",
14 | "invalid_choice": "無効な選択です。{choices}から数字を入力してください",
15 | "program_terminated": "プログラムはユーザーによって終了されました",
16 | "error_occurred": "エラーが発生しました: {error}。もう一度お試しください",
17 | "press_enter": "終了するにはEnterキーを押してください",
18 | "disable_auto_update": "Cursorの自動更新を無効化",
19 | "lifetime_access_enabled": "ライフタイムアクセスが有効化されました",
20 | "totally_reset": "Cursorを完全にリセット",
21 | "outdate": "期限切れ",
22 | "temp_github_register": "一時的なGitHub登録",
23 | "admin_required": "実行ファイルとして実行中、管理者権限が必要です。",
24 | "admin_required_continue": "管理者権限なしで続行します。",
25 | "coming_soon": "近日公開",
26 | "fixed_soon": "近日修正予定",
27 | "contribute": "プロジェクトに貢献",
28 | "config": "設定を表示",
29 | "delete_google_account": "CursorのGoogleアカウントを削除",
30 | "continue_prompt": "続行しますか?(y/N): ",
31 | "operation_cancelled_by_user": "操作はユーザーによってキャンセルされました",
32 | "exiting": "終了中……",
33 | "bypass_version_check": "Cursorのバージョンチェックをバイパス",
34 | "check_user_authorized": "ユーザーの認証を確認",
35 | "bypass_token_limit": "トークン制限をバイパス",
36 | "language_config_saved": "言語設定が正常に保存されました",
37 | "lang_invalid_choice": "無効な選択です。以下のオプションから選択してください: ({lang_choices})",
38 | "restore_machine_id": "バックアップからマシンIDを復元"
39 | },
40 | "languages": {
41 | "ar": "アラビア語",
42 | "en": "英語",
43 | "zh_cn": "簡体字中国語",
44 | "zh_tw": "繁体字中国語",
45 | "vi": "ベトナム語",
46 | "nl": "オランダ語",
47 | "de": "ドイツ語",
48 | "fr": "フランス語",
49 | "pt": "ポルトガル語",
50 | "ru": "ロシア語",
51 | "tr": "トルコ語",
52 | "bg": "ブルガリア語",
53 | "es": "スペイン語",
54 | "ja": "日本語",
55 | "it": "イタリア語"
56 | }
57 | }
--------------------------------------------------------------------------------
/logo.py:
--------------------------------------------------------------------------------
1 | from colorama import Fore, Style, init
2 | from dotenv import load_dotenv
3 | import os
4 | import shutil
5 | import re
6 |
7 | # Get the current script directory
8 | current_dir = os.path.dirname(os.path.abspath(__file__))
9 | # Build the full path to the .env file
10 | env_path = os.path.join(current_dir, '.env')
11 |
12 | # Load environment variables, specifying the .env file path
13 | load_dotenv(env_path)
14 | # Get the version number, using the default value if not found
15 | version = os.getenv('VERSION', '1.0.0')
16 |
17 | # Initialize colorama
18 | init()
19 |
20 | # get terminal width
21 | def get_terminal_width():
22 | try:
23 | columns, _ = shutil.get_terminal_size()/2
24 | return columns
25 | except:
26 | return 80 # default width
27 |
28 | # center display text (not handling Chinese characters)
29 | def center_multiline_text(text, handle_chinese=False):
30 | width = get_terminal_width()
31 | lines = text.split('\n')
32 | centered_lines = []
33 |
34 | for line in lines:
35 | # calculate actual display width (remove ANSI color codes)
36 | clean_line = line
37 | for color in [Fore.CYAN, Fore.YELLOW, Fore.GREEN, Fore.RED, Fore.BLUE, Style.RESET_ALL]:
38 | clean_line = clean_line.replace(color, '')
39 |
40 | # remove all ANSI escape sequences to get the actual length
41 | ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
42 | clean_line = ansi_escape.sub('', clean_line)
43 |
44 | # calculate display width
45 | if handle_chinese:
46 | # consider Chinese characters occupying two positions
47 | display_width = 0
48 | for char in clean_line:
49 | if ord(char) > 127: # non-ASCII characters
50 | display_width += 2
51 | else:
52 | display_width += 1
53 | else:
54 | # not handling Chinese characters
55 | display_width = len(clean_line)
56 |
57 | # calculate the number of spaces to add
58 | padding = max(0, (width - display_width) // 2)
59 | centered_lines.append(' ' * padding + line)
60 |
61 | return '\n'.join(centered_lines)
62 |
63 | # original LOGO text
64 | LOGO_TEXT = f"""{Fore.CYAN}
65 | ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗
66 | ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ ██╔══██╗██╔══██╗██╔═══██╗
67 | ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ ██████╔╝██████╔╝██║ ██║
68 | ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ ██╔═══╝ ██╔══██╗██║ ██║
69 | ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ██║ ██║ ██║╚██████╔╝
70 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝
71 | {Style.RESET_ALL}"""
72 |
73 | DESCRIPTION_TEXT = f"""{Fore.YELLOW}
74 | Pro Version Activator v{version}{Fore.GREEN}
75 | Author: Pin Studios (yeongpin)"""
76 |
77 | CONTRIBUTORS_TEXT = f"""{Fore.BLUE}
78 | Contributors:
79 | BasaiCorp aliensb handwerk2016 Nigel1992
80 | UntaDotMy RenjiYuusei imbajin ahmed98Osama
81 | bingoohuang mALIk-sHAHId MFaiqKhan httpmerak
82 | muhammedfurkan plamkatawe Lucaszmv
83 | """
84 | OTHER_INFO_TEXT = f"""{Fore.YELLOW}
85 | Github: https://github.com/yeongpin/cursor-free-vip{Fore.RED}
86 | Press 4 to change language | 按下 4 键切换语言{Style.RESET_ALL}"""
87 |
88 | # center display LOGO and DESCRIPTION
89 | CURSOR_LOGO = center_multiline_text(LOGO_TEXT, handle_chinese=False)
90 | CURSOR_DESCRIPTION = center_multiline_text(DESCRIPTION_TEXT, handle_chinese=False)
91 | CURSOR_CONTRIBUTORS = center_multiline_text(CONTRIBUTORS_TEXT, handle_chinese=False)
92 | CURSOR_OTHER_INFO = center_multiline_text(OTHER_INFO_TEXT, handle_chinese=True)
93 |
94 | def print_logo():
95 | print(CURSOR_LOGO)
96 | print(CURSOR_DESCRIPTION)
97 | # print(CURSOR_CONTRIBUTORS)
98 | print(CURSOR_OTHER_INFO)
99 |
100 | if __name__ == "__main__":
101 | print_logo()
102 |
--------------------------------------------------------------------------------
/quit_cursor.py:
--------------------------------------------------------------------------------
1 | import psutil
2 | import time
3 | from colorama import Fore, Style, init
4 | import sys
5 | import os
6 |
7 | # Initialize colorama
8 | init()
9 |
10 | # Define emoji constants
11 | EMOJI = {
12 | "PROCESS": "⚙️",
13 | "SUCCESS": "✅",
14 | "ERROR": "❌",
15 | "INFO": "ℹ️",
16 | "WAIT": "⏳"
17 | }
18 |
19 | class CursorQuitter:
20 | def __init__(self, timeout=5, translator=None):
21 | self.timeout = timeout
22 | self.translator = translator # Use the passed translator
23 |
24 | def quit_cursor(self):
25 | """Gently close Cursor processes"""
26 | try:
27 | print(f"{Fore.CYAN}{EMOJI['PROCESS']} {self.translator.get('quit_cursor.start')}...{Style.RESET_ALL}")
28 | cursor_processes = []
29 |
30 | # Collect all Cursor processes
31 | for proc in psutil.process_iter(['pid', 'name']):
32 | try:
33 | if proc.info['name'].lower() in ['cursor.exe', 'cursor']:
34 | cursor_processes.append(proc)
35 | except (psutil.NoSuchProcess, psutil.AccessDenied):
36 | continue
37 |
38 | if not cursor_processes:
39 | print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('quit_cursor.no_process')}{Style.RESET_ALL}")
40 | return True
41 |
42 | # Gently request processes to terminate
43 | for proc in cursor_processes:
44 | try:
45 | if proc.is_running():
46 | print(f"{Fore.YELLOW}{EMOJI['PROCESS']} {self.translator.get('quit_cursor.terminating', pid=proc.pid)}...{Style.RESET_ALL}")
47 | proc.terminate()
48 | except (psutil.NoSuchProcess, psutil.AccessDenied):
49 | continue
50 |
51 | # Wait for processes to terminate naturally
52 | print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('quit_cursor.waiting')}...{Style.RESET_ALL}")
53 | start_time = time.time()
54 | while time.time() - start_time < self.timeout:
55 | still_running = []
56 | for proc in cursor_processes:
57 | try:
58 | if proc.is_running():
59 | still_running.append(proc)
60 | except (psutil.NoSuchProcess, psutil.AccessDenied):
61 | continue
62 |
63 | if not still_running:
64 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('quit_cursor.success')}{Style.RESET_ALL}")
65 | return True
66 |
67 | time.sleep(0.5)
68 |
69 | # If processes are still running after timeout
70 | if still_running:
71 | process_list = ", ".join([str(p.pid) for p in still_running])
72 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('quit_cursor.timeout', pids=process_list)}{Style.RESET_ALL}")
73 | return False
74 |
75 | return True
76 |
77 | except Exception as e:
78 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('quit_cursor.error', error=str(e))}{Style.RESET_ALL}")
79 | return False
80 |
81 | def quit_cursor(translator=None, timeout=5):
82 | """Convenient function for directly calling the quit function"""
83 | quitter = CursorQuitter(timeout, translator)
84 | return quitter.quit_cursor()
85 |
86 | if __name__ == "__main__":
87 | # If run directly, use the default translator
88 | from main import translator as main_translator
89 | quit_cursor(main_translator)
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | watchdog
2 | python-dotenv>=1.0.0
3 | colorama>=0.4.6
4 | requests
5 | psutil>=5.8.0
6 | pywin32; platform_system == "Windows"
7 | pyinstaller
8 | DrissionPage>=4.0.0
9 | selenium
10 | webdriver_manager
11 | arabic-reshaper
12 | python-bidi
13 | faker
--------------------------------------------------------------------------------
/restore_machine_id.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import json
4 | import uuid
5 | import hashlib
6 | import shutil
7 | import sqlite3
8 | import platform
9 | import re
10 | import glob
11 | import tempfile
12 | from colorama import Fore, Style, init
13 | from typing import Tuple
14 | import configparser
15 | import traceback
16 | from config import get_config
17 | from datetime import datetime
18 |
19 | # 导入共享函数
20 | from reset_machine_manual import get_cursor_machine_id_path, get_user_documents_path
21 |
22 | # 初始化 colorama
23 | init()
24 |
25 | # 定义表情符号常量
26 | EMOJI = {
27 | "FILE": "📄",
28 | "BACKUP": "💾",
29 | "SUCCESS": "✅",
30 | "ERROR": "❌",
31 | "INFO": "ℹ️",
32 | "RESET": "🔄",
33 | "WARNING": "⚠️",
34 | }
35 |
36 | class ConfigError(Exception):
37 | """配置错误异常"""
38 | pass
39 |
40 | class MachineIDRestorer:
41 | def __init__(self, translator=None):
42 | self.translator = translator
43 |
44 | # 读取配置
45 | config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
46 | config_file = os.path.join(config_dir, "config.ini")
47 | config = configparser.ConfigParser()
48 |
49 | if not os.path.exists(config_file):
50 | raise FileNotFoundError(f"Config file not found: {config_file}")
51 |
52 | config.read(config_file, encoding='utf-8')
53 |
54 | # 根据操作系统获取路径
55 | if sys.platform == "win32": # Windows
56 | appdata = os.getenv("APPDATA")
57 | if appdata is None:
58 | raise EnvironmentError("APPDATA Environment Variable Not Set")
59 |
60 | if not config.has_section('WindowsPaths'):
61 | raise ConfigError("WindowsPaths section not found in config")
62 |
63 | self.db_path = config.get('WindowsPaths', 'storage_path')
64 | self.sqlite_path = config.get('WindowsPaths', 'sqlite_path')
65 |
66 | elif sys.platform == "darwin": # macOS
67 | if not config.has_section('MacPaths'):
68 | raise ConfigError("MacPaths section not found in config")
69 |
70 | self.db_path = config.get('MacPaths', 'storage_path')
71 | self.sqlite_path = config.get('MacPaths', 'sqlite_path')
72 |
73 | elif sys.platform == "linux": # Linux
74 | if not config.has_section('LinuxPaths'):
75 | raise ConfigError("LinuxPaths section not found in config")
76 |
77 | self.db_path = config.get('LinuxPaths', 'storage_path')
78 | self.sqlite_path = config.get('LinuxPaths', 'sqlite_path')
79 |
80 | else:
81 | raise NotImplementedError(f"Not Supported OS: {sys.platform}")
82 |
83 | def find_backups(self):
84 | """查找可用的备份文件"""
85 | db_dir = os.path.dirname(self.db_path)
86 | db_name = os.path.basename(self.db_path)
87 |
88 | # 查找格式为 {db_name}.bak.{timestamp} 的文件
89 | backup_pattern = f"{db_name}.bak.*"
90 | backups = glob.glob(os.path.join(db_dir, backup_pattern))
91 |
92 | # 按创建时间排序(最新的在前)
93 | backups.sort(key=os.path.getctime, reverse=True)
94 |
95 | return backups
96 |
97 | def list_backups(self):
98 | """列出所有可用备份"""
99 | backups = self.find_backups()
100 |
101 | if not backups:
102 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('restore.no_backups_found')}{Style.RESET_ALL}")
103 | return None
104 |
105 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('restore.available_backups')}:{Style.RESET_ALL}")
106 | for i, backup in enumerate(backups, 1):
107 | # 获取备份文件信息
108 | timestamp_str = backup.split('.')[-1]
109 | try:
110 | # 尝试解析时间戳(如果格式为 YYYYmmdd_HHMMSS)
111 | timestamp = datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
112 | date_str = timestamp.strftime("%Y-%m-%d %H:%M:%S")
113 | except ValueError:
114 | date_str = "未知日期"
115 |
116 | # 获取文件大小
117 | size = os.path.getsize(backup)
118 | size_str = f"{size / 1024:.1f} KB"
119 |
120 | print(f"{i}. {Fore.GREEN}{os.path.basename(backup)}{Style.RESET_ALL} ({date_str}, {size_str})")
121 |
122 | return backups
123 |
124 | def select_backup(self):
125 | """让用户选择要恢复的备份"""
126 | backups = self.list_backups()
127 |
128 | if not backups:
129 | return None
130 |
131 | while True:
132 | try:
133 | choice = input(f"{EMOJI['INFO']} {self.translator.get('restore.select_backup')} (1-{len(backups)}, 0 {self.translator.get('restore.to_cancel')}): ")
134 |
135 | if choice.strip() == '0':
136 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('restore.operation_cancelled')}{Style.RESET_ALL}")
137 | return None
138 |
139 | index = int(choice) - 1
140 | if 0 <= index < len(backups):
141 | return backups[index]
142 | else:
143 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.invalid_selection')}{Style.RESET_ALL}")
144 | except ValueError:
145 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.please_enter_number')}{Style.RESET_ALL}")
146 |
147 | def extract_ids_from_backup(self, backup_path):
148 | """从备份文件中提取机器ID"""
149 | try:
150 | with open(backup_path, "r", encoding="utf-8") as f:
151 | backup_data = json.load(f)
152 |
153 | # 提取需要恢复的ID
154 | ids = {
155 | "telemetry.devDeviceId": backup_data.get("telemetry.devDeviceId", ""),
156 | "telemetry.macMachineId": backup_data.get("telemetry.macMachineId", ""),
157 | "telemetry.machineId": backup_data.get("telemetry.machineId", ""),
158 | "telemetry.sqmId": backup_data.get("telemetry.sqmId", ""),
159 | "storage.serviceMachineId": backup_data.get("storage.serviceMachineId",
160 | backup_data.get("telemetry.devDeviceId", ""))
161 | }
162 |
163 | # 确保所有ID都存在
164 | for key, value in ids.items():
165 | if not value:
166 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('restore.missing_id', id=key)}{Style.RESET_ALL}")
167 |
168 | return ids
169 | except Exception as e:
170 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.read_backup_failed', error=str(e))}{Style.RESET_ALL}")
171 | return None
172 |
173 | def update_current_file(self, ids):
174 | """更新当前的storage.json文件"""
175 | try:
176 | if not os.path.exists(self.db_path):
177 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.current_file_not_found')}: {self.db_path}{Style.RESET_ALL}")
178 | return False
179 |
180 | # 读取当前文件
181 | with open(self.db_path, "r", encoding="utf-8") as f:
182 | current_data = json.load(f)
183 |
184 | # 创建当前文件的备份
185 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
186 | backup_path = f"{self.db_path}.restore_bak.{timestamp}"
187 | shutil.copy2(self.db_path, backup_path)
188 | print(f"{Fore.GREEN}{EMOJI['BACKUP']} {self.translator.get('restore.current_backup_created')}: {backup_path}{Style.RESET_ALL}")
189 |
190 | # 更新ID
191 | current_data.update(ids)
192 |
193 | # 保存更新后的文件
194 | with open(self.db_path, "w", encoding="utf-8") as f:
195 | json.dump(current_data, f, indent=4)
196 |
197 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.storage_updated')}{Style.RESET_ALL}")
198 | return True
199 | except Exception as e:
200 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.update_failed', error=str(e))}{Style.RESET_ALL}")
201 | return False
202 |
203 | def update_sqlite_db(self, ids):
204 | """更新SQLite数据库中的ID"""
205 | try:
206 | if not os.path.exists(self.sqlite_path):
207 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.sqlite_not_found')}: {self.sqlite_path}{Style.RESET_ALL}")
208 | return False
209 |
210 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('restore.updating_sqlite')}...{Style.RESET_ALL}")
211 |
212 | conn = sqlite3.connect(self.sqlite_path)
213 | cursor = conn.cursor()
214 |
215 | cursor.execute("""
216 | CREATE TABLE IF NOT EXISTS ItemTable (
217 | key TEXT PRIMARY KEY,
218 | value TEXT
219 | )
220 | """)
221 |
222 | for key, value in ids.items():
223 | cursor.execute("""
224 | INSERT OR REPLACE INTO ItemTable (key, value)
225 | VALUES (?, ?)
226 | """, (key, value))
227 | print(f"{EMOJI['INFO']} {Fore.CYAN} {self.translator.get('restore.updating_pair')}: {key}{Style.RESET_ALL}")
228 |
229 | conn.commit()
230 | conn.close()
231 |
232 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.sqlite_updated')}{Style.RESET_ALL}")
233 | return True
234 | except Exception as e:
235 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.sqlite_update_failed', error=str(e))}{Style.RESET_ALL}")
236 | return False
237 |
238 | def update_machine_id_file(self, dev_device_id):
239 | """更新machineId文件"""
240 | try:
241 | machine_id_path = get_cursor_machine_id_path(self.translator)
242 |
243 | # 创建目录(如果不存在)
244 | os.makedirs(os.path.dirname(machine_id_path), exist_ok=True)
245 |
246 | # 备份当前文件(如果存在)
247 | if os.path.exists(machine_id_path):
248 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
249 | backup_path = f"{machine_id_path}.restore_bak.{timestamp}"
250 | try:
251 | shutil.copy2(machine_id_path, backup_path)
252 | print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('restore.machine_id_backup_created')}: {backup_path}{Style.RESET_ALL}")
253 | except Exception as e:
254 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('restore.backup_creation_failed', error=str(e))}{Style.RESET_ALL}")
255 |
256 | # 写入新的ID
257 | with open(machine_id_path, "w", encoding="utf-8") as f:
258 | f.write(dev_device_id)
259 |
260 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.machine_id_updated')}{Style.RESET_ALL}")
261 | return True
262 | except Exception as e:
263 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.machine_id_update_failed', error=str(e))}{Style.RESET_ALL}")
264 | return False
265 |
266 | def update_system_ids(self, ids):
267 | """更新系统级ID(特定于操作系统)"""
268 | try:
269 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('restore.updating_system_ids')}...{Style.RESET_ALL}")
270 |
271 | if sys.platform.startswith("win"):
272 | self._update_windows_system_ids(ids)
273 | elif sys.platform == "darwin":
274 | self._update_macos_system_ids(ids)
275 |
276 | return True
277 | except Exception as e:
278 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.system_ids_update_failed', error=str(e))}{Style.RESET_ALL}")
279 | return False
280 |
281 | def _update_windows_system_ids(self, ids):
282 | """更新Windows系统ID"""
283 | try:
284 | import winreg
285 |
286 | # 更新MachineGuid
287 | guid = ids.get("telemetry.devDeviceId", "")
288 | if guid:
289 | try:
290 | key = winreg.OpenKey(
291 | winreg.HKEY_LOCAL_MACHINE,
292 | "SOFTWARE\\Microsoft\\Cryptography",
293 | 0,
294 | winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY
295 | )
296 | winreg.SetValueEx(key, "MachineGuid", 0, winreg.REG_SZ, guid)
297 | winreg.CloseKey(key)
298 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.windows_machine_guid_updated')}{Style.RESET_ALL}")
299 | except PermissionError:
300 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.permission_denied')}{Style.RESET_ALL}")
301 | except Exception as e:
302 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.update_windows_machine_guid_failed', error=str(e))}{Style.RESET_ALL}")
303 |
304 | # 更新SQMClient MachineId
305 | sqm_id = ids.get("telemetry.sqmId", "")
306 | if sqm_id:
307 | try:
308 | key = winreg.OpenKey(
309 | winreg.HKEY_LOCAL_MACHINE,
310 | r"SOFTWARE\Microsoft\SQMClient",
311 | 0,
312 | winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY
313 | )
314 | winreg.SetValueEx(key, "MachineId", 0, winreg.REG_SZ, sqm_id)
315 | winreg.CloseKey(key)
316 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.windows_machine_id_updated')}{Style.RESET_ALL}")
317 | except FileNotFoundError:
318 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('restore.sqm_client_key_not_found')}{Style.RESET_ALL}")
319 | except PermissionError:
320 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.permission_denied')}{Style.RESET_ALL}")
321 | except Exception as e:
322 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.update_windows_machine_id_failed', error=str(e))}{Style.RESET_ALL}")
323 | except Exception as e:
324 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.update_windows_system_ids_failed', error=str(e))}{Style.RESET_ALL}")
325 |
326 | def _update_macos_system_ids(self, ids):
327 | """更新macOS系统ID"""
328 | try:
329 | uuid_file = "/var/root/Library/Preferences/SystemConfiguration/com.apple.platform.uuid.plist"
330 | if os.path.exists(uuid_file):
331 | mac_id = ids.get("telemetry.macMachineId", "")
332 | if mac_id:
333 | cmd = f'sudo plutil -replace "UUID" -string "{mac_id}" "{uuid_file}"'
334 | result = os.system(cmd)
335 | if result == 0:
336 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.macos_platform_uuid_updated')}{Style.RESET_ALL}")
337 | else:
338 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.failed_to_execute_plutil_command')}{Style.RESET_ALL}")
339 | except Exception as e:
340 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.update_macos_system_ids_failed', error=str(e))}{Style.RESET_ALL}")
341 |
342 | def restore_machine_ids(self):
343 | """恢复之前备份的机器ID"""
344 | try:
345 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('restore.starting')}...{Style.RESET_ALL}")
346 |
347 | # 选择要恢复的备份
348 | backup_path = self.select_backup()
349 | if not backup_path:
350 | return False
351 |
352 | # 从备份中提取ID
353 | ids = self.extract_ids_from_backup(backup_path)
354 | if not ids:
355 | return False
356 |
357 | # 显示将要恢复的ID
358 | print(f"\n{Fore.CYAN}{self.translator.get('restore.ids_to_restore')}:{Style.RESET_ALL}")
359 | for key, value in ids.items():
360 | print(f"{EMOJI['INFO']} {key}: {Fore.GREEN}{value}{Style.RESET_ALL}")
361 |
362 | # 确认恢复
363 | confirm = input(f"\n{EMOJI['WARNING']} {self.translator.get('restore.confirm')} (y/n): ")
364 | if confirm.lower() != 'y':
365 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('restore.operation_cancelled')}{Style.RESET_ALL}")
366 | return False
367 |
368 | # 更新当前文件
369 | if not self.update_current_file(ids):
370 | return False
371 |
372 | # 更新SQLite数据库
373 | self.update_sqlite_db(ids)
374 |
375 | # 更新machineId文件
376 | self.update_machine_id_file(ids.get("telemetry.devDeviceId", ""))
377 |
378 | # 更新系统ID
379 | self.update_system_ids(ids)
380 |
381 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.success')}{Style.RESET_ALL}")
382 | return True
383 |
384 | except Exception as e:
385 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.process_error', error=str(e))}{Style.RESET_ALL}")
386 | return False
387 |
388 | def run(translator=None):
389 | """恢复机器ID的主函数"""
390 | config = get_config(translator)
391 | if not config:
392 | return False
393 |
394 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
395 | print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('restore.title')}{Style.RESET_ALL}")
396 | print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
397 |
398 | restorer = MachineIDRestorer(translator)
399 | restorer.restore_machine_ids()
400 |
401 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
402 | input(f"{EMOJI['INFO']} {translator.get('restore.press_enter')}...")
403 |
404 | if __name__ == "__main__":
405 | from main import translator as main_translator
406 | run(main_translator)
--------------------------------------------------------------------------------
/scripts/install.ps1:
--------------------------------------------------------------------------------
1 | # set color theme
2 | $Theme = @{
3 | Primary = 'Cyan'
4 | Success = 'Green'
5 | Warning = 'Yellow'
6 | Error = 'Red'
7 | Info = 'White'
8 | }
9 |
10 | # ASCII Logo
11 | $Logo = @"
12 | ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗
13 | ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ ██╔══██╗██╔══██╗██╔═══██╗
14 | ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ ██████╔╝██████╔╝██║ ██║
15 | ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ ██╔═══╝ ██╔══██╗██║ ██║
16 | ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ██║ ██║ ██║╚██████╔╝
17 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝
18 | "@
19 |
20 | # Beautiful Output Function
21 | function Write-Styled {
22 | param (
23 | [string]$Message,
24 | [string]$Color = $Theme.Info,
25 | [string]$Prefix = "",
26 | [switch]$NoNewline
27 | )
28 | $symbol = switch ($Color) {
29 | $Theme.Success { "[OK]" }
30 | $Theme.Error { "[X]" }
31 | $Theme.Warning { "[!]" }
32 | default { "[*]" }
33 | }
34 |
35 | $output = if ($Prefix) { "$symbol $Prefix :: $Message" } else { "$symbol $Message" }
36 | if ($NoNewline) {
37 | Write-Host $output -ForegroundColor $Color -NoNewline
38 | } else {
39 | Write-Host $output -ForegroundColor $Color
40 | }
41 | }
42 |
43 | # Get version number function
44 | function Get-LatestVersion {
45 | try {
46 | $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest"
47 | return @{
48 | Version = $latestRelease.tag_name.TrimStart('v')
49 | Assets = $latestRelease.assets
50 | }
51 | } catch {
52 | Write-Styled $_.Exception.Message -Color $Theme.Error -Prefix "Error"
53 | throw "Cannot get latest version"
54 | }
55 | }
56 |
57 | # Show Logo
58 | Write-Host $Logo -ForegroundColor $Theme.Primary
59 | $releaseInfo = Get-LatestVersion
60 | $version = $releaseInfo.Version
61 | Write-Host "Version $version" -ForegroundColor $Theme.Info
62 | Write-Host "Created by YeongPin`n" -ForegroundColor $Theme.Info
63 |
64 | # Set TLS 1.2
65 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
66 |
67 | # Main installation function
68 | function Install-CursorFreeVIP {
69 | Write-Styled "Start downloading Cursor Free VIP" -Color $Theme.Primary -Prefix "Download"
70 |
71 | try {
72 | # Get latest version
73 | Write-Styled "Checking latest version..." -Color $Theme.Primary -Prefix "Update"
74 | $releaseInfo = Get-LatestVersion
75 | $version = $releaseInfo.Version
76 | Write-Styled "Found latest version: $version" -Color $Theme.Success -Prefix "Version"
77 |
78 | # Find corresponding resources
79 | $asset = $releaseInfo.Assets | Where-Object { $_.name -eq "CursorFreeVIP_${version}_windows.exe" }
80 | if (!$asset) {
81 | Write-Styled "File not found: CursorFreeVIP_${version}_windows.exe" -Color $Theme.Error -Prefix "Error"
82 | Write-Styled "Available files:" -Color $Theme.Warning -Prefix "Info"
83 | $releaseInfo.Assets | ForEach-Object {
84 | Write-Styled "- $($_.name)" -Color $Theme.Info
85 | }
86 | throw "Cannot find target file"
87 | }
88 |
89 | # Check if Downloads folder already exists for the corresponding version
90 | $DownloadsPath = [Environment]::GetFolderPath("UserProfile") + "\Downloads"
91 | $downloadPath = Join-Path $DownloadsPath "CursorFreeVIP_${version}_windows.exe"
92 |
93 | if (Test-Path $downloadPath) {
94 | Write-Styled "Found existing installation file" -Color $Theme.Success -Prefix "Found"
95 | Write-Styled "Location: $downloadPath" -Color $Theme.Info -Prefix "Location"
96 |
97 | # Check if running with administrator privileges
98 | $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
99 |
100 | if (-not $isAdmin) {
101 | Write-Styled "Requesting administrator privileges..." -Color $Theme.Warning -Prefix "Admin"
102 |
103 | # Create new process with administrator privileges
104 | $startInfo = New-Object System.Diagnostics.ProcessStartInfo
105 | $startInfo.FileName = $downloadPath
106 | $startInfo.UseShellExecute = $true
107 | $startInfo.Verb = "runas"
108 |
109 | try {
110 | [System.Diagnostics.Process]::Start($startInfo)
111 | Write-Styled "Program started with admin privileges" -Color $Theme.Success -Prefix "Launch"
112 | return
113 | }
114 | catch {
115 | Write-Styled "Failed to start with admin privileges. Starting normally..." -Color $Theme.Warning -Prefix "Warning"
116 | Start-Process $downloadPath
117 | return
118 | }
119 | }
120 |
121 | # If already running with administrator privileges, start directly
122 | Start-Process $downloadPath
123 | return
124 | }
125 |
126 | Write-Styled "No existing installation file found, starting download..." -Color $Theme.Primary -Prefix "Download"
127 |
128 | # Create WebClient and add progress event
129 | $webClient = New-Object System.Net.WebClient
130 | $webClient.Headers.Add("User-Agent", "PowerShell Script")
131 |
132 | # Define progress variables
133 | $Global:downloadedBytes = 0
134 | $Global:totalBytes = 0
135 | $Global:lastProgress = 0
136 | $Global:lastBytes = 0
137 | $Global:lastTime = Get-Date
138 |
139 | # Download progress event
140 | $eventId = [guid]::NewGuid()
141 | Register-ObjectEvent -InputObject $webClient -EventName DownloadProgressChanged -Action {
142 | $Global:downloadedBytes = $EventArgs.BytesReceived
143 | $Global:totalBytes = $EventArgs.TotalBytesToReceive
144 | $progress = [math]::Round(($Global:downloadedBytes / $Global:totalBytes) * 100, 1)
145 |
146 | # Only update display when progress changes by more than 1%
147 | if ($progress -gt $Global:lastProgress + 1) {
148 | $Global:lastProgress = $progress
149 | $downloadedMB = [math]::Round($Global:downloadedBytes / 1MB, 2)
150 | $totalMB = [math]::Round($Global:totalBytes / 1MB, 2)
151 |
152 | # Calculate download speed
153 | $currentTime = Get-Date
154 | $timeSpan = ($currentTime - $Global:lastTime).TotalSeconds
155 | if ($timeSpan -gt 0) {
156 | $bytesChange = $Global:downloadedBytes - $Global:lastBytes
157 | $speed = $bytesChange / $timeSpan
158 |
159 | # Choose appropriate unit based on speed
160 | $speedDisplay = if ($speed -gt 1MB) {
161 | "$([math]::Round($speed / 1MB, 2)) MB/s"
162 | } elseif ($speed -gt 1KB) {
163 | "$([math]::Round($speed / 1KB, 2)) KB/s"
164 | } else {
165 | "$([math]::Round($speed, 2)) B/s"
166 | }
167 |
168 | Write-Host "`rDownloading: $downloadedMB MB / $totalMB MB ($progress%) - $speedDisplay" -NoNewline -ForegroundColor Cyan
169 |
170 | # Update last data
171 | $Global:lastBytes = $Global:downloadedBytes
172 | $Global:lastTime = $currentTime
173 | }
174 | }
175 | } | Out-Null
176 |
177 | # Download completed event
178 | Register-ObjectEvent -InputObject $webClient -EventName DownloadFileCompleted -Action {
179 | Write-Host "`r" -NoNewline
180 | Write-Styled "Download completed!" -Color $Theme.Success -Prefix "Complete"
181 | Unregister-Event -SourceIdentifier $eventId
182 | } | Out-Null
183 |
184 | # Start download
185 | $webClient.DownloadFileAsync([Uri]$asset.browser_download_url, $downloadPath)
186 |
187 | # Wait for download to complete
188 | while ($webClient.IsBusy) {
189 | Start-Sleep -Milliseconds 100
190 | }
191 |
192 | Write-Styled "File location: $downloadPath" -Color $Theme.Info -Prefix "Location"
193 | Write-Styled "Starting program..." -Color $Theme.Primary -Prefix "Launch"
194 |
195 | # Run program
196 | Start-Process $downloadPath
197 | }
198 | catch {
199 | Write-Styled $_.Exception.Message -Color $Theme.Error -Prefix "Error"
200 | throw
201 | }
202 | }
203 |
204 | # Execute installation
205 | try {
206 | Install-CursorFreeVIP
207 | }
208 | catch {
209 | Write-Styled "Download failed" -Color $Theme.Error -Prefix "Error"
210 | Write-Styled $_.Exception.Message -Color $Theme.Error
211 | }
212 | finally {
213 | Write-Host "`nPress any key to exit..." -ForegroundColor $Theme.Info
214 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
215 | }
216 |
--------------------------------------------------------------------------------
/scripts/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Color definitions
4 | RED='\033[0;31m'
5 | GREEN='\033[0;32m'
6 | YELLOW='\033[1;33m'
7 | BLUE='\033[0;34m'
8 | CYAN='\033[0;36m'
9 | NC='\033[0m' # No Color
10 |
11 | # Logo
12 | print_logo() {
13 | echo -e "${CYAN}"
14 | cat << "EOF"
15 | ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗
16 | ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ ██╔══██╗██╔══██╗██╔═══██╗
17 | ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ ██████╔╝██████╔╝██║ ██║
18 | ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ ██╔═══╝ ██╔══██╗██║ ██║
19 | ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ██║ ██║ ██║╚██████╔╝
20 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝
21 | EOF
22 | echo -e "${NC}"
23 | }
24 |
25 | # Get download folder path
26 | get_downloads_dir() {
27 | if [[ "$(uname)" == "Darwin" ]]; then
28 | echo "$HOME/Downloads"
29 | else
30 | if [ -f "$HOME/.config/user-dirs.dirs" ]; then
31 | . "$HOME/.config/user-dirs.dirs"
32 | echo "${XDG_DOWNLOAD_DIR:-$HOME/Downloads}"
33 | else
34 | echo "$HOME/Downloads"
35 | fi
36 | fi
37 | }
38 |
39 | # Get latest version
40 | get_latest_version() {
41 | echo -e "${CYAN}ℹ️ Checking latest version...${NC}"
42 | latest_release=$(curl -s https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest) || {
43 | echo -e "${RED}❌ Cannot get latest version information${NC}"
44 | exit 1
45 | }
46 |
47 | VERSION=$(echo "$latest_release" | grep -o '"tag_name": ".*"' | cut -d'"' -f4 | tr -d 'v')
48 | if [ -z "$VERSION" ]; then
49 | echo -e "${RED}❌ Failed to parse version from GitHub API response:\n${latest_release}"
50 | exit 1
51 | fi
52 |
53 | echo -e "${GREEN}✅ Found latest version: ${VERSION}${NC}"
54 | }
55 |
56 | # Detect system type and architecture
57 | detect_os() {
58 | if [[ "$(uname)" == "Darwin" ]]; then
59 | # Detect macOS architecture
60 | ARCH=$(uname -m)
61 | if [[ "$ARCH" == "arm64" ]]; then
62 | OS="mac_arm64"
63 | echo -e "${CYAN}ℹ️ Detected macOS ARM64 architecture${NC}"
64 | else
65 | OS="mac_intel"
66 | echo -e "${CYAN}ℹ️ Detected macOS Intel architecture${NC}"
67 | fi
68 | elif [[ "$(uname)" == "Linux" ]]; then
69 | # Detect Linux architecture
70 | ARCH=$(uname -m)
71 | if [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then
72 | OS="linux_arm64"
73 | echo -e "${CYAN}ℹ️ Detected Linux ARM64 architecture${NC}"
74 | else
75 | OS="linux_x64"
76 | echo -e "${CYAN}ℹ️ Detected Linux x64 architecture${NC}"
77 | fi
78 | else
79 | # Assume Windows
80 | OS="windows"
81 | echo -e "${CYAN}ℹ️ Detected Windows system${NC}"
82 | fi
83 | }
84 |
85 | # Install and download
86 | install_cursor_free_vip() {
87 | local downloads_dir=$(get_downloads_dir)
88 | local binary_name="CursorFreeVIP_${VERSION}_${OS}"
89 | local binary_path="${downloads_dir}/${binary_name}"
90 | local download_url="https://github.com/yeongpin/cursor-free-vip/releases/download/v${VERSION}/${binary_name}"
91 |
92 | # Check if file already exists
93 | if [ -f "${binary_path}" ]; then
94 | echo -e "${GREEN}✅ Found existing installation file${NC}"
95 | echo -e "${CYAN}ℹ️ Location: ${binary_path}${NC}"
96 |
97 | # Check if running as root
98 | if [ "$EUID" -ne 0 ]; then
99 | echo -e "${YELLOW}⚠️ Requesting administrator privileges...${NC}"
100 | if command -v sudo >/dev/null 2>&1; then
101 | echo -e "${CYAN}ℹ️ Starting program with sudo...${NC}"
102 | sudo chmod +x "${binary_path}"
103 | sudo "${binary_path}"
104 | else
105 | echo -e "${YELLOW}⚠️ sudo not found, trying to run normally...${NC}"
106 | chmod +x "${binary_path}"
107 | "${binary_path}"
108 | fi
109 | else
110 | # Already running as root
111 | echo -e "${CYAN}ℹ️ Already running as root, starting program...${NC}"
112 | chmod +x "${binary_path}"
113 | "${binary_path}"
114 | fi
115 | return
116 | fi
117 |
118 | echo -e "${CYAN}ℹ️ No existing installation file found, starting download...${NC}"
119 | echo -e "${CYAN}ℹ️ Downloading to ${downloads_dir}...${NC}"
120 | echo -e "${CYAN}ℹ️ Download link: ${download_url}${NC}"
121 |
122 | # Check if file exists
123 | if curl --output /dev/null --silent --head --fail "$download_url"; then
124 | echo -e "${GREEN}✅ File exists, starting download...${NC}"
125 | else
126 | echo -e "${RED}❌ Download link does not exist: ${download_url}${NC}"
127 | echo -e "${YELLOW}⚠️ Trying without architecture...${NC}"
128 |
129 | # Try without architecture
130 | if [[ "$OS" == "mac_arm64" || "$OS" == "mac_intel" ]]; then
131 | OS="mac"
132 | binary_name="CursorFreeVIP_${VERSION}_${OS}"
133 | download_url="https://github.com/yeongpin/cursor-free-vip/releases/download/v${VERSION}/${binary_name}"
134 | echo -e "${CYAN}ℹ️ New download link: ${download_url}${NC}"
135 |
136 | if ! curl --output /dev/null --silent --head --fail "$download_url"; then
137 | echo -e "${RED}❌ New download link does not exist${NC}"
138 | exit 1
139 | fi
140 | elif [[ "$OS" == "linux_x64" || "$OS" == "linux_arm64" ]]; then
141 | OS="linux"
142 | binary_name="CursorFreeVIP_${VERSION}_${OS}"
143 | download_url="https://github.com/yeongpin/cursor-free-vip/releases/download/v${VERSION}/${binary_name}"
144 | echo -e "${CYAN}ℹ️ New download link: ${download_url}${NC}"
145 |
146 | if ! curl --output /dev/null --silent --head --fail "$download_url"; then
147 | echo -e "${RED}❌ New download link does not exist${NC}"
148 | exit 1
149 | fi
150 | else
151 | exit 1
152 | fi
153 | fi
154 |
155 | # Download file
156 | if ! curl -L -o "${binary_path}" "$download_url"; then
157 | echo -e "${RED}❌ Download failed${NC}"
158 | exit 1
159 | fi
160 |
161 | # Check downloaded file size
162 | local file_size=$(stat -f%z "${binary_path}" 2>/dev/null || stat -c%s "${binary_path}" 2>/dev/null)
163 | echo -e "${CYAN}ℹ️ Downloaded file size: ${file_size} bytes${NC}"
164 |
165 | # If file is too small, it might be an error message
166 | if [ "$file_size" -lt 1000 ]; then
167 | echo -e "${YELLOW}⚠️ Warning: Downloaded file is too small, possibly not a valid executable file${NC}"
168 | echo -e "${YELLOW}⚠️ File content:${NC}"
169 | cat "${binary_path}"
170 | echo ""
171 | echo -e "${RED}❌ Download failed, please check version and operating system${NC}"
172 | exit 1
173 | fi
174 |
175 | echo -e "${CYAN}ℹ️ Setting executable permissions...${NC}"
176 | if chmod +x "${binary_path}"; then
177 | echo -e "${GREEN}✅ Installation completed!${NC}"
178 | echo -e "${CYAN}ℹ️ Program downloaded to: ${binary_path}${NC}"
179 | echo -e "${CYAN}ℹ️ Starting program...${NC}"
180 |
181 | # Run program directly
182 | "${binary_path}"
183 | else
184 | echo -e "${RED}❌ Installation failed${NC}"
185 | exit 1
186 | fi
187 | }
188 |
189 | # Main program
190 | main() {
191 | print_logo
192 | get_latest_version
193 | detect_os
194 | install_cursor_free_vip
195 | }
196 |
197 | # Run main program
198 | main
199 |
--------------------------------------------------------------------------------
/scripts/reset.ps1:
--------------------------------------------------------------------------------
1 | # 檢查是否是通過權限提升啟動的
2 | param(
3 | [switch]$Elevated
4 | )
5 |
6 | # 設置顏色主題
7 | $Theme = @{
8 | Primary = 'Cyan'
9 | Success = 'Green'
10 | Warning = 'Yellow'
11 | Error = 'Red'
12 | Info = 'White'
13 | }
14 |
15 | # ASCII Logo
16 | $Logo = @"
17 | ██████╗ ███████╗███████╗███████╗████████╗ ████████╗ ██████╗ ██████╗ ██╗
18 | ██╔══██╗██╔════╝██╔════╝██╔════╝╚══██╔══╝ ╚══██╔══╝██╔═══██╗██╔═══██╗██║
19 | ██████╔╝█████╗ ███████╗█████╗ ██║ ██║ ██║ ██║██║ ██║██║
20 | ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██║ ██║ ██║ ██║██║ ██║██║
21 | ██║ ██║███████╗███████║███████╗ ██║ ██║ ╚██████╔╝╚██████╔╝███████╗
22 | ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝
23 | "@
24 |
25 | # 美化輸出函數
26 | function Write-Styled {
27 | param (
28 | [string]$Message,
29 | [string]$Color = $Theme.Info,
30 | [string]$Prefix = "",
31 | [switch]$NoNewline
32 | )
33 | $emoji = switch ($Color) {
34 | $Theme.Success { "✅" }
35 | $Theme.Error { "❌" }
36 | $Theme.Warning { "⚠️" }
37 | default { "ℹ️" }
38 | }
39 |
40 | $output = if ($Prefix) { "$emoji $Prefix :: $Message" } else { "$emoji $Message" }
41 | if ($NoNewline) {
42 | Write-Host $output -ForegroundColor $Color -NoNewline
43 | } else {
44 | Write-Host $output -ForegroundColor $Color
45 | }
46 | }
47 |
48 | # 檢查管理員權限
49 | $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
50 | if (-NOT $isAdmin) {
51 | Write-Styled "需要管理員權限來運行重置工具" -Color $Theme.Warning -Prefix "權限"
52 | Write-Styled "正在請求管理員權限..." -Color $Theme.Primary -Prefix "提升"
53 |
54 | # 顯示操作選項
55 | Write-Host "`n選擇操作:" -ForegroundColor $Theme.Primary
56 | Write-Host "1. 請求管理員權限" -ForegroundColor $Theme.Info
57 | Write-Host "2. 退出程序" -ForegroundColor $Theme.Info
58 |
59 | $choice = Read-Host "`n請輸入選項 (1-2)"
60 |
61 | if ($choice -ne "1") {
62 | Write-Styled "操作已取消" -Color $Theme.Warning -Prefix "取消"
63 | Write-Host "`n按任意鍵退出..." -ForegroundColor $Theme.Info
64 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
65 | exit
66 | }
67 |
68 | try {
69 | Start-Process powershell.exe -Verb RunAs -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -Elevated"
70 | exit
71 | }
72 | catch {
73 | Write-Styled "無法獲取管理員權限" -Color $Theme.Error -Prefix "錯誤"
74 | Write-Styled "請以管理員身份運行 PowerShell 後重試" -Color $Theme.Warning -Prefix "提示"
75 | Write-Host "`n按任意鍵退出..." -ForegroundColor $Theme.Info
76 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
77 | exit 1
78 | }
79 | }
80 |
81 | # 如果是提升權限後的窗口,等待一下確保窗口可見
82 | if ($Elevated) {
83 | Start-Sleep -Seconds 1
84 | }
85 |
86 | # 顯示 Logo
87 | Write-Host $Logo -ForegroundColor $Theme.Primary
88 | Write-Host "Created by YeongPin`n" -ForegroundColor $Theme.Info
89 |
90 | # 設置 TLS 1.2
91 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
92 |
93 | # 創建臨時目錄
94 | $TmpDir = Join-Path $env:TEMP ([System.Guid]::NewGuid().ToString())
95 | New-Item -ItemType Directory -Path $TmpDir -Force | Out-Null
96 |
97 | # 清理函數
98 | function Cleanup {
99 | if (Test-Path $TmpDir) {
100 | Remove-Item -Recurse -Force $TmpDir -ErrorAction SilentlyContinue
101 | }
102 | }
103 |
104 | try {
105 | # 下載地址
106 | $url = "https://github.com/yeongpin/cursor-free-vip/releases/download/ManualReset/reset_machine_manual.exe"
107 | $output = Join-Path $TmpDir "reset_machine_manual.exe"
108 |
109 | # 下載文件
110 | Write-Styled "正在下載重置工具..." -Color $Theme.Primary -Prefix "下載"
111 | Invoke-WebRequest -Uri $url -OutFile $output
112 | Write-Styled "下載完成!" -Color $Theme.Success -Prefix "完成"
113 |
114 | # 執行重置工具
115 | Write-Styled "正在啟動重置工具..." -Color $Theme.Primary -Prefix "執行"
116 | Start-Process -FilePath $output -Wait
117 | Write-Styled "重置完成!" -Color $Theme.Success -Prefix "完成"
118 | }
119 | catch {
120 | Write-Styled "操作失敗" -Color $Theme.Error -Prefix "錯誤"
121 | Write-Styled $_.Exception.Message -Color $Theme.Error
122 | }
123 | finally {
124 | Cleanup
125 | Write-Host "`n按任意鍵退出..." -ForegroundColor $Theme.Info
126 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown')
127 | }
--------------------------------------------------------------------------------
/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import platform
4 | import random
5 |
6 | def get_user_documents_path():
7 | """Get user documents path"""
8 | if platform.system() == "Windows":
9 | return os.path.expanduser("~\\Documents")
10 | else:
11 | return os.path.expanduser("~/Documents")
12 |
13 | def get_default_driver_path(browser_type='chrome'):
14 | """Get default driver path based on browser type"""
15 | browser_type = browser_type.lower()
16 | if browser_type == 'chrome':
17 | return get_default_chrome_driver_path()
18 | elif browser_type == 'edge':
19 | return get_default_edge_driver_path()
20 | elif browser_type == 'firefox':
21 | return get_default_firefox_driver_path()
22 | elif browser_type == 'brave':
23 | # Brave 使用 Chrome 的 driver
24 | return get_default_chrome_driver_path()
25 | else:
26 | # Default to Chrome if browser type is unknown
27 | return get_default_chrome_driver_path()
28 |
29 | def get_default_chrome_driver_path():
30 | """Get default Chrome driver path"""
31 | if sys.platform == "win32":
32 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "chromedriver.exe")
33 | elif sys.platform == "darwin":
34 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "chromedriver")
35 | else:
36 | return "/usr/local/bin/chromedriver"
37 |
38 | def get_default_edge_driver_path():
39 | """Get default Edge driver path"""
40 | if sys.platform == "win32":
41 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "msedgedriver.exe")
42 | elif sys.platform == "darwin":
43 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "msedgedriver")
44 | else:
45 | return "/usr/local/bin/msedgedriver"
46 |
47 | def get_default_firefox_driver_path():
48 | """Get default Firefox driver path"""
49 | if sys.platform == "win32":
50 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "geckodriver.exe")
51 | elif sys.platform == "darwin":
52 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "geckodriver")
53 | else:
54 | return "/usr/local/bin/geckodriver"
55 |
56 | def get_default_brave_driver_path():
57 | """Get default Brave driver path (uses Chrome driver)"""
58 | # Brave 浏览器基于 Chromium,所以使用相同的 chromedriver
59 | return get_default_chrome_driver_path()
60 |
61 | def get_default_browser_path(browser_type='chrome'):
62 | """Get default browser executable path"""
63 | browser_type = browser_type.lower()
64 |
65 | if sys.platform == "win32":
66 | if browser_type == 'chrome':
67 | # 尝试在 PATH 中找到 Chrome
68 | try:
69 | import shutil
70 | chrome_in_path = shutil.which("chrome")
71 | if chrome_in_path:
72 | return chrome_in_path
73 | except:
74 | pass
75 | # 使用默认路径
76 | return r"C:\Program Files\Google\Chrome\Application\chrome.exe"
77 | elif browser_type == 'edge':
78 | return r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
79 | elif browser_type == 'firefox':
80 | return r"C:\Program Files\Mozilla Firefox\firefox.exe"
81 | elif browser_type == 'opera':
82 | # 尝试多个可能的 Opera 路径
83 | opera_paths = [
84 | r"C:\Program Files\Opera\opera.exe",
85 | r"C:\Program Files (x86)\Opera\opera.exe",
86 | os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera', 'launcher.exe'),
87 | os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera', 'opera.exe')
88 | ]
89 | for path in opera_paths:
90 | if os.path.exists(path):
91 | return path
92 | return opera_paths[0] # 返回第一个路径,即使它不存在
93 | elif browser_type == 'operagx':
94 | # 尝试多个可能的 Opera GX 路径
95 | operagx_paths = [
96 | os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera GX', 'launcher.exe'),
97 | os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera GX', 'opera.exe'),
98 | r"C:\Program Files\Opera GX\opera.exe",
99 | r"C:\Program Files (x86)\Opera GX\opera.exe"
100 | ]
101 | for path in operagx_paths:
102 | if os.path.exists(path):
103 | return path
104 | return operagx_paths[0] # 返回第一个路径,即使它不存在
105 | elif browser_type == 'brave':
106 | # Brave 浏览器的默认安装路径
107 | paths = [
108 | os.path.join(os.environ.get('PROGRAMFILES', ''), 'BraveSoftware/Brave-Browser/Application/brave.exe'),
109 | os.path.join(os.environ.get('PROGRAMFILES(X86)', ''), 'BraveSoftware/Brave-Browser/Application/brave.exe'),
110 | os.path.join(os.environ.get('LOCALAPPDATA', ''), 'BraveSoftware/Brave-Browser/Application/brave.exe')
111 | ]
112 | for path in paths:
113 | if os.path.exists(path):
114 | return path
115 | return paths[0] # 返回第一个路径,即使它不存在
116 |
117 | elif sys.platform == "darwin":
118 | if browser_type == 'chrome':
119 | return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
120 | elif browser_type == 'edge':
121 | return "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
122 | elif browser_type == 'firefox':
123 | return "/Applications/Firefox.app/Contents/MacOS/firefox"
124 | elif browser_type == 'brave':
125 | return "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
126 | elif browser_type == 'opera':
127 | return "/Applications/Opera.app/Contents/MacOS/Opera"
128 | elif browser_type == 'operagx':
129 | return "/Applications/Opera GX.app/Contents/MacOS/Opera"
130 |
131 | else: # Linux
132 | if browser_type == 'chrome':
133 | # 尝试多种可能的名称
134 | chrome_names = ["google-chrome", "chrome", "chromium", "chromium-browser"]
135 | for name in chrome_names:
136 | try:
137 | import shutil
138 | path = shutil.which(name)
139 | if path:
140 | return path
141 | except:
142 | pass
143 | return "/usr/bin/google-chrome"
144 | elif browser_type == 'edge':
145 | return "/usr/bin/microsoft-edge"
146 | elif browser_type == 'firefox':
147 | return "/usr/bin/firefox"
148 | elif browser_type == 'opera':
149 | return "/usr/bin/opera"
150 | elif browser_type == 'operagx':
151 | # 尝试常见的 Opera GX 路径
152 | operagx_names = ["opera-gx"]
153 | for name in operagx_names:
154 | try:
155 | import shutil
156 | path = shutil.which(name)
157 | if path:
158 | return path
159 | except:
160 | pass
161 | return "/usr/bin/opera-gx"
162 | elif browser_type == 'brave':
163 | # 尝试常见的 Brave 路径
164 | brave_names = ["brave", "brave-browser"]
165 | for name in brave_names:
166 | try:
167 | import shutil
168 | path = shutil.which(name)
169 | if path:
170 | return path
171 | except:
172 | pass
173 | return "/usr/bin/brave-browser"
174 |
175 | # 如果找不到指定的浏览器类型,则返回 Chrome 的路径
176 | return get_default_browser_path('chrome')
177 |
178 | def get_linux_cursor_path():
179 | """Get Linux Cursor path"""
180 | possible_paths = [
181 | "/opt/Cursor/resources/app",
182 | "/usr/share/cursor/resources/app",
183 | "/opt/cursor-bin/resources/app",
184 | "/usr/lib/cursor/resources/app",
185 | os.path.expanduser("~/.local/share/cursor/resources/app")
186 | ]
187 |
188 | # return the first path that exists
189 | return next((path for path in possible_paths if os.path.exists(path)), possible_paths[0])
190 |
191 | def get_random_wait_time(config, timing_key):
192 | """Get random wait time based on configuration timing settings
193 |
194 | Args:
195 | config (dict): Configuration dictionary containing timing settings
196 | timing_key (str): Key to look up in the timing settings
197 |
198 | Returns:
199 | float: Random wait time in seconds
200 | """
201 | try:
202 | # Get timing value from config
203 | timing = config.get('Timing', {}).get(timing_key)
204 | if not timing:
205 | # Default to 0.5-1.5 seconds if timing not found
206 | return random.uniform(0.5, 1.5)
207 |
208 | # Check if timing is a range (e.g., "0.5-1.5" or "0.5,1.5")
209 | if isinstance(timing, str):
210 | if '-' in timing:
211 | min_time, max_time = map(float, timing.split('-'))
212 | elif ',' in timing:
213 | min_time, max_time = map(float, timing.split(','))
214 | else:
215 | # Single value, use it as both min and max
216 | min_time = max_time = float(timing)
217 | else:
218 | # If timing is a number, use it as both min and max
219 | min_time = max_time = float(timing)
220 |
221 | return random.uniform(min_time, max_time)
222 |
223 | except (ValueError, TypeError, AttributeError):
224 | # Return default value if any error occurs
225 | return random.uniform(0.5, 1.5)
--------------------------------------------------------------------------------