mirror of
https://github.com/yeongpin/cursor-free-vip.git
synced 2025-08-02 20:47:35 +08:00
feat: Add Google and GitHub OAuth Authentication with Lifetime Access - Add OAuth integrations, update menu system, add multi-language support, and update documentation
This commit is contained in:
parent
66f6197e6d
commit
6bb33cec4c
@ -1,5 +1,13 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## v1.7.08
|
||||||
|
1. Add: Google OAuth Authentication | 增加 Google OAuth 認證
|
||||||
|
2. Add: GitHub OAuth Authentication | 增加 GitHub OAuth 認證
|
||||||
|
3. Add: Lifetime Access for OAuth Users | 增加 OAuth 用戶終身訪問權限
|
||||||
|
4. Add: OAuth Authentication Integration | 增加 OAuth 認證集成
|
||||||
|
5. Update: Menu System with OAuth Options | 更新菜單系統,添加 OAuth 選項
|
||||||
|
6. Add: Multi-language Support for OAuth | 增加 OAuth 多語言支持
|
||||||
|
|
||||||
## v1.7.07
|
## v1.7.07
|
||||||
1. Add: Vietnamese Language | 增加越南語言
|
1. Add: Vietnamese Language | 增加越南語言
|
||||||
2. Add: Admin Privilege Management for Windows Executable | 增加 Windows 可執行文件管理員權限
|
2. Add: Admin Privilege Management for Windows Executable | 增加 Windows 可執行文件管理員權限
|
||||||
|
@ -36,6 +36,10 @@ Cursor's configuration.
|
|||||||
|
|
||||||
## ✨ Features | 功能特點
|
## ✨ Features | 功能特點
|
||||||
|
|
||||||
|
* 🌟 Google OAuth Authentication with Lifetime Access<br>使用 Google OAuth 認證(終身訪問)<br>
|
||||||
|
|
||||||
|
* ⭐ GitHub OAuth Authentication with Lifetime Access<br>使用 GitHub OAuth 認證(終身訪問)<br>
|
||||||
|
|
||||||
* Automatically register Cursor membership<br>自動註冊 Cursor 會員<br>
|
* Automatically register Cursor membership<br>自動註冊 Cursor 會員<br>
|
||||||
|
|
||||||
* Support Windows and macOS systems<br>支持 Windows 和 macOS 系統<br>
|
* Support Windows and macOS systems<br>支持 Windows 和 macOS 系統<br>
|
||||||
@ -44,6 +48,8 @@ Cursor's configuration.
|
|||||||
|
|
||||||
* Reset Cursor's configuration<br>重置 Cursor 的配置<br>
|
* Reset Cursor's configuration<br>重置 Cursor 的配置<br>
|
||||||
|
|
||||||
|
* Multi-language support (English, 简体中文, 繁體中文, Vietnamese)<br>多語言支持(英文、简体中文、繁體中文、越南語)<br>
|
||||||
|
|
||||||
## 💻 System Support | 系統支持
|
## 💻 System Support | 系統支持
|
||||||
|
|
||||||
| Windows | x64 | ✅ | macOS | Intel | ✅ |
|
| Windows | x64 | ✅ | macOS | Intel | ✅ |
|
||||||
|
5
cursor_register_github.py
Normal file
5
cursor_register_github.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from oauth_auth import main as oauth_main
|
||||||
|
|
||||||
|
def main(translator=None):
|
||||||
|
"""Handle GitHub OAuth registration"""
|
||||||
|
oauth_main('github', translator)
|
5
cursor_register_google.py
Normal file
5
cursor_register_google.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from oauth_auth import main as oauth_main
|
||||||
|
|
||||||
|
def main(translator=None):
|
||||||
|
"""Handle Google OAuth registration"""
|
||||||
|
oauth_main('google', translator)
|
@ -4,6 +4,8 @@
|
|||||||
"exit": "Exit Program",
|
"exit": "Exit Program",
|
||||||
"reset": "Reset Machine ID",
|
"reset": "Reset Machine ID",
|
||||||
"register": "Register New Cursor Account",
|
"register": "Register New Cursor Account",
|
||||||
|
"register_google": "Register with Google Account",
|
||||||
|
"register_github": "Register with GitHub Account",
|
||||||
"register_manual": "Register Cursor with Custom Email",
|
"register_manual": "Register Cursor with Custom Email",
|
||||||
"quit": "Close Cursor Application",
|
"quit": "Close Cursor Application",
|
||||||
"select_language": "Change Language",
|
"select_language": "Change Language",
|
||||||
|
@ -4,6 +4,8 @@
|
|||||||
"exit": "退出程序",
|
"exit": "退出程序",
|
||||||
"reset": "重置机器标识",
|
"reset": "重置机器标识",
|
||||||
"register": "注册新 Cursor 账号",
|
"register": "注册新 Cursor 账号",
|
||||||
|
"register_google": "使用 Google 账号注册",
|
||||||
|
"register_github": "使用 GitHub 账号注册",
|
||||||
"register_manual": "使用自定义邮箱注册",
|
"register_manual": "使用自定义邮箱注册",
|
||||||
"quit": "关闭 Cursor 应用",
|
"quit": "关闭 Cursor 应用",
|
||||||
"select_language": "更改语言",
|
"select_language": "更改语言",
|
||||||
|
28
main.py
28
main.py
@ -219,10 +219,14 @@ def print_menu():
|
|||||||
print(f"{Fore.GREEN}0{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.exit')}")
|
print(f"{Fore.GREEN}0{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.exit')}")
|
||||||
print(f"{Fore.GREEN}1{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.reset')}")
|
print(f"{Fore.GREEN}1{Style.RESET_ALL}. {EMOJI['RESET']} {translator.get('menu.reset')}")
|
||||||
print(f"{Fore.GREEN}2{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register')}")
|
print(f"{Fore.GREEN}2{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register')}")
|
||||||
print(f"{Fore.GREEN}3{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}")
|
print(f"{Fore.GREEN}3{Style.RESET_ALL}. 🌟 {translator.get('menu.register_google')}")
|
||||||
print(f"{Fore.GREEN}4{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}")
|
print(f"{Fore.YELLOW} ┗━━ 🔥 LIFETIME ACCESS ENABLED 🔥{Style.RESET_ALL}")
|
||||||
print(f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}")
|
print(f"{Fore.GREEN}4{Style.RESET_ALL}. ⭐ {translator.get('menu.register_github')}")
|
||||||
print(f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}")
|
print(f"{Fore.YELLOW} ┗━━ 🚀 LIFETIME ACCESS ENABLED 🚀{Style.RESET_ALL}")
|
||||||
|
print(f"{Fore.GREEN}5{Style.RESET_ALL}. {EMOJI['SUCCESS']} {translator.get('menu.register_manual')}")
|
||||||
|
print(f"{Fore.GREEN}6{Style.RESET_ALL}. {EMOJI['ERROR']} {translator.get('menu.quit')}")
|
||||||
|
print(f"{Fore.GREEN}7{Style.RESET_ALL}. {EMOJI['LANG']} {translator.get('menu.select_language')}")
|
||||||
|
print(f"{Fore.GREEN}8{Style.RESET_ALL}. {EMOJI['UPDATE']} {translator.get('menu.disable_auto_update')}")
|
||||||
print(f"{Fore.YELLOW}{'─' * 40}{Style.RESET_ALL}")
|
print(f"{Fore.YELLOW}{'─' * 40}{Style.RESET_ALL}")
|
||||||
|
|
||||||
def select_language():
|
def select_language():
|
||||||
@ -358,7 +362,7 @@ def main():
|
|||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices='0-6')}: {Style.RESET_ALL}")
|
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices='0-8')}: {Style.RESET_ALL}")
|
||||||
|
|
||||||
if choice == "0":
|
if choice == "0":
|
||||||
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.exit')}...{Style.RESET_ALL}")
|
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.exit')}...{Style.RESET_ALL}")
|
||||||
@ -373,18 +377,26 @@ def main():
|
|||||||
cursor_register.main(translator)
|
cursor_register.main(translator)
|
||||||
print_menu()
|
print_menu()
|
||||||
elif choice == "3":
|
elif choice == "3":
|
||||||
|
import cursor_register_google
|
||||||
|
cursor_register_google.main(translator)
|
||||||
|
print_menu()
|
||||||
|
elif choice == "4":
|
||||||
|
import cursor_register_github
|
||||||
|
cursor_register_github.main(translator)
|
||||||
|
print_menu()
|
||||||
|
elif choice == "5":
|
||||||
import cursor_register_manual
|
import cursor_register_manual
|
||||||
cursor_register_manual.main(translator)
|
cursor_register_manual.main(translator)
|
||||||
print_menu()
|
print_menu()
|
||||||
elif choice == "4":
|
elif choice == "6":
|
||||||
import quit_cursor
|
import quit_cursor
|
||||||
quit_cursor.quit_cursor(translator)
|
quit_cursor.quit_cursor(translator)
|
||||||
print_menu()
|
print_menu()
|
||||||
elif choice == "5":
|
elif choice == "7":
|
||||||
if select_language():
|
if select_language():
|
||||||
print_menu()
|
print_menu()
|
||||||
continue
|
continue
|
||||||
elif choice == "6":
|
elif choice == "8":
|
||||||
import disable_auto_update
|
import disable_auto_update
|
||||||
disable_auto_update.run(translator)
|
disable_auto_update.run(translator)
|
||||||
print_menu()
|
print_menu()
|
||||||
|
826
oauth_auth.py
Normal file
826
oauth_auth.py
Normal file
@ -0,0 +1,826 @@
|
|||||||
|
import os
|
||||||
|
from colorama import Fore, Style, init
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import webbrowser
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
from DrissionPage import ChromiumPage, ChromiumOptions
|
||||||
|
from cursor_auth import CursorAuth
|
||||||
|
from utils import get_random_wait_time, get_default_chrome_path
|
||||||
|
from config import get_config
|
||||||
|
import platform
|
||||||
|
|
||||||
|
# Initialize colorama
|
||||||
|
init()
|
||||||
|
|
||||||
|
# Define emoji constants
|
||||||
|
EMOJI = {
|
||||||
|
'START': '🚀',
|
||||||
|
'OAUTH': '🔑',
|
||||||
|
'SUCCESS': '✅',
|
||||||
|
'ERROR': '❌',
|
||||||
|
'WAIT': '⏳',
|
||||||
|
'INFO': 'ℹ️'
|
||||||
|
}
|
||||||
|
|
||||||
|
class OAuthHandler:
|
||||||
|
def __init__(self, translator=None):
|
||||||
|
self.translator = translator
|
||||||
|
self.config = get_config(translator)
|
||||||
|
os.environ['BROWSER_HEADLESS'] = 'False'
|
||||||
|
self.browser = None
|
||||||
|
|
||||||
|
def _get_active_profile(self, user_data_dir):
|
||||||
|
"""Find the existing default/active Chrome profile"""
|
||||||
|
try:
|
||||||
|
# List all profile directories
|
||||||
|
profiles = []
|
||||||
|
for item in os.listdir(user_data_dir):
|
||||||
|
if item == 'Default' or (item.startswith('Profile ') and os.path.isdir(os.path.join(user_data_dir, item))):
|
||||||
|
profiles.append(item)
|
||||||
|
|
||||||
|
if not profiles:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} No Chrome profiles found, using Default{Style.RESET_ALL}")
|
||||||
|
return 'Default'
|
||||||
|
|
||||||
|
# First check if Default profile exists
|
||||||
|
if 'Default' in profiles:
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Found Default Chrome profile{Style.RESET_ALL}")
|
||||||
|
return 'Default'
|
||||||
|
|
||||||
|
# If no Default profile, check Local State for last used profile
|
||||||
|
local_state_path = os.path.join(user_data_dir, 'Local State')
|
||||||
|
if os.path.exists(local_state_path):
|
||||||
|
with open(local_state_path, 'r', encoding='utf-8') as f:
|
||||||
|
local_state = json.load(f)
|
||||||
|
|
||||||
|
# Get info about last used profile
|
||||||
|
profile_info = local_state.get('profile', {})
|
||||||
|
last_used = profile_info.get('last_used', '')
|
||||||
|
info_cache = profile_info.get('info_cache', {})
|
||||||
|
|
||||||
|
# Try to find an active profile
|
||||||
|
for profile in profiles:
|
||||||
|
profile_path = profile.replace('\\', '/')
|
||||||
|
if profile_path in info_cache:
|
||||||
|
#print(f"{Fore.CYAN}{EMOJI['INFO']} Using existing Chrome profile: {profile}{Style.RESET_ALL}")
|
||||||
|
return profile
|
||||||
|
|
||||||
|
# If no profile found in Local State, use the first available profile
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Using first available Chrome profile: {profiles[0]}{Style.RESET_ALL}")
|
||||||
|
return profiles[0]
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Error finding Chrome profile, using Default: {str(e)}{Style.RESET_ALL}")
|
||||||
|
return 'Default'
|
||||||
|
|
||||||
|
def setup_browser(self):
|
||||||
|
"""Setup browser for OAuth flow using active profile"""
|
||||||
|
try:
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Initializing browser setup...{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Platform-specific initialization
|
||||||
|
platform_name = platform.system().lower()
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Detected platform: {platform_name}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Kill existing browser processes
|
||||||
|
self._kill_browser_processes()
|
||||||
|
|
||||||
|
# Get browser paths and user data directory
|
||||||
|
user_data_dir = self._get_user_data_directory()
|
||||||
|
chrome_path = self._get_browser_path()
|
||||||
|
|
||||||
|
if not chrome_path:
|
||||||
|
raise Exception(f"No compatible browser found. Please install Google Chrome or Chromium.\nSupported browsers for {platform_name}:\n" +
|
||||||
|
"- Windows: Google Chrome, Chromium\n" +
|
||||||
|
"- macOS: Google Chrome, Chromium\n" +
|
||||||
|
"- Linux: Google Chrome, Chromium, chromium-browser")
|
||||||
|
|
||||||
|
# Get active profile
|
||||||
|
active_profile = self._get_active_profile(user_data_dir)
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Using browser profile: {active_profile}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Configure browser options
|
||||||
|
co = self._configure_browser_options(chrome_path, user_data_dir, active_profile)
|
||||||
|
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting browser at: {chrome_path}{Style.RESET_ALL}")
|
||||||
|
self.browser = ChromiumPage(co)
|
||||||
|
|
||||||
|
# Verify browser launched successfully
|
||||||
|
if not self.browser:
|
||||||
|
raise Exception("Failed to initialize browser instance")
|
||||||
|
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Browser setup completed successfully{Style.RESET_ALL}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Browser setup failed: {str(e)}{Style.RESET_ALL}")
|
||||||
|
if "DevToolsActivePort file doesn't exist" in str(e):
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Try running with administrator/root privileges{Style.RESET_ALL}")
|
||||||
|
elif "Chrome failed to start" in str(e):
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Make sure Chrome/Chromium is properly installed{Style.RESET_ALL}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _kill_browser_processes(self):
|
||||||
|
"""Kill existing browser processes based on platform"""
|
||||||
|
try:
|
||||||
|
if os.name == 'nt': # Windows
|
||||||
|
processes = ['chrome.exe', 'chromium.exe']
|
||||||
|
for proc in processes:
|
||||||
|
os.system(f'taskkill /f /im {proc} >nul 2>&1')
|
||||||
|
else: # Linux/Mac
|
||||||
|
processes = ['chrome', 'chromium', 'chromium-browser']
|
||||||
|
for proc in processes:
|
||||||
|
os.system(f'pkill -f {proc} >/dev/null 2>&1')
|
||||||
|
|
||||||
|
time.sleep(1) # Wait for processes to close
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Warning: Could not kill existing browser processes: {e}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
def _get_user_data_directory(self):
|
||||||
|
"""Get the appropriate user data directory based on platform"""
|
||||||
|
try:
|
||||||
|
if os.name == 'nt': # Windows
|
||||||
|
possible_paths = [
|
||||||
|
os.path.expandvars(r'%LOCALAPPDATA%\Google\Chrome\User Data'),
|
||||||
|
os.path.expandvars(r'%LOCALAPPDATA%\Chromium\User Data')
|
||||||
|
]
|
||||||
|
elif sys.platform == 'darwin': # macOS
|
||||||
|
possible_paths = [
|
||||||
|
os.path.expanduser('~/Library/Application Support/Google/Chrome'),
|
||||||
|
os.path.expanduser('~/Library/Application Support/Chromium')
|
||||||
|
]
|
||||||
|
else: # Linux
|
||||||
|
possible_paths = [
|
||||||
|
os.path.expanduser('~/.config/google-chrome'),
|
||||||
|
os.path.expanduser('~/.config/chromium'),
|
||||||
|
'/usr/bin/google-chrome',
|
||||||
|
'/usr/bin/chromium-browser'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Try each possible path
|
||||||
|
for path in possible_paths:
|
||||||
|
if os.path.exists(path):
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Found browser data directory: {path}{Style.RESET_ALL}")
|
||||||
|
return path
|
||||||
|
|
||||||
|
# Create temporary profile if no existing profile found
|
||||||
|
temp_profile = os.path.join(os.path.expanduser('~'), '.cursor_temp_profile')
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Creating temporary profile at: {temp_profile}{Style.RESET_ALL}")
|
||||||
|
os.makedirs(temp_profile, exist_ok=True)
|
||||||
|
return temp_profile
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Error getting user data directory: {e}{Style.RESET_ALL}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _get_browser_path(self):
|
||||||
|
"""Get the browser executable path based on platform"""
|
||||||
|
try:
|
||||||
|
# Try default path first
|
||||||
|
chrome_path = get_default_chrome_path()
|
||||||
|
if chrome_path and os.path.exists(chrome_path):
|
||||||
|
return chrome_path
|
||||||
|
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Searching for alternative browser installations...{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Platform-specific paths
|
||||||
|
if os.name == 'nt': # Windows
|
||||||
|
alt_paths = [
|
||||||
|
r'C:\Program Files\Google\Chrome\Application\chrome.exe',
|
||||||
|
r'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe',
|
||||||
|
r'C:\Program Files\Chromium\Application\chrome.exe',
|
||||||
|
os.path.expandvars(r'%ProgramFiles%\Google\Chrome\Application\chrome.exe'),
|
||||||
|
os.path.expandvars(r'%ProgramFiles(x86)%\Google\Chrome\Application\chrome.exe')
|
||||||
|
]
|
||||||
|
elif sys.platform == 'darwin': # macOS
|
||||||
|
alt_paths = [
|
||||||
|
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
||||||
|
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
||||||
|
'~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
||||||
|
'~/Applications/Chromium.app/Contents/MacOS/Chromium'
|
||||||
|
]
|
||||||
|
else: # Linux
|
||||||
|
alt_paths = [
|
||||||
|
'/usr/bin/google-chrome',
|
||||||
|
'/usr/bin/chromium-browser',
|
||||||
|
'/usr/bin/chromium',
|
||||||
|
'/snap/bin/chromium',
|
||||||
|
'/usr/local/bin/chrome',
|
||||||
|
'/usr/local/bin/chromium'
|
||||||
|
]
|
||||||
|
|
||||||
|
# Try each alternative path
|
||||||
|
for path in alt_paths:
|
||||||
|
expanded_path = os.path.expanduser(path)
|
||||||
|
if os.path.exists(expanded_path):
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Found browser at: {expanded_path}{Style.RESET_ALL}")
|
||||||
|
return expanded_path
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Error finding browser path: {e}{Style.RESET_ALL}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _configure_browser_options(self, chrome_path, user_data_dir, active_profile):
|
||||||
|
"""Configure browser options based on platform"""
|
||||||
|
try:
|
||||||
|
co = ChromiumOptions()
|
||||||
|
co.set_paths(browser_path=chrome_path, user_data_path=user_data_dir)
|
||||||
|
co.set_argument(f'--profile-directory={active_profile}')
|
||||||
|
|
||||||
|
# Basic options
|
||||||
|
co.set_argument('--no-first-run')
|
||||||
|
co.set_argument('--no-default-browser-check')
|
||||||
|
co.set_argument('--disable-gpu')
|
||||||
|
|
||||||
|
# Platform-specific options
|
||||||
|
if sys.platform.startswith('linux'):
|
||||||
|
co.set_argument('--no-sandbox')
|
||||||
|
co.set_argument('--disable-dev-shm-usage')
|
||||||
|
co.set_argument('--disable-setuid-sandbox')
|
||||||
|
elif sys.platform == 'darwin':
|
||||||
|
co.set_argument('--disable-gpu-compositing')
|
||||||
|
elif os.name == 'nt':
|
||||||
|
co.set_argument('--disable-features=TranslateUI')
|
||||||
|
co.set_argument('--disable-features=RendererCodeIntegrity')
|
||||||
|
|
||||||
|
return co
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Error configuring browser options: {e}{Style.RESET_ALL}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def handle_google_auth(self):
|
||||||
|
"""Handle Google OAuth authentication"""
|
||||||
|
try:
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.google_start')}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Setup browser
|
||||||
|
if not self.setup_browser():
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_failed')}{Style.RESET_ALL}")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
# Navigate to auth URL
|
||||||
|
try:
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to authentication page...{Style.RESET_ALL}")
|
||||||
|
self.browser.get("https://authenticator.cursor.sh/sign-up")
|
||||||
|
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||||
|
|
||||||
|
# Look for Google auth button
|
||||||
|
selectors = [
|
||||||
|
"//a[contains(@href,'GoogleOAuth')]",
|
||||||
|
"//a[contains(@class,'auth-method-button') and contains(@href,'GoogleOAuth')]",
|
||||||
|
"(//a[contains(@class,'auth-method-button')])[1]" # First auth button as fallback
|
||||||
|
]
|
||||||
|
|
||||||
|
auth_btn = None
|
||||||
|
for selector in selectors:
|
||||||
|
try:
|
||||||
|
auth_btn = self.browser.ele(f"xpath:{selector}", timeout=2)
|
||||||
|
if auth_btn and auth_btn.is_displayed():
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not auth_btn:
|
||||||
|
raise Exception("Could not find Google authentication button")
|
||||||
|
|
||||||
|
# Click the button and wait for page load
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting Google authentication...{Style.RESET_ALL}")
|
||||||
|
auth_btn.click()
|
||||||
|
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||||
|
|
||||||
|
# Check if we're on account selection page
|
||||||
|
if "accounts.google.com" in self.browser.url:
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Please select your Google account to continue...{Style.RESET_ALL}")
|
||||||
|
try:
|
||||||
|
self.browser.run_js("""
|
||||||
|
alert('Please select your Google account to continue with Cursor authentication');
|
||||||
|
""")
|
||||||
|
except:
|
||||||
|
pass # Alert is optional
|
||||||
|
|
||||||
|
# Wait for authentication to complete
|
||||||
|
auth_info = self._wait_for_auth()
|
||||||
|
if not auth_info:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout')}{Style.RESET_ALL}")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success')}{Style.RESET_ALL}")
|
||||||
|
return True, auth_info
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication error: {str(e)}{Style.RESET_ALL}")
|
||||||
|
return False, None
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
if self.browser:
|
||||||
|
self.browser.quit()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed', error=str(e))}{Style.RESET_ALL}")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
def _wait_for_auth(self):
|
||||||
|
"""Wait for authentication to complete and extract auth info"""
|
||||||
|
try:
|
||||||
|
max_wait = 300 # 5 minutes
|
||||||
|
start_time = time.time()
|
||||||
|
check_interval = 2 # Check every 2 seconds
|
||||||
|
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['WAIT']} Waiting for authentication (timeout: 5 minutes)...{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
while time.time() - start_time < max_wait:
|
||||||
|
try:
|
||||||
|
# Check for authentication cookies
|
||||||
|
cookies = self.browser.cookies()
|
||||||
|
|
||||||
|
for cookie in cookies:
|
||||||
|
if cookie.get("name") == "WorkosCursorSessionToken":
|
||||||
|
value = cookie.get("value", "")
|
||||||
|
token = None
|
||||||
|
|
||||||
|
if "::" in value:
|
||||||
|
token = value.split("::")[-1]
|
||||||
|
elif "%3A%3A" in value:
|
||||||
|
token = value.split("%3A%3A")[-1]
|
||||||
|
|
||||||
|
if token:
|
||||||
|
# Get email from settings page
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Authentication successful, getting account info...{Style.RESET_ALL}")
|
||||||
|
self.browser.get("https://www.cursor.com/settings")
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
email = None
|
||||||
|
try:
|
||||||
|
email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)")
|
||||||
|
if email_element:
|
||||||
|
email = email_element.text
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {email}{Style.RESET_ALL}")
|
||||||
|
except:
|
||||||
|
email = "user@cursor.sh" # Fallback email
|
||||||
|
|
||||||
|
# Check usage count
|
||||||
|
try:
|
||||||
|
usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)")
|
||||||
|
if usage_element:
|
||||||
|
usage_text = usage_element.text
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Check if account is expired
|
||||||
|
if usage_text.strip() == "150 / 150":
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, creating new account...{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Delete current account
|
||||||
|
if self._delete_current_account():
|
||||||
|
# Start new authentication
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new authentication process...{Style.RESET_ALL}")
|
||||||
|
return self.handle_google_auth()
|
||||||
|
else:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete expired account{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not check usage count: {str(e)}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
return {"email": email, "token": token}
|
||||||
|
|
||||||
|
# Also check URL as backup
|
||||||
|
if "cursor.com/settings" in self.browser.url:
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Detected successful login{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Waiting for authentication... ({str(e)}){Style.RESET_ALL}")
|
||||||
|
|
||||||
|
time.sleep(check_interval)
|
||||||
|
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication timeout{Style.RESET_ALL}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Error while waiting for authentication: {str(e)}{Style.RESET_ALL}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def handle_github_auth(self):
|
||||||
|
"""Handle GitHub OAuth authentication"""
|
||||||
|
try:
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('oauth.github_start')}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Setup browser
|
||||||
|
if not self.setup_browser():
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.browser_failed')}{Style.RESET_ALL}")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
# Navigate to auth URL
|
||||||
|
try:
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to authentication page...{Style.RESET_ALL}")
|
||||||
|
self.browser.get("https://authenticator.cursor.sh/sign-up")
|
||||||
|
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||||
|
|
||||||
|
# Look for GitHub auth button
|
||||||
|
selectors = [
|
||||||
|
"//a[contains(@href,'GitHubOAuth')]",
|
||||||
|
"//a[contains(@class,'auth-method-button') and contains(@href,'GitHubOAuth')]",
|
||||||
|
"(//a[contains(@class,'auth-method-button')])[2]" # Second auth button as fallback
|
||||||
|
]
|
||||||
|
|
||||||
|
auth_btn = None
|
||||||
|
for selector in selectors:
|
||||||
|
try:
|
||||||
|
auth_btn = self.browser.ele(f"xpath:{selector}", timeout=2)
|
||||||
|
if auth_btn and auth_btn.is_displayed():
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not auth_btn:
|
||||||
|
raise Exception("Could not find GitHub authentication button")
|
||||||
|
|
||||||
|
# Click the button and wait for page load
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting GitHub authentication...{Style.RESET_ALL}")
|
||||||
|
auth_btn.click()
|
||||||
|
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||||
|
|
||||||
|
# Wait for authentication to complete
|
||||||
|
auth_info = self._wait_for_auth()
|
||||||
|
if not auth_info:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.timeout')}{Style.RESET_ALL}")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('oauth.success')}{Style.RESET_ALL}")
|
||||||
|
return True, auth_info
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication error: {str(e)}{Style.RESET_ALL}")
|
||||||
|
return False, None
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
if self.browser:
|
||||||
|
self.browser.quit()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('oauth.failed', error=str(e))}{Style.RESET_ALL}")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
def _handle_oauth(self, auth_type):
|
||||||
|
"""Handle OAuth authentication for both Google and GitHub
|
||||||
|
|
||||||
|
Args:
|
||||||
|
auth_type (str): Type of authentication ('google' or 'github')
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if not self.setup_browser():
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
# Navigate to auth URL
|
||||||
|
self.browser.get("https://authenticator.cursor.sh/sign-up")
|
||||||
|
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||||
|
|
||||||
|
# Set selectors based on auth type
|
||||||
|
if auth_type == "google":
|
||||||
|
selectors = [
|
||||||
|
"//a[@class='rt-reset rt-BaseButton rt-r-size-3 rt-variant-surface rt-high-contrast rt-Button auth-method-button_AuthMethodButton__irESX'][contains(@href,'GoogleOAuth')]",
|
||||||
|
"(//a[@class='rt-reset rt-BaseButton rt-r-size-3 rt-variant-surface rt-high-contrast rt-Button auth-method-button_AuthMethodButton__irESX'])[1]"
|
||||||
|
]
|
||||||
|
else: # github
|
||||||
|
selectors = [
|
||||||
|
"(//a[@class='rt-reset rt-BaseButton rt-r-size-3 rt-variant-surface rt-high-contrast rt-Button auth-method-button_AuthMethodButton__irESX'])[2]"
|
||||||
|
]
|
||||||
|
|
||||||
|
# Wait for the button to be available
|
||||||
|
auth_btn = None
|
||||||
|
max_button_wait = 30 # 30 seconds
|
||||||
|
button_start_time = time.time()
|
||||||
|
|
||||||
|
while time.time() - button_start_time < max_button_wait:
|
||||||
|
for selector in selectors:
|
||||||
|
try:
|
||||||
|
auth_btn = self.browser.ele(f"xpath:{selector}", timeout=1)
|
||||||
|
if auth_btn and auth_btn.is_displayed():
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
if auth_btn:
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
if auth_btn:
|
||||||
|
# Click the button and wait for page load
|
||||||
|
auth_btn.click()
|
||||||
|
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||||
|
|
||||||
|
# Check if we're on account selection page
|
||||||
|
if auth_type == "google" and "accounts.google.com" in self.browser.url:
|
||||||
|
alert_js = """
|
||||||
|
alert('Please select your Google account manually to continue with Cursor authentication');
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self.browser.run_js(alert_js)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Alert display failed: {str(e)}{Style.RESET_ALL}")
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Please select your Google account manually to continue with Cursor authentication...{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Waiting for authentication to complete...{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Wait for authentication to complete
|
||||||
|
max_wait = 300 # 5 minutes
|
||||||
|
start_time = time.time()
|
||||||
|
last_url = self.browser.url
|
||||||
|
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['WAIT']} Checking authentication status...{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
while time.time() - start_time < max_wait:
|
||||||
|
try:
|
||||||
|
# Check for authentication cookies
|
||||||
|
cookies = self.browser.cookies()
|
||||||
|
|
||||||
|
for cookie in cookies:
|
||||||
|
if cookie.get("name") == "WorkosCursorSessionToken":
|
||||||
|
value = cookie.get("value", "")
|
||||||
|
if "::" in value:
|
||||||
|
token = value.split("::")[-1]
|
||||||
|
elif "%3A%3A" in value:
|
||||||
|
token = value.split("%3A%3A")[-1]
|
||||||
|
|
||||||
|
if token:
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Authentication successful!{Style.RESET_ALL}")
|
||||||
|
# Navigate to settings page
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Navigating to settings page...{Style.RESET_ALL}")
|
||||||
|
self.browser.get("https://www.cursor.com/settings")
|
||||||
|
time.sleep(3) # Wait for settings page to load
|
||||||
|
|
||||||
|
# Get email from settings page
|
||||||
|
try:
|
||||||
|
email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)")
|
||||||
|
if email_element:
|
||||||
|
actual_email = email_element.text
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {actual_email}{Style.RESET_ALL}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find email: {str(e)}{Style.RESET_ALL}")
|
||||||
|
actual_email = "user@cursor.sh"
|
||||||
|
|
||||||
|
# Check usage count
|
||||||
|
try:
|
||||||
|
usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)")
|
||||||
|
if usage_element:
|
||||||
|
usage_text = usage_element.text
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Check if account is expired
|
||||||
|
if usage_text.strip() == "150 / 150": # Changed back to actual condition
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, deleting...{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
delete_js = """
|
||||||
|
function deleteAccount() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fetch('https://www.cursor.com/api/dashboard/delete-account', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
resolve('Account deleted successfully');
|
||||||
|
} else {
|
||||||
|
reject('Failed to delete account: ' + response.status);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject('Error: ' + error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return deleteAccount();
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = self.browser.run_js(delete_js)
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Delete account result: {result}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Navigate back to auth page and repeat authentication
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting re-authentication process...{Style.RESET_ALL}")
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Redirecting to authenticator.cursor.sh...{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Explicitly navigate to the authentication page
|
||||||
|
#self.browser.get("https://authenticator.cursor.sh/sign-up")
|
||||||
|
# time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||||
|
|
||||||
|
# Call handle_google_auth again to repeat the entire process
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new Google authentication...{Style.RESET_ALL}")
|
||||||
|
return self.handle_google_auth()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete account or re-authenticate: {str(e)}{Style.RESET_ALL}")
|
||||||
|
else:
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account is still valid (Usage: {usage_text}){Style.RESET_ALL}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find usage count: {str(e)}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Remove the browser stay open prompt and input wait
|
||||||
|
return True, {"email": actual_email, "token": token}
|
||||||
|
|
||||||
|
# Also check URL as backup
|
||||||
|
current_url = self.browser.url
|
||||||
|
if "cursor.com/settings" in current_url:
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Already on settings page!{Style.RESET_ALL}")
|
||||||
|
time.sleep(1)
|
||||||
|
cookies = self.browser.cookies()
|
||||||
|
for cookie in cookies:
|
||||||
|
if cookie.get("name") == "WorkosCursorSessionToken":
|
||||||
|
value = cookie.get("value", "")
|
||||||
|
if "::" in value:
|
||||||
|
token = value.split("::")[-1]
|
||||||
|
elif "%3A%3A" in value:
|
||||||
|
token = value.split("%3A%3A")[-1]
|
||||||
|
if token:
|
||||||
|
# Get email and check usage here too
|
||||||
|
try:
|
||||||
|
email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)")
|
||||||
|
if email_element:
|
||||||
|
actual_email = email_element.text
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Found email: {actual_email}{Style.RESET_ALL}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find email: {str(e)}{Style.RESET_ALL}")
|
||||||
|
actual_email = "user@cursor.sh"
|
||||||
|
|
||||||
|
# Check usage count
|
||||||
|
try:
|
||||||
|
usage_element = self.browser.ele("css:div[class='flex flex-col gap-4 lg:flex-row'] div:nth-child(1) div:nth-child(1) span:nth-child(2)")
|
||||||
|
if usage_element:
|
||||||
|
usage_text = usage_element.text
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Usage count: {usage_text}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Check if account is expired
|
||||||
|
if usage_text.strip() == "150 / 150": # Changed back to actual condition
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Account has reached maximum usage, deleting...{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
delete_js = """
|
||||||
|
function deleteAccount() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fetch('https://www.cursor.com/api/dashboard/delete-account', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
credentials: 'include'
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
resolve('Account deleted successfully');
|
||||||
|
} else {
|
||||||
|
reject('Failed to delete account: ' + response.status);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject('Error: ' + error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return deleteAccount();
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = self.browser.run_js(delete_js)
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Delete account result: {result}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Navigate back to auth page and repeat authentication
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting re-authentication process...{Style.RESET_ALL}")
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Redirecting to authenticator.cursor.sh...{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Explicitly navigate to the authentication page
|
||||||
|
self.browser.get("https://authenticator.cursor.sh/sign-up")
|
||||||
|
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||||
|
|
||||||
|
# Call handle_google_auth again to repeat the entire process
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Starting new Google authentication...{Style.RESET_ALL}")
|
||||||
|
return self.handle_google_auth()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to delete account or re-authenticate: {str(e)}{Style.RESET_ALL}")
|
||||||
|
else:
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Account is still valid (Usage: {usage_text}){Style.RESET_ALL}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Could not find usage count: {str(e)}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
# Remove the browser stay open prompt and input wait
|
||||||
|
return True, {"email": actual_email, "token": token}
|
||||||
|
elif current_url != last_url:
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Page changed, checking auth...{Style.RESET_ALL}")
|
||||||
|
last_url = current_url
|
||||||
|
time.sleep(get_random_wait_time(self.config, 'page_load_wait'))
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Status check error: {str(e)}{Style.RESET_ALL}")
|
||||||
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication timeout{Style.RESET_ALL}")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication button not found{Style.RESET_ALL}")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Authentication failed: {str(e)}{Style.RESET_ALL}")
|
||||||
|
return False, None
|
||||||
|
finally:
|
||||||
|
if self.browser:
|
||||||
|
self.browser.quit()
|
||||||
|
|
||||||
|
def _extract_auth_info(self):
|
||||||
|
"""Extract authentication information after successful OAuth"""
|
||||||
|
try:
|
||||||
|
# Get cookies with retry
|
||||||
|
max_retries = 3
|
||||||
|
for attempt in range(max_retries):
|
||||||
|
try:
|
||||||
|
cookies = self.browser.cookies()
|
||||||
|
if cookies:
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
except:
|
||||||
|
if attempt == max_retries - 1:
|
||||||
|
raise
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# Debug cookie information
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Found {len(cookies)} cookies{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
email = None
|
||||||
|
token = None
|
||||||
|
|
||||||
|
for cookie in cookies:
|
||||||
|
name = cookie.get("name", "")
|
||||||
|
if name == "WorkosCursorSessionToken":
|
||||||
|
try:
|
||||||
|
value = cookie.get("value", "")
|
||||||
|
if "::" in value:
|
||||||
|
token = value.split("::")[-1]
|
||||||
|
elif "%3A%3A" in value:
|
||||||
|
token = value.split("%3A%3A")[-1]
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.YELLOW}{EMOJI['INFO']} Token extraction error: {str(e)}{Style.RESET_ALL}")
|
||||||
|
elif name == "cursor_email":
|
||||||
|
email = cookie.get("value")
|
||||||
|
|
||||||
|
if email and token:
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Authentication successful - Email: {email}{Style.RESET_ALL}")
|
||||||
|
return True, {"email": email, "token": token}
|
||||||
|
else:
|
||||||
|
missing = []
|
||||||
|
if not email:
|
||||||
|
missing.append("email")
|
||||||
|
if not token:
|
||||||
|
missing.append("token")
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Missing authentication data: {', '.join(missing)}{Style.RESET_ALL}")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to extract auth info: {str(e)}{Style.RESET_ALL}")
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
def main(auth_type, translator=None):
|
||||||
|
"""Main function to handle OAuth authentication
|
||||||
|
|
||||||
|
Args:
|
||||||
|
auth_type (str): Type of authentication ('google' or 'github')
|
||||||
|
translator: Translator instance for internationalization
|
||||||
|
"""
|
||||||
|
handler = OAuthHandler(translator)
|
||||||
|
|
||||||
|
if auth_type.lower() == 'google':
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.google_start')}{Style.RESET_ALL}")
|
||||||
|
success, auth_info = handler.handle_google_auth()
|
||||||
|
elif auth_type.lower() == 'github':
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('oauth.github_start')}{Style.RESET_ALL}")
|
||||||
|
success, auth_info = handler.handle_github_auth()
|
||||||
|
else:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} Invalid authentication type{Style.RESET_ALL}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if success and auth_info:
|
||||||
|
# Update Cursor authentication
|
||||||
|
auth_manager = CursorAuth(translator)
|
||||||
|
if auth_manager.update_auth(
|
||||||
|
email=auth_info["email"],
|
||||||
|
access_token=auth_info["token"],
|
||||||
|
refresh_token=auth_info["token"]
|
||||||
|
):
|
||||||
|
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('oauth.auth_update_success')}{Style.RESET_ALL}")
|
||||||
|
# Close the browser after successful authentication
|
||||||
|
if handler.browser:
|
||||||
|
handler.browser.quit()
|
||||||
|
print(f"{Fore.CYAN}{EMOJI['INFO']} Browser closed{Style.RESET_ALL}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('oauth.auth_update_failed')}{Style.RESET_ALL}")
|
||||||
|
|
||||||
|
return False
|
37
utils.py
37
utils.py
@ -1,6 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import platform
|
import platform
|
||||||
|
import random
|
||||||
|
|
||||||
def get_user_documents_path():
|
def get_user_documents_path():
|
||||||
"""Get user documents path"""
|
"""Get user documents path"""
|
||||||
@ -30,3 +31,39 @@ def get_linux_cursor_path():
|
|||||||
|
|
||||||
# return the first path that exists
|
# return the first path that exists
|
||||||
return next((path for path in possible_paths if os.path.exists(path)), possible_paths[0])
|
return next((path for path in possible_paths if os.path.exists(path)), possible_paths[0])
|
||||||
|
|
||||||
|
def get_random_wait_time(config, timing_key):
|
||||||
|
"""Get random wait time based on configuration timing settings
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config (dict): Configuration dictionary containing timing settings
|
||||||
|
timing_key (str): Key to look up in the timing settings
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
float: Random wait time in seconds
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# Get timing value from config
|
||||||
|
timing = config.get('Timing', {}).get(timing_key)
|
||||||
|
if not timing:
|
||||||
|
# Default to 0.5-1.5 seconds if timing not found
|
||||||
|
return random.uniform(0.5, 1.5)
|
||||||
|
|
||||||
|
# Check if timing is a range (e.g., "0.5-1.5" or "0.5,1.5")
|
||||||
|
if isinstance(timing, str):
|
||||||
|
if '-' in timing:
|
||||||
|
min_time, max_time = map(float, timing.split('-'))
|
||||||
|
elif ',' in timing:
|
||||||
|
min_time, max_time = map(float, timing.split(','))
|
||||||
|
else:
|
||||||
|
# Single value, use it as both min and max
|
||||||
|
min_time = max_time = float(timing)
|
||||||
|
else:
|
||||||
|
# If timing is a number, use it as both min and max
|
||||||
|
min_time = max_time = float(timing)
|
||||||
|
|
||||||
|
return random.uniform(min_time, max_time)
|
||||||
|
|
||||||
|
except (ValueError, TypeError, AttributeError):
|
||||||
|
# Return default value if any error occurs
|
||||||
|
return random.uniform(0.5, 1.5)
|
Loading…
x
Reference in New Issue
Block a user