蜂鸟Pro v2.0.1 - 基础框架版本 (待完善)
## 当前状态 - 插件界面已完成重命名 (cursorpro → hummingbird) - 双账号池 UI 已实现 (Auto/Pro 卡片) - 后端已切换到 MySQL 数据库 - 添加了 Cursor 官方用量 API 文档 ## 已知问题 (待修复) 1. 激活时检查账号导致无账号时激活失败 2. 未启用无感换号时不应获取账号 3. 账号用量模块不显示 (seamless 未启用时应隐藏) 4. 积分显示为 0 (后端未正确返回) 5. Auto/Pro 双密钥逻辑混乱,状态不同步 6. 账号添加后无自动分析功能 ## 下一版本计划 - 重构数据模型,优化账号状态管理 - 实现 Cursor API 自动分析账号 - 修复激活流程,不依赖账号 - 启用无感时才分配账号 - 完善账号用量实时显示 ## 文件说明 - docs/系统设计文档.md - 完整架构设计 - cursor 官方用量接口.md - Cursor API 文档 - 参考计费/ - Vibeviewer 开源项目参考 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
199
test_cursor_api.js
Normal file
199
test_cursor_api.js
Normal file
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* Cursor 官方用量接口测试脚本
|
||||
*/
|
||||
const https = require('https');
|
||||
|
||||
const TOKEN = 'user_01KCP4PQM80HPAZA7NY8RFR1V6::eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhdXRoMHx1c2VyXzAxS0NQNFBRTTgwSFBBWkE3Tlk4UkZSMVY2IiwidGltZSI6IjE3NjU5NzQ3MTEiLCJyYW5kb21uZXNzIjoiNzMyNGMwOWItZTk2ZS00Y2YzIiwiZXhwIjoxNzcxMTU4NzExLCJpc3MiOiJodHRwczovL2F1dGhlbnRpY2F0aW9uLmN1cnNvci5zaCIsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwgb2ZmbGluZV9hY2Nlc3MiLCJhdWQiOiJodHRwczovL2N1cnNvci5jb20iLCJ0eXBlIjoid2ViIn0.oy_GRvz-3hIUj5BlXahE1QeTb5NuOrM-3pqemw_FEQw';
|
||||
|
||||
const COOKIE = `WorkosCursorSessionToken=${TOKEN}`;
|
||||
|
||||
function request(options, postData = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
try {
|
||||
resolve({ status: res.statusCode, data: JSON.parse(data) });
|
||||
} catch (e) {
|
||||
resolve({ status: res.statusCode, data: data });
|
||||
}
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
if (postData) req.write(postData);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function getHeaders(isPost = false) {
|
||||
const headers = {
|
||||
'Cookie': COOKIE,
|
||||
'accept': '*/*',
|
||||
'origin': 'https://cursor.com',
|
||||
'referer': 'https://cursor.com/dashboard'
|
||||
};
|
||||
if (isPost) {
|
||||
headers['content-type'] = 'application/json';
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
async function testGetMe() {
|
||||
console.log('\n========== 1. 获取用户信息 (GET /api/dashboard/get-me) ==========');
|
||||
const result = await request({
|
||||
hostname: 'cursor.com',
|
||||
path: '/api/dashboard/get-me',
|
||||
method: 'GET',
|
||||
headers: getHeaders()
|
||||
});
|
||||
console.log('Status:', result.status);
|
||||
console.log('Response:', JSON.stringify(result.data, null, 2));
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async function testUsageSummary() {
|
||||
console.log('\n========== 2. 获取用量摘要 (GET /api/usage-summary) ==========');
|
||||
const result = await request({
|
||||
hostname: 'cursor.com',
|
||||
path: '/api/usage-summary',
|
||||
method: 'GET',
|
||||
headers: getHeaders()
|
||||
});
|
||||
console.log('Status:', result.status);
|
||||
console.log('Response:', JSON.stringify(result.data, null, 2));
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async function testBillingCycle() {
|
||||
console.log('\n========== 3. 获取计费周期 (POST /api/dashboard/get-current-billing-cycle) ==========');
|
||||
const result = await request({
|
||||
hostname: 'cursor.com',
|
||||
path: '/api/dashboard/get-current-billing-cycle',
|
||||
method: 'POST',
|
||||
headers: getHeaders(true)
|
||||
}, '{}');
|
||||
console.log('Status:', result.status);
|
||||
console.log('Response:', JSON.stringify(result.data, null, 2));
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async function testFilteredUsage(userId, startMs, endMs) {
|
||||
console.log('\n========== 4. 获取使用事件 (POST /api/dashboard/get-filtered-usage-events) ==========');
|
||||
// 尝试不同的参数组合
|
||||
const body = JSON.stringify({
|
||||
startDate: startMs,
|
||||
endDate: endMs,
|
||||
userId: userId || undefined,
|
||||
page: 1,
|
||||
pageSize: 5
|
||||
});
|
||||
console.log('Request body:', body);
|
||||
const result = await request({
|
||||
hostname: 'cursor.com',
|
||||
path: '/api/dashboard/get-filtered-usage-events',
|
||||
method: 'POST',
|
||||
headers: getHeaders(true)
|
||||
}, body);
|
||||
console.log('Status:', result.status);
|
||||
console.log('Response:', JSON.stringify(result.data, null, 2));
|
||||
|
||||
if (result.data && result.data.totalUsageEventsCount !== undefined) {
|
||||
console.log('\n>>> 总对话次数 (totalUsageEventsCount):', result.data.totalUsageEventsCount);
|
||||
}
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async function testFilteredUsageNoUser(startMs, endMs) {
|
||||
console.log('\n========== 4b. 获取使用事件 - 不带userId ==========');
|
||||
const body = JSON.stringify({
|
||||
startDate: startMs,
|
||||
endDate: endMs,
|
||||
page: 1,
|
||||
pageSize: 5
|
||||
});
|
||||
console.log('Request body:', body);
|
||||
const result = await request({
|
||||
hostname: 'cursor.com',
|
||||
path: '/api/dashboard/get-filtered-usage-events',
|
||||
method: 'POST',
|
||||
headers: getHeaders(true)
|
||||
}, body);
|
||||
console.log('Status:', result.status);
|
||||
console.log('Response:', JSON.stringify(result.data, null, 2));
|
||||
|
||||
if (result.data && result.data.totalUsageEventsCount !== undefined) {
|
||||
console.log('\n>>> 总对话次数 (totalUsageEventsCount):', result.data.totalUsageEventsCount);
|
||||
}
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async function testAggregatedUsage(startMs) {
|
||||
console.log('\n========== 5. 获取聚合用量 (POST /api/dashboard/get-aggregated-usage-events) ==========');
|
||||
const body = JSON.stringify({
|
||||
startDate: parseInt(startMs)
|
||||
});
|
||||
const result = await request({
|
||||
hostname: 'cursor.com',
|
||||
path: '/api/dashboard/get-aggregated-usage-events',
|
||||
method: 'POST',
|
||||
headers: getHeaders(true)
|
||||
}, body);
|
||||
console.log('Status:', result.status);
|
||||
console.log('Response:', JSON.stringify(result.data, null, 2));
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('====================================================');
|
||||
console.log(' Cursor 官方用量接口测试');
|
||||
console.log('====================================================');
|
||||
console.log('Cookie:', COOKIE.substring(0, 50) + '...');
|
||||
|
||||
try {
|
||||
// 1. 获取用户信息
|
||||
const me = await testGetMe();
|
||||
const userId = me.userId;
|
||||
console.log('\n>>> 用户ID:', userId);
|
||||
console.log('>>> 邮箱:', me.email);
|
||||
console.log('>>> 团队ID:', me.teamId);
|
||||
|
||||
// 2. 获取用量摘要
|
||||
const summary = await testUsageSummary();
|
||||
console.log('\n>>> 会员类型:', summary.membershipType);
|
||||
console.log('>>> 计费周期:', summary.billingCycleStart, '至', summary.billingCycleEnd);
|
||||
if (summary.individualUsage) {
|
||||
const plan = summary.individualUsage.plan;
|
||||
console.log('>>> 套餐用量:', plan.used, '/', plan.limit, '(剩余', plan.remaining, ')');
|
||||
}
|
||||
|
||||
// 3. 获取计费周期
|
||||
const billing = await testBillingCycle();
|
||||
const startMs = billing.startDateEpochMillis;
|
||||
const endMs = billing.endDateEpochMillis;
|
||||
console.log('\n>>> 计费开始:', new Date(parseInt(startMs)).toISOString());
|
||||
console.log('>>> 计费结束:', new Date(parseInt(endMs)).toISOString());
|
||||
|
||||
// 4. 获取使用事件 - 不带 userId
|
||||
await testFilteredUsageNoUser(startMs, endMs);
|
||||
|
||||
// 4b. 如果有 userId,也试试带 userId 的
|
||||
if (userId) {
|
||||
await testFilteredUsage(userId, startMs, endMs);
|
||||
}
|
||||
|
||||
// 5. 获取聚合用量
|
||||
if (startMs) {
|
||||
await testAggregatedUsage(startMs);
|
||||
}
|
||||
|
||||
console.log('\n====================================================');
|
||||
console.log(' 测试完成');
|
||||
console.log('====================================================');
|
||||
|
||||
} catch (error) {
|
||||
console.error('测试出错:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user