每日获取 Cursor Token (内联脚本) #3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: 每日获取 Cursor Token (内联脚本) | |
on: | |
schedule: | |
- cron: '0 22 * * *' # 每天 UTC 时间 22:00 执行 (北京时间早上 6:00) | |
workflow_dispatch: | |
jobs: | |
fetch-token: | |
runs-on: ubuntu-latest | |
steps: | |
- name: 检出代码 | |
uses: actions/checkout@v4 | |
- name: 设置 Python 环境 | |
uses: actions/setup-python@v5 | |
with: | |
python-version: '3.10' | |
- name: 安装依赖 | |
run: pip install requests psutil | |
- name: 恢复缓存 | |
id: cache-tokens | |
uses: actions/cache@v4 | |
with: | |
path: token_cache.json | |
key: ${{ runner.os }}-tokens-${{ github.sha }} # 使用 commit SHA 作为缓存键 | |
restore-keys: | | |
${{ runner.os }}-tokens- | |
- name: 运行 Token 获取脚本 | |
env: | |
ACCESS_CODE: ${{ secrets.ACCESS_CODE }} | |
run: | | |
python -c ' | |
import json | |
import logging | |
import os | |
import platform | |
import time | |
import requests | |
from dataclasses import dataclass | |
from pathlib import Path | |
from typing import Dict, Optional, Tuple | |
# 配置日志记录 | |
logging.basicConfig( | |
level=logging.INFO, | |
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" | |
) | |
logger = logging.getLogger(__name__) | |
# 常量配置类 (与之前相同) | |
class Config: | |
API_URL = "https://cursor.ccopilot.org/api/get_next_token.php" | |
PROCESS_TIMEOUT = 5 | |
CURSOR_PROCESS_NAMES = ["cursor.exe", "cursor"] | |
DB_KEYS = { | |
"email": "cursorAuth/cachedEmail", | |
"access_token": "cursorAuth/accessToken", | |
"refresh_token": "cursorAuth/refreshToken", | |
} | |
MIN_PATCH_VERSION = "0.45.0" | |
VERSION_PATTERN = r"^\d+\.\d+\.\d+$" | |
@dataclass | |
class TokenMetadata: | |
"""Token 元数据(不包含 token 本身)""" | |
mac_machine_id: str | |
machine_id: str | |
dev_device_id: str | |
email: str | |
fetch_time: str # 添加获取时间 | |
@classmethod | |
def from_dict(cls, data: Dict[str, str]) -> "TokenMetadata": | |
"""从字典创建 TokenMetadata 实例""" | |
return cls( | |
mac_machine_id=data["mac_machine_id"], | |
machine_id=data["machine_id"], | |
dev_device_id=data["dev_device_id"], | |
email=data["email"], | |
fetch_time=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()), # 获取当前时间 | |
) | |
class TokenManager: | |
"""Token 管理(修改为返回包含 token 的完整响应)""" | |
@staticmethod | |
def fetch_token_data(access_code: str) -> Optional[Dict]: | |
"""获取Token数据,直接返回原始响应""" | |
logger.info(f"正在使用授权码 {access_code} 获取 Token 数据...") | |
try: | |
response = requests.get(f"{Config.API_URL}?accessCode={access_code}") | |
response.raise_for_status() # 检查请求是否成功 | |
data = response.json() # 假设响应是 JSON 格式 | |
if data.get("code") == 0: | |
logger.info("成功获取 Token 数据") | |
else: | |
logger.warning(f"获取 Token 失败: {data.get('message', '未知错误')}") # 修正: 使用单引号 | |
return data # 直接返回 | |
except requests.RequestException as e: | |
logger.error(f"获取 Token 数据时发生网络错误: {e}") | |
return None | |
except json.JSONDecodeError: | |
logger.error("响应内容不是有效的 JSON 格式") | |
return None | |
except Exception as e: | |
logger.error(f"获取 Token 数据时发生未知错误: {e}") | |
return None | |
class FilePathManager: | |
"""文件路径管理器(本脚本中仅用于确定输出路径,简化)""" | |
@staticmethod | |
def get_output_dir() -> Path: | |
"""获取输出目录""" | |
return Path.cwd() # 当前工作目录 | |
@staticmethod | |
def get_storage_path() -> Path: | |
"""获取storage.json文件路径""" | |
system = platform.system() | |
if system == "Windows": | |
return Path(os.getenv("APPDATA")) / "Cursor" / "User" / "globalStorage" / "storage.json" | |
elif system == "Darwin": | |
return Path.home() / "Library" / "Application Support" / "Cursor" / "User" / "globalStorage" / "storage.json" | |
elif system == "Linux": | |
return Path.home() / ".config" / "Cursor" / "User" / "globalStorage" / "storage.json" | |
raise OSError(f"不支持的操作系统: {system}") | |
@staticmethod | |
def get_db_path() -> Path: | |
"""获取数据库文件路径""" | |
system = platform.system() | |
if system == "Windows": | |
return Path(os.getenv("APPDATA")) / "Cursor" / "User" / "globalStorage" / "state.vscdb" | |
elif system == "Darwin": | |
return Path.home() / "Library" / "Application Support" / "Cursor" / "User" / "globalStorage" / "state.vscdb" | |
elif system == "Linux": | |
return Path.home() / ".config" / "Cursor" / "User" / "globalStorage" / "state.vscdb" | |
raise OSError(f"不支持的操作系统: {system}") | |
@staticmethod | |
def get_cursor_app_paths() -> Tuple[Path, Path]: | |
"""获取Cursor应用相关路径""" | |
system = platform.system() | |
if system == "Windows": | |
base_path = Path(os.getenv("LOCALAPPDATA", "")) / "Programs" / "Cursor" / "resources" / "app" | |
elif system == "Darwin": | |
base_path = Path("/Applications/Cursor.app/Contents/Resources/app") | |
elif system == "Linux": | |
# 检查可能的Linux安装路径 | |
possible_paths = [ | |
Path("/opt/Cursor/resources/app"), | |
Path("/usr/share/cursor/resources/app"), | |
] | |
base_path = next((p for p in possible_paths if p.exists()), None) | |
if not base_path: | |
raise OSError("在Linux系统上未找到Cursor安装路径") | |
else: | |
raise OSError(f"不支持的操作系统: {system}") | |
return base_path / "package.json", base_path / "out" / "main.js" | |
def main() -> None: | |
"""主程序(GitHub Actions 版本,使用缓存)""" | |
access_code = os.getenv("ACCESS_CODE") | |
if not access_code: | |
logger.error("未设置 ACCESS_CODE 环境变量,程序退出。") | |
return | |
num_attempts = 5 | |
delay_between_attempts = 2.0 | |
output_dir = FilePathManager.get_output_dir() | |
output_file = output_dir / "token_metadata.txt" # 存储元数据的文件 | |
cache_file = output_dir / "token_cache.json" # 缓存文件 | |
# 尝试从缓存文件加载现有 token 数据 | |
cached_tokens = [] | |
if cache_file.exists(): | |
try: | |
with open(cache_file, "r", encoding="utf-8") as f: | |
cached_tokens = json.load(f) | |
if not isinstance(cached_tokens, list): | |
cached_tokens = [cached_tokens] | |
except json.JSONDecodeError: | |
logger.warning(f"缓存文件 {cache_file} 内容不是有效的 JSON,将忽略。") | |
cached_tokens = [] | |
# 尝试读取现有元数据文件内容 (如果文件存在) | |
existing_metadata = [] | |
if output_file.exists(): | |
try: | |
with open(output_file, "r", encoding="utf-8") as f: | |
existing_metadata = json.load(f) | |
if not isinstance(existing_metadata, list): | |
existing_metadata = [existing_metadata] | |
except json.JSONDecodeError: | |
logger.warning(f"文件 {output_file} 内容不是有效的 JSON,将覆盖。") | |
existing_metadata = [] | |
new_tokens = [] # 用于存储本次获取的新 token | |
for attempt in range(1, num_attempts + 1): | |
logger.info(f"尝试第 {attempt} 次获取...") | |
if token_data := TokenManager.fetch_token_data(access_code): | |
try: | |
# 提取元数据 | |
metadata = TokenMetadata.from_dict(token_data) | |
existing_metadata.append(metadata.__dict__) # 将元数据对象转换为字典并存储 | |
# 将完整的 token 数据添加到 new_tokens 列表 | |
new_tokens.append(token_data) | |
logger.info(f"第 {attempt} 次获取成功.") | |
except Exception as e: | |
logger.error(f"处理第 {attempt} 次获取的数据时出错: {e}") | |
else: | |
logger.error(f"第 {attempt} 次获取失败。") | |
if attempt < num_attempts: | |
logger.info(f"等待 {delay_between_attempts} 秒...") | |
time.sleep(delay_between_attempts) | |
# 合并新旧 token 数据, 并去重 | |
all_tokens = cached_tokens + new_tokens | |
unique_tokens = [] | |
seen_tokens = set() # 使用集合来跟踪已经见过的 token | |
for token in all_tokens: | |
token_value = token.get("token") | |
if token_value and token_value not in seen_tokens: | |
unique_tokens.append(token) | |
seen_tokens.add(token_value) | |
# 将元数据写入文件 | |
try: | |
with open(output_file, "w", encoding="utf-8") as f: | |
json.dump(existing_metadata, f, indent=4, ensure_ascii=False) | |
logger.info(f"Token 元数据已追加到: {output_file}") | |
except Exception as e: | |
logger.error(f"写入元数据文件时出错: {e}") | |
# 将更新后的 token 数据写入缓存文件 | |
try: | |
with open(cache_file, "w", encoding="utf-8") as f: | |
json.dump(unique_tokens, f, indent=4, ensure_ascii=False) | |
logger.info(f"Token 数据已更新到缓存文件: {cache_file}") | |
except Exception as e: | |
logger.error(f"写入缓存文件时出错: {e}") | |
# 运行主函数 | |
if __name__ == "__main__": | |
main() | |
' | |
- name: 提交元数据更改 | |
run: | | |
git config --local user.email "[email protected]" | |
git config --local user.name "GitHub Action" | |
git add token_metadata.txt | |
git commit -m "自动更新 Token 元数据" || echo "没有新的 Token 元数据" | |
git push | |
- name: 保存缓存 | |
if: steps.cache-tokens.outputs.cache-hit != 'true' | |
run: echo "缓存已更新" |