├── .gitignore ├── turnstilePatch ├── readme.txt ├── script.js └── manifest.json ├── config.txt ├── README.md ├── update_cursor_auth.py ├── cursor_auth_manager.py ├── gpt-accesstoken.py ├── mail_api.py └── cursor_pro_keep_alive.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | __pycache__ -------------------------------------------------------------------------------- /turnstilePatch/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /config.txt: -------------------------------------------------------------------------------- 1 | [microsoft] 2 | client_id = your_client_id 3 | 4 | [tokens] 5 | refresh_token = your_refresh_token 6 | access_token = 7 | expires_at = 8 | 9 | [account] 10 | email = your_alias@outlook.com 11 | password = your_password 12 | first_name = your_first_name 13 | last_name = your_last_name 14 | -------------------------------------------------------------------------------- /turnstilePatch/script.js: -------------------------------------------------------------------------------- 1 | function getRandomInt(min, max) { 2 | return Math.floor(Math.random() * (max - min + 1)) + min; 3 | } 4 | 5 | // old method wouldn't work on 4k screens 6 | 7 | let screenX = getRandomInt(800, 1200); 8 | let screenY = getRandomInt(400, 600); 9 | 10 | Object.defineProperty(MouseEvent.prototype, 'screenX', { value: screenX }); 11 | 12 | Object.defineProperty(MouseEvent.prototype, 'screenY', { value: screenY }); -------------------------------------------------------------------------------- /turnstilePatch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Turnstile Patcher", 4 | "version": "2.1", 5 | "content_scripts": [ 6 | { 7 | "js": [ 8 | "./script.js" 9 | ], 10 | "matches": [ 11 | "" 12 | ], 13 | "run_at": "document_start", 14 | "all_frames": true, 15 | "world": "MAIN" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gpt-cursor-auto 2 | 3 | ### 注意:发行版中的 Crazy Cursor 工具与本仓库代码没有直接关联,详见说明!!! 4 | 5 | Python脚本 一键获取 ChatGpt 的 Access Token -- gpt-accesstoken.py 6 | *********************************************************************************************** 7 | Python脚本 一键获取 Cursor Pro 自动保活 -- cursor_pro_keep_alive.py 8 | 9 | 2024.12.14 新增自动获取token,无需再手动登录账号关联Cursor,需要 依赖 cursor_auth_manager.py 10 | 11 | 2025.01.19 增加 config.txt 和 mail_api.py ,换用微软别名邮箱(需配置client_id 和 refresh_token,获取方法可参考[outlook-mail-automation](https://github.com/hmhm2022/outlook-mail-automation)中的详细指引) 12 | -------------------------------------------------------------------------------- /update_cursor_auth.py: -------------------------------------------------------------------------------- 1 | from cursor_auth_manager import CursorAuthManager 2 | 3 | def update_cursor_auth(email=None, access_token=None, refresh_token=None): 4 | """ 5 | 更新Cursor的认证信息的便捷函数 6 | """ 7 | auth_manager = CursorAuthManager() 8 | return auth_manager.update_auth(email, access_token, refresh_token) 9 | 10 | def main(): 11 | # 示例用法 12 | print("请选择要更新的项目:") 13 | print("1. 更新邮箱") 14 | print("2. 更新访问令牌") 15 | print("3. 更新刷新令牌") 16 | print("4. 更新多个值") 17 | print("0. 退出") 18 | 19 | choice = input("\n请输入选项数字: ") 20 | 21 | if choice == "1": 22 | email = input("请输入新的邮箱: ") 23 | update_cursor_auth(email=email) 24 | elif choice == "2": 25 | token = input("请输入新的访问令牌: ") 26 | update_cursor_auth(access_token=token) 27 | elif choice == "3": 28 | token = input("请输入新的刷新令牌: ") 29 | update_cursor_auth(refresh_token=token) 30 | elif choice == "4": 31 | email = input("请输入新的邮箱 (直接回车跳过): ") 32 | access_token = input("请输入新的访问令牌 (直接回车跳过): ") 33 | refresh_token = input("请输入新的刷新令牌 (直接回车跳过): ") 34 | 35 | update_cursor_auth( 36 | email=email if email else None, 37 | access_token=access_token if access_token else None, 38 | refresh_token=refresh_token if refresh_token else None 39 | ) 40 | elif choice == "0": 41 | print("退出程序") 42 | else: 43 | print("无效的选项") 44 | 45 | if __name__ == "__main__": 46 | main() -------------------------------------------------------------------------------- /cursor_auth_manager.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import os 3 | 4 | class CursorAuthManager: 5 | """Cursor认证信息管理器""" 6 | 7 | def __init__(self): 8 | # 判断操作系统 9 | if os.name == 'nt': # Windows 10 | self.db_path = os.path.join(os.getenv('APPDATA'), 'Cursor', 'User', 'globalStorage', 'state.vscdb') 11 | else: # macOS 12 | self.db_path = os.path.expanduser('~/Library/Application Support/Cursor/User/globalStorage/state.vscdb') 13 | 14 | 15 | def update_auth(self, email=None, access_token=None, refresh_token=None): 16 | """ 17 | 更新Cursor的认证信息 18 | :param email: 新的邮箱地址 19 | :param access_token: 新的访问令牌 20 | :param refresh_token: 新的刷新令牌 21 | :return: bool 是否成功更新 22 | """ 23 | updates = [] 24 | if email is not None: 25 | updates.append(('cursorAuth/cachedEmail', email)) 26 | if access_token is not None: 27 | updates.append(('cursorAuth/accessToken', access_token)) 28 | if refresh_token is not None: 29 | updates.append(('cursorAuth/refreshToken', refresh_token)) 30 | 31 | if not updates: 32 | print("没有提供任何要更新的值") 33 | return False 34 | 35 | conn = None 36 | try: 37 | conn = sqlite3.connect(self.db_path) 38 | cursor = conn.cursor() 39 | 40 | for key, value in updates: 41 | query = "UPDATE itemTable SET value = ? WHERE key = ?" 42 | cursor.execute(query, (value, key)) 43 | 44 | if cursor.rowcount > 0: 45 | print(f"成功更新 {key.split('/')[-1]}") 46 | else: 47 | print(f"未找到 {key.split('/')[-1]} 或值未变化") 48 | 49 | conn.commit() 50 | return True 51 | 52 | except sqlite3.Error as e: 53 | print("数据库错误:", str(e)) 54 | return False 55 | except Exception as e: 56 | print("发生错误:", str(e)) 57 | return False 58 | finally: 59 | if conn: 60 | conn.close() 61 | -------------------------------------------------------------------------------- /gpt-accesstoken.py: -------------------------------------------------------------------------------- 1 | from DrissionPage import ChromiumOptions, Chromium 2 | import random 3 | import time 4 | 5 | def handle_turnstile(tab): 6 | """处理 Turnstile 验证""" 7 | print("准备处理验证") 8 | try: 9 | while True: 10 | if tab.ele('@id=email-input', timeout=2): 11 | print("无需验证 - 邮箱输入框已加载") 12 | return True 13 | 14 | if tab.ele('@id=password', timeout=2): 15 | print("无需验证 - 密码输入框已加载") 16 | return True 17 | 18 | try: 19 | challenge_element = (tab.ele("@name=cf-turnstile-response", timeout=2) 20 | .parent() 21 | .shadow_root 22 | .ele("tag:iframe") 23 | .ele("tag:body") 24 | .sr("tag:input")) 25 | 26 | if challenge_element: 27 | print("验证框加载完成") 28 | time.sleep(random.uniform(1, 3)) 29 | challenge_element.click() 30 | print("验证按钮已点击,等待验证完成...") 31 | time.sleep(2) 32 | return True 33 | except: 34 | pass 35 | 36 | time.sleep(2) 37 | 38 | except Exception as e: 39 | print(f"验证处理出错: {str(e)}") 40 | print('跳过验证') 41 | return False 42 | 43 | account = 'your_chatgpt_account' 44 | password = 'your_chatgpt_password' 45 | 46 | co = ChromiumOptions() 47 | co.add_extension("turnstilePatch") 48 | # co.headless() 49 | co.set_user_agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36') 50 | co.set_pref('credentials_enable_service', False) 51 | co.set_argument('--hide-crash-restore-bubble') 52 | co.auto_port() 53 | 54 | browser = Chromium(co) 55 | tab = browser.latest_tab 56 | tab.run_js("try { turnstile.reset() } catch(e) { }") 57 | 58 | print("\n步骤1: 开始访问网站...") 59 | 60 | tab.get('https://chatgpt.com') 61 | print('等待页面加载...') 62 | 63 | print("\n步骤2: 开始登录...") 64 | for _ in range(5): 65 | try: 66 | if tab.ele('xpath:/html/body/div[1]/div[1]/main/div[1]/div[1]/div/div[1]/div/div[3]/div/button[1]'): 67 | signin_btn = tab.ele('xpath:/html/body/div[1]/div[1]/main/div[1]/div[1]/div/div[1]/div/div[3]/div/button[1]') 68 | print("找到黑色登录按钮:", signin_btn.text) 69 | break 70 | if tab.ele('@data-testid=login-button'): 71 | signin_btn = tab.ele('@data-testid=login-button') 72 | print("找到蓝色登录按钮:", signin_btn.text) 73 | break 74 | if tab.ele("@name=cf-turnstile-response"): 75 | print('加载页面时出现CF验证, IP 质量太差, 请更换 IP 重新尝试!') 76 | browser.quit() 77 | exit() 78 | time.sleep(3) 79 | except Exception as e: 80 | print(f"处理登录按钮时出错: {str(e)}") 81 | 82 | for _ in range(5): 83 | try: 84 | if signin_btn: 85 | signin_btn.click() 86 | print("点击登录按钮") 87 | break 88 | except Exception as e: 89 | print(f"处理登录按钮时出错: {str(e)}") 90 | time.sleep(3) 91 | else: 92 | print("尝试点击登录按钮失败,程序退出") 93 | exit() 94 | 95 | handle_turnstile(tab) 96 | 97 | print("\n步骤3: 输入邮箱...") 98 | for _ in range(5): 99 | try: 100 | if tab.ele('@id=email-input'): 101 | tab.actions.click('@id=email-input').type(account) 102 | time.sleep(0.5) 103 | tab.ele('@class=continue-btn').click() 104 | print("输入邮箱并点击继续") 105 | break 106 | except Exception as e: 107 | print(f"加载邮箱输入框时出错: {str(e)}") 108 | time.sleep(3) 109 | else: 110 | print("尝试加载邮箱输入框失败,程序退出") 111 | browser.quit() 112 | exit() 113 | 114 | handle_turnstile(tab) 115 | 116 | print("\n步骤4: 输入密码...") 117 | for _ in range(5): 118 | try: 119 | if tab.ele('@id=password'): 120 | print("密码输入框加载完成") 121 | tab.actions.click('@id=password').input(password) 122 | time.sleep(2) 123 | tab.ele('@type=submit').click('js') 124 | # tab.actions.click('@type=submit') 125 | print("输入密码并JS点击登录") 126 | break 127 | except Exception as e: 128 | print(f"输入密码时出错: {str(e)}") 129 | time.sleep(3) 130 | else: 131 | print("尝试加载密码输入框失败,程序退出") 132 | browser.quit() 133 | exit() 134 | 135 | for _ in range(5): 136 | try: 137 | if tab.ele('有什么可以帮忙的?'): 138 | print('登录成功!') 139 | break 140 | if tab.ele('重新发送电子邮件'): 141 | print('提示需要邮箱验证码,脚本终止,请手动获取') 142 | exit() 143 | except Exception as e: 144 | print(f"登录可能遇到问题: {str(e)}") 145 | time.sleep(3) 146 | else: 147 | print("登录失败,程序退出") 148 | browser.quit() 149 | exit() 150 | 151 | time.sleep(random.uniform(1,2)) 152 | print('\n',"步骤5: 获取access_token...") 153 | browser.new_tab('https://chatgpt.com/api/auth/session') 154 | tab = browser.latest_tab 155 | time.sleep(1) 156 | response_json = tab.json 157 | if response_json and 'accessToken' in response_json: 158 | access_token = response_json['accessToken'] 159 | print('\n',"请复制保存你的access_token:",'\n') 160 | print(access_token) 161 | else: 162 | print("错误:未找到access token") 163 | 164 | # input("\n按Enter键关闭浏览器...") 165 | browser.quit() 166 | 167 | -------------------------------------------------------------------------------- /mail_api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Microsoft邮件处理脚本 4 | 用于获取邮箱中的验证码 5 | """ 6 | 7 | import requests 8 | import logging 9 | from datetime import datetime 10 | from typing import Dict, List 11 | import configparser 12 | import winreg 13 | import time 14 | import re 15 | 16 | def get_proxy(): 17 | try: 18 | with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Internet Settings") as key: 19 | proxy_enable, _ = winreg.QueryValueEx(key, "ProxyEnable") 20 | proxy_server, _ = winreg.QueryValueEx(key, "ProxyServer") 21 | 22 | if proxy_enable and proxy_server: 23 | proxy_parts = proxy_server.split(":") 24 | if len(proxy_parts) == 2: 25 | return {"http": f"http://{proxy_server}", "https": f"http://{proxy_server}"} 26 | except WindowsError: 27 | pass 28 | return {"http": None, "https": None} 29 | 30 | def load_config(): 31 | """从config.txt加载配置""" 32 | config = configparser.ConfigParser() 33 | config.read('config.txt', encoding='utf-8') 34 | return config 35 | 36 | def save_config(config): 37 | """保存配置到config.txt""" 38 | with open('config.txt', 'w', encoding='utf-8') as f: 39 | config.write(f) 40 | 41 | logging.basicConfig(level=logging.INFO) 42 | logger = logging.getLogger(__name__) 43 | 44 | config = load_config() 45 | microsoft_config = config['microsoft'] 46 | 47 | CLIENT_ID = microsoft_config['client_id'] 48 | GRAPH_API_ENDPOINT = 'https://graph.microsoft.com/v1.0' 49 | TOKEN_URL = 'https://login.microsoftonline.com/consumers/oauth2/v2.0/token' 50 | 51 | class EmailClient: 52 | def __init__(self): 53 | config = load_config() 54 | if not config.has_section('tokens'): 55 | config.add_section('tokens') 56 | self.config = config 57 | self.refresh_token = config['tokens'].get('refresh_token', '') 58 | self.access_token = config['tokens'].get('access_token', '') 59 | expires_at_str = config['tokens'].get('expires_at', '1970-01-01 00:00:00') 60 | self.expires_at = datetime.strptime(expires_at_str, '%Y-%m-%d %H:%M:%S').timestamp() 61 | 62 | def is_token_expired(self) -> bool: 63 | """检查access token是否过期或即将过期""" 64 | buffer_time = 300 65 | return datetime.now().timestamp() + buffer_time >= self.expires_at 66 | 67 | def refresh_access_token(self) -> None: 68 | """刷新访问令牌""" 69 | refresh_params = { 70 | 'client_id': CLIENT_ID, 71 | 'refresh_token': self.refresh_token, 72 | 'grant_type': 'refresh_token', 73 | } 74 | 75 | try: 76 | response = requests.post(TOKEN_URL, data=refresh_params, proxies=get_proxy()) 77 | response.raise_for_status() 78 | tokens = response.json() 79 | 80 | self.access_token = tokens['access_token'] 81 | self.expires_at = time.time() + tokens['expires_in'] 82 | expires_at_str = datetime.fromtimestamp(self.expires_at).strftime('%Y-%m-%d %H:%M:%S') 83 | 84 | self.config['tokens']['access_token'] = self.access_token 85 | self.config['tokens']['expires_at'] = expires_at_str 86 | 87 | if 'refresh_token' in tokens: 88 | self.refresh_token = tokens['refresh_token'] 89 | self.config['tokens']['refresh_token'] = self.refresh_token 90 | save_config(self.config) 91 | except requests.RequestException as e: 92 | logger.error(f"刷新访问令牌失败: {e}") 93 | raise 94 | 95 | def ensure_token_valid(self): 96 | """确保token有效""" 97 | if not self.access_token or self.is_token_expired(): 98 | self.refresh_access_token() 99 | 100 | def get_latest_messages(self, top: int = 10) -> List[Dict]: 101 | """获取最新的邮件(包括收件箱和垃圾邮件) 102 | 103 | Args: 104 | top: 获取的邮件数量 105 | 106 | Returns: 107 | List[Dict]: 按时间倒序排列的邮件列表 108 | """ 109 | self.ensure_token_valid() 110 | 111 | headers = { 112 | 'Authorization': f'Bearer {self.access_token}', 113 | 'Accept': 'application/json', 114 | 'Prefer': 'outlook.body-content-type="text"' 115 | } 116 | 117 | query_params = { 118 | '$top': top, 119 | '$select': 'subject,receivedDateTime,from,body,parentFolderId', 120 | '$orderby': 'receivedDateTime DESC', 121 | '$filter': "parentFolderId eq 'inbox' or parentFolderId eq 'junkemail'" 122 | } 123 | 124 | try: 125 | response = requests.get( 126 | f'{GRAPH_API_ENDPOINT}/me/messages', 127 | headers=headers, 128 | params=query_params, 129 | proxies=get_proxy() 130 | ) 131 | response.raise_for_status() 132 | return response.json()['value'] 133 | except requests.RequestException as e: 134 | logger.error(f"获取邮件失败: {e}") 135 | if response.status_code == 401: 136 | self.refresh_access_token() 137 | return self.get_latest_messages(top) 138 | raise 139 | 140 | def extract_verification_code(self, content: str, subject: str) -> str: 141 | """从邮件内容中提取验证码 142 | 143 | Args: 144 | content: 邮件内容 145 | subject: 邮件主题 146 | 147 | Returns: 148 | str: 提取到的验证码,如果没有找到则返回None 149 | """ 150 | verify_subjects = ['verify', '验证', '验证码', 'code', '校验码'] 151 | 152 | if not any(keyword.lower() in subject.lower() for keyword in verify_subjects): 153 | return None 154 | 155 | code_contexts = [ 156 | r'验证码[是为::]\s*(\d{6})', 157 | r'verification code[: ]?(\d{6})', 158 | r'code[: ]?(\d{6})', 159 | r'[\s::](\d{6})[\s.]', 160 | ] 161 | 162 | for pattern in code_contexts: 163 | matches = re.findall(pattern, content, re.IGNORECASE) 164 | if matches: 165 | return matches[0] 166 | 167 | return None 168 | 169 | def get_latest_verification_codes(self, top: int = 1) -> List[str]: 170 | """获取最新邮件中的验证码 171 | 172 | Args: 173 | top: 获取的邮件数量 174 | 175 | Returns: 176 | List[str]: 验证码列表 177 | """ 178 | result = [] 179 | messages = self.get_latest_messages(top=top) 180 | 181 | for msg in messages: 182 | code = self.extract_verification_code(msg['body']['content'], msg['subject']) 183 | if code and code not in result: 184 | result.append(code) 185 | 186 | return result 187 | 188 | def delete_all_messages(self) -> bool: 189 | """删除所有邮件(包括收件箱和垃圾邮件) 190 | 191 | Returns: 192 | bool: 删除是否成功 193 | """ 194 | self.ensure_token_valid() 195 | 196 | headers = { 197 | 'Authorization': f'Bearer {self.access_token}', 198 | 'Accept': 'application/json' 199 | } 200 | 201 | try: 202 | query_params = { 203 | '$select': 'id', 204 | '$filter': "parentFolderId eq 'inbox' or parentFolderId eq 'junkemail'" 205 | } 206 | 207 | response = requests.get( 208 | f'{GRAPH_API_ENDPOINT}/me/messages', 209 | headers=headers, 210 | params=query_params, 211 | proxies=get_proxy() 212 | ) 213 | response.raise_for_status() 214 | messages = response.json()['value'] 215 | 216 | # 删除所有邮件 217 | for msg in messages: 218 | delete_response = requests.delete( 219 | f'{GRAPH_API_ENDPOINT}/me/messages/{msg["id"]}', 220 | headers=headers, 221 | proxies=get_proxy() 222 | ) 223 | delete_response.raise_for_status() 224 | 225 | logger.info(f"成功删除 {len(messages)} 封邮件") 226 | return True 227 | 228 | except requests.RequestException as e: 229 | logger.error(f"删除邮件失败: {e}") 230 | if response.status_code == 401: 231 | self.refresh_access_token() 232 | return self.delete_all_messages() 233 | return False 234 | 235 | def main(): 236 | try: 237 | client = EmailClient() 238 | 239 | verification_codes = client.get_latest_verification_codes(top=1) 240 | 241 | print("\n最新邮件中的验证码:") 242 | if verification_codes: 243 | for code in verification_codes: 244 | print(f"找到验证码: {code}") 245 | 246 | print("\n已获取到验证码,正在清理邮箱...") 247 | if client.delete_all_messages(): 248 | print("所有邮件已清除") 249 | else: 250 | print("清除邮件失败") 251 | else: 252 | print("未找到验证码,不执行清理操作") 253 | 254 | except Exception as e: 255 | logger.error(f"程序执行出错: {e}") 256 | raise 257 | 258 | if __name__ == '__main__': 259 | main() 260 | -------------------------------------------------------------------------------- /cursor_pro_keep_alive.py: -------------------------------------------------------------------------------- 1 | from DrissionPage import ChromiumOptions, Chromium 2 | from DrissionPage.common import Keys 3 | import time 4 | import random 5 | from cursor_auth_manager import CursorAuthManager 6 | from mail_api import EmailClient 7 | import configparser 8 | 9 | 10 | def get_veri_code(): 11 | """获取验证码""" 12 | try: 13 | email_client = EmailClient() 14 | 15 | max_retries = 3 16 | for attempt in range(max_retries): 17 | codes = email_client.get_latest_verification_codes(top=1) 18 | 19 | if codes and len(codes) > 0: 20 | code = codes[0] 21 | print('获取到验证码:', code) 22 | 23 | email_client.delete_all_messages() 24 | return code 25 | 26 | print(f"未找到验证码,重试 {attempt + 1}/{max_retries}") 27 | time.sleep(5) 28 | 29 | print('未能获取验证码') 30 | return None 31 | 32 | except Exception as e: 33 | print(f"获取验证码时出错: {e}") 34 | return None 35 | 36 | def handle_turnstile(tab): 37 | """处理 Turnstile 验证""" 38 | print("准备处理验证") 39 | try: 40 | while True: 41 | try: 42 | challengeCheck = (tab.ele('@id=cf-turnstile', timeout=2) 43 | .child() 44 | .shadow_root 45 | .ele("tag:iframe") 46 | .ele("tag:body") 47 | .sr("tag:input")) 48 | 49 | if challengeCheck: 50 | print("验证框加载完成") 51 | time.sleep(random.uniform(1, 3)) 52 | challengeCheck.click() 53 | print("验证按钮已点击,等待验证完成...") 54 | time.sleep(2) 55 | return True 56 | except: 57 | pass 58 | 59 | if tab.ele('@name=password', timeout=2): 60 | print("无需验证") 61 | break 62 | if tab.ele('@data-index=0', timeout=2): 63 | print("无需验证") 64 | break 65 | if tab.ele('Account Settings', timeout=2): 66 | print("无需验证") 67 | break 68 | 69 | time.sleep(random.uniform(1,2)) 70 | except Exception as e: 71 | print(e) 72 | print('跳过验证') 73 | return False 74 | 75 | def delete_account(browser, tab): 76 | """删除账户流程""" 77 | print("\n开始删除账户...") 78 | 79 | try: 80 | if tab.ele('@name=email'): 81 | tab.ele('@name=email').input(account) 82 | print("输入账号") 83 | time.sleep(random.uniform(1,3)) 84 | except Exception as e: 85 | print(f"输入账号失败: {str(e)}") 86 | 87 | try: 88 | if tab.ele('Continue'): 89 | tab.ele('Continue').click() 90 | print("点击Continue") 91 | except Exception as e: 92 | print(f"点击Continue失败: {str(e)}") 93 | 94 | handle_turnstile(tab) 95 | time.sleep(5) 96 | 97 | try: 98 | if tab.ele('@name=password'): 99 | tab.ele('@name=password').input(password) 100 | print("输入密码") 101 | time.sleep(random.uniform(1,3)) 102 | except Exception as e: 103 | print("输入密码失败") 104 | 105 | sign_in_button = tab.ele('xpath:/html/body/div[1]/div/div/div[2]/div/form/div/button') 106 | try: 107 | if sign_in_button: 108 | sign_in_button.click(by_js=True) 109 | print("点击Sign in") 110 | except Exception as e: 111 | print(f"点击Sign in失败: {str(e)}") 112 | 113 | handle_turnstile(tab) 114 | 115 | while True: 116 | try: 117 | if tab.ele('Account Settings'): 118 | break 119 | if tab.ele('@data-index=0'): 120 | # tab_mail = browser.new_tab(mail_url) 121 | # browser.activate_tab(tab_mail) 122 | print("打开邮箱页面") 123 | code = get_veri_code() 124 | 125 | if code: 126 | print("获取验证码成功:", code) 127 | browser.activate_tab(tab) 128 | else: 129 | print("获取验证码失败,程序退出") 130 | return False 131 | 132 | i = 0 133 | for digit in code: 134 | tab.ele(f'@data-index={i}').input(digit) 135 | time.sleep(random.uniform(0.1,0.3)) 136 | i += 1 137 | break 138 | except Exception as e: 139 | print(e) 140 | 141 | handle_turnstile(tab) 142 | time.sleep(random.uniform(1,3)) 143 | tab.get_screenshot('sign-in_success.png') 144 | print("登录账户截图") 145 | 146 | tab.get(settings_url) 147 | print("进入设置页面") 148 | 149 | try: 150 | if tab.ele('@class=mt-1'): 151 | tab.ele('@class=mt-1').click() 152 | print("点击Adavance") 153 | time.sleep(random.uniform(1,2)) 154 | except Exception as e: 155 | print(f"点击Adavance失败: {str(e)}") 156 | 157 | try: 158 | if tab.ele('Delete Account'): 159 | tab.ele('Delete Account').click() 160 | print("点击Delete Account") 161 | time.sleep(random.uniform(1,2)) 162 | except Exception as e: 163 | print(f"点击Delete Account失败: {str(e)}") 164 | 165 | try: 166 | if tab.ele('tag:input'): 167 | tab.actions.click('tag:input').type('delete') 168 | print("输入delete") 169 | time.sleep(random.uniform(1,2)) 170 | except Exception as e: 171 | print(f"输入delete失败: {str(e)}") 172 | 173 | delete_button = tab.ele('xpath:/html/body/main/div/div/div/div/div/div[1]/div[2]/div[3]/div[2]/div/div/div[2]/button[2]') 174 | try: 175 | if delete_button: 176 | print("点击Delete") 177 | delete_button.click() 178 | time.sleep(5) 179 | tab.get_screenshot('delete_account.png') 180 | print("删除账户截图") 181 | return True 182 | except Exception as e: 183 | print(f"点击Delete失败: {str(e)}") 184 | return False 185 | 186 | 187 | def get_cursor_session_token(tab): 188 | """获取cursor session token""" 189 | cookies = tab.cookies() 190 | cursor_session_token = None 191 | for cookie in cookies: 192 | if cookie['name'] == 'WorkosCursorSessionToken': 193 | cursor_session_token = cookie['value'].split('%3A%3A')[1] 194 | break 195 | return cursor_session_token 196 | 197 | 198 | def update_cursor_auth(email=None, access_token=None, refresh_token=None): 199 | """ 200 | 更新Cursor的认证信息的便捷函数 201 | """ 202 | auth_manager = CursorAuthManager() 203 | return auth_manager.update_auth(email, access_token, refresh_token) 204 | 205 | def sign_up_account(browser, tab): 206 | """注册账户流程""" 207 | print("\n开始注册新账户...") 208 | tab.get(sign_up_url) 209 | 210 | try: 211 | if tab.ele('@name=first_name'): 212 | print("已打开注册页面") 213 | tab.actions.click('@name=first_name').input(first_name) 214 | time.sleep(random.uniform(1,3)) 215 | 216 | tab.actions.click('@name=last_name').input(last_name) 217 | time.sleep(random.uniform(1,3)) 218 | 219 | tab.actions.click('@name=email').input(account) 220 | print("输入邮箱" ) 221 | time.sleep(random.uniform(1,3)) 222 | 223 | tab.actions.click('@type=submit') 224 | print("点击注册按钮") 225 | 226 | except Exception as e: 227 | print("打开注册页面失败") 228 | return False 229 | 230 | handle_turnstile(tab) 231 | 232 | try: 233 | if tab.ele('@name=password'): 234 | tab.ele('@name=password').input(password) 235 | print("输入密码") 236 | time.sleep(random.uniform(1,3)) 237 | 238 | tab.ele('@type=submit').click() 239 | print("点击Continue按钮") 240 | 241 | except Exception as e: 242 | print("输入密码失败") 243 | return False 244 | 245 | time.sleep(random.uniform(1,3)) 246 | if tab.ele('This email is not available.'): 247 | print('This email is not available.') 248 | return False 249 | 250 | handle_turnstile(tab) 251 | 252 | while True: 253 | try: 254 | if tab.ele('Account Settings'): 255 | break 256 | if tab.ele('@data-index=0'): 257 | # tab_mail = browser.new_tab(mail_url) 258 | # browser.activate_tab(tab_mail) 259 | print("准备获取验证码") 260 | code = get_veri_code() 261 | 262 | if code: 263 | print("获取验证码成功:", code) 264 | browser.activate_tab(tab) 265 | else: 266 | print("获取验证码失败,程序退出") 267 | return False 268 | 269 | i = 0 270 | for digit in code: 271 | tab.ele(f'@data-index={i}').input(digit) 272 | time.sleep(random.uniform(0.1,0.3)) 273 | i += 1 274 | break 275 | except Exception as e: 276 | print(e) 277 | 278 | handle_turnstile(tab) 279 | 280 | time.sleep(random.uniform(1,3)) 281 | print("进入设置页面") 282 | tab.get(settings_url) 283 | try: 284 | usage_ele = tab.ele('xpath:/html/body/main/div/div/div/div/div/div[2]/div/div/div/div[1]/div[1]/span[2]') 285 | if usage_ele: 286 | usage_info = usage_ele.text 287 | total_usage = usage_info.split('/')[-1].strip() 288 | print("可用上限: " + total_usage) 289 | except Exception as e: 290 | print("获取可用上限失败") 291 | tab.get_screenshot("sign_up_success.png") 292 | print("注册账户截图") 293 | print("注册完成") 294 | print("Cursor 账号: " + account) 295 | print(" 密码: " + password) 296 | return True 297 | 298 | if __name__ == "__main__": 299 | # 配置信息 300 | login_url = 'https://authenticator.cursor.sh' 301 | sign_up_url = 'https://authenticator.cursor.sh/sign-up' 302 | settings_url = 'https://www.cursor.com/settings' 303 | 304 | config = configparser.ConfigParser() 305 | config.read('config.txt', encoding='utf-8') 306 | account = config['account']['email'] 307 | password = config['account']['password'] 308 | first_name = config['account']['first_name'] 309 | last_name = config['account']['last_name'] 310 | 311 | auto_update_cursor_auth = True 312 | 313 | # 浏览器配置 314 | co = ChromiumOptions() 315 | co.add_extension("turnstilePatch") 316 | co.headless() #无头模式 317 | # co.set_user_agent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36') 318 | co.set_pref('credentials_enable_service', False) 319 | co.set_argument('--hide-crash-restore-bubble') 320 | co.auto_port() 321 | # co.set_argument('--no-sandbox') # 无沙盒模式 用于linux 322 | # co.set_argument('--headless=new') #无界面系统启动参数 用于linux 323 | # co.set_proxy('127.0.0.1:10809') #设置代理 324 | 325 | browser = Chromium(co) 326 | tab = browser.latest_tab 327 | tab.run_js("try { turnstile.reset() } catch(e) { }") 328 | 329 | print("开始执行删除和注册流程") 330 | print("***请确认已经用指定的邮箱成功申请过cursor账号!***") 331 | tab.get(login_url) 332 | 333 | # 执行删除和注册流程 334 | if delete_account(browser, tab): 335 | print("账户删除成功") 336 | time.sleep(3) 337 | if sign_up_account(browser, tab): 338 | token = get_cursor_session_token(tab) 339 | print(f"CursorSessionToken: {token}") 340 | print("账户注册成功") 341 | if auto_update_cursor_auth: 342 | update_cursor_auth(email=account, access_token=token, refresh_token=token) 343 | else: 344 | print("账户注册失败") 345 | else: 346 | print("账户删除失败") 347 | 348 | print("脚本执行完毕") 349 | browser.quit() 350 | --------------------------------------------------------------------------------