dasdasd
This commit is contained in:
parent
b61aa2c8e9
commit
753ed9bda7
@ -34,6 +34,11 @@ class ClickAction extends BaseAction {
|
|||||||
|
|
||||||
this.log('debug', '✓ 点击完成');
|
this.log('debug', '✓ 点击完成');
|
||||||
|
|
||||||
|
// 验证点击后的变化(新元素出现 / 旧元素消失)
|
||||||
|
if (this.config.verifyAfter) {
|
||||||
|
await this.verifyAfterClick(this.config.verifyAfter);
|
||||||
|
}
|
||||||
|
|
||||||
// 等待页面变化(如果配置了)
|
// 等待页面变化(如果配置了)
|
||||||
if (this.config.waitForPageChange) {
|
if (this.config.waitForPageChange) {
|
||||||
await this.waitForPageChange(this.config.checkSelector);
|
await this.waitForPageChange(this.config.checkSelector);
|
||||||
@ -47,6 +52,39 @@ class ClickAction extends BaseAction {
|
|||||||
return { success: true };
|
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,8 +13,20 @@ class NavigateAction extends BaseAction {
|
|||||||
|
|
||||||
this.log('info', `导航到: ${url}`);
|
this.log('info', `导航到: ${url}`);
|
||||||
|
|
||||||
|
try {
|
||||||
await this.page.goto(url, options);
|
await this.page.goto(url, options);
|
||||||
|
|
||||||
|
// 验证页面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) {
|
if (this.config.waitAfter) {
|
||||||
await new Promise(resolve => setTimeout(resolve, this.config.waitAfter));
|
await new Promise(resolve => setTimeout(resolve, this.config.waitAfter));
|
||||||
@ -22,7 +34,29 @@ class NavigateAction extends BaseAction {
|
|||||||
|
|
||||||
this.log('info', `✓ 页面加载完成`);
|
this.log('info', `✓ 页面加载完成`);
|
||||||
|
|
||||||
return { success: true, url };
|
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('debug', `✓ 已验证 ${selectors.length} 个关键元素`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
const BaseAction = require('./base-action');
|
const BaseAction = require('../core/base-action');
|
||||||
const logger = require('../../tools/account-register/utils/logger');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重试块动作 - 将一组步骤作为整体进行重试
|
* 重试块动作 - 将一组步骤作为整体进行重试
|
||||||
|
|||||||
@ -5,13 +5,19 @@ site:
|
|||||||
|
|
||||||
# 工作流定义
|
# 工作流定义
|
||||||
workflow:
|
workflow:
|
||||||
# ==================== 步骤 1: 填写基本信息 ====================
|
# ==================== 步骤 1: 打开注册页面 ====================
|
||||||
- action: navigate
|
- action: navigate
|
||||||
name: "打开注册页面"
|
name: "打开注册页面"
|
||||||
url: "{{site.url}}"
|
url: "https://windsurf.com/account/register"
|
||||||
options:
|
options:
|
||||||
waitUntil: networkidle2
|
waitUntil: 'networkidle2'
|
||||||
timeout: 30000
|
timeout: 30000
|
||||||
|
# 验证关键元素存在(SPA应用验证页面加载)
|
||||||
|
verifyElements:
|
||||||
|
- '#firstName'
|
||||||
|
- '#lastName'
|
||||||
|
- '#email'
|
||||||
|
- 'input[type="checkbox"]'
|
||||||
waitAfter: 2000
|
waitAfter: 2000
|
||||||
|
|
||||||
- action: fillForm
|
- action: fillForm
|
||||||
@ -48,6 +54,11 @@ workflow:
|
|||||||
selector:
|
selector:
|
||||||
- css: 'button[type="submit"]'
|
- css: 'button[type="submit"]'
|
||||||
- text: 'Continue'
|
- text: 'Continue'
|
||||||
|
# 验证点击后密码页面出现
|
||||||
|
verifyAfter:
|
||||||
|
appears:
|
||||||
|
- '#password'
|
||||||
|
- 'input[type="password"]'
|
||||||
waitAfter: 2000
|
waitAfter: 2000
|
||||||
|
|
||||||
# ==================== 步骤 2: 设置密码 ====================
|
# ==================== 步骤 2: 设置密码 ====================
|
||||||
@ -87,6 +98,12 @@ workflow:
|
|||||||
handler: "handleTurnstile"
|
handler: "handleTurnstile"
|
||||||
params:
|
params:
|
||||||
timeout: 30000
|
timeout: 30000
|
||||||
|
maxRetries: 3
|
||||||
|
# 重试策略:
|
||||||
|
# - 'refresh': 刷新页面(适用于保持状态的网站)
|
||||||
|
# - 'restart': 刷新后重新填写(适用于刷新=重置的网站,如 Windsurf)
|
||||||
|
# - 'wait': 只等待不刷新
|
||||||
|
retryStrategy: 'restart' # Windsurf 刷新会回到第一步
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
# ==================== 步骤 3: 邮箱验证 ====================
|
# ==================== 步骤 3: 邮箱验证 ====================
|
||||||
|
|||||||
@ -43,10 +43,47 @@ class SiteAdapter {
|
|||||||
* 生命周期钩子 - 工作流执行前
|
* 生命周期钩子 - 工作流执行前
|
||||||
*/
|
*/
|
||||||
async beforeWorkflow() {
|
async beforeWorkflow() {
|
||||||
|
this.log('info', `开始执行 ${this.siteName} 工作流`);
|
||||||
|
|
||||||
|
// 清除浏览器状态(Cookie、缓存、localStorage)
|
||||||
|
await this.clearBrowserState();
|
||||||
|
|
||||||
this.log('debug', '执行 beforeWorkflow 钩子');
|
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,26 +97,105 @@ class SiteAdapter {
|
|||||||
* @param {Error} error - 错误对象
|
* @param {Error} error - 错误对象
|
||||||
*/
|
*/
|
||||||
async onError(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} 个步骤`);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行入口
|
* 执行入口
|
||||||
* @returns {Promise<Object>}
|
* @returns {Promise<Object>}
|
||||||
|
|||||||
@ -15,9 +15,10 @@ class WindsurfAdapter extends SiteAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 工作流执行前 - 生成账户数据
|
* 工作流执行前 - 清理状态 + 生成账户数据
|
||||||
*/
|
*/
|
||||||
async beforeWorkflow() {
|
async beforeWorkflow() {
|
||||||
|
// 先调用父类清理浏览器状态
|
||||||
await super.beforeWorkflow();
|
await super.beforeWorkflow();
|
||||||
|
|
||||||
this.log('info', '生成账户数据...');
|
this.log('info', '生成账户数据...');
|
||||||
@ -50,14 +51,26 @@ class WindsurfAdapter extends SiteAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 步骤 2.5: Cloudflare Turnstile 验证
|
* 步骤 2.5: Cloudflare Turnstile 验证(带智能重试)
|
||||||
*/
|
*/
|
||||||
async handleTurnstile(params) {
|
async handleTurnstile(params) {
|
||||||
const { timeout = 30000 } = params;
|
const {
|
||||||
|
timeout = 30000,
|
||||||
|
maxRetries = 3,
|
||||||
|
retryStrategy = 'refresh' // 'refresh' | 'wait' | 'restart'
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
for (let retryCount = 0; retryCount <= maxRetries; retryCount++) {
|
||||||
|
try {
|
||||||
|
if (retryCount > 0) {
|
||||||
|
this.log('warn', `Turnstile 超时,执行重试策略: ${retryStrategy} (${retryCount}/${maxRetries})...`);
|
||||||
|
|
||||||
|
// 根据策略执行不同的重试行为
|
||||||
|
await this.executeRetryStrategy(retryStrategy, retryCount);
|
||||||
|
}
|
||||||
|
|
||||||
this.log('info', 'Cloudflare Turnstile 人机验证');
|
this.log('info', 'Cloudflare Turnstile 人机验证');
|
||||||
|
|
||||||
try {
|
|
||||||
// 等待 Turnstile 验证框出现
|
// 等待 Turnstile 验证框出现
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||||
|
|
||||||
@ -112,7 +125,13 @@ class WindsurfAdapter extends SiteAdapter {
|
|||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error('Turnstile 验证超时');
|
// 超时了,如果还有重试次数就继续循环
|
||||||
|
if (retryCount < maxRetries) {
|
||||||
|
this.log('warn', `Turnstile 验证超时(${timeout}ms)`);
|
||||||
|
continue; // 进入下一次重试
|
||||||
|
} else {
|
||||||
|
throw new Error('Turnstile 验证超时,已达最大重试次数');
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.log('info', '未检测到 Turnstile,跳过');
|
this.log('info', '未检测到 Turnstile,跳过');
|
||||||
@ -120,10 +139,33 @@ class WindsurfAdapter extends SiteAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.log('warn', `Turnstile 处理失败: ${error.message}`);
|
if (retryCount >= maxRetries) {
|
||||||
// Turnstile 是可选的,失败也继续
|
this.log('error', `Turnstile 处理最终失败: ${error.message}`);
|
||||||
return { success: true, error: error.message };
|
// Turnstile 是可选的,失败也继续(但记录错误)
|
||||||
|
return { success: true, error: error.message, failed: true };
|
||||||
}
|
}
|
||||||
|
// 否则继续重试
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重启后的恢复钩子(Windsurf 特定实现)
|
||||||
|
* Windsurf 刷新后回到第一步,返回需要重新执行的 YAML 步骤
|
||||||
|
* @returns {Array} 需要重新执行的步骤名称
|
||||||
|
*/
|
||||||
|
async onRestart(options) {
|
||||||
|
this.log('info', 'Windsurf 刷新后回到第一步,重新执行前面的步骤...');
|
||||||
|
|
||||||
|
// 返回步骤名称,框架会自动从 YAML 查找并执行
|
||||||
|
return [
|
||||||
|
'填写基本信息',
|
||||||
|
'勾选同意条款',
|
||||||
|
'点击 Continue (基本信息)',
|
||||||
|
'等待密码页面',
|
||||||
|
'设置密码',
|
||||||
|
'提交密码'
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -382,9 +424,84 @@ class WindsurfAdapter extends SiteAdapter {
|
|||||||
async getSubscriptionInfo(params) {
|
async getSubscriptionInfo(params) {
|
||||||
this.log('info', '获取订阅信息');
|
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) {
|
async saveToDatabase(params) {
|
||||||
this.log('info', '保存到数据库');
|
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