190 lines
4.9 KiB
TypeScript
190 lines
4.9 KiB
TypeScript
/**
|
||
* AdsPower Provider的BaseAction
|
||
* 扩展了核心BaseAction,添加人类行为模拟和变量替换
|
||
*/
|
||
|
||
import { Page } from 'puppeteer';
|
||
|
||
export interface ActionContext {
|
||
page: Page;
|
||
logger?: any;
|
||
data?: any;
|
||
siteConfig?: any;
|
||
config?: any;
|
||
siteName?: string;
|
||
adapter?: any; // 自定义适配器,用于CustomAction和WaitAction
|
||
}
|
||
|
||
export abstract class BaseAction {
|
||
protected context: ActionContext;
|
||
protected config: any;
|
||
protected page: Page;
|
||
protected logger: any;
|
||
|
||
constructor(context: ActionContext, config: any) {
|
||
this.context = context;
|
||
this.config = config;
|
||
this.page = context.page;
|
||
this.logger = context.logger;
|
||
}
|
||
|
||
/**
|
||
* 执行动作(子类必须实现)
|
||
*/
|
||
abstract execute(): Promise<any>;
|
||
|
||
/**
|
||
* 替换配置中的变量(增强版)
|
||
*
|
||
* 支持特性:
|
||
* - 多数据源:{{account.email}}, {{site.url}}, {{config.timeout}}
|
||
* - 默认值:{{var|default}}, {{user.name|Guest}}
|
||
* - 变量不存在时警告
|
||
*/
|
||
replaceVariables(value: any): any {
|
||
if (typeof value !== 'string') return value;
|
||
|
||
return value.replace(/\{\{(.+?)\}\}/g, (match, expression) => {
|
||
// 解析默认值:{{var|default}}
|
||
const [path, defaultValue] = expression.split('|').map((s: string) => s.trim());
|
||
|
||
// 获取变量值
|
||
const result = this.resolveVariablePath(path);
|
||
|
||
// 如果找到值,返回
|
||
if (result !== undefined && result !== null) {
|
||
return result;
|
||
}
|
||
|
||
// 如果有默认值,使用默认值
|
||
if (defaultValue !== undefined) {
|
||
this.log('debug', `变量 "${path}" 不存在,使用默认值: "${defaultValue}"`);
|
||
return defaultValue;
|
||
}
|
||
|
||
// 变量不存在且无默认值,发出警告
|
||
this.log('warn', `⚠️ 变量 "${path}" 不存在,返回原始值: ${match}`);
|
||
return match;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 解析变量路径,支持多个数据源
|
||
*/
|
||
resolveVariablePath(path: string): any {
|
||
const keys = path.split('.');
|
||
const rootKey = keys[0];
|
||
|
||
// 确定数据源
|
||
let dataSource: any;
|
||
let startIndex = 1; // 从第二个key开始
|
||
|
||
switch (rootKey) {
|
||
case 'site':
|
||
// {{site.url}} -> context.siteConfig.url
|
||
dataSource = this.context.siteConfig;
|
||
break;
|
||
|
||
case 'config':
|
||
// {{config.timeout}} -> context.config
|
||
dataSource = this.context.config;
|
||
break;
|
||
|
||
case 'env':
|
||
// {{env.API_KEY}} -> process.env
|
||
dataSource = process.env;
|
||
break;
|
||
|
||
default:
|
||
// 默认从 context.data 读取
|
||
// {{account.email}} -> context.data.account.email
|
||
dataSource = this.context.data;
|
||
startIndex = 0; // 从第一个key开始
|
||
}
|
||
|
||
if (!dataSource) {
|
||
return undefined;
|
||
}
|
||
|
||
// 遍历路径获取值
|
||
let result = dataSource;
|
||
for (let i = startIndex; i < keys.length; i++) {
|
||
if (result && typeof result === 'object') {
|
||
result = result[keys[i]];
|
||
} else {
|
||
return undefined;
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 记录日志
|
||
*/
|
||
log(level: string, message: string): void {
|
||
if (this.logger && this.logger[level]) {
|
||
this.logger[level](this.context.siteName || 'Automation', message);
|
||
} else {
|
||
console.log(`[${level.toUpperCase()}] ${message}`);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 人类行为延迟工具方法(模拟真实用户操作节奏)
|
||
*/
|
||
|
||
// 随机延迟
|
||
async randomDelay(min: number, max: number): Promise<void> {
|
||
const delay = min + Math.random() * (max - min);
|
||
await new Promise(resolve => setTimeout(resolve, delay));
|
||
}
|
||
|
||
// 阅读页面延迟(2-5秒)- 模拟用户查看页面内容
|
||
async readPageDelay(): Promise<void> {
|
||
await this.randomDelay(2000, 5000);
|
||
}
|
||
|
||
// 思考延迟(1-2.5秒)- 模拟填写表单后的思考
|
||
async thinkDelay(): Promise<void> {
|
||
await this.randomDelay(1000, 2500);
|
||
}
|
||
|
||
// 短暂停顿(300-800ms)- 模拟操作间的自然停顿
|
||
async pauseDelay(): Promise<void> {
|
||
await this.randomDelay(300, 800);
|
||
}
|
||
|
||
// 步骤间延迟(1.5-3秒)- 模拟步骤之间的过渡
|
||
async stepDelay(): Promise<void> {
|
||
await this.randomDelay(1500, 3000);
|
||
}
|
||
|
||
/**
|
||
* 获取Action类(用于动态加载)
|
||
*/
|
||
getActionClass(actionType: string): any {
|
||
const actionMap: any = {
|
||
navigate: require('./NavigateAction').default,
|
||
fillForm: require('./FillFormAction').default,
|
||
click: require('./ClickAction').default,
|
||
wait: require('./WaitAction').default,
|
||
custom: require('./CustomAction').default,
|
||
scroll: require('./ScrollAction').default,
|
||
verify: require('./VerifyAction').default,
|
||
extract: require('./ExtractAction').default,
|
||
retryBlock: require('./RetryBlockAction').default
|
||
};
|
||
|
||
const ActionClass = actionMap[actionType];
|
||
|
||
if (!ActionClass) {
|
||
throw new Error(`未知的 action 类型: ${actionType}`);
|
||
}
|
||
|
||
return ActionClass;
|
||
}
|
||
}
|
||
|
||
export default BaseAction;
|