first commit
This commit is contained in:
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# PyInstaller
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.spec
|
||||||
|
!CursorKeepAlive.mac.spec
|
||||||
|
!CursorKeepAlive.win.spec
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Mac
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
venv/
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
.env
|
||||||
|
|
||||||
|
screenshots/
|
||||||
47
README.EN.md
Normal file
47
README.EN.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Cursor Pro Automation Tool User Guide
|
||||||
|
|
||||||
|
README also available in: [中文](./README.md)
|
||||||
|
|
||||||
|
## Online Documentation
|
||||||
|
[cursor-auto-free-doc.vercel.app](https://cursor-auto-free-doc.vercel.app)
|
||||||
|
|
||||||
|
## Note
|
||||||
|
Recently, some users have sold this software on platforms like Xianyu. Please avoid such practices—there's no need to earn money this way.
|
||||||
|
|
||||||
|
## Sponsor for More Updates
|
||||||
|

|
||||||
|
|
||||||
|
## License
|
||||||
|
This project is licensed under [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/).
|
||||||
|
This means you may:
|
||||||
|
- **Share** — Copy and redistribute the material in any medium or format.
|
||||||
|
But you must comply with the following conditions:
|
||||||
|
- **Non-commercial** — You may not use the material for commercial purposes.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
Automated account registration and token refreshing to free your hands.
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
1. **Ensure you have Chrome installed. If not, [download here](https://www.google.com/intl/en_pk/chrome/).**
|
||||||
|
2. **You must log into your account, regardless of its validity. Logged-in is mandatory.**
|
||||||
|
3. **A stable internet connection is required, preferably via an overseas node. Do not enable global proxy.**
|
||||||
|
|
||||||
|
## Configuration Instructions
|
||||||
|
Please refer to our [online documentation](https://cursor-auto-free-doc.vercel.app) for detailed configuration instructions.
|
||||||
|
|
||||||
|
## Download
|
||||||
|
[https://github.com/chengazhen/cursor-auto-free/releases](https://github.com/chengazhen/cursor-auto-free/releases)
|
||||||
|
|
||||||
|
## Update Log
|
||||||
|
- **2025-01-09**: Added logs and auto-build feature.
|
||||||
|
- **2025-01-10**: Switched to Cloudflare domain email.
|
||||||
|
- **2025-01-11**: Added headless mode and proxy configuration through .env file.
|
||||||
|
- **2025-01-20**: Added IMAP to replace tempmail.plus.
|
||||||
|
|
||||||
|
## Special Thanks
|
||||||
|
This project has received support and help from many open source projects and community members. We would like to express our special gratitude to:
|
||||||
|
|
||||||
|
### Open Source Projects
|
||||||
|
- [go-cursor-help](https://github.com/yuaotian/go-cursor-help) - An excellent Cursor machine code reset tool with 9.1k Stars. Our machine code reset functionality is implemented using this project, which is one of the most popular Cursor auxiliary tools.
|
||||||
|
|
||||||
|
Inspired by [gpt-cursor-auto](https://github.com/hmhm2022/gpt-cursor-auto); optimized verification and email auto-registration logic; solved the issue of not being able to receive email verification codes.
|
||||||
131
account_sync.py
Normal file
131
account_sync.py
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
import os
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import requests
|
||||||
|
import logging
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Optional
|
||||||
|
from urllib3.exceptions import InsecureRequestWarning
|
||||||
|
|
||||||
|
# 禁用 SSL 警告
|
||||||
|
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||||
|
|
||||||
|
class AccountSync:
|
||||||
|
def __init__(self, api_choice: int = 1, max_retries: int = 3, retry_delay: int = 5):
|
||||||
|
self.api_base_url = "https://cursorapi.nosqli.com/admin" # API地址写死
|
||||||
|
self.api_choice = api_choice # 1=听泉助手池, 2=高质量号池
|
||||||
|
self.max_retries = max_retries
|
||||||
|
self.retry_delay = retry_delay
|
||||||
|
|
||||||
|
def get_machine_id(self) -> str:
|
||||||
|
"""生成随机machine_id"""
|
||||||
|
return "".join(random.choices(string.ascii_letters + string.digits, k=32))
|
||||||
|
|
||||||
|
def sync_account(self, account_info: dict) -> bool:
|
||||||
|
"""同步账号信息到服务器"""
|
||||||
|
retry_count = 0
|
||||||
|
while retry_count < self.max_retries:
|
||||||
|
try:
|
||||||
|
# 根据选择确定API端点
|
||||||
|
endpoint = f"{self.api_base_url}/api.account/{'addpool' if self.api_choice == 2 else 'add'}"
|
||||||
|
logging.info(f"使用API端点: {endpoint}")
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 获取machine_id
|
||||||
|
machine_id = self.get_machine_id()
|
||||||
|
logging.info(f"使用随机生成的 machine_id: {machine_id}")
|
||||||
|
|
||||||
|
proxy_host = account_info.get("proxy_host", "")
|
||||||
|
proxy_port = account_info.get("proxy_port", "")
|
||||||
|
proxy_username = account_info.get("proxy_username", "")
|
||||||
|
proxy_password = account_info.get("proxy_password", "")
|
||||||
|
current_proxies = {
|
||||||
|
'http': f'http://{proxy_username}:{proxy_password}@{proxy_host}:{proxy_port}',
|
||||||
|
'https': f'http://{proxy_username}:{proxy_password}@{proxy_host}:{proxy_port}'
|
||||||
|
}
|
||||||
|
# 准备请求数据
|
||||||
|
data = {
|
||||||
|
"email": account_info["email"],
|
||||||
|
"password": account_info["password"],
|
||||||
|
"access_token": account_info.get("token", ""), # 使用token作为access_token
|
||||||
|
"refresh_token": account_info.get("token", ""), # 使用token作为refresh_token
|
||||||
|
"machine_id": machine_id,
|
||||||
|
"user_agent": os.getenv("BROWSER_USER_AGENT", account_info.get("user_agent", ""),),
|
||||||
|
"registration_time": account_info.get("register_time", datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
|
||||||
|
"first_name": account_info.get("first_name", ""),
|
||||||
|
"last_name": account_info.get("last_name", ""),
|
||||||
|
"ip_address": proxy_host,
|
||||||
|
"proxy_info": current_proxies,
|
||||||
|
"proxy_data": account_info.get("proxy_data", ""),
|
||||||
|
"country": account_info.get("country", ""),
|
||||||
|
"batch_index": account_info.get("batch_index", "")
|
||||||
|
}
|
||||||
|
logging.info(f"请求数据: {json.dumps(data, ensure_ascii=False)}")
|
||||||
|
|
||||||
|
# 打印请求信息
|
||||||
|
logging.info(f"请求 URL: {endpoint}")
|
||||||
|
logging.info(f"请求数据: {json.dumps(data, ensure_ascii=False)}")
|
||||||
|
|
||||||
|
# 发送POST请求
|
||||||
|
response = requests.post(
|
||||||
|
endpoint,
|
||||||
|
json=data,
|
||||||
|
headers=headers,
|
||||||
|
timeout=10,
|
||||||
|
verify=False,
|
||||||
|
proxies=current_proxies
|
||||||
|
)
|
||||||
|
|
||||||
|
# 打印响应状态和内容
|
||||||
|
logging.info(f"响应状态码: {response.status_code}")
|
||||||
|
logging.info(f"响应头: {dict(response.headers)}")
|
||||||
|
logging.info(f"响应内容: {response.text[:200]}")
|
||||||
|
|
||||||
|
if response.status_code == 502:
|
||||||
|
logging.error("服务器网关错误(502),请检查 API 地址是否正确")
|
||||||
|
time.sleep(self.retry_delay)
|
||||||
|
retry_count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
response_data = response.json()
|
||||||
|
if response_data["code"] == 200 and response_data.get("msg") == "添加成功":
|
||||||
|
logging.info(f"账号 {account_info['email']} 同步成功")
|
||||||
|
return True
|
||||||
|
elif response_data["code"] == 400:
|
||||||
|
logging.warning(f"账号 {account_info['email']} 已存在")
|
||||||
|
return False
|
||||||
|
elif response_data["code"] == 500:
|
||||||
|
logging.error(f"服务器内部错误(500),第 {retry_count + 1} 次重试")
|
||||||
|
time.sleep(self.retry_delay)
|
||||||
|
retry_count += 1
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logging.error(f"账号 {account_info['email']} 同步失败: {response_data.get('msg', '未知错误')}")
|
||||||
|
time.sleep(self.retry_delay)
|
||||||
|
retry_count += 1
|
||||||
|
continue
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
logging.error(f"服务器返回的不是有效的JSON数据: {response.text[:200]}")
|
||||||
|
time.sleep(self.retry_delay)
|
||||||
|
retry_count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
logging.error(f"请求失败: {str(e)}")
|
||||||
|
time.sleep(self.retry_delay)
|
||||||
|
retry_count += 1
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"同步账号 {account_info['email']} 时出错: {str(e)}")
|
||||||
|
time.sleep(self.retry_delay)
|
||||||
|
retry_count += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
logging.error(f"账号 {account_info['email']} 同步失败,已重试 {self.max_retries} 次")
|
||||||
|
return False
|
||||||
54
backup/.env.example
Normal file
54
backup/.env.example
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# 临时邮箱配置
|
||||||
|
DOMAIN=nosqli.com
|
||||||
|
TEMP_MAIL=awegko
|
||||||
|
TEMP_MAIL_EXT=@mailto.plus
|
||||||
|
BROWSER_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
|
||||||
|
MAIL_SERVER=https://tempmail.plus
|
||||||
|
|
||||||
|
|
||||||
|
DOMAIN=586vip.cn
|
||||||
|
TEMP_MAIL=ademyyk
|
||||||
|
TEMP_MAIL_EXT=@mailto.plus
|
||||||
|
BROWSER_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
|
||||||
|
MAIL_SERVER=https://tempmail.plus
|
||||||
|
|
||||||
|
|
||||||
|
DOMAIN=wuen.site
|
||||||
|
TEMP_MAIL=actoke
|
||||||
|
TEMP_MAIL_EXT=@mailto.plus
|
||||||
|
BROWSER_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
|
||||||
|
MAIL_SERVER=https://tempmail.plus
|
||||||
|
|
||||||
|
|
||||||
|
DOMAIN=jxyweb.site
|
||||||
|
TEMP_MAIL=exvet
|
||||||
|
TEMP_MAIL_EXT=@mailto.plus
|
||||||
|
BROWSER_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
|
||||||
|
MAIL_SERVER=https://tempmail.plus
|
||||||
|
TEMP_MAIL_EPIN=889944
|
||||||
|
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"DOMAIN": "nosqli.com",
|
||||||
|
"TEMP_MAIL": "ozamdyw",
|
||||||
|
"TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
"BROWSER_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",
|
||||||
|
"MAIL_SERVER": "https://tempmail.plus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DOMAIN": "nosqli.com",
|
||||||
|
"TEMP_MAIL": "xkwmvp",
|
||||||
|
"TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
"BROWSER_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",
|
||||||
|
"MAIL_SERVER": "https://tempmail.plus"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DOMAIN": "nosqli.com",
|
||||||
|
"TEMP_MAIL": "yzqnlr",
|
||||||
|
"TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
"BROWSER_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",
|
||||||
|
"MAIL_SERVER": "https://tempmail.plus"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
212
backup/config.py
Normal file
212
backup/config.py
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
from logger import logging
|
||||||
|
|
||||||
|
# 预定义的配置列表
|
||||||
|
CONFIGS = [
|
||||||
|
|
||||||
|
# {
|
||||||
|
# "DOMAIN": "nosqli.com",
|
||||||
|
# "TEMP_MAIL": "awegko",
|
||||||
|
# "TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
# "BROWSER_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",
|
||||||
|
# "MAIL_SERVER": "https://tempmail.plus",
|
||||||
|
# "TEMP_MAIL_EPIN": ""
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "DOMAIN": "586vip.cn",
|
||||||
|
# "TEMP_MAIL": "emufu",
|
||||||
|
# "TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
# "BROWSER_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",
|
||||||
|
# "MAIL_SERVER": "https://tempmail.plus",
|
||||||
|
# "TEMP_MAIL_EPIN": ""
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "DOMAIN": "wuen.site",
|
||||||
|
# "TEMP_MAIL": "actoke",
|
||||||
|
# "TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
# "BROWSER_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",
|
||||||
|
# "MAIL_SERVER": "https://tempmail.plus",
|
||||||
|
# "TEMP_MAIL_EPIN": ""
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "DOMAIN": "jxyweb.site",
|
||||||
|
# "TEMP_MAIL": "exvet",
|
||||||
|
# "TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
# "BROWSER_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",
|
||||||
|
# "MAIL_SERVER": "https://tempmail.plus",
|
||||||
|
# # "TEMP_MAIL_EPIN": "889944"
|
||||||
|
# },
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# {
|
||||||
|
# "DOMAIN": "cursorpro.asia",
|
||||||
|
# "TEMP_MAIL": "ybeudu",
|
||||||
|
# "TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
# "BROWSER_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",
|
||||||
|
# "MAIL_SERVER": "https://tempmail.plus",
|
||||||
|
# "TEMP_MAIL_EPIN": ""
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "DOMAIN": "cursorpro.xyz",
|
||||||
|
# "TEMP_MAIL": "neyxwub",
|
||||||
|
# "TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
# "BROWSER_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",
|
||||||
|
# "MAIL_SERVER": "https://tempmail.plus",
|
||||||
|
# "TEMP_MAIL_EPIN": ""
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "DOMAIN": "cursorpro.xin",
|
||||||
|
# "TEMP_MAIL": "qeznosa",
|
||||||
|
# "TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
# "BROWSER_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",
|
||||||
|
# "MAIL_SERVER": "https://tempmail.plus",
|
||||||
|
# "TEMP_MAIL_EPIN": ""
|
||||||
|
# },
|
||||||
|
{
|
||||||
|
"DOMAIN": "cursorpro.com.cn",
|
||||||
|
"TEMP_MAIL": "oducbum",
|
||||||
|
"TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
"BROWSER_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",
|
||||||
|
"MAIL_SERVER": "https://tempmail.plus",
|
||||||
|
"TEMP_MAIL_EPIN": ""
|
||||||
|
},
|
||||||
|
# {
|
||||||
|
# "DOMAIN": "jpgoogle.online",
|
||||||
|
# "TEMP_MAIL": "oahpuza",
|
||||||
|
# "TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
# "BROWSER_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",
|
||||||
|
# "MAIL_SERVER": "https://tempmail.plus",
|
||||||
|
# "TEMP_MAIL_EPIN": ""
|
||||||
|
# },
|
||||||
|
# {
|
||||||
|
# "DOMAIN": "jpgoogle.asia",
|
||||||
|
# "TEMP_MAIL": "emufu",
|
||||||
|
# "TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
# "BROWSER_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",
|
||||||
|
# "MAIL_SERVER": "https://tempmail.plus",
|
||||||
|
# "TEMP_MAIL_EPIN": ""
|
||||||
|
# },
|
||||||
|
{
|
||||||
|
"DOMAIN": "jpgoogle.xyz",
|
||||||
|
"TEMP_MAIL": "ybeudu",
|
||||||
|
"TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
"BROWSER_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",
|
||||||
|
"MAIL_SERVER": "https://tempmail.plus",
|
||||||
|
"TEMP_MAIL_EPIN": ""
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"DOMAIN": "jpgoogle.xin",
|
||||||
|
"TEMP_MAIL": "actoke",
|
||||||
|
"TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
"BROWSER_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",
|
||||||
|
"MAIL_SERVER": "https://tempmail.plus",
|
||||||
|
"TEMP_MAIL_EPIN": ""
|
||||||
|
},
|
||||||
|
# {
|
||||||
|
# "DOMAIN": "tengxu2024.xyz",
|
||||||
|
# "TEMP_MAIL": "exvet",
|
||||||
|
# "TEMP_MAIL_EXT": "@mailto.plus",
|
||||||
|
# "BROWSER_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",
|
||||||
|
# "MAIL_SERVER": "https://tempmail.plus",
|
||||||
|
# "TEMP_MAIL_EPIN": "889944"
|
||||||
|
# },
|
||||||
|
]
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
def __init__(self, config_index=None):
|
||||||
|
# 获取应用程序的根目录路径
|
||||||
|
if getattr(sys, "frozen", False):
|
||||||
|
# 如果是打包后的可执行文件
|
||||||
|
application_path = os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
# 如果是开发环境
|
||||||
|
application_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
# 如果指定了索引,使用指定配置,否则随机选择
|
||||||
|
if config_index is not None and 0 <= config_index < len(CONFIGS):
|
||||||
|
self.current_config = CONFIGS[config_index]
|
||||||
|
else:
|
||||||
|
self.current_config = random.choice(CONFIGS)
|
||||||
|
|
||||||
|
# 设置当前配置
|
||||||
|
self.domain = self.current_config.get("DOMAIN", "")
|
||||||
|
self.temp_mail = self.current_config.get("TEMP_MAIL", "").strip()
|
||||||
|
self.temp_mail_ext = self.current_config.get("TEMP_MAIL_EXT", "").strip()
|
||||||
|
self.temp_mail_epin = self.current_config.get("TEMP_MAIL_EPIN", "").strip()
|
||||||
|
self.browser_user_agent = self.current_config.get("BROWSER_USER_AGENT", "")
|
||||||
|
self.mail_server = self.current_config.get("MAIL_SERVER", "")
|
||||||
|
|
||||||
|
# IMAP相关配置
|
||||||
|
self.imap = False
|
||||||
|
if self.temp_mail == "null":
|
||||||
|
self.imap = True
|
||||||
|
self.imap_server = self.current_config.get("IMAP_SERVER", "")
|
||||||
|
self.imap_port = self.current_config.get("IMAP_PORT", "")
|
||||||
|
self.imap_user = self.current_config.get("IMAP_USER", "")
|
||||||
|
self.imap_pass = self.current_config.get("IMAP_PASS", "")
|
||||||
|
self.imap_dir = self.current_config.get("IMAP_DIR", "inbox")
|
||||||
|
|
||||||
|
def get_config_count(self):
|
||||||
|
"""获取配置总数"""
|
||||||
|
return len(CONFIGS)
|
||||||
|
|
||||||
|
def get_temp_mail(self):
|
||||||
|
return self.temp_mail
|
||||||
|
|
||||||
|
def get_temp_mail_ext(self):
|
||||||
|
return self.temp_mail_ext
|
||||||
|
|
||||||
|
def get_temp_mail_epin(self):
|
||||||
|
return self.temp_mail_epin
|
||||||
|
|
||||||
|
def get_domain(self):
|
||||||
|
return self.domain
|
||||||
|
|
||||||
|
def get_browser_user_agent(self):
|
||||||
|
return self.browser_user_agent
|
||||||
|
|
||||||
|
def get_mail_server(self):
|
||||||
|
return self.mail_server
|
||||||
|
|
||||||
|
def get_imap(self):
|
||||||
|
"""获取IMAP配置"""
|
||||||
|
if not self.imap:
|
||||||
|
return False
|
||||||
|
return {
|
||||||
|
"imap_server": self.imap_server,
|
||||||
|
"imap_port": self.imap_port,
|
||||||
|
"imap_user": self.imap_user,
|
||||||
|
"imap_pass": self.imap_pass,
|
||||||
|
"imap_dir": self.imap_dir,
|
||||||
|
}
|
||||||
|
|
||||||
|
def print_config(self):
|
||||||
|
"""打印当前使用的配置"""
|
||||||
|
logging.info("\n=== 当前使用的配置 ===")
|
||||||
|
if self.imap:
|
||||||
|
logging.info(f"IMAP服务器: {self.imap_server}")
|
||||||
|
logging.info(f"IMAP端口: {self.imap_port}")
|
||||||
|
logging.info(f"IMAP用户名: {self.imap_user}")
|
||||||
|
logging.info(f"IMAP密码: {'*' * len(self.imap_pass)}")
|
||||||
|
logging.info(f"IMAP收件箱目录: {self.imap_dir}")
|
||||||
|
else:
|
||||||
|
logging.info(f"域名: {self.domain}")
|
||||||
|
logging.info(f"临时邮箱: {self.temp_mail}{self.temp_mail_ext}")
|
||||||
|
logging.info(f"邮件服务器: {self.mail_server}")
|
||||||
|
logging.info("=" * 20)
|
||||||
|
|
||||||
|
|
||||||
|
# 使用示例
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
config = Config()
|
||||||
|
print("环境变量加载成功!")
|
||||||
|
config.print_config()
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"错误: {e}")
|
||||||
86
backup/cursor_auth_manager.py
Normal file
86
backup/cursor_auth_manager.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
class CursorAuthManager:
|
||||||
|
"""Cursor认证信息管理器"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
# 判断操作系统
|
||||||
|
if sys.platform == "win32": # Windows
|
||||||
|
appdata = os.getenv("APPDATA")
|
||||||
|
if appdata is None:
|
||||||
|
raise EnvironmentError("APPDATA 环境变量未设置")
|
||||||
|
self.db_path = os.path.join(
|
||||||
|
appdata, "Cursor", "User", "globalStorage", "state.vscdb"
|
||||||
|
)
|
||||||
|
elif sys.platform == "darwin": # macOS
|
||||||
|
self.db_path = os.path.abspath(os.path.expanduser(
|
||||||
|
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb"
|
||||||
|
))
|
||||||
|
elif sys.platform == "linux" : # Linux 和其他类Unix系统
|
||||||
|
self.db_path = os.path.abspath(os.path.expanduser(
|
||||||
|
"~/.config/Cursor/User/globalStorage/state.vscdb"
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
raise NotImplementedError(f"不支持的操作系统: {sys.platform}")
|
||||||
|
|
||||||
|
def update_auth(self, email=None, access_token=None, refresh_token=None):
|
||||||
|
"""
|
||||||
|
更新Cursor的认证信息
|
||||||
|
:param email: 新的邮箱地址
|
||||||
|
:param access_token: 新的访问令牌
|
||||||
|
:param refresh_token: 新的刷新令牌
|
||||||
|
:return: bool 是否成功更新
|
||||||
|
"""
|
||||||
|
updates = []
|
||||||
|
# 登录状态
|
||||||
|
updates.append(("cursorAuth/cachedSignUpType", "Auth_0"))
|
||||||
|
|
||||||
|
if email is not None:
|
||||||
|
updates.append(("cursorAuth/cachedEmail", email))
|
||||||
|
if access_token is not None:
|
||||||
|
updates.append(("cursorAuth/accessToken", access_token))
|
||||||
|
if refresh_token is not None:
|
||||||
|
updates.append(("cursorAuth/refreshToken", refresh_token))
|
||||||
|
|
||||||
|
if not updates:
|
||||||
|
print("没有提供任何要更新的值")
|
||||||
|
return False
|
||||||
|
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(self.db_path)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
for key, value in updates:
|
||||||
|
|
||||||
|
# 如果没有更新任何行,说明key不存在,执行插入
|
||||||
|
# 检查 accessToken 是否存在
|
||||||
|
check_query = f"SELECT COUNT(*) FROM itemTable WHERE key = ?"
|
||||||
|
cursor.execute(check_query, (key,))
|
||||||
|
if cursor.fetchone()[0] == 0:
|
||||||
|
insert_query = "INSERT INTO itemTable (key, value) VALUES (?, ?)"
|
||||||
|
cursor.execute(insert_query, (key, value))
|
||||||
|
else:
|
||||||
|
update_query = "UPDATE itemTable SET value = ? WHERE key = ?"
|
||||||
|
cursor.execute(update_query, (value, key))
|
||||||
|
|
||||||
|
if cursor.rowcount > 0:
|
||||||
|
print(f"成功更新 {key.split('/')[-1]}")
|
||||||
|
else:
|
||||||
|
print(f"未找到 {key.split('/')[-1]} 或值未变化")
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
return True
|
||||||
|
|
||||||
|
except sqlite3.Error as e:
|
||||||
|
print("数据库错误:", str(e))
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print("发生错误:", str(e))
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
if conn:
|
||||||
|
conn.close()
|
||||||
504
backup/cursor_pro_keep_alive.py
Normal file
504
backup/cursor_pro_keep_alive.py
Normal file
@@ -0,0 +1,504 @@
|
|||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from colorama import Fore, Style
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from exit_cursor import ExitCursor
|
||||||
|
import go_cursor_help
|
||||||
|
import patch_cursor_get_machine_id
|
||||||
|
from reset_machine import MachineIDResetter
|
||||||
|
|
||||||
|
os.environ["PYTHONVERBOSE"] = "0"
|
||||||
|
os.environ["PYINSTALLER_VERBOSE"] = "0"
|
||||||
|
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
from cursor_auth_manager import CursorAuthManager
|
||||||
|
import os
|
||||||
|
from logger import logging
|
||||||
|
from browser_utils import BrowserManager
|
||||||
|
from get_email_code import EmailVerificationHandler
|
||||||
|
from logo import print_logo
|
||||||
|
from config import Config
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# 定义 EMOJI 字典
|
||||||
|
EMOJI = {"ERROR": "❌", "WARNING": "⚠️", "INFO": "ℹ️"}
|
||||||
|
|
||||||
|
|
||||||
|
class VerificationStatus(Enum):
|
||||||
|
"""验证状态枚举"""
|
||||||
|
|
||||||
|
PASSWORD_PAGE = "@name=password"
|
||||||
|
CAPTCHA_PAGE = "@data-index=0"
|
||||||
|
ACCOUNT_SETTINGS = "Account Settings"
|
||||||
|
|
||||||
|
|
||||||
|
class TurnstileError(Exception):
|
||||||
|
"""Turnstile 验证相关异常"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def save_screenshot(tab, stage: str, timestamp: bool = True) -> None:
|
||||||
|
"""
|
||||||
|
保存页面截图
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tab: 浏览器标签页对象
|
||||||
|
stage: 截图阶段标识
|
||||||
|
timestamp: 是否添加时间戳
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 创建 screenshots 目录
|
||||||
|
screenshot_dir = "screenshots"
|
||||||
|
if not os.path.exists(screenshot_dir):
|
||||||
|
os.makedirs(screenshot_dir)
|
||||||
|
|
||||||
|
# 生成文件名
|
||||||
|
if timestamp:
|
||||||
|
filename = f"turnstile_{stage}_{int(time.time())}.png"
|
||||||
|
else:
|
||||||
|
filename = f"turnstile_{stage}.png"
|
||||||
|
|
||||||
|
filepath = os.path.join(screenshot_dir, filename)
|
||||||
|
|
||||||
|
# 保存截图
|
||||||
|
tab.get_screenshot(filepath)
|
||||||
|
logging.debug(f"截图已保存: {filepath}")
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"截图保存失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
def check_verification_success(tab) -> Optional[VerificationStatus]:
|
||||||
|
"""
|
||||||
|
检查验证是否成功
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
VerificationStatus: 验证成功时返回对应状态,失败返回 None
|
||||||
|
"""
|
||||||
|
for status in VerificationStatus:
|
||||||
|
if tab.ele(status.value):
|
||||||
|
logging.info(f"验证成功 - 已到达{status.name}页面")
|
||||||
|
return status
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def handle_turnstile(tab, max_retries: int = 2, retry_interval: tuple = (1, 2)) -> bool:
|
||||||
|
"""
|
||||||
|
处理 Turnstile 验证
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tab: 浏览器标签页对象
|
||||||
|
max_retries: 最大重试次数
|
||||||
|
retry_interval: 重试间隔时间范围(最小值, 最大值)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 验证是否成功
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TurnstileError: 验证过程中出现异常
|
||||||
|
"""
|
||||||
|
logging.info("正在检测 Turnstile 验证...")
|
||||||
|
save_screenshot(tab, "start")
|
||||||
|
|
||||||
|
retry_count = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
while retry_count < max_retries:
|
||||||
|
retry_count += 1
|
||||||
|
logging.debug(f"第 {retry_count} 次尝试验证")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 定位验证框元素
|
||||||
|
challenge_check = (
|
||||||
|
tab.ele("@id=cf-turnstile", timeout=2)
|
||||||
|
.child()
|
||||||
|
.shadow_root.ele("tag:iframe")
|
||||||
|
.ele("tag:body")
|
||||||
|
.sr("tag:input")
|
||||||
|
)
|
||||||
|
|
||||||
|
if challenge_check:
|
||||||
|
logging.info("检测到 Turnstile 验证框,开始处理...")
|
||||||
|
# 随机延时后点击验证
|
||||||
|
time.sleep(random.uniform(1, 3))
|
||||||
|
challenge_check.click()
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# 保存验证后的截图
|
||||||
|
save_screenshot(tab, "clicked")
|
||||||
|
|
||||||
|
# 检查验证结果
|
||||||
|
if check_verification_success(tab):
|
||||||
|
logging.info("Turnstile 验证通过")
|
||||||
|
save_screenshot(tab, "success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug(f"当前尝试未成功: {str(e)}")
|
||||||
|
|
||||||
|
# 检查是否已经验证成功
|
||||||
|
if check_verification_success(tab):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 随机延时后继续下一次尝试
|
||||||
|
time.sleep(random.uniform(*retry_interval))
|
||||||
|
|
||||||
|
# 超出最大重试次数
|
||||||
|
logging.error(f"验证失败 - 已达到最大重试次数 {max_retries}")
|
||||||
|
logging.error(
|
||||||
|
"请前往开源项目查看更多信息:https://github.com/chengazhen/cursor-auto-free"
|
||||||
|
)
|
||||||
|
save_screenshot(tab, "failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Turnstile 验证过程发生异常: {str(e)}"
|
||||||
|
logging.error(error_msg)
|
||||||
|
save_screenshot(tab, "error")
|
||||||
|
raise TurnstileError(error_msg)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cursor_session_token(tab, max_attempts=3, retry_interval=2):
|
||||||
|
"""
|
||||||
|
获取Cursor会话token,带有重试机制
|
||||||
|
:param tab: 浏览器标签页
|
||||||
|
:param max_attempts: 最大尝试次数
|
||||||
|
:param retry_interval: 重试间隔(秒)
|
||||||
|
:return: session token 或 None
|
||||||
|
"""
|
||||||
|
logging.info("开始获取cookie")
|
||||||
|
attempts = 0
|
||||||
|
|
||||||
|
while attempts < max_attempts:
|
||||||
|
try:
|
||||||
|
cookies = tab.cookies()
|
||||||
|
for cookie in cookies:
|
||||||
|
if cookie.get("name") == "WorkosCursorSessionToken":
|
||||||
|
return cookie["value"].split("%3A%3A")[1]
|
||||||
|
|
||||||
|
attempts += 1
|
||||||
|
if attempts < max_attempts:
|
||||||
|
logging.warning(
|
||||||
|
f"第 {attempts} 次尝试未获取到CursorSessionToken,{retry_interval}秒后重试..."
|
||||||
|
)
|
||||||
|
time.sleep(retry_interval)
|
||||||
|
else:
|
||||||
|
logging.error(
|
||||||
|
f"已达到最大尝试次数({max_attempts}),获取CursorSessionToken失败"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"获取cookie失败: {str(e)}")
|
||||||
|
attempts += 1
|
||||||
|
if attempts < max_attempts:
|
||||||
|
logging.info(f"将在 {retry_interval} 秒后重试...")
|
||||||
|
time.sleep(retry_interval)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def update_cursor_auth(email=None, access_token=None, refresh_token=None):
|
||||||
|
"""
|
||||||
|
更新Cursor的认证信息的便捷函数
|
||||||
|
"""
|
||||||
|
auth_manager = CursorAuthManager()
|
||||||
|
return auth_manager.update_auth(email, access_token, refresh_token)
|
||||||
|
|
||||||
|
|
||||||
|
def sign_up_account(browser, tab):
|
||||||
|
logging.info("=== 开始注册账号流程 ===")
|
||||||
|
logging.info(f"正在访问注册页面: {sign_up_url}")
|
||||||
|
tab.get(sign_up_url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if tab.ele("@name=first_name"):
|
||||||
|
logging.info("正在填写个人信息...")
|
||||||
|
tab.actions.click("@name=first_name").input(first_name)
|
||||||
|
logging.info(f"已输入名字: {first_name}")
|
||||||
|
time.sleep(random.uniform(1, 3))
|
||||||
|
|
||||||
|
tab.actions.click("@name=last_name").input(last_name)
|
||||||
|
logging.info(f"已输入姓氏: {last_name}")
|
||||||
|
time.sleep(random.uniform(1, 3))
|
||||||
|
|
||||||
|
tab.actions.click("@name=email").input(account)
|
||||||
|
logging.info(f"已输入邮箱: {account}")
|
||||||
|
time.sleep(random.uniform(1, 3))
|
||||||
|
|
||||||
|
logging.info("提交个人信息...")
|
||||||
|
tab.actions.click("@type=submit")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"注册页面访问失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
handle_turnstile(tab)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if tab.ele("@name=password"):
|
||||||
|
logging.info("正在设置密码...")
|
||||||
|
tab.ele("@name=password").input(password)
|
||||||
|
time.sleep(random.uniform(1, 3))
|
||||||
|
|
||||||
|
logging.info("提交密码...")
|
||||||
|
tab.ele("@type=submit").click()
|
||||||
|
logging.info("密码设置完成,等待系统响应...")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"密码设置失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if tab.ele("This email is not available."):
|
||||||
|
logging.error("注册失败:邮箱已被使用")
|
||||||
|
return False
|
||||||
|
|
||||||
|
handle_turnstile(tab)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
if tab.ele("Account Settings"):
|
||||||
|
logging.info("注册成功 - 已进入账户设置页面")
|
||||||
|
break
|
||||||
|
if tab.ele("@data-index=0"):
|
||||||
|
logging.info("正在获取邮箱验证码...")
|
||||||
|
code = email_handler.get_verification_code()
|
||||||
|
if not code:
|
||||||
|
logging.error("获取验证码失败")
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.info(f"成功获取验证码: {code}")
|
||||||
|
logging.info("正在输入验证码...")
|
||||||
|
i = 0
|
||||||
|
for digit in code:
|
||||||
|
tab.ele(f"@data-index={i}").input(digit)
|
||||||
|
time.sleep(random.uniform(0.1, 0.3))
|
||||||
|
i += 1
|
||||||
|
logging.info("验证码输入完成")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"验证码处理过程出错: {str(e)}")
|
||||||
|
|
||||||
|
handle_turnstile(tab)
|
||||||
|
wait_time = random.randint(3, 6)
|
||||||
|
for i in range(wait_time):
|
||||||
|
logging.info(f"等待系统处理中... 剩余 {wait_time-i} 秒")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
logging.info("正在获取账户信息...")
|
||||||
|
tab.get(settings_url)
|
||||||
|
try:
|
||||||
|
usage_selector = (
|
||||||
|
"css:div.col-span-2 > div > div > div > div > "
|
||||||
|
"div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > "
|
||||||
|
"span.font-mono.text-sm\\/\\[0\\.875rem\\]"
|
||||||
|
)
|
||||||
|
usage_ele = tab.ele(usage_selector)
|
||||||
|
if usage_ele:
|
||||||
|
usage_info = usage_ele.text
|
||||||
|
total_usage = usage_info.split("/")[-1].strip()
|
||||||
|
logging.info(f"账户可用额度上限: {total_usage}")
|
||||||
|
logging.info(
|
||||||
|
"请前往开源项目查看更多信息:https://github.com/chengazhen/cursor-auto-free"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"获取账户额度信息失败: {str(e)}")
|
||||||
|
|
||||||
|
logging.info("\n=== 注册完成 ===")
|
||||||
|
account_info = f"Cursor 账号信息:\n邮箱: {account}\n密码: {password}"
|
||||||
|
logging.info(account_info)
|
||||||
|
time.sleep(5)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class EmailGenerator:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
password="".join(
|
||||||
|
random.choices(
|
||||||
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*",
|
||||||
|
k=12,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
):
|
||||||
|
configInstance = Config()
|
||||||
|
configInstance.print_config()
|
||||||
|
self.domain = configInstance.get_domain()
|
||||||
|
self.default_password = password
|
||||||
|
self.default_first_name = self.generate_random_name()
|
||||||
|
self.default_last_name = self.generate_random_name()
|
||||||
|
|
||||||
|
def generate_random_name(self, length=6):
|
||||||
|
"""生成随机用户名"""
|
||||||
|
first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||||
|
rest_letters = "".join(
|
||||||
|
random.choices("abcdefghijklmnopqrstuvwxyz", k=length - 1)
|
||||||
|
)
|
||||||
|
return first_letter + rest_letters
|
||||||
|
|
||||||
|
def generate_email(self, length=8):
|
||||||
|
"""生成随机邮箱地址"""
|
||||||
|
random_str = "".join(random.choices("abcdefghijklmnopqrstuvwxyz", k=length))
|
||||||
|
timestamp = str(int(time.time()))[-6:] # 使用时间戳后6位
|
||||||
|
return f"{random_str}{timestamp}@{self.domain}"
|
||||||
|
|
||||||
|
def get_account_info(self):
|
||||||
|
"""获取完整的账号信息"""
|
||||||
|
return {
|
||||||
|
"email": self.generate_email(),
|
||||||
|
"password": self.default_password,
|
||||||
|
"first_name": self.default_first_name,
|
||||||
|
"last_name": self.default_last_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_agent():
|
||||||
|
"""获取user_agent"""
|
||||||
|
try:
|
||||||
|
# 使用JavaScript获取user agent
|
||||||
|
browser_manager = BrowserManager()
|
||||||
|
browser = browser_manager.init_browser()
|
||||||
|
user_agent = browser.latest_tab.run_js("return navigator.userAgent")
|
||||||
|
browser_manager.quit()
|
||||||
|
return user_agent
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"获取user agent失败: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def check_cursor_version():
|
||||||
|
"""检查cursor版本"""
|
||||||
|
pkg_path, main_path = patch_cursor_get_machine_id.get_cursor_paths()
|
||||||
|
with open(pkg_path, "r", encoding="utf-8") as f:
|
||||||
|
version = json.load(f)["version"]
|
||||||
|
return patch_cursor_get_machine_id.version_check(version, min_version="0.45.0")
|
||||||
|
|
||||||
|
|
||||||
|
def reset_machine_id(greater_than_0_45):
|
||||||
|
if greater_than_0_45:
|
||||||
|
# 提示请手动执行脚本 https://github.com/chengazhen/cursor-auto-free/blob/main/patch_cursor_get_machine_id.py
|
||||||
|
go_cursor_help.go_cursor_help()
|
||||||
|
else:
|
||||||
|
MachineIDResetter().reset_machine_ids()
|
||||||
|
|
||||||
|
|
||||||
|
def print_end_message():
|
||||||
|
logging.info("\n\n\n\n\n")
|
||||||
|
logging.info("=" * 30)
|
||||||
|
logging.info("所有操作已完成")
|
||||||
|
logging.info("\n=== 获取更多信息 ===")
|
||||||
|
logging.info("🔥 QQ交流群: 1034718338")
|
||||||
|
logging.info("📺 B站UP主: 想回家的前端")
|
||||||
|
logging.info("=" * 30)
|
||||||
|
logging.info(
|
||||||
|
"请前往开源项目查看更多信息:https://github.com/chengazhen/cursor-auto-free"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print_logo()
|
||||||
|
greater_than_0_45 = 8.8
|
||||||
|
browser_manager = None
|
||||||
|
try:
|
||||||
|
logging.info("\n=== 初始化程序 ===")
|
||||||
|
# ExitCursor()
|
||||||
|
|
||||||
|
# 提示用户选择操作模式
|
||||||
|
print("\n请选择操作模式:")
|
||||||
|
print("1. 仅重置机器码")
|
||||||
|
print("2. 完整注册流程")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
choice = int(input("请输入选项 (1 或 2): ").strip())
|
||||||
|
if choice in [1, 2]:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("无效的选项,请重新输入")
|
||||||
|
except ValueError:
|
||||||
|
print("请输入有效的数字")
|
||||||
|
|
||||||
|
if choice == 1:
|
||||||
|
# 仅执行重置机器码
|
||||||
|
reset_machine_id(greater_than_0_45)
|
||||||
|
logging.info("机器码重置完成")
|
||||||
|
print_end_message()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
logging.info("正在初始化浏览器...")
|
||||||
|
|
||||||
|
# 获取user_agent
|
||||||
|
user_agent = get_user_agent()
|
||||||
|
if not user_agent:
|
||||||
|
logging.error("获取user agent失败,使用默认值")
|
||||||
|
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||||
|
|
||||||
|
# 剔除user_agent中的"HeadlessChrome"
|
||||||
|
user_agent = user_agent.replace("HeadlessChrome", "Chrome")
|
||||||
|
|
||||||
|
browser_manager = BrowserManager()
|
||||||
|
browser = browser_manager.init_browser(user_agent)
|
||||||
|
|
||||||
|
# 获取并打印浏览器的user-agent
|
||||||
|
user_agent = browser.latest_tab.run_js("return navigator.userAgent")
|
||||||
|
|
||||||
|
logging.info("正在初始化邮箱验证模块...")
|
||||||
|
email_handler = EmailVerificationHandler()
|
||||||
|
logging.info(
|
||||||
|
"请前往开源项目查看更多信息:https://github.com/chengazhen/cursor-auto-free"
|
||||||
|
)
|
||||||
|
logging.info("\n=== 配置信息 ===")
|
||||||
|
login_url = "https://authenticator.cursor.sh"
|
||||||
|
sign_up_url = "https://authenticator.cursor.sh/sign-up"
|
||||||
|
settings_url = "https://www.cursor.com/settings"
|
||||||
|
mail_url = "https://tempmail.plus"
|
||||||
|
|
||||||
|
logging.info("正在生成随机账号信息...")
|
||||||
|
email_generator = EmailGenerator()
|
||||||
|
account = email_generator.generate_email()
|
||||||
|
password = email_generator.default_password
|
||||||
|
first_name = email_generator.default_first_name
|
||||||
|
last_name = email_generator.default_last_name
|
||||||
|
|
||||||
|
logging.info(f"生成的邮箱账号: {account}")
|
||||||
|
auto_update_cursor_auth = True
|
||||||
|
|
||||||
|
tab = browser.latest_tab
|
||||||
|
|
||||||
|
tab.run_js("try { turnstile.reset() } catch(e) { }")
|
||||||
|
|
||||||
|
logging.info("\n=== 开始注册流程 ===")
|
||||||
|
logging.info(f"正在访问登录页面: {login_url}")
|
||||||
|
tab.get(login_url)
|
||||||
|
|
||||||
|
if sign_up_account(browser, tab):
|
||||||
|
logging.info("正在获取会话令牌...")
|
||||||
|
token = get_cursor_session_token(tab)
|
||||||
|
if token:
|
||||||
|
logging.info("更新认证信息...")
|
||||||
|
update_cursor_auth(
|
||||||
|
email=account, access_token=token, refresh_token=token
|
||||||
|
)
|
||||||
|
logging.info(
|
||||||
|
"请前往开源项目查看更多信息:https://github.com/chengazhen/cursor-auto-free"
|
||||||
|
)
|
||||||
|
logging.info("重置机器码...")
|
||||||
|
reset_machine_id(greater_than_0_45)
|
||||||
|
logging.info("所有操作已完成")
|
||||||
|
print_end_message()
|
||||||
|
else:
|
||||||
|
logging.error("获取会话令牌失败,注册流程未完成")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"程序执行出现错误: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
logging.error(traceback.format_exc())
|
||||||
|
finally:
|
||||||
|
if browser_manager:
|
||||||
|
browser_manager.quit()
|
||||||
|
input("\n程序执行完毕,按回车键退出...")
|
||||||
68
backup/exit_cursor.py
Normal file
68
backup/exit_cursor.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import psutil
|
||||||
|
from logger import logging
|
||||||
|
import time
|
||||||
|
|
||||||
|
def ExitCursor(timeout=5):
|
||||||
|
"""
|
||||||
|
温和地关闭 Cursor 进程
|
||||||
|
|
||||||
|
Args:
|
||||||
|
timeout (int): 等待进程自然终止的超时时间(秒)
|
||||||
|
Returns:
|
||||||
|
bool: 是否成功关闭所有进程
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logging.info("开始退出Cursor...")
|
||||||
|
cursor_processes = []
|
||||||
|
# 收集所有 Cursor 进程
|
||||||
|
for proc in psutil.process_iter(['pid', 'name']):
|
||||||
|
try:
|
||||||
|
if proc.info['name'].lower() in ['cursor.exe', 'cursor']:
|
||||||
|
cursor_processes.append(proc)
|
||||||
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not cursor_processes:
|
||||||
|
logging.info("未发现运行中的 Cursor 进程")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 温和地请求进程终止
|
||||||
|
for proc in cursor_processes:
|
||||||
|
try:
|
||||||
|
if proc.is_running():
|
||||||
|
proc.terminate() # 发送终止信号
|
||||||
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 等待进程自然终止
|
||||||
|
start_time = time.time()
|
||||||
|
while time.time() - start_time < timeout:
|
||||||
|
still_running = []
|
||||||
|
for proc in cursor_processes:
|
||||||
|
try:
|
||||||
|
if proc.is_running():
|
||||||
|
still_running.append(proc)
|
||||||
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not still_running:
|
||||||
|
logging.info("所有 Cursor 进程已正常关闭")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 等待一小段时间再检查
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
# 如果超时后仍有进程在运行
|
||||||
|
if still_running:
|
||||||
|
process_list = ", ".join([str(p.pid) for p in still_running])
|
||||||
|
logging.warning(f"以下进程未能在规定时间内关闭: {process_list}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"关闭 Cursor 进程时发生错误: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
ExitCursor()
|
||||||
126
backup/get_veri_code_test.py
Normal file
126
backup/get_veri_code_test.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
from DrissionPage import ChromiumOptions, Chromium
|
||||||
|
from DrissionPage.common import Keys
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def get_extension_path():
|
||||||
|
"""获取插件路径"""
|
||||||
|
root_dir = os.getcwd()
|
||||||
|
extension_path = os.path.join(root_dir, "turnstilePatch")
|
||||||
|
|
||||||
|
if hasattr(sys, "_MEIPASS"):
|
||||||
|
print("运行在打包环境中")
|
||||||
|
extension_path = os.path.join(sys._MEIPASS, "turnstilePatch")
|
||||||
|
|
||||||
|
print(f"尝试加载插件路径: {extension_path}")
|
||||||
|
|
||||||
|
if not os.path.exists(extension_path):
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f"插件不存在: {extension_path}\n请确保 turnstilePatch 文件夹在正确位置"
|
||||||
|
)
|
||||||
|
|
||||||
|
return extension_path
|
||||||
|
|
||||||
|
|
||||||
|
def get_browser_options():
|
||||||
|
co = ChromiumOptions()
|
||||||
|
try:
|
||||||
|
extension_path = get_extension_path()
|
||||||
|
co.add_extension(extension_path)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
print(f"警告: {e}")
|
||||||
|
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
co.set_pref("credentials_enable_service", False)
|
||||||
|
co.set_argument("--hide-crash-restore-bubble")
|
||||||
|
co.auto_port()
|
||||||
|
|
||||||
|
# Mac 系统特殊处理
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
co.set_argument("--no-sandbox")
|
||||||
|
co.set_argument("--disable-gpu")
|
||||||
|
|
||||||
|
return co
|
||||||
|
|
||||||
|
|
||||||
|
def get_veri_code(username):
|
||||||
|
# 使用相同的浏览器配置
|
||||||
|
co = get_browser_options()
|
||||||
|
browser = Chromium(co)
|
||||||
|
code = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 获取当前标签页
|
||||||
|
tab = browser.latest_tab
|
||||||
|
tab.run_js("try { turnstile.reset() } catch(e) { }")
|
||||||
|
|
||||||
|
# 打开临时邮箱网站
|
||||||
|
tab.get("https://tempmail.plus/zh")
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# 设置邮箱用户名
|
||||||
|
while True:
|
||||||
|
if tab.ele("@id=pre_button"):
|
||||||
|
# 点击输入框
|
||||||
|
tab.actions.click("@id=pre_button")
|
||||||
|
time.sleep(1)
|
||||||
|
# 删除之前的内容
|
||||||
|
tab.run_js('document.getElementById("pre_button").value = ""')
|
||||||
|
|
||||||
|
# 输入新用户名并回车
|
||||||
|
tab.actions.input(username).key_down(Keys.ENTER).key_up(Keys.ENTER)
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# 等待并获取新邮件
|
||||||
|
while True:
|
||||||
|
new_mail = tab.ele("@class=mail")
|
||||||
|
if new_mail:
|
||||||
|
if new_mail.text:
|
||||||
|
print("最新的邮件:", new_mail.text)
|
||||||
|
tab.actions.click("@class=mail")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print(new_mail)
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# 提取验证码
|
||||||
|
if tab.ele("@class=overflow-auto mb-20"):
|
||||||
|
email_content = tab.ele("@class=overflow-auto mb-20").text
|
||||||
|
verification_code = re.search(
|
||||||
|
r"verification code is (\d{6})", email_content
|
||||||
|
)
|
||||||
|
if verification_code:
|
||||||
|
code = verification_code.group(1)
|
||||||
|
print("验证码:", code)
|
||||||
|
else:
|
||||||
|
print("未找到验证码")
|
||||||
|
|
||||||
|
# 删除邮件
|
||||||
|
if tab.ele("@id=delete_mail"):
|
||||||
|
tab.actions.click("@id=delete_mail")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
if tab.ele("@id=confirm_mail"):
|
||||||
|
tab.actions.click("@id=confirm_mail")
|
||||||
|
print("删除邮件")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"发生错误: {str(e)}")
|
||||||
|
finally:
|
||||||
|
browser.quit()
|
||||||
|
|
||||||
|
return code
|
||||||
|
|
||||||
|
|
||||||
|
# 测试运行
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_username = "test_user" # 替换为你要测试的用户名
|
||||||
|
code = get_veri_code(test_username)
|
||||||
|
print(f"获取到的验证码: {code}")
|
||||||
29
backup/go_cursor_help.py
Normal file
29
backup/go_cursor_help.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import platform
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from logger import logging
|
||||||
|
|
||||||
|
def go_cursor_help():
|
||||||
|
system = platform.system()
|
||||||
|
logging.info(f"当前操作系统: {system}")
|
||||||
|
|
||||||
|
base_url = "https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run"
|
||||||
|
|
||||||
|
if system == "Darwin": # macOS
|
||||||
|
cmd = f'curl -fsSL {base_url}/cursor_mac_id_modifier.sh | sudo bash'
|
||||||
|
logging.info("执行macOS命令")
|
||||||
|
os.system(cmd)
|
||||||
|
elif system == "Linux":
|
||||||
|
cmd = f'curl -fsSL {base_url}/cursor_linux_id_modifier.sh | sudo bash'
|
||||||
|
logging.info("执行Linux命令")
|
||||||
|
os.system(cmd)
|
||||||
|
elif system == "Windows":
|
||||||
|
cmd = f'irm {base_url}/cursor_win_id_modifier.ps1 | iex'
|
||||||
|
logging.info("执行Windows命令")
|
||||||
|
# 在Windows上使用PowerShell执行命令
|
||||||
|
subprocess.run(["powershell", "-Command", cmd], shell=True)
|
||||||
|
else:
|
||||||
|
logging.error(f"不支持的操作系统: {system}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
302
backup/patch_cursor_get_machine_id.py
Normal file
302
backup/patch_cursor_get_machine_id.py
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
|
||||||
|
# 配置日志
|
||||||
|
def setup_logging() -> logging.Logger:
|
||||||
|
"""配置并返回logger实例"""
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.setLevel(logging.INFO)
|
||||||
|
handler = logging.StreamHandler()
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
"%(asctime)s - %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
handler.setFormatter(formatter)
|
||||||
|
logger.addHandler(handler)
|
||||||
|
return logger
|
||||||
|
|
||||||
|
|
||||||
|
logger = setup_logging()
|
||||||
|
|
||||||
|
|
||||||
|
def get_cursor_paths() -> Tuple[str, str]:
|
||||||
|
"""
|
||||||
|
根据不同操作系统获取 Cursor 相关路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple[str, str]: (package.json路径, main.js路径)的元组
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
OSError: 当找不到有效路径或系统不支持时抛出
|
||||||
|
"""
|
||||||
|
system = platform.system()
|
||||||
|
|
||||||
|
paths_map = {
|
||||||
|
"Darwin": {
|
||||||
|
"base": "/Applications/Cursor.app/Contents/Resources/app",
|
||||||
|
"package": "package.json",
|
||||||
|
"main": "out/main.js",
|
||||||
|
},
|
||||||
|
"Windows": {
|
||||||
|
"base": os.path.join(
|
||||||
|
os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"
|
||||||
|
),
|
||||||
|
"package": "package.json",
|
||||||
|
"main": "out/main.js",
|
||||||
|
},
|
||||||
|
"Linux": {
|
||||||
|
"bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"],
|
||||||
|
"package": "package.json",
|
||||||
|
"main": "out/main.js",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if system not in paths_map:
|
||||||
|
raise OSError(f"不支持的操作系统: {system}")
|
||||||
|
|
||||||
|
if system == "Linux":
|
||||||
|
for base in paths_map["Linux"]["bases"]:
|
||||||
|
pkg_path = os.path.join(base, paths_map["Linux"]["package"])
|
||||||
|
if os.path.exists(pkg_path):
|
||||||
|
return (pkg_path, os.path.join(base, paths_map["Linux"]["main"]))
|
||||||
|
raise OSError("在 Linux 系统上未找到 Cursor 安装路径")
|
||||||
|
|
||||||
|
base_path = paths_map[system]["base"]
|
||||||
|
return (
|
||||||
|
os.path.join(base_path, paths_map[system]["package"]),
|
||||||
|
os.path.join(base_path, paths_map[system]["main"]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_system_requirements(pkg_path: str, main_path: str) -> bool:
|
||||||
|
"""
|
||||||
|
检查系统要求
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pkg_path: package.json 文件路径
|
||||||
|
main_path: main.js 文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 检查是否通过
|
||||||
|
"""
|
||||||
|
for file_path in [pkg_path, main_path]:
|
||||||
|
if not os.path.isfile(file_path):
|
||||||
|
logger.error(f"文件不存在: {file_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not os.access(file_path, os.W_OK):
|
||||||
|
logger.error(f"没有文件写入权限: {file_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def version_check(version: str, min_version: str = "", max_version: str = "") -> bool:
|
||||||
|
"""
|
||||||
|
版本号检查
|
||||||
|
|
||||||
|
Args:
|
||||||
|
version: 当前版本号
|
||||||
|
min_version: 最小版本号要求
|
||||||
|
max_version: 最大版本号要求
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 版本号是否符合要求
|
||||||
|
"""
|
||||||
|
version_pattern = r"^\d+\.\d+\.\d+$"
|
||||||
|
try:
|
||||||
|
if not re.match(version_pattern, version):
|
||||||
|
logger.error(f"无效的版本号格式: {version}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def parse_version(ver: str) -> Tuple[int, ...]:
|
||||||
|
return tuple(map(int, ver.split(".")))
|
||||||
|
|
||||||
|
current = parse_version(version)
|
||||||
|
|
||||||
|
if min_version and current < parse_version(min_version):
|
||||||
|
logger.error(f"版本号 {version} 小于最小要求 {min_version}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if max_version and current > parse_version(max_version):
|
||||||
|
logger.error(f"版本号 {version} 大于最大要求 {max_version}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"版本检查失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def modify_main_js(main_path: str) -> bool:
|
||||||
|
"""
|
||||||
|
修改 main.js 文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
main_path: main.js 文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 修改是否成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 获取原始文件的权限和所有者信息
|
||||||
|
original_stat = os.stat(main_path)
|
||||||
|
original_mode = original_stat.st_mode
|
||||||
|
original_uid = original_stat.st_uid
|
||||||
|
original_gid = original_stat.st_gid
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file:
|
||||||
|
with open(main_path, "r", encoding="utf-8") as main_file:
|
||||||
|
content = main_file.read()
|
||||||
|
|
||||||
|
# 执行替换
|
||||||
|
patterns = {
|
||||||
|
r"async getMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMachineId(){return \1}",
|
||||||
|
r"async getMacMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMacMachineId(){return \1}",
|
||||||
|
}
|
||||||
|
|
||||||
|
for pattern, replacement in patterns.items():
|
||||||
|
content = re.sub(pattern, replacement, content)
|
||||||
|
|
||||||
|
tmp_file.write(content)
|
||||||
|
tmp_path = tmp_file.name
|
||||||
|
|
||||||
|
# 使用 shutil.copy2 保留文件权限
|
||||||
|
shutil.copy2(main_path, main_path + ".old")
|
||||||
|
shutil.move(tmp_path, main_path)
|
||||||
|
|
||||||
|
# 恢复原始文件的权限和所有者
|
||||||
|
os.chmod(main_path, original_mode)
|
||||||
|
if os.name != "nt": # 在非Windows系统上设置所有者
|
||||||
|
os.chown(main_path, original_uid, original_gid)
|
||||||
|
|
||||||
|
logger.info("文件修改成功")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"修改文件时发生错误: {str(e)}")
|
||||||
|
if "tmp_path" in locals():
|
||||||
|
os.unlink(tmp_path)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def backup_files(pkg_path: str, main_path: str) -> bool:
|
||||||
|
"""
|
||||||
|
备份原始文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pkg_path: package.json 文件路径(未使用)
|
||||||
|
main_path: main.js 文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 备份是否成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 只备份 main.js
|
||||||
|
if os.path.exists(main_path):
|
||||||
|
backup_main = f"{main_path}.bak"
|
||||||
|
shutil.copy2(main_path, backup_main)
|
||||||
|
logger.info(f"已备份 main.js: {backup_main}")
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"备份文件失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def restore_backup_files(pkg_path: str, main_path: str) -> bool:
|
||||||
|
"""
|
||||||
|
恢复备份文件
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pkg_path: package.json 文件路径(未使用)
|
||||||
|
main_path: main.js 文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 恢复是否成功
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 只恢复 main.js
|
||||||
|
backup_main = f"{main_path}.bak"
|
||||||
|
if os.path.exists(backup_main):
|
||||||
|
shutil.copy2(backup_main, main_path)
|
||||||
|
logger.info(f"已恢复 main.js")
|
||||||
|
return True
|
||||||
|
|
||||||
|
logger.error("未找到备份文件")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"恢复备份失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def patch_cursor_get_machine_id(restore_mode=False) -> None:
|
||||||
|
"""
|
||||||
|
主函数
|
||||||
|
|
||||||
|
Args:
|
||||||
|
restore_mode: 是否为恢复模式
|
||||||
|
"""
|
||||||
|
logger.info("开始执行脚本...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 获取路径
|
||||||
|
pkg_path, main_path = get_cursor_paths()
|
||||||
|
|
||||||
|
# 检查系统要求
|
||||||
|
if not check_system_requirements(pkg_path, main_path):
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if restore_mode:
|
||||||
|
# 恢复备份
|
||||||
|
if restore_backup_files(pkg_path, main_path):
|
||||||
|
logger.info("备份恢复完成")
|
||||||
|
else:
|
||||||
|
logger.error("备份恢复失败")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 获取版本号
|
||||||
|
try:
|
||||||
|
with open(pkg_path, "r", encoding="utf-8") as f:
|
||||||
|
version = json.load(f)["version"]
|
||||||
|
logger.info(f"当前 Cursor 版本: {version}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"无法读取版本号: {str(e)}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 检查版本
|
||||||
|
if not version_check(version, min_version="0.45.0"):
|
||||||
|
logger.error("版本不符合要求(需 >= 0.45.x)")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
logger.info("版本检查通过,准备修改文件")
|
||||||
|
|
||||||
|
# 备份文件
|
||||||
|
if not backup_files(pkg_path, main_path):
|
||||||
|
logger.error("文件备份失败,终止操作")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 修改文件
|
||||||
|
if not modify_main_js(main_path):
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
logger.info("脚本执行完成")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"执行过程中发生错误: {str(e)}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
patch_cursor_get_machine_id()
|
||||||
351
browser_utils.py
Normal file
351
browser_utils.py
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
from DrissionPage import ChromiumOptions, Chromium
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import tempfile
|
||||||
|
import zipfile
|
||||||
|
import string
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
PROXY_HOST = "h464.kdltpspro.com"
|
||||||
|
PROXY_PORT = "15818"
|
||||||
|
PROXY_USERNAME = "h464" # 代理用户名
|
||||||
|
PROXY_PASSWORD = "kdltpspro" # 代理密码
|
||||||
|
PROXY_URL = f"http://{PROXY_HOST}:{PROXY_PORT}"
|
||||||
|
|
||||||
|
|
||||||
|
class BrowserManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.browser = None
|
||||||
|
self.current_proxy_info = None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def create_proxyauth_extension(self, proxy_host, proxy_port, proxy_username, proxy_password, scheme='http', plugin_folder=None):
|
||||||
|
"""
|
||||||
|
创建Chrome代理认证插件
|
||||||
|
"""
|
||||||
|
if plugin_folder is None:
|
||||||
|
# 创建插件在脚本所在目录
|
||||||
|
current_directory = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
plugin_folder = os.path.join(current_directory, 'kdl_Chromium_Proxy')
|
||||||
|
|
||||||
|
# 确保文件夹存在
|
||||||
|
if not os.path.exists(plugin_folder):
|
||||||
|
os.makedirs(plugin_folder)
|
||||||
|
|
||||||
|
logging.info(f"创建代理插件,路径: {plugin_folder}")
|
||||||
|
|
||||||
|
manifest_json = """
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "kdl_Chromium_Proxy",
|
||||||
|
"permissions": [
|
||||||
|
"proxy",
|
||||||
|
"tabs",
|
||||||
|
"unlimitedStorage",
|
||||||
|
"storage",
|
||||||
|
"<all_urls>",
|
||||||
|
"webRequest",
|
||||||
|
"webRequestBlocking",
|
||||||
|
"browsingData"
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"scripts": ["background.js"]
|
||||||
|
},
|
||||||
|
"minimum_chrome_version":"22.0.0"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
background_js = string.Template("""
|
||||||
|
var config = {
|
||||||
|
mode: "fixed_servers",
|
||||||
|
rules: {
|
||||||
|
singleProxy: {
|
||||||
|
scheme: "${scheme}",
|
||||||
|
host: "${host}",
|
||||||
|
port: parseInt(${port})
|
||||||
|
},
|
||||||
|
bypassList: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chrome.proxy.settings.set({value: config, scope: "regular"}, function() {});
|
||||||
|
|
||||||
|
function callbackFn(details) {
|
||||||
|
return {
|
||||||
|
authCredentials: {
|
||||||
|
username: "${username}",
|
||||||
|
password: "${password}"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.webRequest.onAuthRequired.addListener(
|
||||||
|
callbackFn,
|
||||||
|
{urls: ["<all_urls>"]},
|
||||||
|
['blocking']
|
||||||
|
);
|
||||||
|
""").substitute(
|
||||||
|
host=proxy_host,
|
||||||
|
port=proxy_port,
|
||||||
|
username=proxy_username,
|
||||||
|
password=proxy_password,
|
||||||
|
scheme=scheme,
|
||||||
|
)
|
||||||
|
with open(os.path.join(plugin_folder, "manifest.json"), "w") as manifest_file:
|
||||||
|
manifest_file.write(manifest_json)
|
||||||
|
with open(os.path.join(plugin_folder, "background.js"), "w") as background_file:
|
||||||
|
background_file.write(background_js)
|
||||||
|
return plugin_folder
|
||||||
|
|
||||||
|
def get_proxy(self, use_api=False, mode=False, proxy_choice=None, custom_api=None):
|
||||||
|
"""
|
||||||
|
获取代理配置
|
||||||
|
Args:
|
||||||
|
use_api: 是否使用API获取代理
|
||||||
|
mode: 如果开启 返回格式要改变成 host port username password
|
||||||
|
proxy_choice: 代理选择 (1=本地代理, 2=全局代理)
|
||||||
|
custom_api: 自定义代理API地址
|
||||||
|
Returns:
|
||||||
|
str 或 tuple: 代理URL或(host, port, username, password)元组
|
||||||
|
"""
|
||||||
|
logging.info(f"获取代理配置, use_api={use_api}, mode={mode}, proxy_choice={proxy_choice}")
|
||||||
|
if use_api:
|
||||||
|
try:
|
||||||
|
# 从API获取代理
|
||||||
|
import requests
|
||||||
|
logging.info("正在从API获取代理...")
|
||||||
|
|
||||||
|
# 根据选择使用不同的API
|
||||||
|
if custom_api:
|
||||||
|
api_url = custom_api
|
||||||
|
logging.info(f"使用自定义API: {api_url}")
|
||||||
|
elif proxy_choice == 1:
|
||||||
|
api_url = "https://cursorapi.nosqli.com/admin/api.proxyinfo/getproxyarmybendi"
|
||||||
|
logging.info("使用本地代理API")
|
||||||
|
elif proxy_choice == 2 or proxy_choice is None: # 如果未指定或选择全局代理
|
||||||
|
api_url = "https://cursorapi.nosqli.com/admin/api.GlobalProxyip/get_proxy"
|
||||||
|
logging.info("使用全局代理API")
|
||||||
|
else:
|
||||||
|
logging.warning(f"未知的代理选择: {proxy_choice},使用cn代理")
|
||||||
|
api_url = "https://cursorapi.nosqli.com/admin/api.proxyinfo/getproxyarmybendi"
|
||||||
|
|
||||||
|
response = requests.get(api_url)
|
||||||
|
logging.info(f"API响应状态码: {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
proxy_data = response.json()
|
||||||
|
logging.info(f"API返回数据: {proxy_data}")
|
||||||
|
|
||||||
|
if proxy_data.get("code") == 0:
|
||||||
|
proxy_info = proxy_data.get("data", {})
|
||||||
|
proxy_host = proxy_info.get("host")
|
||||||
|
proxy_port = proxy_info.get("port")
|
||||||
|
proxy_username = proxy_info.get("username", "")
|
||||||
|
proxy_password = proxy_info.get("password", "")
|
||||||
|
proxy_data = proxy_info.get("proxy_data", "")
|
||||||
|
logging.info(f"解析到代理信息 - host:{proxy_host}, port:{proxy_port}")
|
||||||
|
|
||||||
|
if proxy_host and proxy_port:
|
||||||
|
if mode:
|
||||||
|
logging.info(f"使用API代理(mode=True): 返回元组格式")
|
||||||
|
return proxy_host, proxy_port, proxy_username, proxy_password, proxy_data
|
||||||
|
else:
|
||||||
|
proxy_url = f"http://{proxy_host}:{proxy_port}"
|
||||||
|
logging.info(f"使用API代理: {proxy_url}")
|
||||||
|
return proxy_url
|
||||||
|
|
||||||
|
logging.warning("从API获取代理失败,使用默认代理配置")
|
||||||
|
# 使用默认代理
|
||||||
|
if mode:
|
||||||
|
# 使用环境变量或常量中的默认值
|
||||||
|
proxy_host = os.getenv("PROXY_HOST", PROXY_HOST)
|
||||||
|
proxy_port = os.getenv("PROXY_PORT", PROXY_PORT)
|
||||||
|
proxy_username = os.getenv("PROXY_USERNAME", PROXY_USERNAME )
|
||||||
|
proxy_password = os.getenv("PROXY_PASSWORD", PROXY_PASSWORD)
|
||||||
|
proxy_data = os.getenv("PROXY_DATA", "")
|
||||||
|
logging.info(f"使用默认代理信息(mode=True): host={proxy_host}, port={proxy_port}")
|
||||||
|
return proxy_host, proxy_port, proxy_username, proxy_password, proxy_data
|
||||||
|
else:
|
||||||
|
default_proxy = os.getenv("BROWSER_PROXY", PROXY_URL)
|
||||||
|
logging.info(f"使用默认代理: {default_proxy}")
|
||||||
|
return default_proxy
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"获取代理出错: {str(e)}")
|
||||||
|
# 发生异常时使用默认代理
|
||||||
|
if mode:
|
||||||
|
proxy_host = os.getenv("PROXY_HOST", PROXY_HOST)
|
||||||
|
proxy_port = os.getenv("PROXY_PORT", PROXY_PORT)
|
||||||
|
proxy_username = os.getenv("PROXY_USERNAME", PROXY_USERNAME)
|
||||||
|
proxy_password = os.getenv("PROXY_PASSWORD", PROXY_PASSWORD)
|
||||||
|
proxy_data = os.getenv("PROXY_DATA", "")
|
||||||
|
logging.info(f"异常情况下使用默认代理信息(mode=True)")
|
||||||
|
return proxy_host, proxy_port, proxy_username, proxy_password, proxy_data
|
||||||
|
else:
|
||||||
|
default_proxy = os.getenv("BROWSER_PROXY", PROXY_URL)
|
||||||
|
logging.info(f"使用默认代理: {default_proxy}")
|
||||||
|
return default_proxy
|
||||||
|
|
||||||
|
# 不使用API时返回默认配置
|
||||||
|
if mode:
|
||||||
|
proxy_host = os.getenv("PROXY_HOST", PROXY_HOST)
|
||||||
|
proxy_port = os.getenv("PROXY_PORT", PROXY_PORT)
|
||||||
|
proxy_username = os.getenv("PROXY_USERNAME", PROXY_USERNAME)
|
||||||
|
proxy_password = os.getenv("PROXY_PASSWORD", PROXY_PASSWORD)
|
||||||
|
proxy_data = os.getenv("PROXY_DATA", "")
|
||||||
|
logging.info(f"不使用API,返回默认代理信息(mode=True)")
|
||||||
|
return proxy_host, proxy_port, proxy_username, proxy_password, proxy_data
|
||||||
|
else:
|
||||||
|
default_proxy = os.getenv("BROWSER_PROXY", PROXY_URL)
|
||||||
|
logging.info(f"不使用API,直接返回默认代理: {default_proxy}")
|
||||||
|
return default_proxy
|
||||||
|
|
||||||
|
def get_plugin_folder(self):
|
||||||
|
"""获取代理插件文件夹路径"""
|
||||||
|
# 使用当前目录下的kdl_Chromium_Proxy文件夹
|
||||||
|
current_directory = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
plugin_folder = os.path.join(current_directory, 'kdl_Chromium_Proxy')
|
||||||
|
|
||||||
|
# 确保文件夹存在
|
||||||
|
if not os.path.exists(plugin_folder):
|
||||||
|
os.makedirs(plugin_folder)
|
||||||
|
|
||||||
|
logging.info(f"代理插件文件夹路径: {plugin_folder}")
|
||||||
|
return plugin_folder
|
||||||
|
|
||||||
|
def init_browser(self, user_agent=None, proxy_choice=None, custom_api=None):
|
||||||
|
"""
|
||||||
|
初始化浏览器实例
|
||||||
|
Args:
|
||||||
|
user_agent: 自定义User-Agent
|
||||||
|
proxy_choice: 代理选择 (1=本地代理, 2=全局代理)
|
||||||
|
custom_api: 自定义代理API地址
|
||||||
|
Returns:
|
||||||
|
tuple: (browser实例, 代理信息元组)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 获取代理配置
|
||||||
|
proxy_info = self.get_proxy(use_api=True, mode=True, proxy_choice=proxy_choice, custom_api=custom_api)
|
||||||
|
proxy_host, proxy_port, proxy_username, proxy_password, proxy_data = proxy_info
|
||||||
|
|
||||||
|
# 创建代理认证插件
|
||||||
|
plugin_folder = self.create_proxyauth_extension(
|
||||||
|
proxy_host=proxy_host,
|
||||||
|
proxy_port=proxy_port,
|
||||||
|
proxy_username=proxy_username,
|
||||||
|
proxy_password=proxy_password
|
||||||
|
)
|
||||||
|
|
||||||
|
# 设置浏览器选项
|
||||||
|
co = ChromiumOptions()
|
||||||
|
co.set_argument("--hide-crash-restore-bubble")
|
||||||
|
co.set_pref("credentials_enable_service", False)
|
||||||
|
|
||||||
|
# 添加代理插件
|
||||||
|
co.add_extension(plugin_folder)
|
||||||
|
|
||||||
|
# 设置用户代理
|
||||||
|
if user_agent:
|
||||||
|
co.set_user_agent(user_agent)
|
||||||
|
|
||||||
|
# 设置无头模式
|
||||||
|
co.headless(True)
|
||||||
|
|
||||||
|
# Mac系统特殊处理
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
co.set_argument("--no-sandbox")
|
||||||
|
co.set_argument("--disable-gpu")
|
||||||
|
|
||||||
|
# 创建浏览器实例
|
||||||
|
browser = Chromium(co)
|
||||||
|
self.browser = browser
|
||||||
|
self.current_proxy_info = proxy_info
|
||||||
|
|
||||||
|
logging.info("浏览器实例创建成功")
|
||||||
|
return browser, proxy_info
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"初始化浏览器失败: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _get_browser_options(self, user_agent=None):
|
||||||
|
"""获取浏览器配置"""
|
||||||
|
co = ChromiumOptions()
|
||||||
|
try:
|
||||||
|
extension_path = self._get_extension_path()
|
||||||
|
co.add_extension(extension_path)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
logging.warning(f"警告: {e}")
|
||||||
|
|
||||||
|
co.set_pref("credentials_enable_service", False)
|
||||||
|
co.set_argument("--hide-crash-restore-bubble")
|
||||||
|
|
||||||
|
proxy = self.get_proxy(True,True)
|
||||||
|
logging.info(f"代理-----: {proxy}")
|
||||||
|
|
||||||
|
# 在类的__init__方法中声明self.current_proxy_info = None
|
||||||
|
if isinstance(proxy, tuple) and len(proxy) == 4:
|
||||||
|
self.current_proxy_info = proxy
|
||||||
|
else:
|
||||||
|
logging.error("代理信息格式错误")
|
||||||
|
self.current_proxy_info = None
|
||||||
|
|
||||||
|
proxy_host, proxy_port, proxy_username, proxy_password = proxy
|
||||||
|
try:
|
||||||
|
# 使用类方法
|
||||||
|
# proxyauth_plugin_folder = BrowserManager.create_proxyauth_extension(
|
||||||
|
proxyauth_plugin_folder = self.create_proxyauth_extension(
|
||||||
|
proxy_host=proxy_host,
|
||||||
|
proxy_port=proxy_port,
|
||||||
|
proxy_username=proxy_username,
|
||||||
|
proxy_password=proxy_password
|
||||||
|
)
|
||||||
|
# 使用函数返回的插件文件夹路径而不是硬编码路径
|
||||||
|
logging.info(f"代理插件文件夹路径: {proxyauth_plugin_folder}")
|
||||||
|
co.add_extension(proxyauth_plugin_folder)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"创建代理扩展失败: {str(e)}")
|
||||||
|
# 如果创建扩展失败,尝试直接设置代理
|
||||||
|
proxy_url = f"http://{proxy_username}:{proxy_password}@{proxy_host}:{proxy_port}"
|
||||||
|
co.set_proxy(proxy_url)
|
||||||
|
logging.info(f"直接设置代理URL: {proxy_url}")
|
||||||
|
|
||||||
|
co.auto_port()
|
||||||
|
if user_agent:
|
||||||
|
co.set_user_agent(user_agent)
|
||||||
|
|
||||||
|
# 设置为有头模式以便观察验证过程
|
||||||
|
co.headless(True) # 使用有头模式进行测试
|
||||||
|
|
||||||
|
# Mac 系统特殊处理
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
co.set_argument("--no-sandbox")
|
||||||
|
co.set_argument("--disable-gpu")
|
||||||
|
|
||||||
|
return co
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _get_extension_path(self):
|
||||||
|
"""获取插件路径"""
|
||||||
|
root_dir = os.getcwd()
|
||||||
|
extension_path = os.path.join(root_dir, "turnstilePatch")
|
||||||
|
|
||||||
|
if hasattr(sys, "_MEIPASS"):
|
||||||
|
extension_path = os.path.join(sys._MEIPASS, "turnstilePatch")
|
||||||
|
|
||||||
|
if not os.path.exists(extension_path):
|
||||||
|
raise FileNotFoundError(f"插件不存在: {extension_path}")
|
||||||
|
|
||||||
|
return extension_path
|
||||||
|
|
||||||
|
def quit(self):
|
||||||
|
"""关闭浏览器"""
|
||||||
|
if self.browser:
|
||||||
|
try:
|
||||||
|
self.browser.quit()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
125
browser_utils_copy.py
Normal file
125
browser_utils_copy.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
from DrissionPage import ChromiumOptions, Chromium
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
PROXY_HOST = "h464.kdltpspro.com"
|
||||||
|
PROXY_PORT = "15818"
|
||||||
|
PROXY_URL = f"http://{PROXY_HOST}:{PROXY_PORT}"
|
||||||
|
|
||||||
|
class BrowserManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.browser = None
|
||||||
|
|
||||||
|
def get_proxy(self, use_api=False):
|
||||||
|
"""
|
||||||
|
获取代理配置
|
||||||
|
Args:
|
||||||
|
use_api: 是否使用API获取代理
|
||||||
|
Returns:
|
||||||
|
str: 代理URL
|
||||||
|
"""
|
||||||
|
# 从日志可以看出,use_api=False,所以直接返回了默认代理配置
|
||||||
|
# 没有调用API获取新的代理,而是使用了预设的PROXY_URL
|
||||||
|
# PROXY_URL 定义为 http://h464.kdltpspro.com:15818
|
||||||
|
|
||||||
|
logging.info(f"获取代理配置, use_api={use_api}")
|
||||||
|
|
||||||
|
if use_api:
|
||||||
|
try:
|
||||||
|
# 从API获取代理
|
||||||
|
import requests
|
||||||
|
logging.info("正在从API获取代理...")
|
||||||
|
response = requests.get("http://127.0.0.1:46880/admin/api.proxyinfo/getproxyarmy")
|
||||||
|
logging.info(f"API响应状态码: {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
proxy_data = response.json()
|
||||||
|
logging.info(f"API返回数据: {proxy_data}")
|
||||||
|
|
||||||
|
if proxy_data.get("code") == 0:
|
||||||
|
proxy_info = proxy_data.get("data", {})
|
||||||
|
proxy_host = proxy_info.get("host")
|
||||||
|
proxy_port = proxy_info.get("port")
|
||||||
|
logging.info(f"解析到代理信息 - host:{proxy_host}, port:{proxy_port}")
|
||||||
|
|
||||||
|
if proxy_host and proxy_port:
|
||||||
|
proxy_url = f"http://{proxy_host}:{proxy_port}"
|
||||||
|
logging.info(f"使用API代理: {proxy_url}")
|
||||||
|
return proxy_url
|
||||||
|
|
||||||
|
logging.warning("从API获取代理失败,使用默认代理配置")
|
||||||
|
default_proxy = os.getenv("BROWSER_PROXY", PROXY_URL)
|
||||||
|
logging.info(f"使用默认代理: {default_proxy}")
|
||||||
|
return default_proxy
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"获取代理出错: {str(e)}")
|
||||||
|
default_proxy = os.getenv("BROWSER_PROXY", PROXY_URL)
|
||||||
|
logging.info(f"使用默认代理: {default_proxy}")
|
||||||
|
return default_proxy
|
||||||
|
|
||||||
|
# 不使用API时返回默认配置
|
||||||
|
default_proxy = os.getenv("BROWSER_PROXY", PROXY_URL)
|
||||||
|
logging.info(f"不使用API,直接返回默认代理: {default_proxy}")
|
||||||
|
return default_proxy
|
||||||
|
def init_browser(self, user_agent=None):
|
||||||
|
"""初始化浏览器"""
|
||||||
|
co = self._get_browser_options(user_agent)
|
||||||
|
self.browser = Chromium(co)
|
||||||
|
return self.browser
|
||||||
|
|
||||||
|
def _get_browser_options(self, user_agent=None):
|
||||||
|
"""获取浏览器配置"""
|
||||||
|
co = ChromiumOptions()
|
||||||
|
try:
|
||||||
|
extension_path = self._get_extension_path()
|
||||||
|
co.add_extension(extension_path)
|
||||||
|
except FileNotFoundError as e:
|
||||||
|
logging.warning(f"警告: {e}")
|
||||||
|
|
||||||
|
co.set_pref("credentials_enable_service", False)
|
||||||
|
co.set_argument("--hide-crash-restore-bubble")
|
||||||
|
|
||||||
|
|
||||||
|
proxy = self.get_proxy(True)
|
||||||
|
logging.info(f"代理-----: {proxy}")
|
||||||
|
if proxy:
|
||||||
|
co.set_proxy(proxy)
|
||||||
|
|
||||||
|
co.auto_port()
|
||||||
|
if user_agent:
|
||||||
|
co.set_user_agent(user_agent)
|
||||||
|
|
||||||
|
# 设置为有头模式以便观察验证过程
|
||||||
|
co.headless(False) # 使用有头模式进行测试
|
||||||
|
|
||||||
|
# Mac 系统特殊处理
|
||||||
|
if sys.platform == "darwin":
|
||||||
|
co.set_argument("--no-sandbox")
|
||||||
|
co.set_argument("--disable-gpu")
|
||||||
|
|
||||||
|
return co
|
||||||
|
|
||||||
|
def _get_extension_path(self):
|
||||||
|
"""获取插件路径"""
|
||||||
|
root_dir = os.getcwd()
|
||||||
|
extension_path = os.path.join(root_dir, "turnstilePatch")
|
||||||
|
|
||||||
|
if hasattr(sys, "_MEIPASS"):
|
||||||
|
extension_path = os.path.join(sys._MEIPASS, "turnstilePatch")
|
||||||
|
|
||||||
|
if not os.path.exists(extension_path):
|
||||||
|
raise FileNotFoundError(f"插件不存在: {extension_path}")
|
||||||
|
|
||||||
|
return extension_path
|
||||||
|
|
||||||
|
def quit(self):
|
||||||
|
"""关闭浏览器"""
|
||||||
|
if self.browser:
|
||||||
|
try:
|
||||||
|
self.browser.quit()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
848
cursor_pro_register.py
Normal file
848
cursor_pro_register.py
Normal file
@@ -0,0 +1,848 @@
|
|||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
from colorama import Fore, Style
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from account_sync import AccountSync
|
||||||
|
|
||||||
|
os.environ["PYTHONVERBOSE"] = "0"
|
||||||
|
os.environ["PYINSTALLER_VERBOSE"] = "0"
|
||||||
|
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
import os
|
||||||
|
from logger import logging
|
||||||
|
from browser_utils import BrowserManager
|
||||||
|
from logo import print_logo
|
||||||
|
from datetime import datetime
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# 定义 EMOJI 字典
|
||||||
|
EMOJI = {"ERROR": "❌", "WARNING": "⚠️", "INFO": "ℹ️"}
|
||||||
|
|
||||||
|
|
||||||
|
class VerificationStatus(Enum):
|
||||||
|
"""验证状态枚举"""
|
||||||
|
|
||||||
|
PASSWORD_PAGE = "@name=password"
|
||||||
|
CAPTCHA_PAGE = "@data-index=0"
|
||||||
|
ACCOUNT_SETTINGS = "Account Settings"
|
||||||
|
|
||||||
|
|
||||||
|
class TurnstileError(Exception):
|
||||||
|
"""Turnstile 验证相关异常"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def save_screenshot(tab, stage: str, timestamp: bool = True) -> None:
|
||||||
|
"""
|
||||||
|
保存页面截图
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tab: 浏览器标签页对象
|
||||||
|
stage: 截图阶段标识
|
||||||
|
timestamp: 是否添加时间戳
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 创建 screenshots 目录
|
||||||
|
screenshot_dir = "screenshots"
|
||||||
|
if not os.path.exists(screenshot_dir):
|
||||||
|
os.makedirs(screenshot_dir)
|
||||||
|
|
||||||
|
# 生成文件名
|
||||||
|
if timestamp:
|
||||||
|
filename = f"turnstile_{stage}_{int(time.time())}.png"
|
||||||
|
else:
|
||||||
|
filename = f"turnstile_{stage}.png"
|
||||||
|
|
||||||
|
filepath = os.path.join(screenshot_dir, filename)
|
||||||
|
|
||||||
|
# 保存截图
|
||||||
|
tab.get_screenshot(filepath)
|
||||||
|
logging.debug(f"截图已保存: {filepath}")
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"截图保存失败: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
def check_verification_success(tab) -> Optional[VerificationStatus]:
|
||||||
|
"""
|
||||||
|
检查验证是否成功
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
VerificationStatus: 验证成功时返回对应状态,失败返回 None
|
||||||
|
"""
|
||||||
|
for status in VerificationStatus:
|
||||||
|
if tab.ele(status.value):
|
||||||
|
logging.info(f"验证成功 - 已到达{status.name}页面")
|
||||||
|
return status
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def handle_turnstile(tab, max_retries: int = 2, retry_interval: tuple = (1, 2), start_time=None) -> bool:
|
||||||
|
"""
|
||||||
|
处理 Turnstile 验证
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tab: 浏览器标签页对象
|
||||||
|
max_retries: 最大重试次数
|
||||||
|
retry_interval: 重试间隔时间范围(最小值, 最大值)
|
||||||
|
start_time: 注册开始时间戳,用于超时检查
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 验证是否成功
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
TurnstileError: 验证过程中出现异常
|
||||||
|
"""
|
||||||
|
logging.info("正在检测 Turnstile 验证...")
|
||||||
|
# save_screenshot(tab, "start")
|
||||||
|
|
||||||
|
retry_count = 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
while retry_count < max_retries:
|
||||||
|
# 检查是否超时
|
||||||
|
if start_time and check_timeout(start_time):
|
||||||
|
logging.warning("注册超时,放弃Turnstile验证")
|
||||||
|
return False
|
||||||
|
|
||||||
|
retry_count += 1
|
||||||
|
logging.debug(f"第 {retry_count} 次尝试验证")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 定位验证框元素
|
||||||
|
challenge_check = (
|
||||||
|
tab.ele("@id=cf-turnstile", timeout=2)
|
||||||
|
.child()
|
||||||
|
.shadow_root.ele("tag:iframe")
|
||||||
|
.ele("tag:body")
|
||||||
|
.sr("tag:input")
|
||||||
|
)
|
||||||
|
|
||||||
|
if challenge_check:
|
||||||
|
logging.info("检测到 Turnstile 验证框,开始处理...")
|
||||||
|
# 随机延时后点击验证
|
||||||
|
time.sleep(random.uniform(1, 3))
|
||||||
|
challenge_check.click()
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# 保存验证后的截图
|
||||||
|
# save_screenshot(tab, "clicked")
|
||||||
|
|
||||||
|
# 检查验证结果
|
||||||
|
if check_verification_success(tab):
|
||||||
|
logging.info("Turnstile 验证通过")
|
||||||
|
# save_screenshot(tab, "success")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.debug(f"当前尝试未成功: {str(e)}")
|
||||||
|
|
||||||
|
# 检查是否已经验证成功
|
||||||
|
if check_verification_success(tab):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# 随机延时后继续下一次尝试
|
||||||
|
time.sleep(random.uniform(*retry_interval))
|
||||||
|
|
||||||
|
# 超出最大重试次数
|
||||||
|
logging.error(f"验证失败 - 已达到最大重试次数 {max_retries}")
|
||||||
|
logging.error(
|
||||||
|
"请前往开源项目查看更多信息:https://github.com/chengazhen/cursor-auto-free"
|
||||||
|
)
|
||||||
|
# save_screenshot(tab, "failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Turnstile 验证过程发生异常: {str(e)}"
|
||||||
|
logging.error(error_msg)
|
||||||
|
# save_screenshot(tab, "error")
|
||||||
|
raise TurnstileError(error_msg)
|
||||||
|
|
||||||
|
|
||||||
|
def get_cursor_session_token(tab, max_attempts=3, retry_interval=2, start_time=None):
|
||||||
|
"""
|
||||||
|
获取Cursor会话token,带有重试机制
|
||||||
|
:param tab: 浏览器标签页
|
||||||
|
:param max_attempts: 最大尝试次数
|
||||||
|
:param retry_interval: 重试间隔(秒)
|
||||||
|
:param start_time: 注册开始时间戳,用于超时检查
|
||||||
|
:return: session token 或 None
|
||||||
|
"""
|
||||||
|
logging.info("开始获取cookie")
|
||||||
|
attempts = 0
|
||||||
|
|
||||||
|
while attempts < max_attempts:
|
||||||
|
# 检查是否超时
|
||||||
|
if start_time and check_timeout(start_time):
|
||||||
|
logging.warning("注册超时,放弃获取会话令牌")
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
cookies = tab.cookies()
|
||||||
|
for cookie in cookies:
|
||||||
|
if cookie.get("name") == "WorkosCursorSessionToken":
|
||||||
|
return cookie["value"].split("%3A%3A")[1]
|
||||||
|
|
||||||
|
attempts += 1
|
||||||
|
if attempts < max_attempts:
|
||||||
|
logging.warning(
|
||||||
|
f"第 {attempts} 次尝试未获取到CursorSessionToken,{retry_interval}秒后重试..."
|
||||||
|
)
|
||||||
|
time.sleep(retry_interval)
|
||||||
|
else:
|
||||||
|
logging.error(
|
||||||
|
f"已达到最大尝试次数({max_attempts}),获取CursorSessionToken失败"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"获取cookie失败: {str(e)}")
|
||||||
|
attempts += 1
|
||||||
|
if attempts < max_attempts:
|
||||||
|
logging.info(f"将在 {retry_interval} 秒后重试...")
|
||||||
|
time.sleep(retry_interval)
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
registration_timeout = 240 # 默认超时时间,4分钟
|
||||||
|
|
||||||
|
def check_timeout(start_time, timeout=registration_timeout):
|
||||||
|
"""检查是否超时
|
||||||
|
|
||||||
|
Args:
|
||||||
|
start_time: 开始时间戳
|
||||||
|
timeout: 超时时间(秒)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: 是否超时
|
||||||
|
"""
|
||||||
|
elapsed_time = time.time() - start_time
|
||||||
|
if elapsed_time > timeout:
|
||||||
|
logging.warning(f"⚠️ 注册超时!已经过去 {elapsed_time:.1f} 秒,超过设定的 {timeout} 秒限制")
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def sign_up_account(browser, tab, start_time=None):
|
||||||
|
logging.info("=== 开始注册账号流程 ===")
|
||||||
|
logging.info(f"正在访问注册页面: {sign_up_url}")
|
||||||
|
tab.get(sign_up_url)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 检查是否超时
|
||||||
|
if start_time and check_timeout(start_time):
|
||||||
|
logging.warning("注册超时,放弃当前注册流程")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if tab.ele("@name=first_name"):
|
||||||
|
logging.info("正在填写个人信息...")
|
||||||
|
tab.actions.click("@name=first_name").input(first_name)
|
||||||
|
logging.info(f"已输入名字: {first_name}")
|
||||||
|
time.sleep(random.uniform(1, 3))
|
||||||
|
|
||||||
|
# 检查是否超时
|
||||||
|
if start_time and check_timeout(start_time):
|
||||||
|
logging.warning("注册超时,放弃当前注册流程")
|
||||||
|
return False
|
||||||
|
|
||||||
|
tab.actions.click("@name=last_name").input(last_name)
|
||||||
|
logging.info(f"已输入姓氏: {last_name}")
|
||||||
|
time.sleep(random.uniform(1, 3))
|
||||||
|
|
||||||
|
# 检查是否超时
|
||||||
|
if start_time and check_timeout(start_time):
|
||||||
|
logging.warning("注册超时,放弃当前注册流程")
|
||||||
|
return False
|
||||||
|
|
||||||
|
tab.actions.click("@name=email").input(account)
|
||||||
|
logging.info(f"已输入邮箱: {account}")
|
||||||
|
time.sleep(random.uniform(1, 3))
|
||||||
|
|
||||||
|
# 检查是否超时
|
||||||
|
if start_time and check_timeout(start_time):
|
||||||
|
logging.warning("注册超时,放弃当前注册流程")
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.info("提交个人信息...")
|
||||||
|
tab.actions.click("@type=submit")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"注册页面访问失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查是否超时
|
||||||
|
if start_time and check_timeout(start_time):
|
||||||
|
logging.warning("注册超时,放弃当前注册流程")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 进行第一次Turnstile验证,如果失败则返回False
|
||||||
|
if not handle_turnstile(tab, start_time=start_time):
|
||||||
|
logging.error("第一次Turnstile验证失败,停止当前注册流程")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查是否超时
|
||||||
|
if start_time and check_timeout(start_time):
|
||||||
|
logging.warning("注册超时,放弃当前注册流程")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
if tab.ele("@name=password"):
|
||||||
|
logging.info("正在设置密码...")
|
||||||
|
tab.ele("@name=password").input(password)
|
||||||
|
time.sleep(random.uniform(1, 3))
|
||||||
|
|
||||||
|
# 检查是否超时
|
||||||
|
if start_time and check_timeout(start_time):
|
||||||
|
logging.warning("注册超时,放弃当前注册流程")
|
||||||
|
return False
|
||||||
|
|
||||||
|
logging.info("提交密码...")
|
||||||
|
tab.ele("@type=submit").click()
|
||||||
|
logging.info("密码设置完成,等待系统响应...")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"密码设置失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查是否超时
|
||||||
|
if start_time and check_timeout(start_time):
|
||||||
|
logging.warning("注册超时,放弃当前注册流程")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if tab.ele("This email is not available."):
|
||||||
|
logging.error("注册失败:邮箱已被使用")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查是否超时
|
||||||
|
if start_time and check_timeout(start_time):
|
||||||
|
logging.warning("注册超时,放弃当前注册流程")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 进行第二次Turnstile验证,如果失败则返回False
|
||||||
|
if not handle_turnstile(tab, start_time=start_time):
|
||||||
|
logging.error("第二次Turnstile验证失败,停止当前注册流程")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 检查是否超时
|
||||||
|
if start_time and check_timeout(start_time):
|
||||||
|
logging.warning("注册超时,放弃当前注册流程")
|
||||||
|
return False
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
if tab.ele("Account Settings"):
|
||||||
|
logging.info("注册成功 - 已进入账户设置页面")
|
||||||
|
break
|
||||||
|
if tab.ele("@data-index=0"):
|
||||||
|
logging.info("正在获取邮箱验证码...")
|
||||||
|
response = requests.get('https://rnemail.nosqli.com/latest_email?recipient=' + account)
|
||||||
|
if response.status_code == 200:
|
||||||
|
email_data = response.json()
|
||||||
|
code = email_data.get('code')
|
||||||
|
if not code:
|
||||||
|
logging.error("获取验证码失败")
|
||||||
|
return False
|
||||||
|
logging.info(f"成功获取验证码: {code}")
|
||||||
|
logging.info("正在输入验证码...")
|
||||||
|
i = 0
|
||||||
|
for digit in code:
|
||||||
|
tab.ele(f"@data-index={i}").input(digit)
|
||||||
|
time.sleep(random.uniform(0.1, 0.3))
|
||||||
|
i += 1
|
||||||
|
logging.info("验证码输入完成")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
logging.error("获取验证码请求失败")
|
||||||
|
return False
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"验证码处理过程出错: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 进行第三次Turnstile验证,如果失败则返回False
|
||||||
|
if not handle_turnstile(tab):
|
||||||
|
logging.error("第三次Turnstile验证失败,但继续执行后续流程")
|
||||||
|
# 这里我们选择继续执行,因为此时可能已经成功注册
|
||||||
|
|
||||||
|
wait_time = random.randint(3, 6)
|
||||||
|
for i in range(wait_time):
|
||||||
|
logging.info(f"等待系统处理中... 剩余 {wait_time-i} 秒")
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
logging.info("正在获取账户信息...")
|
||||||
|
tab.get(settings_url)
|
||||||
|
try:
|
||||||
|
usage_selector = (
|
||||||
|
"css:div.col-span-2 > div > div > div > div > "
|
||||||
|
"div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > "
|
||||||
|
"span.font-mono.text-sm\\/\\[0\\.875rem\\]"
|
||||||
|
)
|
||||||
|
usage_ele = tab.ele(usage_selector)
|
||||||
|
if usage_ele:
|
||||||
|
usage_info = usage_ele.text
|
||||||
|
total_usage = usage_info.split("/")[-1].strip()
|
||||||
|
logging.info(f"账户可用额度上限: {total_usage}")
|
||||||
|
logging.info(
|
||||||
|
"请使用听泉助手:https://cursorapi.nosqli.com/upload/听泉cursor助手v3.5.3.zip "
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"获取账户额度信息失败: {str(e)}")
|
||||||
|
|
||||||
|
logging.info("\n=== 注册完成 ===")
|
||||||
|
account_info = f"Cursor 账号信息:\n邮箱: {account}\n密码: {password}"
|
||||||
|
logging.info(account_info)
|
||||||
|
time.sleep(5)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def get_allowed_domains(max_attempts=3, retry_interval=2):
|
||||||
|
"""获取当前允许的域名列表"""
|
||||||
|
attempts = 0
|
||||||
|
while attempts < max_attempts:
|
||||||
|
try:
|
||||||
|
response = requests.get('https://rnemail.nosqli.com/allowed_domains/list')
|
||||||
|
if response.status_code == 200:
|
||||||
|
# 解析字符串格式的 JSON
|
||||||
|
return json.loads(response.text) # 使用 json.loads 解析
|
||||||
|
else:
|
||||||
|
logging.error(f"获取域名列表失败: {response.status_code}")
|
||||||
|
return []
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"请求域名列表时发生错误: {str(e)}")
|
||||||
|
attempts += 1
|
||||||
|
if attempts < max_attempts:
|
||||||
|
logging.info(f"将在 {retry_interval} 秒后重试...")
|
||||||
|
time.sleep(retry_interval)
|
||||||
|
return [] # 返回空列表
|
||||||
|
|
||||||
|
|
||||||
|
class EmailGenerator:
|
||||||
|
def __init__(self,
|
||||||
|
password="".join(
|
||||||
|
random.choices(
|
||||||
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*",
|
||||||
|
k=12,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
selected_domain=None,
|
||||||
|
):
|
||||||
|
if not selected_domain:
|
||||||
|
logging.error("没有可用的域名,无法生成邮箱")
|
||||||
|
raise ValueError("没有可用的域名") # 抛出异常
|
||||||
|
self.selected_domain = selected_domain # 直接使用传入的域名
|
||||||
|
self.default_password = password
|
||||||
|
self.default_first_name = self.generate_random_name()
|
||||||
|
self.default_last_name = self.generate_random_name()
|
||||||
|
|
||||||
|
def generate_random_name(self, length=6):
|
||||||
|
"""生成随机用户名"""
|
||||||
|
first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||||
|
rest_letters = "".join(
|
||||||
|
random.choices("abcdefghijklmnopqrstuvwxyz", k=length - 1)
|
||||||
|
)
|
||||||
|
return first_letter + rest_letters
|
||||||
|
|
||||||
|
def generate_email(self):
|
||||||
|
"""生成随机邮箱地址"""
|
||||||
|
# 使用用户的名字和姓氏的首字母生成邮箱前缀
|
||||||
|
email_prefix = f"{self.default_first_name[0].lower()}{self.default_last_name.lower()}"
|
||||||
|
# 添加简短的随机数字以增加唯一性
|
||||||
|
random_suffix = "".join(random.choices("0123456789", k=2)) # 生成2位随机数字
|
||||||
|
return f"{email_prefix}{random_suffix}@{self.selected_domain}" # 使用选定的域名
|
||||||
|
|
||||||
|
def get_account_info(self):
|
||||||
|
"""获取完整的账号信息"""
|
||||||
|
return {
|
||||||
|
"email": self.generate_email(),
|
||||||
|
"password": self.default_password,
|
||||||
|
"first_name": self.default_first_name,
|
||||||
|
"last_name": self.default_last_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_agent(proxy_choice=None, custom_api=None):
|
||||||
|
"""获取user_agent
|
||||||
|
Args:
|
||||||
|
proxy_choice: 代理选择 (1=本地代理, 2=全局代理)
|
||||||
|
custom_api: 自定义代理API地址
|
||||||
|
"""
|
||||||
|
browser_manager = None
|
||||||
|
try:
|
||||||
|
# 使用JavaScript获取user agent
|
||||||
|
logging.info("初始化临时浏览器以获取User-Agent...")
|
||||||
|
browser_manager = BrowserManager()
|
||||||
|
browser, proxy_info = browser_manager.init_browser(
|
||||||
|
proxy_choice=proxy_choice,
|
||||||
|
custom_api=custom_api
|
||||||
|
)
|
||||||
|
user_agent = browser.latest_tab.run_js("return navigator.userAgent")
|
||||||
|
logging.info(f"获取到User-Agent: {user_agent}")
|
||||||
|
proxy_host, proxy_port, proxy_username, proxy_password = proxy_info
|
||||||
|
|
||||||
|
logging.info(f"初始化proxy: {proxy_host}:{proxy_port}")
|
||||||
|
return user_agent
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"获取user agent失败: {str(e)}")
|
||||||
|
return None
|
||||||
|
finally:
|
||||||
|
# 确保浏览器实例被正确关闭
|
||||||
|
if browser_manager:
|
||||||
|
logging.info("关闭临时浏览器...")
|
||||||
|
browser_manager.quit()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def print_end_message():
|
||||||
|
logging.info("\n\n\n\n\n")
|
||||||
|
logging.info("=" * 30)
|
||||||
|
logging.info("所有操作已完成")
|
||||||
|
logging.info("\n=== 获取更多信息 ===")
|
||||||
|
logging.info("🔥 vx: behikcigar")
|
||||||
|
logging.info("=" * 30)
|
||||||
|
logging.info(
|
||||||
|
"请使用听泉助手:https://cursorapi.nosqli.com/upload/听泉cursor助手v3.5.3.zip "
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print_logo()
|
||||||
|
browser_manager = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
logging.info("\n=== 初始化程序 ===")
|
||||||
|
|
||||||
|
# 选择账号同步API
|
||||||
|
print("\n请选择账号同步API:")
|
||||||
|
print("1. 听泉助手池")
|
||||||
|
print("2. 高质量号池")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
sync_api_choice = int(input("请输入选项 (1 或 2): ").strip())
|
||||||
|
if sync_api_choice in [1, 2]:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("无效的选项,请重新输入")
|
||||||
|
except ValueError:
|
||||||
|
print("请输入有效的数字")
|
||||||
|
|
||||||
|
# 根据选择创建AccountSync实例
|
||||||
|
account_sync = AccountSync(api_choice=sync_api_choice)
|
||||||
|
|
||||||
|
# 选择代理模式
|
||||||
|
print("\n请选择代理模式:")
|
||||||
|
print("1. 本地代理")
|
||||||
|
print("2. 全局代理")
|
||||||
|
print("3. 自定义代理API")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
proxy_choice = int(input("请输入选项 (1、2 或 3): ").strip())
|
||||||
|
if proxy_choice in [1, 2, 3]:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("无效的选项,请重新输入")
|
||||||
|
except ValueError:
|
||||||
|
print("请输入有效的数字")
|
||||||
|
|
||||||
|
# 如果选择自定义API,获取API地址
|
||||||
|
custom_api = None
|
||||||
|
if proxy_choice == 3:
|
||||||
|
custom_api = input("请输入自定义代理API地址: ").strip()
|
||||||
|
if not custom_api:
|
||||||
|
print("API地址不能为空,将使用默认全局代理")
|
||||||
|
proxy_choice = 2
|
||||||
|
|
||||||
|
# 将代理选择保存为全局变量,供后续使用
|
||||||
|
global_proxy_choice = proxy_choice
|
||||||
|
global_custom_api = custom_api
|
||||||
|
|
||||||
|
# 提示用户选择操作模式
|
||||||
|
print("\n请选择操作模式:")
|
||||||
|
print("1. 仅重置机器码")
|
||||||
|
print("2. 单个注册流程")
|
||||||
|
print("3. 批量注册流程")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
choice = int(input("请输入选项 (1、2 或 3): ").strip())
|
||||||
|
if choice in [1, 2, 3]:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("无效的选项,请重新输入")
|
||||||
|
except ValueError:
|
||||||
|
print("请输入有效的数字")
|
||||||
|
|
||||||
|
if choice == 1:
|
||||||
|
# 仅执行重置机器码
|
||||||
|
logging.info("机器码重置完成")
|
||||||
|
print_end_message()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# 获取注册数量
|
||||||
|
register_count = 1
|
||||||
|
if choice == 3:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
register_count = int(input("请输入要注册的账号数量: ").strip())
|
||||||
|
if register_count > 0:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("请输入大于0的数字")
|
||||||
|
except ValueError:
|
||||||
|
print("请输入有效的数字")
|
||||||
|
|
||||||
|
logging.info("正在初始化浏览器...")
|
||||||
|
|
||||||
|
# 获取user_agent,传递代理选择参数
|
||||||
|
user_agent = get_user_agent(proxy_choice=global_proxy_choice, custom_api=global_custom_api)
|
||||||
|
if not user_agent:
|
||||||
|
logging.error("获取user agent失败,使用默认值")
|
||||||
|
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||||
|
|
||||||
|
# 剔除user_agent中的"HeadlessChrome"
|
||||||
|
user_agent = user_agent.replace("HeadlessChrome", "Chrome")
|
||||||
|
|
||||||
|
# 获取并打印浏览器的user-agent
|
||||||
|
# user_agent = browser.latest_tab.run_js("return navigator.userAgent")
|
||||||
|
|
||||||
|
logging.info("正在初始化邮箱验证模块...")
|
||||||
|
email_handler = None # 先初始化为None,后面根据配置创建
|
||||||
|
logging.info("\n=== 配置信息 ===")
|
||||||
|
login_url = "https://authenticator.cursor.sh"
|
||||||
|
sign_up_url = "https://authenticator.cursor.sh/sign-up"
|
||||||
|
settings_url = "https://www.cursor.com/settings"
|
||||||
|
|
||||||
|
# 获取允许的域名
|
||||||
|
allowed_domains = get_allowed_domains() # 获取允许的域名列表
|
||||||
|
if not allowed_domains:
|
||||||
|
logging.error("没有可用的域名,无法进行注册")
|
||||||
|
sys.exit(1) # 退出程序
|
||||||
|
|
||||||
|
# 提示用户选择域名使用方式
|
||||||
|
print("请选择域名使用方式:")
|
||||||
|
print("1. 指定域名")
|
||||||
|
print("2. 随机轮询域名")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
choice = int(input("请输入选项 (1 或 2): ").strip())
|
||||||
|
if choice in [1, 2]:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("无效的选项,请重新输入")
|
||||||
|
except ValueError:
|
||||||
|
print("请输入有效的数字")
|
||||||
|
|
||||||
|
selected_domains = []
|
||||||
|
|
||||||
|
if choice == 1:
|
||||||
|
# 用户指定域名
|
||||||
|
print("可用域名:")
|
||||||
|
for index, domain in enumerate(allowed_domains):
|
||||||
|
print(f"{index + 1}. {domain}")
|
||||||
|
print("请输入要使用的域名索引,以逗号分隔或范围(如 1-20):")
|
||||||
|
indices = input("例如: 1,2,3 或 1-20").strip().split(",")
|
||||||
|
|
||||||
|
selected_domains = []
|
||||||
|
for index in indices:
|
||||||
|
if '-' in index:
|
||||||
|
start, end = index.split('-')
|
||||||
|
try:
|
||||||
|
start = int(start)
|
||||||
|
end = int(end)
|
||||||
|
selected_domains.extend(allowed_domains[start - 1:end]) # 添加范围内的域名
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
logging.error(f"范围输入无效: {index}")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
idx = int(index)
|
||||||
|
selected_domains.append(allowed_domains[idx - 1]) # 添加单个域名
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
logging.error(f"索引输入无效: {index}")
|
||||||
|
else:
|
||||||
|
# 随机轮询域名
|
||||||
|
selected_domains = allowed_domains
|
||||||
|
|
||||||
|
# 成功账号列表
|
||||||
|
successful_accounts = []
|
||||||
|
# 超时账号计数
|
||||||
|
timeout_accounts = 0
|
||||||
|
# 配置数量
|
||||||
|
config_count = len(selected_domains) # 使用用户选择的域名数量
|
||||||
|
|
||||||
|
for i in range(register_count):
|
||||||
|
logging.info(f"\n=== 开始注册第 {i + 1}/{register_count} 个账号 ===")
|
||||||
|
current_config_index = i % config_count # 当前使用的配置索引
|
||||||
|
logging.info(f"使用配置 {current_config_index + 1} / {config_count}") # 打印当前使用的配置和总配置
|
||||||
|
selected_domain = selected_domains[current_config_index] # 选择当前域名
|
||||||
|
logging.info(f"使用域名: {selected_domain}")
|
||||||
|
|
||||||
|
# 记录注册开始时间
|
||||||
|
registration_start_time = time.time()
|
||||||
|
# 设置注册超时时间(4分钟 = 240秒)
|
||||||
|
registration_timeout = 240
|
||||||
|
logging.info(f"注册超时设置为 {registration_timeout} 秒")
|
||||||
|
|
||||||
|
# 为每个账号创建新的浏览器实例,确保账号纯净
|
||||||
|
if browser_manager:
|
||||||
|
logging.info("关闭上一个浏览器实例...")
|
||||||
|
browser_manager.quit()
|
||||||
|
|
||||||
|
logging.info("创建新的浏览器实例...")
|
||||||
|
browser_manager = BrowserManager()
|
||||||
|
# 使用选择的代理模式初始化浏览器
|
||||||
|
browser, proxy_info = browser_manager.init_browser(
|
||||||
|
user_agent,
|
||||||
|
proxy_choice=global_proxy_choice,
|
||||||
|
custom_api=global_custom_api
|
||||||
|
)
|
||||||
|
|
||||||
|
proxy_host, proxy_port, proxy_username, proxy_password, proxy_data = proxy_info
|
||||||
|
current_proxies = {
|
||||||
|
'http': f'http://{proxy_username}:{proxy_password}@{proxy_host}:{proxy_port}',
|
||||||
|
'https': f'http://{proxy_username}:{proxy_password}@{proxy_host}:{proxy_port}'
|
||||||
|
}
|
||||||
|
logging.info(f"API将使用相同代理: {proxy_host}:{proxy_port}")
|
||||||
|
logging.info(f"API将使用相同代理: {proxy_data}")
|
||||||
|
|
||||||
|
# 生成邮箱
|
||||||
|
email_generator = EmailGenerator(selected_domain=selected_domain) # 传递选定的域名
|
||||||
|
account_info = email_generator.get_account_info() # 获取账号信息
|
||||||
|
account = account_info['email'] # 使用生成的邮箱
|
||||||
|
password = account_info['password'] # 使用生成的密码
|
||||||
|
first_name = account_info['first_name'] # 使用生成的名字
|
||||||
|
last_name = account_info['last_name'] # 使用生成的姓氏
|
||||||
|
|
||||||
|
logging.info(f"生成的邮箱账号: {account}")
|
||||||
|
auto_update_cursor_auth = True
|
||||||
|
|
||||||
|
tab = browser.latest_tab
|
||||||
|
tab.run_js("try { turnstile.reset() } catch(e) { }")
|
||||||
|
|
||||||
|
logging.info("\n=== 开始注册流程 ===")
|
||||||
|
logging.info(f"正在访问登录页面: {login_url}")
|
||||||
|
tab.get(login_url)
|
||||||
|
|
||||||
|
# 检查是否超时
|
||||||
|
if check_timeout(registration_start_time):
|
||||||
|
logging.warning("放弃当前注册,继续下一个账号")
|
||||||
|
timeout_accounts += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
if sign_up_account(browser, tab, registration_start_time):
|
||||||
|
# 检查是否超时
|
||||||
|
if check_timeout(registration_start_time):
|
||||||
|
logging.warning("放弃当前注册,继续下一个账号")
|
||||||
|
timeout_accounts += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
logging.info("正在获取会话令牌...")
|
||||||
|
token = get_cursor_session_token(tab, start_time=registration_start_time)
|
||||||
|
if token:
|
||||||
|
# 检查是否超时
|
||||||
|
if check_timeout(registration_start_time):
|
||||||
|
logging.warning("放弃当前注册,继续下一个账号")
|
||||||
|
timeout_accounts += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
logging.info("更新认证信息...")
|
||||||
|
|
||||||
|
logging.info("重置机器码...")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
logging.info(f"API将使用相同代理: {proxy_host}:{proxy_port}")
|
||||||
|
|
||||||
|
# 保存成功注册的账号信息
|
||||||
|
account_data = {
|
||||||
|
"email": account,
|
||||||
|
"password": password,
|
||||||
|
"token": token,
|
||||||
|
"register_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
"first_name": first_name,
|
||||||
|
"last_name": last_name,
|
||||||
|
'user_agent': user_agent,
|
||||||
|
'proxy_host': proxy_host,
|
||||||
|
'proxy_port': proxy_port,
|
||||||
|
'proxy_username': proxy_username,
|
||||||
|
'proxy_password': proxy_password,
|
||||||
|
'proxy_data': proxy_data
|
||||||
|
}
|
||||||
|
|
||||||
|
logging.info("更新认证信息...")
|
||||||
|
|
||||||
|
# 添加到成功账号列表
|
||||||
|
successful_accounts.append(account_data)
|
||||||
|
|
||||||
|
# # 同步到服务器
|
||||||
|
if account_sync.sync_account(account_data):
|
||||||
|
logging.info("账号已成功同步到服务器")
|
||||||
|
else:
|
||||||
|
logging.warning("账号同步到服务器失败")
|
||||||
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
logging.error("获取会话令牌失败,注册流程未完成")
|
||||||
|
|
||||||
|
# 计算本次注册用时
|
||||||
|
registration_elapsed = time.time() - registration_start_time
|
||||||
|
logging.info(f"本次注册用时: {registration_elapsed:.1f} 秒")
|
||||||
|
|
||||||
|
# 注册间隔,避免频率过快
|
||||||
|
if i < register_count - 1: # 最后一个账号不需要等待
|
||||||
|
wait_time = random.randint(1, 4)
|
||||||
|
logging.info(f"等待 {wait_time} 秒后继续下一个注册...")
|
||||||
|
time.sleep(wait_time)
|
||||||
|
|
||||||
|
# 即使注册失败,也关闭当前浏览器实例,保证下一个注册过程使用全新实例
|
||||||
|
if browser_manager:
|
||||||
|
logging.info("关闭当前浏览器实例...")
|
||||||
|
browser_manager.quit()
|
||||||
|
browser_manager = None
|
||||||
|
|
||||||
|
# 打印注册结果统计
|
||||||
|
logging.info("\n\n=== 注册结果统计 ===")
|
||||||
|
logging.info(f"总计尝试: {register_count}")
|
||||||
|
logging.info(f"成功注册: {len(successful_accounts)}")
|
||||||
|
logging.info(f"超时注册: {timeout_accounts}")
|
||||||
|
logging.info(f"其他失败: {register_count - len(successful_accounts) - timeout_accounts}")
|
||||||
|
logging.info("所有成功注册的账号信息已保存到 successful_accounts.json")
|
||||||
|
|
||||||
|
print_end_message()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"程序执行出现错误: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
logging.error(traceback.format_exc())
|
||||||
|
finally:
|
||||||
|
# 确保浏览器实例被正确关闭
|
||||||
|
if browser_manager:
|
||||||
|
logging.info("清理资源:正在关闭浏览器...")
|
||||||
|
try:
|
||||||
|
browser_manager.quit()
|
||||||
|
logging.info("浏览器已成功关闭")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"关闭浏览器时出错: {str(e)}")
|
||||||
|
|
||||||
|
logging.info("程序执行完毕")
|
||||||
|
# 使用消息框代替input函数
|
||||||
|
try:
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import messagebox
|
||||||
|
root = tk.Tk()
|
||||||
|
root.withdraw() # 隐藏主窗口
|
||||||
|
messagebox.showinfo("程序执行完毕", "程序执行完毕,点击确定退出...")
|
||||||
|
root.destroy()
|
||||||
|
except Exception as e:
|
||||||
|
# 如果tkinter不可用,则使用其他方式
|
||||||
|
import time
|
||||||
|
logging.info(f"无法显示消息框: {str(e)}")
|
||||||
|
print("\n程序执行完毕,5秒后自动退出...")
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
30
kdl_Chromium_Proxy/background.js
Normal file
30
kdl_Chromium_Proxy/background.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
var config = {
|
||||||
|
mode: "fixed_servers",
|
||||||
|
rules: {
|
||||||
|
singleProxy: {
|
||||||
|
scheme: "http",
|
||||||
|
host: "60.188.78.8",
|
||||||
|
port: parseInt(20138)
|
||||||
|
},
|
||||||
|
bypassList: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chrome.proxy.settings.set({value: config, scope: "regular"}, function() {});
|
||||||
|
|
||||||
|
function callbackFn(details) {
|
||||||
|
return {
|
||||||
|
authCredentials: {
|
||||||
|
username: "CC353276",
|
||||||
|
password: "0F2F15563675"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
chrome.webRequest.onAuthRequired.addListener(
|
||||||
|
callbackFn,
|
||||||
|
{urls: ["<all_urls>"]},
|
||||||
|
['blocking']
|
||||||
|
);
|
||||||
|
|
||||||
21
kdl_Chromium_Proxy/manifest.json
Normal file
21
kdl_Chromium_Proxy/manifest.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
|
||||||
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "kdl_Chromium_Proxy",
|
||||||
|
"permissions": [
|
||||||
|
"proxy",
|
||||||
|
"tabs",
|
||||||
|
"unlimitedStorage",
|
||||||
|
"storage",
|
||||||
|
"<all_urls>",
|
||||||
|
"webRequest",
|
||||||
|
"webRequestBlocking",
|
||||||
|
"browsingData"
|
||||||
|
],
|
||||||
|
"background": {
|
||||||
|
"scripts": ["background.js"]
|
||||||
|
},
|
||||||
|
"minimum_chrome_version":"22.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
84
logger.py
Normal file
84
logger.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
# Configure logging
|
||||||
|
log_dir = "logs"
|
||||||
|
if not os.path.exists(log_dir):
|
||||||
|
os.makedirs(log_dir)
|
||||||
|
|
||||||
|
|
||||||
|
class PrefixFormatter(logging.Formatter):
|
||||||
|
"""自定义格式化器,为 DEBUG 级别日志添加开源项目前缀"""
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
if record.levelno == logging.DEBUG: # 只给 DEBUG 级别添加前缀
|
||||||
|
record.msg = f"[开源项目:https://github.com/chengazhen/cursor-auto-free] {record.msg}"
|
||||||
|
return super().format(record)
|
||||||
|
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.DEBUG,
|
||||||
|
format="%(asctime)s - %(levelname)s - %(message)s",
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler(
|
||||||
|
os.path.join(log_dir, f"{datetime.now().strftime('%Y-%m-%d')}.log"),
|
||||||
|
encoding="utf-8",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
# 为文件处理器设置自定义格式化器
|
||||||
|
for handler in logging.getLogger().handlers:
|
||||||
|
if isinstance(handler, logging.FileHandler):
|
||||||
|
handler.setFormatter(
|
||||||
|
PrefixFormatter("%(asctime)s - %(levelname)s - %(message)s")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# 创建控制台处理器
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setLevel(logging.INFO)
|
||||||
|
console_handler.setFormatter(PrefixFormatter("%(message)s"))
|
||||||
|
|
||||||
|
# 将控制台处理器添加到日志记录器
|
||||||
|
logging.getLogger().addHandler(console_handler)
|
||||||
|
|
||||||
|
# 打印日志目录所在路径
|
||||||
|
logging.info(f"Logger initialized, log directory: {os.path.abspath(log_dir)}")
|
||||||
|
|
||||||
|
|
||||||
|
def main_task():
|
||||||
|
"""
|
||||||
|
Main task execution function. Simulates a workflow and handles errors.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
logging.info("Starting the main task...")
|
||||||
|
|
||||||
|
# Simulated task and error condition
|
||||||
|
if some_condition():
|
||||||
|
raise ValueError("Simulated error occurred.")
|
||||||
|
|
||||||
|
logging.info("Main task completed successfully.")
|
||||||
|
|
||||||
|
except ValueError as ve:
|
||||||
|
logging.error(f"ValueError occurred: {ve}", exc_info=True)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Unexpected error occurred: {e}", exc_info=True)
|
||||||
|
finally:
|
||||||
|
logging.info("Task execution finished.")
|
||||||
|
|
||||||
|
|
||||||
|
def some_condition():
|
||||||
|
"""
|
||||||
|
Simulates an error condition. Returns True to trigger an error.
|
||||||
|
Replace this logic with actual task conditions.
|
||||||
|
"""
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Application workflow
|
||||||
|
logging.info("Application started.")
|
||||||
|
main_task()
|
||||||
|
logging.info("Application exited.")
|
||||||
16
logo.py
Normal file
16
logo.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
CURSOR_LOGO = """
|
||||||
|
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
|
||||||
|
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
|
||||||
|
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝
|
||||||
|
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗
|
||||||
|
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
|
||||||
|
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def print_logo():
|
||||||
|
print(CURSOR_LOGO)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print_logo()
|
||||||
36458
names-dataset.txt
Normal file
36458
names-dataset.txt
Normal file
File diff suppressed because it is too large
Load Diff
150
package.py
Normal file
150
package.py
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import platform
|
||||||
|
import datetime
|
||||||
|
import argparse
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
def check_pyinstaller():
|
||||||
|
"""检查是否已安装 PyInstaller,如果没有则安装"""
|
||||||
|
try:
|
||||||
|
subprocess.run(["pyinstaller", "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True)
|
||||||
|
print("✅ PyInstaller 已安装")
|
||||||
|
return True
|
||||||
|
except (subprocess.SubprocessError, FileNotFoundError):
|
||||||
|
print("❌ PyInstaller 未安装,正在安装...")
|
||||||
|
try:
|
||||||
|
subprocess.run([sys.executable, "-m", "pip", "install", "pyinstaller"], check=True)
|
||||||
|
print("✅ PyInstaller 安装成功")
|
||||||
|
return True
|
||||||
|
except subprocess.SubprocessError as e:
|
||||||
|
print(f"❌ PyInstaller 安装失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def create_executable(app_name, keep_previous):
|
||||||
|
"""使用 PyInstaller 创建可执行文件"""
|
||||||
|
print(f"开始打包 cursor_pro_register.py 为 {app_name}...")
|
||||||
|
|
||||||
|
dist_folder = "dist"
|
||||||
|
build_folder = "build"
|
||||||
|
|
||||||
|
# 如果不保留旧版本,则清除dist和build目录
|
||||||
|
if not keep_previous:
|
||||||
|
for folder in [dist_folder, build_folder]:
|
||||||
|
if os.path.exists(folder):
|
||||||
|
shutil.rmtree(folder)
|
||||||
|
print(f"已清理 {folder} 文件夹")
|
||||||
|
else:
|
||||||
|
# 确保目录存在
|
||||||
|
os.makedirs(dist_folder, exist_ok=True)
|
||||||
|
os.makedirs(build_folder, exist_ok=True)
|
||||||
|
print("将保留先前版本")
|
||||||
|
|
||||||
|
# 生成版本标识(用于区分构建文件夹)
|
||||||
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
build_dir = os.path.join(build_folder, f"build_{timestamp}")
|
||||||
|
|
||||||
|
# 检查是否存在 spec 文件并删除
|
||||||
|
if os.path.exists("cursor_pro_register.spec"):
|
||||||
|
os.remove("cursor_pro_register.spec")
|
||||||
|
print("已删除旧的 spec 文件")
|
||||||
|
|
||||||
|
# 确定输出文件名
|
||||||
|
output_name = app_name if app_name else "cursor动态账号注册机"
|
||||||
|
print(f"输出文件名设置为: {output_name}")
|
||||||
|
|
||||||
|
# 构建命令
|
||||||
|
cmd = [
|
||||||
|
"pyinstaller",
|
||||||
|
"--onefile", # 创建单个可执行文件
|
||||||
|
"--name", output_name,
|
||||||
|
"--clean", # 清理临时文件
|
||||||
|
"--workpath", build_dir, # 使用唯一的构建目录
|
||||||
|
# 移除noconsole选项,使用控制台模式
|
||||||
|
"--noupx", # 不使用UPX压缩,提高兼容性
|
||||||
|
"--add-data", f"turnstilePatch{os.pathsep}turnstilePatch", # 添加额外文件
|
||||||
|
"cursor_pro_register.py"
|
||||||
|
]
|
||||||
|
|
||||||
|
# 过滤空字符串
|
||||||
|
cmd = [item for item in cmd if item]
|
||||||
|
|
||||||
|
print(f"执行命令: {' '.join(cmd)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 运行 PyInstaller
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
executable_name = f"{output_name}.exe" if platform.system() == "Windows" else output_name
|
||||||
|
executable_path = os.path.join("dist", executable_name)
|
||||||
|
|
||||||
|
if os.path.exists(executable_path):
|
||||||
|
print(f"\n✅ 打包成功! 可执行文件位于: {executable_path}")
|
||||||
|
|
||||||
|
# 获取文件大小
|
||||||
|
size_bytes = os.path.getsize(executable_path)
|
||||||
|
size_mb = size_bytes / (1024 * 1024)
|
||||||
|
print(f"可执行文件大小: {size_mb:.2f} MB")
|
||||||
|
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"❌ 打包过程可能出错,找不到可执行文件: {executable_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except subprocess.SubprocessError as e:
|
||||||
|
print(f"❌ 打包失败: {e}")
|
||||||
|
traceback.print_exc() # 打印详细的错误信息
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 50)
|
||||||
|
print("Cursor Pro Register 打包工具")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# 解析命令行参数
|
||||||
|
parser = argparse.ArgumentParser(description="Cursor Pro Register 打包工具")
|
||||||
|
parser.add_argument("app_name", nargs="?", default="cursor动态账号注册机", help="应用名称")
|
||||||
|
parser.add_argument("keep_previous", nargs="?", type=int, default=0, help="是否保留先前版本 (1=是, 0=否)")
|
||||||
|
|
||||||
|
try:
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
print(f"接收到的参数: 应用名称=\"{args.app_name}\", 保留旧版本={bool(args.keep_previous)}")
|
||||||
|
|
||||||
|
# 检查当前目录是否有 cursor_pro_register.py
|
||||||
|
if not os.path.exists("cursor_pro_register.py"):
|
||||||
|
print("❌ 当前目录找不到 cursor_pro_register.py 文件!")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 检查是否有 turnstilePatch 目录
|
||||||
|
if not os.path.exists("turnstilePatch"):
|
||||||
|
print("⚠️ 警告: 找不到 turnstilePatch 目录,打包可能会缺少必要文件")
|
||||||
|
try:
|
||||||
|
choice = input("是否继续打包? (y/n): ")
|
||||||
|
if choice.lower() != 'y':
|
||||||
|
print("打包已取消")
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception:
|
||||||
|
# 如果input()出错,默认继续
|
||||||
|
print("⚠️ 无法获取用户输入,默认继续打包...")
|
||||||
|
|
||||||
|
# 检查并安装 PyInstaller
|
||||||
|
if not check_pyinstaller():
|
||||||
|
print("❌ 无法安装 PyInstaller,打包终止")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 创建可执行文件
|
||||||
|
if create_executable(args.app_name, bool(args.keep_previous)):
|
||||||
|
print("\n打包过程完成!")
|
||||||
|
else:
|
||||||
|
print("\n打包过程出现错误,请检查上面的日志信息")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ 执行过程中出现未预期的错误: {e}")
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
print("程序将在5秒后自动退出...")
|
||||||
|
time.sleep(5)
|
||||||
223
package_spec.py
Normal file
223
package_spec.py
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
"""
|
||||||
|
更高级的打包脚本,使用 spec 文件进行更精细的控制
|
||||||
|
这个脚本会生成一个 .spec 文件并基于它进行打包
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import platform
|
||||||
|
import datetime
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def install_requirements():
|
||||||
|
"""安装必要的依赖"""
|
||||||
|
requirements = ["pyinstaller", "colorama", "requests"]
|
||||||
|
|
||||||
|
for req in requirements:
|
||||||
|
print(f"正在检查 {req}...")
|
||||||
|
try:
|
||||||
|
__import__(req)
|
||||||
|
print(f"✅ {req} 已安装")
|
||||||
|
except ImportError:
|
||||||
|
print(f"❌ {req} 未安装,正在安装...")
|
||||||
|
try:
|
||||||
|
subprocess.run([sys.executable, "-m", "pip", "install", req], check=True)
|
||||||
|
print(f"✅ {req} 安装成功")
|
||||||
|
except subprocess.SubprocessError as e:
|
||||||
|
print(f"❌ {req} 安装失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def create_spec_file(app_name):
|
||||||
|
"""创建 .spec 文件"""
|
||||||
|
print("正在创建 .spec 文件...")
|
||||||
|
|
||||||
|
# 获取当前工作目录
|
||||||
|
current_dir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
# 自动检测项目中的所有 Python 文件
|
||||||
|
python_files = []
|
||||||
|
for file in os.listdir(current_dir):
|
||||||
|
if file.endswith('.py') and file != 'package.py' and file != 'package_spec.py':
|
||||||
|
python_files.append(file)
|
||||||
|
|
||||||
|
main_script = 'cursor_pro_register.py'
|
||||||
|
if main_script not in python_files:
|
||||||
|
print(f"❌ 找不到主脚本文件 {main_script}!")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 确保 turnstilePatch 目录存在
|
||||||
|
turnstile_path = os.path.join(current_dir, 'turnstilePatch')
|
||||||
|
if not os.path.exists(turnstile_path):
|
||||||
|
print("⚠️ 警告: 找不到 turnstilePatch 目录,功能可能受限")
|
||||||
|
|
||||||
|
# 获取资源文件
|
||||||
|
datas = []
|
||||||
|
if os.path.exists(turnstile_path):
|
||||||
|
datas.append((turnstile_path, 'turnstilePatch'))
|
||||||
|
|
||||||
|
# 检查其他可能的资源文件夹
|
||||||
|
for folder in ['assets', 'config', 'data']:
|
||||||
|
folder_path = os.path.join(current_dir, folder)
|
||||||
|
if os.path.exists(folder_path) and os.path.isdir(folder_path):
|
||||||
|
datas.append((folder_path, folder))
|
||||||
|
|
||||||
|
# 确定输出文件名
|
||||||
|
output_name = app_name if app_name else "cursor动态账号注册机"
|
||||||
|
print(f"输出文件名设置为: {output_name}")
|
||||||
|
|
||||||
|
# 生成 spec 文件内容
|
||||||
|
spec_content = f"""# -*- mode: python ; coding: utf-8 -*-
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
block_cipher = None
|
||||||
|
|
||||||
|
# 收集项目文件
|
||||||
|
a = Analysis(
|
||||||
|
['{main_script}'],
|
||||||
|
pathex=['{current_dir}'],
|
||||||
|
binaries=[],
|
||||||
|
datas={datas},
|
||||||
|
hiddenimports=[
|
||||||
|
'colorama', 'requests', 'json', 'random', 'datetime', 'enum', 'dotenv',
|
||||||
|
'tkinter', 'tkinter.messagebox', 'tkinter.ttk',
|
||||||
|
'threading', 'queue', 'urllib', 'urllib.request', 'urllib.parse',
|
||||||
|
'time', 'sys', 'os', 'traceback', 'logging', 'typing', 're', 'base64',
|
||||||
|
'platform', 'collections'
|
||||||
|
],
|
||||||
|
hookspath=[],
|
||||||
|
hooksconfig={{}},
|
||||||
|
runtime_hooks=[],
|
||||||
|
excludes=[],
|
||||||
|
win_no_prefer_redirects=False,
|
||||||
|
win_private_assemblies=False,
|
||||||
|
cipher=block_cipher,
|
||||||
|
noarchive=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 创建可执行文件
|
||||||
|
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
|
||||||
|
|
||||||
|
exe = EXE(
|
||||||
|
pyz,
|
||||||
|
a.scripts,
|
||||||
|
a.binaries,
|
||||||
|
a.zipfiles,
|
||||||
|
a.datas,
|
||||||
|
[],
|
||||||
|
name='{output_name}',
|
||||||
|
debug=False,
|
||||||
|
bootloader_ignore_signals=False,
|
||||||
|
strip=False,
|
||||||
|
upx=False,
|
||||||
|
upx_exclude=[],
|
||||||
|
runtime_tmpdir=None,
|
||||||
|
console=True,
|
||||||
|
disable_windowed_traceback=False,
|
||||||
|
argv_emulation=False,
|
||||||
|
target_arch=None,
|
||||||
|
codesign_identity=None,
|
||||||
|
entitlements_file=None,
|
||||||
|
icon=None,
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
spec_file = 'cursor_pro_register.spec'
|
||||||
|
with open(spec_file, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(spec_content)
|
||||||
|
|
||||||
|
print(f"✅ 已创建 {spec_file}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
def build_from_spec(keep_previous, app_name):
|
||||||
|
"""从 spec 文件构建可执行文件"""
|
||||||
|
print("开始从 spec 文件构建可执行文件...")
|
||||||
|
|
||||||
|
dist_folder = "dist"
|
||||||
|
build_folder = "build"
|
||||||
|
|
||||||
|
# 如果不保留旧版本,则清除dist和build目录
|
||||||
|
if not keep_previous:
|
||||||
|
for folder in [dist_folder, build_folder]:
|
||||||
|
if os.path.exists(folder):
|
||||||
|
shutil.rmtree(folder)
|
||||||
|
print(f"已清理 {folder} 文件夹")
|
||||||
|
else:
|
||||||
|
# 确保目录存在
|
||||||
|
os.makedirs(dist_folder, exist_ok=True)
|
||||||
|
os.makedirs(build_folder, exist_ok=True)
|
||||||
|
print("将保留先前版本")
|
||||||
|
|
||||||
|
# 生成版本标识(用于区分构建文件夹)
|
||||||
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
build_dir = os.path.join(build_folder, f"build_{timestamp}")
|
||||||
|
|
||||||
|
# 确定输出文件名
|
||||||
|
output_name = app_name if app_name else "cursor动态账号注册机"
|
||||||
|
|
||||||
|
# 构建命令
|
||||||
|
cmd = ["pyinstaller", "--workpath", build_dir, "cursor_pro_register.spec", "--clean", "--noupx"]
|
||||||
|
print(f"执行命令: {' '.join(cmd)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 运行 PyInstaller 使用 spec 文件
|
||||||
|
subprocess.run(cmd, check=True)
|
||||||
|
|
||||||
|
# 检查构建结果
|
||||||
|
executable_name = f"{output_name}.exe" if platform.system() == "Windows" else output_name
|
||||||
|
executable_path = os.path.join("dist", executable_name)
|
||||||
|
|
||||||
|
if os.path.exists(executable_path):
|
||||||
|
print(f"\n✅ 打包成功! 可执行文件位于: {executable_path}")
|
||||||
|
|
||||||
|
# 获取文件大小
|
||||||
|
size_bytes = os.path.getsize(executable_path)
|
||||||
|
size_mb = size_bytes / (1024 * 1024)
|
||||||
|
print(f"可执行文件大小: {size_mb:.2f} MB")
|
||||||
|
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"❌ 打包过程可能出错,找不到可执行文件: {executable_path}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except subprocess.SubprocessError as e:
|
||||||
|
print(f"❌ 打包失败: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 50)
|
||||||
|
print("Cursor Pro Register 高级打包工具")
|
||||||
|
print("=" * 50)
|
||||||
|
|
||||||
|
# 解析命令行参数
|
||||||
|
parser = argparse.ArgumentParser(description="Cursor Pro Register 高级打包工具")
|
||||||
|
parser.add_argument("app_name", nargs="?", default="cursor动态账号注册机", help="应用名称")
|
||||||
|
parser.add_argument("keep_previous", nargs="?", type=int, default=0, help="是否保留先前版本 (1=是, 0=否)")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
print(f"接收到的参数: 应用名称=\"{args.app_name}\", 保留旧版本={bool(args.keep_previous)}")
|
||||||
|
|
||||||
|
# 安装必要的依赖
|
||||||
|
if not install_requirements():
|
||||||
|
print("❌ 安装依赖失败,打包终止")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 创建 spec 文件
|
||||||
|
if not create_spec_file(args.app_name):
|
||||||
|
print("❌ 创建 spec 文件失败,打包终止")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 从 spec 文件构建
|
||||||
|
if build_from_spec(bool(args.keep_previous), args.app_name):
|
||||||
|
print("\n打包过程完成!")
|
||||||
|
else:
|
||||||
|
print("\n打包过程出现错误,请检查上面的日志信息")
|
||||||
|
|
||||||
|
input("按任意键退出...")
|
||||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
DrissionPage==4.1.0.9
|
||||||
|
colorama==0.4.6
|
||||||
|
python-dotenv==1.0.0
|
||||||
|
pyinstaller
|
||||||
169
run.bat
Normal file
169
run.bat
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
@echo off
|
||||||
|
rem 使用更可靠的方式设置UTF-8编码
|
||||||
|
chcp 65001 > nul
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
title Cursor Pro Register 打包工具
|
||||||
|
|
||||||
|
cls
|
||||||
|
echo ================================================
|
||||||
|
echo Cursor Pro Register 打包工具
|
||||||
|
echo ================================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
rem 检测Python是否已安装
|
||||||
|
python --version > nul 2>&1
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo [错误] 未检测到Python安装,请先安装Python 3.8或更高版本
|
||||||
|
echo 您可以从 https://www.python.org/downloads/ 下载安装
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [信息] 检测到Python已安装
|
||||||
|
echo.
|
||||||
|
|
||||||
|
rem 获取当前日期和时间
|
||||||
|
for /f "tokens=2 delims==" %%a in ('wmic OS Get localdatetime /value') do set "dt=%%a"
|
||||||
|
set "YEAR=%dt:~0,4%"
|
||||||
|
set "MONTH=%dt:~4,2%"
|
||||||
|
set "DAY=%dt:~6,2%"
|
||||||
|
set "TODAY=%YEAR%%MONTH%%DAY%"
|
||||||
|
|
||||||
|
rem 默认设置
|
||||||
|
set "DEFAULT_VERSION=0.1"
|
||||||
|
set "DEFAULT_NAME=cursor注册机"
|
||||||
|
set "APP_VERSION=%DEFAULT_VERSION%"
|
||||||
|
set "APP_NAME=%DEFAULT_NAME%"
|
||||||
|
set "KEEP_PREVIOUS=1"
|
||||||
|
|
||||||
|
:Menu
|
||||||
|
cls
|
||||||
|
echo ================================================
|
||||||
|
echo Cursor Pro Register 打包工具
|
||||||
|
echo ================================================
|
||||||
|
echo.
|
||||||
|
echo [信息] 检测到Python已安装
|
||||||
|
echo.
|
||||||
|
rem 显示菜单
|
||||||
|
echo 请选择打包操作:
|
||||||
|
echo [1] 快速打包(使用默认设置)
|
||||||
|
echo [2] 自定义打包设置
|
||||||
|
echo [3] 高级打包模式(使用spec文件)
|
||||||
|
echo [4] 退出
|
||||||
|
echo.
|
||||||
|
|
||||||
|
set /p USER_CHOICE="请选择 [1-4]: "
|
||||||
|
|
||||||
|
rem 处理选择
|
||||||
|
if "%USER_CHOICE%"=="1" (
|
||||||
|
echo.
|
||||||
|
echo [信息] 正在使用默认设置进行打包
|
||||||
|
goto StartPackaging
|
||||||
|
) else if "%USER_CHOICE%"=="2" (
|
||||||
|
echo.
|
||||||
|
echo [信息] 请输入自定义设置
|
||||||
|
|
||||||
|
set /p APP_NAME="应用名称 [默认: %DEFAULT_NAME%]: "
|
||||||
|
if "!APP_NAME!"=="" set "APP_NAME=%DEFAULT_NAME%"
|
||||||
|
|
||||||
|
set /p APP_VERSION="版本号 [默认: %DEFAULT_VERSION%]: "
|
||||||
|
if "!APP_VERSION!"=="" set "APP_VERSION=%DEFAULT_VERSION%"
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo 是否保留之前的打包版本?
|
||||||
|
set /p KEEP_CHOICE="是否保留 [Y=是, N=否, 默认=Y]: "
|
||||||
|
|
||||||
|
if /i "!KEEP_CHOICE!"=="N" (
|
||||||
|
set "KEEP_PREVIOUS=0"
|
||||||
|
) else (
|
||||||
|
set "KEEP_PREVIOUS=1"
|
||||||
|
)
|
||||||
|
|
||||||
|
goto StartPackaging
|
||||||
|
) else if "%USER_CHOICE%"=="3" (
|
||||||
|
echo.
|
||||||
|
echo [信息] 正在启动高级打包模式
|
||||||
|
|
||||||
|
set /p APP_NAME="应用名称 [默认: %DEFAULT_NAME%]: "
|
||||||
|
if "!APP_NAME!"=="" set "APP_NAME=%DEFAULT_NAME%"
|
||||||
|
|
||||||
|
set /p APP_VERSION="版本号 [默认: %DEFAULT_VERSION%]: "
|
||||||
|
if "!APP_VERSION!"=="" set "APP_VERSION=%DEFAULT_VERSION%"
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo 是否保留之前的打包版本?
|
||||||
|
set /p KEEP_CHOICE="是否保留 [Y=是, N=否, 默认=Y]: "
|
||||||
|
|
||||||
|
if /i "!KEEP_CHOICE!"=="N" (
|
||||||
|
set "KEEP_PREVIOUS=0"
|
||||||
|
) else (
|
||||||
|
set "KEEP_PREVIOUS=1"
|
||||||
|
)
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo [信息] 正在使用高级打包模式...
|
||||||
|
|
||||||
|
rem 检查Python脚本是否存在
|
||||||
|
if not exist package_spec.py (
|
||||||
|
echo [错误] 找不到package_spec.py文件,无法进行高级打包
|
||||||
|
echo 按任意键返回主菜单...
|
||||||
|
pause > nul
|
||||||
|
goto Menu
|
||||||
|
)
|
||||||
|
|
||||||
|
python package_spec.py "!APP_NAME!v!APP_VERSION!" !KEEP_PREVIOUS!
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo [错误] Python脚本执行失败,请检查上面的错误信息
|
||||||
|
)
|
||||||
|
goto End
|
||||||
|
) else if "%USER_CHOICE%"=="4" (
|
||||||
|
echo.
|
||||||
|
echo [信息] 已取消打包操作
|
||||||
|
goto End
|
||||||
|
) else (
|
||||||
|
echo.
|
||||||
|
echo [错误] 无效的选择,请重新输入...
|
||||||
|
timeout /t 2 > nul
|
||||||
|
goto Menu
|
||||||
|
)
|
||||||
|
|
||||||
|
:StartPackaging
|
||||||
|
rem 构建最终的应用名称
|
||||||
|
set "FINAL_NAME=!APP_NAME!v!APP_VERSION!"
|
||||||
|
echo.
|
||||||
|
echo [信息] 使用以下设置进行打包:
|
||||||
|
echo 应用名称: !FINAL_NAME!
|
||||||
|
echo 保留旧版本: !KEEP_PREVIOUS!
|
||||||
|
echo.
|
||||||
|
|
||||||
|
rem 检查Python脚本是否存在
|
||||||
|
if not exist package.py (
|
||||||
|
echo [错误] 找不到package.py文件,无法进行打包
|
||||||
|
echo 按任意键返回主菜单...
|
||||||
|
pause > nul
|
||||||
|
goto Menu
|
||||||
|
)
|
||||||
|
|
||||||
|
rem 开始打包
|
||||||
|
echo [信息] 开始打包过程...
|
||||||
|
|
||||||
|
rem 直接调用Python执行打包,避免混合输出到控制台的方式
|
||||||
|
python package.py "!FINAL_NAME!" !KEEP_PREVIOUS!
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo [错误] Python脚本执行失败,请检查上面的错误信息
|
||||||
|
)
|
||||||
|
|
||||||
|
:End
|
||||||
|
echo.
|
||||||
|
echo ================================================
|
||||||
|
echo 打包过程已结束
|
||||||
|
echo ================================================
|
||||||
|
echo.
|
||||||
|
echo 是否返回主菜单? [Y=是, N=退出]
|
||||||
|
set /p MENU_CHOICE="选择 [Y/N]: "
|
||||||
|
if /i "!MENU_CHOICE!"=="Y" goto Menu
|
||||||
|
|
||||||
|
echo 感谢使用Cursor Pro Register打包工具,再见!
|
||||||
|
timeout /t 3 > nul
|
||||||
|
endlocal
|
||||||
51
run.sh
Normal file
51
run.sh
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 设置终端颜色
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[0;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
clear
|
||||||
|
echo -e "${BLUE}================================================${NC}"
|
||||||
|
echo -e "${BLUE} Cursor Pro Register 打包工具 ${NC}"
|
||||||
|
echo -e "${BLUE}================================================${NC}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 检测Python是否已安装
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
echo -e "${RED}[错误] 未检测到Python安装,请先安装Python 3.8或更高版本${NC}"
|
||||||
|
echo -e "您可以从 https://www.python.org/downloads/ 下载安装"
|
||||||
|
echo ""
|
||||||
|
read -p "按回车键退出..."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}[信息] 检测到Python已安装${NC}"
|
||||||
|
echo ""
|
||||||
|
echo "请选择打包模式:"
|
||||||
|
echo "1. 基本打包模式(简单快速)"
|
||||||
|
echo "2. 高级打包模式(自定义配置)"
|
||||||
|
echo ""
|
||||||
|
read -p "请选择 [1,2]: " choice
|
||||||
|
|
||||||
|
if [[ $choice == "1" ]]; then
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}[信息] 已选择基本打包模式${NC}"
|
||||||
|
# 确保脚本有执行权限
|
||||||
|
chmod +x package.py
|
||||||
|
python3 package.py
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}[信息] 已选择高级打包模式${NC}"
|
||||||
|
# 确保脚本有执行权限
|
||||||
|
chmod +x package_spec.py
|
||||||
|
python3 package_spec.py
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${BLUE}================================================${NC}"
|
||||||
|
echo -e "${GREEN}打包过程已结束${NC}"
|
||||||
|
echo -e "${BLUE}================================================${NC}"
|
||||||
|
read -p "按回车键退出..."
|
||||||
BIN
screen/28613e3f3f23a935b66a7ba31ff4e3f.jpg
Normal file
BIN
screen/28613e3f3f23a935b66a7ba31ff4e3f.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
BIN
screen/afdian-[未认证]阿臻.jpg
Normal file
BIN
screen/afdian-[未认证]阿臻.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
BIN
screen/c29ea438-ee74-4ba1-bbf6-25e622cdfad5.png
Normal file
BIN
screen/c29ea438-ee74-4ba1-bbf6-25e622cdfad5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
BIN
screen/mm_facetoface_collect_qrcode_1738583247120.png
Normal file
BIN
screen/mm_facetoface_collect_qrcode_1738583247120.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
BIN
screen/qrcode_for_gh_c985615b5f2b_258.jpg
Normal file
BIN
screen/qrcode_for_gh_c985615b5f2b_258.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
BIN
screen/截屏2025-01-04 09.44.48.png
Normal file
BIN
screen/截屏2025-01-04 09.44.48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 86 KiB |
18
turnstilePatch/manifest.json
Normal file
18
turnstilePatch/manifest.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "Turnstile Patcher",
|
||||||
|
"version": "2.1",
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"js": [
|
||||||
|
"./script.js"
|
||||||
|
],
|
||||||
|
"matches": [
|
||||||
|
"<all_urls>"
|
||||||
|
],
|
||||||
|
"run_at": "document_start",
|
||||||
|
"all_frames": true,
|
||||||
|
"world": "MAIN"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1
turnstilePatch/readme.txt
Normal file
1
turnstilePatch/readme.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
12
turnstilePatch/script.js
Normal file
12
turnstilePatch/script.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
function getRandomInt(min, max) {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
// old method wouldn't work on 4k screens
|
||||||
|
|
||||||
|
let screenX = getRandomInt(800, 1200);
|
||||||
|
let screenY = getRandomInt(400, 600);
|
||||||
|
|
||||||
|
Object.defineProperty(MouseEvent.prototype, 'screenX', { value: screenX });
|
||||||
|
|
||||||
|
Object.defineProperty(MouseEvent.prototype, 'screenY', { value: screenY });
|
||||||
Reference in New Issue
Block a user