dasdasd
This commit is contained in:
parent
b61aa2c8e9
commit
753ed9bda7
@ -34,6 +34,11 @@ class ClickAction extends BaseAction {
|
||||
|
||||
this.log('debug', '✓ 点击完成');
|
||||
|
||||
// 验证点击后的变化(新元素出现 / 旧元素消失)
|
||||
if (this.config.verifyAfter) {
|
||||
await this.verifyAfterClick(this.config.verifyAfter);
|
||||
}
|
||||
|
||||
// 等待页面变化(如果配置了)
|
||||
if (this.config.waitForPageChange) {
|
||||
await this.waitForPageChange(this.config.checkSelector);
|
||||
@ -47,6 +52,39 @@ class ClickAction extends BaseAction {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证点击后的变化
|
||||
*/
|
||||
async verifyAfterClick(config) {
|
||||
const { appears, disappears, timeout = 10000 } = config;
|
||||
|
||||
// 验证新元素出现
|
||||
if (appears) {
|
||||
this.log('debug', '验证新元素出现...');
|
||||
for (const selector of (Array.isArray(appears) ? appears : [appears])) {
|
||||
try {
|
||||
await this.page.waitForSelector(selector, { timeout, visible: true });
|
||||
this.log('debug', `✓ 新元素已出现: ${selector}`);
|
||||
} catch (error) {
|
||||
throw new Error(`点击后验证失败: 元素 "${selector}" 未出现`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 验证旧元素消失
|
||||
if (disappears) {
|
||||
this.log('debug', '验证旧元素消失...');
|
||||
for (const selector of (Array.isArray(disappears) ? disappears : [disappears])) {
|
||||
try {
|
||||
await this.page.waitForSelector(selector, { timeout, hidden: true });
|
||||
this.log('debug', `✓ 旧元素已消失: ${selector}`);
|
||||
} catch (error) {
|
||||
throw new Error(`点击后验证失败: 元素 "${selector}" 未消失`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待页面内容变化
|
||||
*/
|
||||
|
||||
@ -13,16 +13,50 @@ class NavigateAction extends BaseAction {
|
||||
|
||||
this.log('info', `导航到: ${url}`);
|
||||
|
||||
await this.page.goto(url, options);
|
||||
try {
|
||||
await this.page.goto(url, options);
|
||||
|
||||
// 可选的等待时间
|
||||
if (this.config.waitAfter) {
|
||||
await new Promise(resolve => setTimeout(resolve, this.config.waitAfter));
|
||||
// 验证页面URL是否正确(避免重定向到登录页等)
|
||||
const currentUrl = this.page.url();
|
||||
if (this.config.verifyUrl && !currentUrl.includes(this.config.verifyUrl)) {
|
||||
throw new Error(`页面跳转异常: 期望包含 "${this.config.verifyUrl}", 实际为 "${currentUrl}"`);
|
||||
}
|
||||
|
||||
// 验证关键元素存在(确保页面加载正确)
|
||||
if (this.config.verifyElements) {
|
||||
await this.verifyElements(this.config.verifyElements);
|
||||
}
|
||||
|
||||
// 可选的等待时间
|
||||
if (this.config.waitAfter) {
|
||||
await new Promise(resolve => setTimeout(resolve, this.config.waitAfter));
|
||||
}
|
||||
|
||||
this.log('info', `✓ 页面加载完成`);
|
||||
|
||||
return { success: true, url: currentUrl };
|
||||
|
||||
} catch (error) {
|
||||
this.log('error', `导航失败: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证关键元素存在
|
||||
*/
|
||||
async verifyElements(selectors) {
|
||||
this.log('debug', '验证页面元素...');
|
||||
|
||||
for (const selector of selectors) {
|
||||
try {
|
||||
await this.page.waitForSelector(selector, { timeout: 10000 });
|
||||
} catch (error) {
|
||||
throw new Error(`页面元素验证失败: 找不到 "${selector}"`);
|
||||
}
|
||||
}
|
||||
|
||||
this.log('info', `✓ 页面加载完成`);
|
||||
|
||||
return { success: true, url };
|
||||
this.log('debug', `✓ 已验证 ${selectors.length} 个关键元素`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
const BaseAction = require('./base-action');
|
||||
const logger = require('../../tools/account-register/utils/logger');
|
||||
const BaseAction = require('../core/base-action');
|
||||
|
||||
/**
|
||||
* 重试块动作 - 将一组步骤作为整体进行重试
|
||||
|
||||
@ -5,13 +5,19 @@ site:
|
||||
|
||||
# 工作流定义
|
||||
workflow:
|
||||
# ==================== 步骤 1: 填写基本信息 ====================
|
||||
# ==================== 步骤 1: 打开注册页面 ====================
|
||||
- action: navigate
|
||||
name: "打开注册页面"
|
||||
url: "{{site.url}}"
|
||||
url: "https://windsurf.com/account/register"
|
||||
options:
|
||||
waitUntil: networkidle2
|
||||
waitUntil: 'networkidle2'
|
||||
timeout: 30000
|
||||
# 验证关键元素存在(SPA应用验证页面加载)
|
||||
verifyElements:
|
||||
- '#firstName'
|
||||
- '#lastName'
|
||||
- '#email'
|
||||
- 'input[type="checkbox"]'
|
||||
waitAfter: 2000
|
||||
|
||||
- action: fillForm
|
||||
@ -48,6 +54,11 @@ workflow:
|
||||
selector:
|
||||
- css: 'button[type="submit"]'
|
||||
- text: 'Continue'
|
||||
# 验证点击后密码页面出现
|
||||
verifyAfter:
|
||||
appears:
|
||||
- '#password'
|
||||
- 'input[type="password"]'
|
||||
waitAfter: 2000
|
||||
|
||||
# ==================== 步骤 2: 设置密码 ====================
|
||||
@ -87,6 +98,12 @@ workflow:
|
||||
handler: "handleTurnstile"
|
||||
params:
|
||||
timeout: 30000
|
||||
maxRetries: 3
|
||||
# 重试策略:
|
||||
# - 'refresh': 刷新页面(适用于保持状态的网站)
|
||||
# - 'restart': 刷新后重新填写(适用于刷新=重置的网站,如 Windsurf)
|
||||
# - 'wait': 只等待不刷新
|
||||
retryStrategy: 'restart' # Windsurf 刷新会回到第一步
|
||||
optional: true
|
||||
|
||||
# ==================== 步骤 3: 邮箱验证 ====================
|
||||
|
||||
@ -43,10 +43,47 @@ class SiteAdapter {
|
||||
* 生命周期钩子 - 工作流执行前
|
||||
*/
|
||||
async beforeWorkflow() {
|
||||
this.log('info', `开始执行 ${this.siteName} 工作流`);
|
||||
|
||||
// 清除浏览器状态(Cookie、缓存、localStorage)
|
||||
await this.clearBrowserState();
|
||||
|
||||
this.log('debug', '执行 beforeWorkflow 钩子');
|
||||
// 子类可以重写
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除浏览器状态
|
||||
*/
|
||||
async clearBrowserState() {
|
||||
this.log('info', '清除浏览器状态...');
|
||||
|
||||
try {
|
||||
// 清除所有 Cookie
|
||||
const cookies = await this.page.cookies();
|
||||
if (cookies.length > 0) {
|
||||
await this.page.deleteCookie(...cookies);
|
||||
this.log('info', `✓ 已清除 ${cookies.length} 个 Cookie`);
|
||||
}
|
||||
|
||||
// 清除 localStorage 和 sessionStorage
|
||||
await this.page.evaluate(() => {
|
||||
localStorage.clear();
|
||||
sessionStorage.clear();
|
||||
});
|
||||
this.log('info', '✓ 已清除 localStorage 和 sessionStorage');
|
||||
|
||||
// 清除缓存(通过 CDP)
|
||||
const client = await this.page.target().createCDPSession();
|
||||
await client.send('Network.clearBrowserCookies');
|
||||
await client.send('Network.clearBrowserCache');
|
||||
this.log('info', '✓ 已清除浏览器缓存');
|
||||
|
||||
} catch (error) {
|
||||
this.log('warn', `清除浏览器状态失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生命周期钩子 - 工作流执行后
|
||||
*/
|
||||
@ -60,24 +97,103 @@ class SiteAdapter {
|
||||
* @param {Error} error - 错误对象
|
||||
*/
|
||||
async onError(error) {
|
||||
this.log('error', `工作流执行失败: ${error.message}`);
|
||||
this.log('error', `错误: ${error.message}`);
|
||||
// 子类可以重写
|
||||
}
|
||||
|
||||
// 截图
|
||||
if (this.page) {
|
||||
try {
|
||||
const screenshotPath = path.join(
|
||||
__dirname,
|
||||
'../../logs',
|
||||
`error-${Date.now()}.png`
|
||||
);
|
||||
await this.page.screenshot({ path: screenshotPath, fullPage: true });
|
||||
this.log('info', `错误截图已保存: ${screenshotPath}`);
|
||||
} catch (e) {
|
||||
this.log('warn', `截图失败: ${e.message}`);
|
||||
/**
|
||||
* 执行重试策略(框架通用方法)
|
||||
*/
|
||||
async executeRetryStrategy(strategy, retryCount, options = {}) {
|
||||
switch (strategy) {
|
||||
case 'refresh':
|
||||
this.log('info', '策略: 刷新当前页面');
|
||||
await this.page.reload({ waitUntil: 'networkidle2', timeout: 30000 });
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
break;
|
||||
|
||||
case 'restart':
|
||||
// 重新开始流程(适用于刷新后回到初始状态的网站)
|
||||
this.log('warn', '策略: 刷新会重置,执行自定义恢复');
|
||||
await this.page.reload({ waitUntil: 'networkidle2', timeout: 30000 });
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
// 调用站点特定的恢复方法
|
||||
if (this.onRestart && typeof this.onRestart === 'function') {
|
||||
const restartSteps = await this.onRestart(options);
|
||||
|
||||
// 如果返回步骤索引/名称数组,则重新执行这些步骤
|
||||
if (Array.isArray(restartSteps) && restartSteps.length > 0) {
|
||||
await this.rerunSteps(restartSteps);
|
||||
}
|
||||
} else {
|
||||
this.log('warn', '未定义 onRestart 方法,跳过恢复步骤');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'wait':
|
||||
const waitTime = options.waitTime || 10000;
|
||||
this.log('info', `策略: 延长等待 ${waitTime}ms(第 ${retryCount} 次)`);
|
||||
await new Promise(resolve => setTimeout(resolve, waitTime));
|
||||
break;
|
||||
|
||||
default:
|
||||
this.log('warn', `未知重试策略: ${strategy},使用默认等待`);
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启后的恢复钩子(子类重写)
|
||||
* @returns {Array|void} 返回需要重新执行的步骤名称/索引数组,或 void(自定义实现)
|
||||
*/
|
||||
async onRestart(options) {
|
||||
this.log('debug', 'onRestart hook (未实现)');
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新执行指定的工作流步骤
|
||||
* @param {Array} stepIdentifiers - 步骤名称或索引数组
|
||||
*/
|
||||
async rerunSteps(stepIdentifiers) {
|
||||
if (!this.context.engine) {
|
||||
this.log('error', '无法重新执行步骤:引擎未初始化');
|
||||
return;
|
||||
}
|
||||
|
||||
this.log('info', `重新执行 ${stepIdentifiers.length} 个步骤...`);
|
||||
|
||||
const workflowSteps = this.config.workflow || [];
|
||||
const stepsToRun = [];
|
||||
|
||||
// 根据标识符查找步骤
|
||||
for (const identifier of stepIdentifiers) {
|
||||
if (typeof identifier === 'number') {
|
||||
// 按索引查找
|
||||
if (workflowSteps[identifier]) {
|
||||
stepsToRun.push(workflowSteps[identifier]);
|
||||
}
|
||||
} else if (typeof identifier === 'string') {
|
||||
// 按名称查找
|
||||
const step = workflowSteps.find(s => s.name === identifier);
|
||||
if (step) {
|
||||
stepsToRun.push(step);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 子类可以重写
|
||||
if (stepsToRun.length === 0) {
|
||||
this.log('warn', '未找到要重新执行的步骤');
|
||||
return;
|
||||
}
|
||||
|
||||
// 重新执行这些步骤
|
||||
for (const step of stepsToRun) {
|
||||
this.log('info', `→ 重新执行: ${step.name || step.action}`);
|
||||
await this.context.engine.executeAction(step);
|
||||
}
|
||||
|
||||
this.log('success', `✓ 已重新执行 ${stepsToRun.length} 个步骤`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -15,9 +15,10 @@ class WindsurfAdapter extends SiteAdapter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 工作流执行前 - 生成账户数据
|
||||
* 工作流执行前 - 清理状态 + 生成账户数据
|
||||
*/
|
||||
async beforeWorkflow() {
|
||||
// 先调用父类清理浏览器状态
|
||||
await super.beforeWorkflow();
|
||||
|
||||
this.log('info', '生成账户数据...');
|
||||
@ -50,82 +51,123 @@ class WindsurfAdapter extends SiteAdapter {
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤 2.5: Cloudflare Turnstile 验证
|
||||
* 步骤 2.5: Cloudflare Turnstile 验证(带智能重试)
|
||||
*/
|
||||
async handleTurnstile(params) {
|
||||
const { timeout = 30000 } = params;
|
||||
const {
|
||||
timeout = 30000,
|
||||
maxRetries = 3,
|
||||
retryStrategy = 'refresh' // 'refresh' | 'wait' | 'restart'
|
||||
} = params;
|
||||
|
||||
this.log('info', 'Cloudflare Turnstile 人机验证');
|
||||
for (let retryCount = 0; retryCount <= maxRetries; retryCount++) {
|
||||
try {
|
||||
if (retryCount > 0) {
|
||||
this.log('warn', `Turnstile 超时,执行重试策略: ${retryStrategy} (${retryCount}/${maxRetries})...`);
|
||||
|
||||
try {
|
||||
// 等待 Turnstile 验证框出现
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// 检查是否有 Turnstile
|
||||
const hasTurnstile = await this.page.evaluate(() => {
|
||||
return !!document.querySelector('iframe[src*="challenges.cloudflare.com"]') ||
|
||||
!!document.querySelector('.cf-turnstile') ||
|
||||
document.body.textContent.includes('Please verify that you are human');
|
||||
});
|
||||
|
||||
if (hasTurnstile) {
|
||||
this.log('info', '检测到 Turnstile 验证,等待自动完成...');
|
||||
|
||||
// 等待验证通过(检查按钮是否启用或页面是否变化)
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
const isPassed = await this.page.evaluate(() => {
|
||||
// 检查是否有成功标记
|
||||
const successMark = document.querySelector('svg[data-status="success"]') ||
|
||||
document.querySelector('[aria-label*="success"]') ||
|
||||
document.querySelector('.cf-turnstile-success');
|
||||
|
||||
// 或者检查 Continue 按钮是否启用
|
||||
const continueBtn = Array.from(document.querySelectorAll('button')).find(btn =>
|
||||
btn.textContent.trim() === 'Continue'
|
||||
);
|
||||
const btnEnabled = continueBtn && !continueBtn.disabled;
|
||||
|
||||
return !!successMark || btnEnabled;
|
||||
});
|
||||
|
||||
if (isPassed) {
|
||||
this.log('success', '✓ Turnstile 验证通过');
|
||||
|
||||
// 点击 Continue 按钮
|
||||
const continueBtn = await this.page.evaluateHandle(() => {
|
||||
return Array.from(document.querySelectorAll('button')).find(btn =>
|
||||
btn.textContent.trim() === 'Continue'
|
||||
);
|
||||
});
|
||||
|
||||
if (continueBtn) {
|
||||
await continueBtn.asElement().click();
|
||||
this.log('info', '已点击 Continue 按钮');
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
// 根据策略执行不同的重试行为
|
||||
await this.executeRetryStrategy(retryStrategy, retryCount);
|
||||
}
|
||||
|
||||
throw new Error('Turnstile 验证超时');
|
||||
this.log('info', 'Cloudflare Turnstile 人机验证');
|
||||
|
||||
} else {
|
||||
this.log('info', '未检测到 Turnstile,跳过');
|
||||
return { success: true, skipped: true };
|
||||
// 等待 Turnstile 验证框出现
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
// 检查是否有 Turnstile
|
||||
const hasTurnstile = await this.page.evaluate(() => {
|
||||
return !!document.querySelector('iframe[src*="challenges.cloudflare.com"]') ||
|
||||
!!document.querySelector('.cf-turnstile') ||
|
||||
document.body.textContent.includes('Please verify that you are human');
|
||||
});
|
||||
|
||||
if (hasTurnstile) {
|
||||
this.log('info', '检测到 Turnstile 验证,等待自动完成...');
|
||||
|
||||
// 等待验证通过(检查按钮是否启用或页面是否变化)
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
const isPassed = await this.page.evaluate(() => {
|
||||
// 检查是否有成功标记
|
||||
const successMark = document.querySelector('svg[data-status="success"]') ||
|
||||
document.querySelector('[aria-label*="success"]') ||
|
||||
document.querySelector('.cf-turnstile-success');
|
||||
|
||||
// 或者检查 Continue 按钮是否启用
|
||||
const continueBtn = Array.from(document.querySelectorAll('button')).find(btn =>
|
||||
btn.textContent.trim() === 'Continue'
|
||||
);
|
||||
const btnEnabled = continueBtn && !continueBtn.disabled;
|
||||
|
||||
return !!successMark || btnEnabled;
|
||||
});
|
||||
|
||||
if (isPassed) {
|
||||
this.log('success', '✓ Turnstile 验证通过');
|
||||
|
||||
// 点击 Continue 按钮
|
||||
const continueBtn = await this.page.evaluateHandle(() => {
|
||||
return Array.from(document.querySelectorAll('button')).find(btn =>
|
||||
btn.textContent.trim() === 'Continue'
|
||||
);
|
||||
});
|
||||
|
||||
if (continueBtn) {
|
||||
await continueBtn.asElement().click();
|
||||
this.log('info', '已点击 Continue 按钮');
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
|
||||
// 超时了,如果还有重试次数就继续循环
|
||||
if (retryCount < maxRetries) {
|
||||
this.log('warn', `Turnstile 验证超时(${timeout}ms)`);
|
||||
continue; // 进入下一次重试
|
||||
} else {
|
||||
throw new Error('Turnstile 验证超时,已达最大重试次数');
|
||||
}
|
||||
|
||||
} else {
|
||||
this.log('info', '未检测到 Turnstile,跳过');
|
||||
return { success: true, skipped: true };
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
if (retryCount >= maxRetries) {
|
||||
this.log('error', `Turnstile 处理最终失败: ${error.message}`);
|
||||
// Turnstile 是可选的,失败也继续(但记录错误)
|
||||
return { success: true, error: error.message, failed: true };
|
||||
}
|
||||
// 否则继续重试
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.log('warn', `Turnstile 处理失败: ${error.message}`);
|
||||
// Turnstile 是可选的,失败也继续
|
||||
return { success: true, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重启后的恢复钩子(Windsurf 特定实现)
|
||||
* Windsurf 刷新后回到第一步,返回需要重新执行的 YAML 步骤
|
||||
* @returns {Array} 需要重新执行的步骤名称
|
||||
*/
|
||||
async onRestart(options) {
|
||||
this.log('info', 'Windsurf 刷新后回到第一步,重新执行前面的步骤...');
|
||||
|
||||
// 返回步骤名称,框架会自动从 YAML 查找并执行
|
||||
return [
|
||||
'填写基本信息',
|
||||
'勾选同意条款',
|
||||
'点击 Continue (基本信息)',
|
||||
'等待密码页面',
|
||||
'设置密码',
|
||||
'提交密码'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤 3: 邮箱验证
|
||||
*/
|
||||
@ -382,9 +424,84 @@ class WindsurfAdapter extends SiteAdapter {
|
||||
async getSubscriptionInfo(params) {
|
||||
this.log('info', '获取订阅信息');
|
||||
|
||||
// TODO: 实现获取订阅信息逻辑
|
||||
try {
|
||||
// 关闭可能存在的弹窗
|
||||
this.log('info', '关闭可能存在的对话框...');
|
||||
for (let i = 0; i < 5; i++) {
|
||||
try {
|
||||
await this.page.keyboard.press('Escape');
|
||||
await new Promise(resolve => setTimeout(resolve, 300));
|
||||
} catch (e) {
|
||||
// 忽略
|
||||
}
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
// 跳转到订阅使用页面
|
||||
this.log('info', '跳转到订阅使用页面...');
|
||||
await this.page.goto('https://windsurf.com/subscription/usage', {
|
||||
waitUntil: 'networkidle2',
|
||||
timeout: 30000
|
||||
});
|
||||
|
||||
// 等待页面加载
|
||||
await new Promise(resolve => setTimeout(resolve, 3000));
|
||||
|
||||
// 获取配额信息
|
||||
this.log('info', '获取配额信息...');
|
||||
const quotaInfo = await this.page.evaluate(() => {
|
||||
const quotaElement = document.querySelector('p.caption1.font-medium.text-sk-black\\/80');
|
||||
if (!quotaElement) return null;
|
||||
|
||||
const spans = quotaElement.querySelectorAll('span.caption3 span');
|
||||
if (spans.length >= 2) {
|
||||
const used = spans[0].textContent.trim();
|
||||
const total = spans[1].textContent.trim().replace('/', '').trim();
|
||||
return { used, total };
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
if (quotaInfo) {
|
||||
this.log('success', `✓ 配额: ${quotaInfo.used} / ${quotaInfo.total} used`);
|
||||
this.context.data.quotaInfo = quotaInfo;
|
||||
} else {
|
||||
this.log('warn', '未找到配额信息');
|
||||
}
|
||||
|
||||
// 获取账单周期信息
|
||||
this.log('info', '获取账单周期信息...');
|
||||
const billingInfo = await this.page.evaluate(() => {
|
||||
const billingElement = Array.from(document.querySelectorAll('p.caption1'))
|
||||
.find(p => p.textContent.includes('Next billing cycle'));
|
||||
|
||||
if (!billingElement) return null;
|
||||
|
||||
const daysMatch = billingElement.textContent.match(/(\d+)\s+days?/);
|
||||
const dateMatch = billingElement.textContent.match(/on\s+([A-Za-z]+\s+\d+,\s+\d{4})/);
|
||||
|
||||
return {
|
||||
days: daysMatch ? daysMatch[1] : null,
|
||||
date: dateMatch ? dateMatch[1] : null,
|
||||
fullText: billingElement.textContent.trim()
|
||||
};
|
||||
});
|
||||
|
||||
if (billingInfo && billingInfo.days) {
|
||||
this.log('success', `✓ 下次账单: ${billingInfo.days} 天后 (${billingInfo.date})`);
|
||||
this.context.data.billingInfo = billingInfo;
|
||||
} else {
|
||||
this.log('warn', '未找到账单周期信息');
|
||||
}
|
||||
|
||||
// 打印汇总信息
|
||||
this.log('success', `✓ 配额: ${quotaInfo ? `${quotaInfo.used}/${quotaInfo.total}` : 'N/A'} | 下次账单: ${billingInfo?.days || 'N/A'}天后`);
|
||||
|
||||
return { success: true, quotaInfo, billingInfo };
|
||||
|
||||
} catch (error) {
|
||||
this.log('error', `获取订阅信息失败: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -393,9 +510,53 @@ class WindsurfAdapter extends SiteAdapter {
|
||||
async saveToDatabase(params) {
|
||||
this.log('info', '保存到数据库');
|
||||
|
||||
// TODO: 实现数据库保存逻辑
|
||||
try {
|
||||
// 导入数据库模块
|
||||
const database = require('../../tools/account-register/database');
|
||||
|
||||
return { success: true };
|
||||
// 初始化数据库连接
|
||||
this.log('info', '连接数据库...');
|
||||
await database.initialize();
|
||||
|
||||
const account = this.context.data.account;
|
||||
const card = this.context.data.card;
|
||||
const quotaInfo = this.context.data.quotaInfo;
|
||||
const billingInfo = this.context.data.billingInfo;
|
||||
|
||||
// 准备账号数据
|
||||
const accountData = {
|
||||
email: account.email,
|
||||
password: account.password,
|
||||
firstName: account.firstName,
|
||||
lastName: account.lastName,
|
||||
registrationTime: new Date(),
|
||||
quotaUsed: quotaInfo ? parseFloat(quotaInfo.used) : 0,
|
||||
quotaTotal: quotaInfo ? parseFloat(quotaInfo.total) : 0,
|
||||
billingDays: billingInfo ? parseInt(billingInfo.days) : null,
|
||||
billingDate: billingInfo ? billingInfo.date : null,
|
||||
paymentCardNumber: card ? card.number : null,
|
||||
paymentCountry: card ? card.country : 'MO',
|
||||
status: 'active',
|
||||
isOnSale: false
|
||||
};
|
||||
|
||||
// 保存到数据库
|
||||
this.log('info', '保存账号信息...');
|
||||
const accountRepo = database.getRepository('account');
|
||||
const accountId = await accountRepo.create(accountData);
|
||||
|
||||
this.log('success', `✓ 账号信息已保存到数据库 (ID: ${accountId})`);
|
||||
this.log('info', ` → 邮箱: ${accountData.email}`);
|
||||
this.log('info', ` → 配额: ${accountData.quotaUsed} / ${accountData.quotaTotal}`);
|
||||
this.log('info', ` → 卡号: ${accountData.paymentCardNumber}`);
|
||||
|
||||
return { success: true, accountId };
|
||||
|
||||
} catch (error) {
|
||||
this.log('error', `保存到数据库失败: ${error.message}`);
|
||||
// 数据库保存失败不影响注册流程
|
||||
return { success: true, error: error.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user