Update agent.user.js to version 2025.08.01, introducing a comprehensive state management system with observers, enhancing task execution capabilities, and adding a README.md for documentation. Improve page detection and navigation for Binance, along with new task and step types for better automation.
This commit is contained in:
473
README.md
Normal file
473
README.md
Normal file
@@ -0,0 +1,473 @@
|
|||||||
|
# Binance Alpha Farm Agent
|
||||||
|
|
||||||
|
Automated trading agent for Binance Alpha Farm with Task-Step architecture.
|
||||||
|
|
||||||
|
## 🏗️ System Architecture
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
|
||||||
|
- **AppState**: Centralized state management with observer pattern
|
||||||
|
- **TaskRunner**: Task execution engine
|
||||||
|
- **StateWatchers**: Observers for state changes
|
||||||
|
- **BAF API**: Server communication module
|
||||||
|
- **BINANCE**: Binance page detection and navigation
|
||||||
|
|
||||||
|
## 📋 Task-Step System
|
||||||
|
|
||||||
|
### Task Types
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
TASK_TYPES = {
|
||||||
|
LOGIN: 'login',
|
||||||
|
GET_ORDER_HISTORY: 'get_order_history',
|
||||||
|
GET_BALANCE: 'get_balance',
|
||||||
|
SWAP: 'swap',
|
||||||
|
NO_TASK: 'no_task'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step Types
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
STEP_TYPES = {
|
||||||
|
// Login task steps
|
||||||
|
NAVIGATE_TO_LOGIN: 'navigate_to_login',
|
||||||
|
SELECT_QR_CODE: 'select_qr_code',
|
||||||
|
SEND_TO_SERVER: 'send_to_server',
|
||||||
|
WAIT_FOR_LOGIN: 'wait_for_login',
|
||||||
|
REPORT_RESULT: 'report_result',
|
||||||
|
|
||||||
|
// Order history task steps
|
||||||
|
NAVIGATE_TO_ORDER_HISTORY: 'navigate_to_order_history',
|
||||||
|
EXTRACT_ORDER_DATA: 'extract_order_data',
|
||||||
|
SEND_ORDER_DATA: 'send_order_data',
|
||||||
|
|
||||||
|
// Balance task steps
|
||||||
|
NAVIGATE_TO_BALANCE: 'navigate_to_balance',
|
||||||
|
EXTRACT_BALANCE_DATA: 'extract_balance_data',
|
||||||
|
SEND_BALANCE_DATA: 'send_balance_data',
|
||||||
|
|
||||||
|
// Swap task steps
|
||||||
|
NAVIGATE_TO_SWAP: 'navigate_to_swap',
|
||||||
|
FILL_SWAP_FORM: 'fill_swap_form',
|
||||||
|
CONFIRM_SWAP: 'confirm_swap',
|
||||||
|
WAIT_FOR_SWAP: 'wait_for_swap',
|
||||||
|
|
||||||
|
// Common steps
|
||||||
|
WAIT: 'wait',
|
||||||
|
ERROR_HANDLING: 'error_handling'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🤖 Bot Status Flow
|
||||||
|
|
||||||
|
### Status Types
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
BOT_STATUS = {
|
||||||
|
READY_FOR_NEW_TASKS: 'ready-for-new-tasks',
|
||||||
|
PAUSE_AUTOMATION: 'pause-automation',
|
||||||
|
PERFORMING_TASKS: 'performing-tasks'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Main Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Initialize │
|
||||||
|
└─────────┬───────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Check Bot Status│
|
||||||
|
└─────────┬───────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ Ready for Tasks │────▶│ Ask Server │
|
||||||
|
└─────────┬───────┘ │ for Tasks │
|
||||||
|
│ └─────────┬───────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ Sleep │ │ No Task Found │
|
||||||
|
│ (Interval) │◀────│ │
|
||||||
|
└─────────┬───────┘ └─────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ Check Bot Status│────▶│ Task Received │
|
||||||
|
└─────────┬───────┘ └─────────┬───────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ Ready for Tasks │ │ Perform Task │
|
||||||
|
└─────────────────┘ └─────────┬───────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Task Complete │
|
||||||
|
└─────────┬───────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Return to Ready │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Status Transitions
|
||||||
|
|
||||||
|
1. **READY_FOR_NEW_TASKS**
|
||||||
|
- Poll server for new tasks
|
||||||
|
- Sleep for `task_poll_interval` if no tasks
|
||||||
|
- Transition to `PERFORMING_TASKS` when task received
|
||||||
|
|
||||||
|
2. **PAUSE_AUTOMATION**
|
||||||
|
- Stop all automation
|
||||||
|
- No polling or task execution
|
||||||
|
- Manual intervention required
|
||||||
|
|
||||||
|
3. **PERFORMING_TASKS**
|
||||||
|
- Execute current task step by step
|
||||||
|
- Update task data during execution
|
||||||
|
- Transition back to `READY_FOR_NEW_TASKS` when complete
|
||||||
|
|
||||||
|
## 📊 Task Data Examples
|
||||||
|
|
||||||
|
### Login Task
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
type: TASK_TYPES.LOGIN,
|
||||||
|
id: "task_123",
|
||||||
|
data: {
|
||||||
|
method: LOGIN_METHOD.QR_CODE, // 'qr_code' | 'password' | 'email'
|
||||||
|
credentials: {
|
||||||
|
email: 'user@example.com', // For password method
|
||||||
|
password: 'password123' // For password method
|
||||||
|
},
|
||||||
|
timeout: 300000, // 5 minutes timeout
|
||||||
|
retry_count: 3
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
STEP_TYPES.NAVIGATE_TO_LOGIN,
|
||||||
|
STEP_TYPES.SELECT_QR_CODE,
|
||||||
|
STEP_TYPES.SEND_TO_SERVER,
|
||||||
|
STEP_TYPES.WAIT_FOR_LOGIN,
|
||||||
|
STEP_TYPES.REPORT_RESULT
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Order History Task
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
type: TASK_TYPES.GET_ORDER_HISTORY,
|
||||||
|
id: "task_124",
|
||||||
|
data: {
|
||||||
|
limit: 100,
|
||||||
|
offset: 0,
|
||||||
|
date_range: {
|
||||||
|
start_date: '2024-01-01',
|
||||||
|
end_date: '2024-12-31'
|
||||||
|
},
|
||||||
|
order_types: [
|
||||||
|
ORDER_TYPE.SWAP,
|
||||||
|
ORDER_TYPE.LIMIT,
|
||||||
|
ORDER_TYPE.MARKET
|
||||||
|
],
|
||||||
|
status: [
|
||||||
|
ORDER_STATUS.COMPLETED,
|
||||||
|
ORDER_STATUS.PENDING,
|
||||||
|
ORDER_STATUS.CANCELLED
|
||||||
|
]
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
STEP_TYPES.NAVIGATE_TO_ORDER_HISTORY,
|
||||||
|
STEP_TYPES.EXTRACT_ORDER_DATA,
|
||||||
|
STEP_TYPES.SEND_ORDER_DATA
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get Balance Task
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
type: TASK_TYPES.GET_BALANCE,
|
||||||
|
id: "task_125",
|
||||||
|
data: {
|
||||||
|
currencies: ['USDT', 'BTC', 'ETH'], // null for all currencies
|
||||||
|
include_zero: false,
|
||||||
|
include_locked: true,
|
||||||
|
format: BALANCE_FORMAT.JSON // 'json' | 'csv'
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
STEP_TYPES.NAVIGATE_TO_BALANCE,
|
||||||
|
STEP_TYPES.EXTRACT_BALANCE_DATA,
|
||||||
|
STEP_TYPES.SEND_BALANCE_DATA
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Swap Task
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
type: TASK_TYPES.SWAP,
|
||||||
|
id: "task_126",
|
||||||
|
data: {
|
||||||
|
from_token: 'USDT',
|
||||||
|
to_token: 'BTC',
|
||||||
|
amount: 100,
|
||||||
|
slippage: 0.5, // percentage
|
||||||
|
gas_fee: GAS_FEE_TYPE.AUTO, // 'auto' | 'fast' | 'slow' | number
|
||||||
|
deadline: 300, // seconds
|
||||||
|
auto_confirm: true
|
||||||
|
},
|
||||||
|
steps: [
|
||||||
|
STEP_TYPES.NAVIGATE_TO_SWAP,
|
||||||
|
STEP_TYPES.FILL_SWAP_FORM,
|
||||||
|
STEP_TYPES.CONFIRM_SWAP,
|
||||||
|
STEP_TYPES.WAIT_FOR_SWAP
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Enums & Constants
|
||||||
|
|
||||||
|
### Login Method
|
||||||
|
```javascript
|
||||||
|
LOGIN_METHOD = {
|
||||||
|
QR_CODE: 'qr_code',
|
||||||
|
PASSWORD: 'password',
|
||||||
|
EMAIL: 'email'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Order Type
|
||||||
|
```javascript
|
||||||
|
ORDER_TYPE = {
|
||||||
|
SWAP: 'swap',
|
||||||
|
LIMIT: 'limit',
|
||||||
|
MARKET: 'market'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Order Status
|
||||||
|
```javascript
|
||||||
|
ORDER_STATUS = {
|
||||||
|
COMPLETED: 'completed',
|
||||||
|
PENDING: 'pending',
|
||||||
|
CANCELLED: 'cancelled'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Balance Format
|
||||||
|
```javascript
|
||||||
|
BALANCE_FORMAT = {
|
||||||
|
JSON: 'json',
|
||||||
|
CSV: 'csv'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Gas Fee Type
|
||||||
|
```javascript
|
||||||
|
GAS_FEE_TYPE = {
|
||||||
|
AUTO: 'auto',
|
||||||
|
FAST: 'fast',
|
||||||
|
SLOW: 'slow'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎯 Page Detection
|
||||||
|
|
||||||
|
### Supported Pages
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
BINANCE_PAGES = {
|
||||||
|
LOGIN: 'login',
|
||||||
|
ALPHA_SWAP: 'alpha-swap',
|
||||||
|
ALPHA_ORDER_HISTORY: 'alpha-order-history',
|
||||||
|
UNKNOWN: 'unknown'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Page URLs
|
||||||
|
|
||||||
|
- **Login**: `https://accounts.binance.com/login`
|
||||||
|
- **Alpha Swap**: `https://www.binance.com/alpha/bsc/`
|
||||||
|
- **Alpha Order History**: `https://www.binance.com/en/my/orders/alpha/orderhistory`
|
||||||
|
|
||||||
|
## 🔄 State Management
|
||||||
|
|
||||||
|
### AppState Structure
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
// Server configuration
|
||||||
|
server_mode: 'prod',
|
||||||
|
server: { label: '🌐 Prod', url: 'https://baf.thuanle.me' },
|
||||||
|
|
||||||
|
// Page state
|
||||||
|
current_page: 'alpha-swap',
|
||||||
|
is_logged_in: true,
|
||||||
|
|
||||||
|
// Bot status
|
||||||
|
bot_status: BOT_STATUS.READY_FOR_NEW_TASKS,
|
||||||
|
|
||||||
|
// Task state
|
||||||
|
current_task: { type: 'login', id: 'task_123', ... },
|
||||||
|
task_data: { method: 'qr_code', timeout: 300000, ... },
|
||||||
|
current_step: 'navigate_to_login',
|
||||||
|
step_data: { attempts: 0, start_time: 1234567890 },
|
||||||
|
|
||||||
|
// UI state
|
||||||
|
is_loading: false,
|
||||||
|
error_message: null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Observer Pattern
|
||||||
|
|
||||||
|
State changes trigger observers:
|
||||||
|
|
||||||
|
- **server_mode** → Save to storage, reload page
|
||||||
|
- **bot_status** → Save to storage, start/stop task polling
|
||||||
|
- **current_page** → Continue current step if task active
|
||||||
|
- **is_logged_in** → Continue current step if task active
|
||||||
|
|
||||||
|
## 🛠️ Helper Functions
|
||||||
|
|
||||||
|
### TaskRunner Helpers
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Validate enum values
|
||||||
|
TaskRunner.validateEnum(value, enumObj, defaultValue)
|
||||||
|
|
||||||
|
// Validate and merge task data
|
||||||
|
TaskRunner.validateTaskData(taskType, taskData)
|
||||||
|
|
||||||
|
// Get current task data
|
||||||
|
TaskRunner.getCurrentTaskData()
|
||||||
|
|
||||||
|
// Update task data during execution
|
||||||
|
TaskRunner.updateTaskData(newData)
|
||||||
|
|
||||||
|
// Check current task type
|
||||||
|
TaskRunner.isCurrentTaskType(TASK_TYPES.LOGIN)
|
||||||
|
|
||||||
|
// Get current task type
|
||||||
|
TaskRunner.getCurrentTaskType()
|
||||||
|
```
|
||||||
|
|
||||||
|
### State Helpers
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Get current state
|
||||||
|
APP_STATE.getData()
|
||||||
|
|
||||||
|
// Update state (triggers observers)
|
||||||
|
APP_STATE.update({ bot_status: BOT_STATUS.PERFORMING_TASKS })
|
||||||
|
|
||||||
|
// Subscribe to state changes
|
||||||
|
APP_STATE.subscribe('bot_status', callback)
|
||||||
|
|
||||||
|
// Unsubscribe from state changes
|
||||||
|
APP_STATE.unsubscribe('bot_status', callback)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎮 Menu Commands
|
||||||
|
|
||||||
|
### Available Commands
|
||||||
|
|
||||||
|
1. **Server**: Switch between local/prod servers
|
||||||
|
2. **Token**: Configure Bearer token for API access
|
||||||
|
3. **Bot Status**: Cycle through bot statuses (ready/pause/performing)
|
||||||
|
4. **Get Tasks**: Manually check for new tasks
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Access via Tampermonkey menu
|
||||||
|
// Right-click extension icon → Binance Alpha Farm Agent
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📡 API Endpoints
|
||||||
|
|
||||||
|
### Server Communication
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Ping server with status
|
||||||
|
BAF.ping(status)
|
||||||
|
|
||||||
|
// Get new task
|
||||||
|
BAF.getTasks()
|
||||||
|
|
||||||
|
// Submit task result
|
||||||
|
BAF.submitTaskResult(taskId, result)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Status Payload
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
logged_in: true,
|
||||||
|
current_page: 'alpha-swap',
|
||||||
|
bot_status: 'ready-for-new-tasks',
|
||||||
|
current_task: 'login',
|
||||||
|
task_data: { method: 'qr_code', ... },
|
||||||
|
current_step: 'navigate_to_login',
|
||||||
|
has_current_task: true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Configuration
|
||||||
|
|
||||||
|
### CONFIG Object
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
CONFIG = {
|
||||||
|
heartbeat_interval: 10000, // 10 seconds
|
||||||
|
task_poll_interval: 10000, // 10 seconds
|
||||||
|
is_debug: true,
|
||||||
|
servers: {
|
||||||
|
local: { label: '🏠 Local', url: 'http://localhost:3000' },
|
||||||
|
prod: { label: '🌐 Prod', url: 'https://baf.thuanle.me' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Getting Started
|
||||||
|
|
||||||
|
1. Install Tampermonkey browser extension
|
||||||
|
2. Install the user script
|
||||||
|
3. Configure server token via menu
|
||||||
|
4. Set bot status to "ready-for-new-tasks"
|
||||||
|
5. Bot will automatically start polling for tasks
|
||||||
|
|
||||||
|
## 📝 Development Notes
|
||||||
|
|
||||||
|
### Adding New Task Types
|
||||||
|
|
||||||
|
1. Add to `TASK_TYPES` enum
|
||||||
|
2. Define task data structure in `TASK_DATA_EXAMPLES`
|
||||||
|
3. Add validation in `validateTaskData()`
|
||||||
|
4. Implement execution method in `TaskRunner`
|
||||||
|
5. Define steps in `STEP_TYPES`
|
||||||
|
|
||||||
|
### Adding New Steps
|
||||||
|
|
||||||
|
1. Add to `STEP_TYPES` enum
|
||||||
|
2. Implement step logic in task execution
|
||||||
|
3. Update step data during execution
|
||||||
|
4. Handle step transitions
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
- Invalid enum values fallback to defaults
|
||||||
|
- Task execution errors return bot to ready state
|
||||||
|
- Network errors trigger retry after interval
|
||||||
|
- Page navigation errors handled by observers
|
||||||
735
agent.user.js
735
agent.user.js
@@ -1,10 +1,11 @@
|
|||||||
// ==UserScript==
|
// ==UserScript==
|
||||||
// @name Binance Alpha Farm Agent
|
// @name Binance Alpha Farm Agent
|
||||||
// @namespace http://baf.thuanle.me
|
// @namespace http://baf.thuanle.me
|
||||||
// @version 2025.07.31
|
// @version 2025.08.01
|
||||||
// @author TL
|
// @author TL
|
||||||
// @description Automated trading agent for Binance Alpha Farm
|
// @description Automated trading agent for Binance Alpha Farm
|
||||||
// @match https://www.binance.com/*
|
// @match https://www.binance.com/*
|
||||||
|
// @match https://accounts.binance.com/*
|
||||||
// @run-at document-idle
|
// @run-at document-idle
|
||||||
// @grant GM_setValue
|
// @grant GM_setValue
|
||||||
// @grant GM_getValue
|
// @grant GM_getValue
|
||||||
@@ -21,35 +22,137 @@
|
|||||||
(async () => {
|
(async () => {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// ====== Servers ======
|
// ====== CONFIGURATION ======
|
||||||
const BAF_SERVERS = {
|
|
||||||
local: { label: '🏠 Local', url: 'http://localhost:3000' },
|
|
||||||
prod: { label: '🌐 Prod', url: 'https://baf.thuanle.me' },
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const CONFIG = {
|
const CONFIG = {
|
||||||
heartbeat_interval: 10000,
|
heartbeat_interval: 10000,
|
||||||
is_debug: false,
|
task_poll_interval: 10000,
|
||||||
|
is_debug: true,
|
||||||
|
servers: {
|
||||||
|
local: { label: '🏠 Local', url: 'http://localhost:3000' },
|
||||||
|
prod: { label: '🌐 Prod', url: 'https://baf.thuanle.me' },
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ====== STATE MANAGEMENT ======
|
||||||
|
class AppState {
|
||||||
|
constructor() {
|
||||||
|
this.data = {
|
||||||
|
// Server configuration
|
||||||
|
server_mode: 'prod',
|
||||||
|
server: null,
|
||||||
|
|
||||||
|
// Page state
|
||||||
|
current_page: null,
|
||||||
|
is_logged_in: false,
|
||||||
|
|
||||||
|
// Bot status
|
||||||
|
bot_status: BOT_STATUS.READY_FOR_NEW_TASKS,
|
||||||
|
|
||||||
|
// Task state
|
||||||
|
current_task: null,
|
||||||
|
task_data: null,
|
||||||
|
current_step: null,
|
||||||
|
step_data: null,
|
||||||
|
|
||||||
|
// UI state
|
||||||
|
is_loading: false,
|
||||||
|
error_message: null
|
||||||
|
};
|
||||||
|
|
||||||
|
this.observers = new Map();
|
||||||
|
this.initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize state from storage and page detection
|
||||||
|
async initialize() {
|
||||||
|
if (this.initialized) return;
|
||||||
|
|
||||||
|
// Load from storage
|
||||||
|
this.data.server_mode = await STORAGE.getServerMode();
|
||||||
|
this.data.server = CONFIG.servers[this.data.server_mode] || CONFIG.servers.prod;
|
||||||
|
this.data.bot_status = await STORAGE.getBotStatus();
|
||||||
|
|
||||||
|
// Detect current page and login state
|
||||||
|
this.data.current_page = BINANCE.detectPage();
|
||||||
|
this.data.is_logged_in = await BINANCE.isLoggedIn();
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
TL.debug('STATE', 'Initialized', this.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current state
|
||||||
|
getData() {
|
||||||
|
return { ...this.data };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update state with partial update
|
||||||
|
async update(updates) {
|
||||||
|
const oldData = { ...this.data };
|
||||||
|
|
||||||
|
// Apply updates
|
||||||
|
Object.assign(this.data, updates);
|
||||||
|
|
||||||
|
// Notify observers
|
||||||
|
await this.notifyObservers(oldData, this.data);
|
||||||
|
|
||||||
|
TL.debug('STATE', 'Updated', updates);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscribe to state changes
|
||||||
|
subscribe(key, callback) {
|
||||||
|
if (!this.observers.has(key)) {
|
||||||
|
this.observers.set(key, []);
|
||||||
|
}
|
||||||
|
this.observers.get(key).push(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsubscribe from state changes
|
||||||
|
unsubscribe(key, callback) {
|
||||||
|
const callbacks = this.observers.get(key);
|
||||||
|
if (callbacks) {
|
||||||
|
const index = callbacks.indexOf(callback);
|
||||||
|
if (index > -1) {
|
||||||
|
callbacks.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify all observers
|
||||||
|
async notifyObservers(oldData, newData) {
|
||||||
|
for (const [key, callbacks] of this.observers) {
|
||||||
|
const oldValue = oldData[key];
|
||||||
|
const newValue = newData[key];
|
||||||
|
|
||||||
|
if (oldValue !== newValue) {
|
||||||
|
for (const callback of callbacks) {
|
||||||
|
try {
|
||||||
|
await callback(oldValue, newValue, newData);
|
||||||
|
} catch (error) {
|
||||||
|
TL.error('STATE', `Observer error for ${key}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ====== STORAGE MODULE ======
|
||||||
|
|
||||||
// ====== Storage ======
|
|
||||||
const STORAGE = {
|
const STORAGE = {
|
||||||
key_token: 'baf-agent-token',
|
key_token: 'baf-agent-token',
|
||||||
|
key_server_mode: 'baf-server-mode',
|
||||||
|
key_bot_status: 'baf-bot-status',
|
||||||
|
|
||||||
getToken: () => GM_getValue(STORAGE.key_token, ''),
|
getToken: () => GM_getValue(STORAGE.key_token, ''),
|
||||||
setToken: token => GM_setValue(STORAGE.key_token, String(token || '').trim().replace(/^Bearer\s+/i, '')),
|
setToken: token => GM_setValue(STORAGE.key_token, String(token || '').trim().replace(/^Bearer\s+/i, '')),
|
||||||
|
|
||||||
key_server_mode: 'baf-server-mode', // 'local' | 'prod'
|
|
||||||
getServerMode: () => GM_getValue(STORAGE.key_server_mode, 'prod'),
|
getServerMode: () => GM_getValue(STORAGE.key_server_mode, 'prod'),
|
||||||
setServerMode: (mode) => GM_setValue(STORAGE.key_server_mode, mode),
|
setServerMode: (mode) => GM_setValue(STORAGE.key_server_mode, mode),
|
||||||
|
|
||||||
|
getBotStatus: () => GM_getValue(STORAGE.key_bot_status, BOT_STATUS.READY_FOR_NEW_TASKS),
|
||||||
|
setBotStatus: (status) => GM_setValue(STORAGE.key_bot_status, status),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ====== UTILITY MODULE ======
|
||||||
|
|
||||||
|
|
||||||
// ====== Utility ======
|
|
||||||
const TL = {
|
const TL = {
|
||||||
debug: (tag, msg, ...args) => CONFIG.is_debug && GM_log(`[TL] [${tag}]\n${msg}`, ...args),
|
debug: (tag, msg, ...args) => CONFIG.is_debug && GM_log(`[TL] [${tag}]\n${msg}`, ...args),
|
||||||
log: (tag, msg, ...args) => GM_log(`[TL] [${tag}]\n${msg}`, ...args),
|
log: (tag, msg, ...args) => GM_log(`[TL] [${tag}]\n${msg}`, ...args),
|
||||||
@@ -60,16 +163,14 @@
|
|||||||
GM_notification({ title, text, timeout });
|
GM_notification({ title, text, timeout });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
alert(`${title}\n${text}`);
|
||||||
alert(`${title}\n${text}`); // fallback
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
delay: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
|
delay: (ms) => new Promise(resolve => setTimeout(resolve, ms)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// DOM helpers
|
||||||
// ====== DOM helpers ======
|
|
||||||
TL.dom = {
|
TL.dom = {
|
||||||
isVisible: (el) => {
|
isVisible: (el) => {
|
||||||
if (!el) return false;
|
if (!el) return false;
|
||||||
@@ -102,7 +203,7 @@
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// ====== Network helpers (GM_xmlhttpRequest) ======
|
// Network helpers
|
||||||
TL.net = {
|
TL.net = {
|
||||||
gmRequest(url, init = {}) {
|
gmRequest(url, init = {}) {
|
||||||
const headersToObject = (h) => {
|
const headersToObject = (h) => {
|
||||||
@@ -137,21 +238,18 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// ====== BAF API ======
|
// ====== BAF API MODULE ======
|
||||||
const BAF = {
|
const BAF = {
|
||||||
// Trả về object {label, url} theo mode hiện tại
|
|
||||||
getServer: async () => {
|
getServer: async () => {
|
||||||
const mode = await STORAGE.getServerMode();
|
const data = APP_STATE.getData();
|
||||||
return BAF_SERVERS[mode] || BAF_SERVERS.prod;
|
return CONFIG.servers[data.server_mode] || CONFIG.servers.prod;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Trả về URL host hiện tại
|
|
||||||
getHost: async () => {
|
getHost: async () => {
|
||||||
const s = await BAF.getServer();
|
const s = await BAF.getServer();
|
||||||
return s.url;
|
return s.url;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Wrapper GMXHR
|
|
||||||
request: async (method, path, { params, body, headers } = {}) => {
|
request: async (method, path, { params, body, headers } = {}) => {
|
||||||
const base = await BAF.getHost();
|
const base = await BAF.getHost();
|
||||||
const url = new URL(path, base);
|
const url = new URL(path, base);
|
||||||
@@ -175,62 +273,58 @@
|
|||||||
post: (path, body, init = {}) => BAF.request('POST', path, { body, headers: init.headers }),
|
post: (path, body, init = {}) => BAF.request('POST', path, { body, headers: init.headers }),
|
||||||
|
|
||||||
ping: (status) => BAF.post('/agent/ping', status),
|
ping: (status) => BAF.post('/agent/ping', status),
|
||||||
|
|
||||||
|
getTasks: () => BAF.get('/agent/tasks'),
|
||||||
|
submitTaskResult: (taskId, result) => BAF.post(`/agent/tasks/${taskId}/result`, result),
|
||||||
};
|
};
|
||||||
|
|
||||||
// ====== Cấu hình menu ======
|
// ====== BINANCE MODULE ======
|
||||||
async function createGM_Menu() {
|
const BINANCE_PAGES = {
|
||||||
const curSrv = await BAF.getServer();
|
LOGIN: 'login',
|
||||||
GM_registerMenuCommand(`Server: ${curSrv.label} (${curSrv.url})`, async () => {
|
ALPHA_SWAP: 'alpha-swap',
|
||||||
try {
|
ALPHA_ORDER_HISTORY: 'alpha-order-history',
|
||||||
const cur = await STORAGE.getServerMode();
|
UNKNOWN: 'unknown'
|
||||||
const next = (cur === 'local') ? 'prod' : 'local';
|
};
|
||||||
await STORAGE.setServerMode(next);
|
|
||||||
const nsv = await BAF.getServer();
|
|
||||||
const msg = `Switched to ${nsv.label} (${nsv.url})`;
|
|
||||||
TL.debug(`BAF`,msg);
|
|
||||||
TL.noti('BAF Server Switched', msg);
|
|
||||||
|
|
||||||
await TL.delay(300);
|
|
||||||
location.reload();
|
|
||||||
} catch (e) {
|
|
||||||
TL.error('BAF','switch server error', e);
|
|
||||||
TL.noti('BAF Server Switched', `Switch server error: ${e.message}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
GM_registerMenuCommand('Token', async () => {
|
|
||||||
try {
|
|
||||||
const curToken = await STORAGE.getToken();
|
|
||||||
const input = prompt('Bearer token:', curToken || '');
|
|
||||||
if (input !== null && input.trim() && input.trim() !== curToken) {
|
|
||||||
await STORAGE.setToken(input);
|
|
||||||
}
|
|
||||||
const s = await BAF.getServer();
|
|
||||||
const res = await BAF.ping();
|
|
||||||
const resStr =
|
|
||||||
`Server: ${s.label} (${s.url})\n` +
|
|
||||||
`Status: ${res.ok ? 'Connected ✅' : 'Failed ❌'} (${res.status})`;
|
|
||||||
TL.debug(`BAF`,resStr);
|
|
||||||
TL.noti('BAF Server', resStr);
|
|
||||||
} catch (e) {
|
|
||||||
const resStr = `ping error: ${e.message}`;
|
|
||||||
TL.error('BAF',resStr);
|
|
||||||
TL.noti('BAF Server', resStr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// ====== BINANCE (isLoggedIn) ======
|
|
||||||
const BINANCE = {
|
const BINANCE = {
|
||||||
|
detectPage: () => {
|
||||||
|
const pathname = window.location.pathname;
|
||||||
|
const hostname = window.location.hostname;
|
||||||
|
|
||||||
|
if (hostname === 'accounts.binance.com') {
|
||||||
|
if (pathname.includes('/login')) {
|
||||||
|
return BINANCE_PAGES.LOGIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hostname === 'www.binance.com') {
|
||||||
|
if (pathname.includes('/alpha/bsc/')) {
|
||||||
|
return BINANCE_PAGES.ALPHA_SWAP;
|
||||||
|
}
|
||||||
|
if (pathname.includes('/my/orders/alpha/orderhistory')) {
|
||||||
|
return BINANCE_PAGES.ALPHA_ORDER_HISTORY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BINANCE_PAGES.UNKNOWN;
|
||||||
|
},
|
||||||
|
|
||||||
|
isOnLoginPage: () => BINANCE.detectPage() === BINANCE_PAGES.LOGIN,
|
||||||
|
isOnAlphaSwapPage: () => BINANCE.detectPage() === BINANCE_PAGES.ALPHA_SWAP,
|
||||||
|
isOnAlphaOrderHistoryPage: () => BINANCE.detectPage() === BINANCE_PAGES.ALPHA_ORDER_HISTORY,
|
||||||
|
|
||||||
detectLoginState: () => {
|
detectLoginState: () => {
|
||||||
|
if (BINANCE.isOnLoginPage()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const loginBtn = document.querySelector('#toLoginPage');
|
const loginBtn = document.querySelector('#toLoginPage');
|
||||||
const regBtn = document.querySelector('#toRegisterPage');
|
const regBtn = document.querySelector('#toRegisterPage');
|
||||||
|
|
||||||
if (TL.dom.isVisible?.(loginBtn) || TL.ui.isVisible?.(regBtn)) return false;
|
if (TL.dom.isVisible?.(loginBtn) || TL.dom.isVisible?.(regBtn)) return false;
|
||||||
if (!loginBtn && !regBtn) return true;
|
if (!loginBtn && !regBtn) return true;
|
||||||
|
|
||||||
return null; // đang load hoặc chưa rõ
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
isLoggedIn: async (timeoutMs = 6000, pollMs = 200) => {
|
isLoggedIn: async (timeoutMs = 6000, pollMs = 200) => {
|
||||||
@@ -249,32 +343,440 @@
|
|||||||
TL.debug(`BINANCE`,`isLoggedIn (timeout, fallback) => ${fallback ? 'true' : 'false'}`);
|
TL.debug(`BINANCE`,`isLoggedIn (timeout, fallback) => ${fallback ? 'true' : 'false'}`);
|
||||||
return fallback;
|
return fallback;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
navigateToLogin: () => {
|
||||||
|
const loginBtn = document.querySelector('#toLoginPage');
|
||||||
|
if (TL.dom.isVisible?.(loginBtn)) {
|
||||||
|
TL.dom.click(loginBtn);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
navigateToAlphaSwap: () => {
|
||||||
|
if (!BINANCE.isOnAlphaSwapPage()) {
|
||||||
|
window.location.href = 'https://www.binance.com/alpha/bsc/';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
navigateToAlphaOrderHistory: () => {
|
||||||
|
if (!BINANCE.isOnAlphaOrderHistoryPage()) {
|
||||||
|
window.location.href = 'https://www.binance.com/en/my/orders/alpha/orderhistory';
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ====== TASK & STEP SYSTEM ======
|
||||||
|
|
||||||
|
// Task Types Enum
|
||||||
|
const TASK_TYPES = {
|
||||||
|
LOGIN: 'login',
|
||||||
|
GET_ORDER_HISTORY: 'get_order_history',
|
||||||
|
GET_BALANCE: 'get_balance',
|
||||||
|
SWAP: 'swap',
|
||||||
|
NO_TASK: 'no_task'
|
||||||
|
};
|
||||||
|
|
||||||
// ====== Main ======
|
// Bot Status Enum
|
||||||
async function welcome_message() {
|
const BOT_STATUS = {
|
||||||
const s = await BAF.getServer();
|
READY_FOR_NEW_TASKS: 'ready-for-new-tasks',
|
||||||
const res = await BAF.ping();
|
PAUSE_AUTOMATION: 'pause-automation',
|
||||||
|
PERFORMING_TASKS: 'performing-tasks'
|
||||||
|
};
|
||||||
|
|
||||||
const resStr =
|
// Login Method Enum
|
||||||
`Server: ${s.label} (${s.url})\n` +
|
const LOGIN_METHOD = {
|
||||||
`Status: ${res.ok ? 'Connected ✅' : 'Failed ❌'} (${res.status})\n`;
|
QR_CODE: 'qr_code',
|
||||||
|
PASSWORD: 'password',
|
||||||
|
EMAIL: 'email'
|
||||||
|
};
|
||||||
|
|
||||||
TL.log(`BAF`,resStr);
|
// Order Type Enum
|
||||||
|
const ORDER_TYPE = {
|
||||||
|
SWAP: 'swap',
|
||||||
|
LIMIT: 'limit',
|
||||||
|
MARKET: 'market'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Order Status Enum
|
||||||
|
const ORDER_STATUS = {
|
||||||
|
COMPLETED: 'completed',
|
||||||
|
PENDING: 'pending',
|
||||||
|
CANCELLED: 'cancelled'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Balance Format Enum
|
||||||
|
const BALANCE_FORMAT = {
|
||||||
|
JSON: 'json',
|
||||||
|
CSV: 'csv'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Gas Fee Type Enum
|
||||||
|
const GAS_FEE_TYPE = {
|
||||||
|
AUTO: 'auto',
|
||||||
|
FAST: 'fast',
|
||||||
|
SLOW: 'slow'
|
||||||
|
};
|
||||||
|
|
||||||
|
const STEP_TYPES = {
|
||||||
|
// Login task steps
|
||||||
|
NAVIGATE_TO_LOGIN: 'navigate_to_login',
|
||||||
|
SELECT_QR_CODE: 'select_qr_code',
|
||||||
|
SEND_TO_SERVER: 'send_to_server',
|
||||||
|
WAIT_FOR_LOGIN: 'wait_for_login',
|
||||||
|
REPORT_RESULT: 'report_result',
|
||||||
|
|
||||||
|
// Order history task steps
|
||||||
|
NAVIGATE_TO_ORDER_HISTORY: 'navigate_to_order_history',
|
||||||
|
EXTRACT_ORDER_DATA: 'extract_order_data',
|
||||||
|
SEND_ORDER_DATA: 'send_order_data',
|
||||||
|
|
||||||
|
// Balance task steps
|
||||||
|
NAVIGATE_TO_BALANCE: 'navigate_to_balance',
|
||||||
|
EXTRACT_BALANCE_DATA: 'extract_balance_data',
|
||||||
|
SEND_BALANCE_DATA: 'send_balance_data',
|
||||||
|
|
||||||
|
// Swap task steps
|
||||||
|
NAVIGATE_TO_SWAP: 'navigate_to_swap',
|
||||||
|
FILL_SWAP_FORM: 'fill_swap_form',
|
||||||
|
CONFIRM_SWAP: 'confirm_swap',
|
||||||
|
WAIT_FOR_SWAP: 'wait_for_swap',
|
||||||
|
|
||||||
|
// Common steps
|
||||||
|
WAIT: 'wait',
|
||||||
|
ERROR_HANDLING: 'error_handling'
|
||||||
|
};
|
||||||
|
|
||||||
|
// ====== TASK RUNNER ======
|
||||||
|
const TaskRunner = {
|
||||||
|
// Helper function to validate enum values
|
||||||
|
validateEnum(value, enumObj, defaultValue) {
|
||||||
|
const validValues = Object.values(enumObj);
|
||||||
|
if (validValues.includes(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
TL.debug('TASK', `Invalid enum value: ${value}, using default: ${defaultValue}`);
|
||||||
|
return defaultValue;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper function to validate and merge task data with defaults
|
||||||
|
validateTaskData(taskType, taskData = {}) {
|
||||||
|
const mergedData = { ...taskData };
|
||||||
|
|
||||||
|
// Validate enum fields if present
|
||||||
|
if (taskType === TASK_TYPES.LOGIN && mergedData.method) {
|
||||||
|
mergedData.method = this.validateEnum(mergedData.method, LOGIN_METHOD, LOGIN_METHOD.QR_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskType === TASK_TYPES.GET_ORDER_HISTORY) {
|
||||||
|
if (mergedData.order_types) {
|
||||||
|
mergedData.order_types = mergedData.order_types.map(type =>
|
||||||
|
this.validateEnum(type, ORDER_TYPE, ORDER_TYPE.SWAP)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (mergedData.status) {
|
||||||
|
mergedData.status = mergedData.status.map(status =>
|
||||||
|
this.validateEnum(status, ORDER_STATUS, ORDER_STATUS.COMPLETED)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskType === TASK_TYPES.GET_BALANCE && mergedData.format) {
|
||||||
|
mergedData.format = this.validateEnum(mergedData.format, BALANCE_FORMAT, BALANCE_FORMAT.JSON);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskType === TASK_TYPES.SWAP && mergedData.gas_fee && typeof mergedData.gas_fee === 'string') {
|
||||||
|
mergedData.gas_fee = this.validateEnum(mergedData.gas_fee, GAS_FEE_TYPE, GAS_FEE_TYPE.AUTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
TL.debug('TASK', `Validated task data for ${taskType}:`, mergedData);
|
||||||
|
return mergedData;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper function to get current task data
|
||||||
|
getCurrentTaskData() {
|
||||||
|
const data = APP_STATE.getData();
|
||||||
|
return data.task_data || {};
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper function to update task data
|
||||||
|
async updateTaskData(newData) {
|
||||||
|
const currentData = this.getCurrentTaskData();
|
||||||
|
const updatedData = { ...currentData, ...newData };
|
||||||
|
await APP_STATE.update({ task_data: updatedData });
|
||||||
|
return updatedData;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper function to check if current task is of specific type
|
||||||
|
isCurrentTaskType(taskType) {
|
||||||
|
const data = APP_STATE.getData();
|
||||||
|
return data.current_task?.type === taskType;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Helper function to get current task type
|
||||||
|
getCurrentTaskType() {
|
||||||
|
const data = APP_STATE.getData();
|
||||||
|
return data.current_task?.type || null;
|
||||||
|
},
|
||||||
|
async checkForNewTasks() {
|
||||||
|
try {
|
||||||
|
const data = APP_STATE.getData();
|
||||||
|
if (data.bot_status !== BOT_STATUS.READY_FOR_NEW_TASKS) {
|
||||||
|
TL.debug('TASK', 'Bot not ready for new tasks, status:', data.bot_status);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await BAF.getTasks();
|
||||||
|
if (response.ok && response.data.task) {
|
||||||
|
const task = response.data.task;
|
||||||
|
TL.log('TASK', `Received new task: ${task.type}`, task);
|
||||||
|
|
||||||
|
// Validate and merge task data with defaults
|
||||||
|
const validatedTaskData = TaskRunner.validateTaskData(task.type, task.data);
|
||||||
|
|
||||||
|
await APP_STATE.update({
|
||||||
|
current_task: task,
|
||||||
|
task_data: validatedTaskData,
|
||||||
|
current_step: task.steps?.[0] || null,
|
||||||
|
bot_status: BOT_STATUS.PERFORMING_TASKS
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start executing the task
|
||||||
|
await this.executeTask(task);
|
||||||
|
} else if (response.ok && response.data.no_task) {
|
||||||
|
TL.debug('TASK', 'No new tasks available');
|
||||||
|
// Sleep for interval then check again
|
||||||
|
setTimeout(() => this.checkForNewTasks(), CONFIG.task_poll_interval);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
TL.error('TASK', 'Failed to check for new tasks:', error);
|
||||||
|
// Retry after interval
|
||||||
|
setTimeout(() => this.checkForNewTasks(), CONFIG.task_poll_interval);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async executeTask(task) {
|
||||||
|
TL.log('TASK', `Executing task: ${task.type}`, task);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await APP_STATE.update({ is_loading: true });
|
||||||
|
|
||||||
|
let result = null;
|
||||||
|
|
||||||
|
switch (task.type) {
|
||||||
|
case TASK_TYPES.LOGIN:
|
||||||
|
result = await this.executeLoginTask(task);
|
||||||
|
break;
|
||||||
|
case TASK_TYPES.GET_ORDER_HISTORY:
|
||||||
|
result = await this.executeGetOrderHistoryTask(task);
|
||||||
|
break;
|
||||||
|
case TASK_TYPES.GET_BALANCE:
|
||||||
|
result = await this.executeGetBalanceTask(task);
|
||||||
|
break;
|
||||||
|
case TASK_TYPES.SWAP:
|
||||||
|
result = await this.executeSwapTask(task);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown task type: ${task.type}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit result to server
|
||||||
|
await BAF.submitTaskResult(task.id, result);
|
||||||
|
|
||||||
|
// Task completed, return to ready state
|
||||||
|
await APP_STATE.update({
|
||||||
|
current_task: null,
|
||||||
|
task_data: null,
|
||||||
|
current_step: null,
|
||||||
|
step_data: null,
|
||||||
|
bot_status: BOT_STATUS.READY_FOR_NEW_TASKS,
|
||||||
|
is_loading: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for next task after a short delay
|
||||||
|
setTimeout(() => this.checkForNewTasks(), 1000);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
TL.error('TASK', `Task execution failed:`, error);
|
||||||
|
await APP_STATE.update({
|
||||||
|
error_message: error.message,
|
||||||
|
bot_status: BOT_STATUS.READY_FOR_NEW_TASKS,
|
||||||
|
is_loading: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Retry after interval
|
||||||
|
setTimeout(() => this.checkForNewTasks(), CONFIG.task_poll_interval);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async executeLoginTask(task) {
|
||||||
|
const taskData = this.getCurrentTaskData();
|
||||||
|
TL.log('TASK', 'Executing login task', taskData);
|
||||||
|
|
||||||
|
// Implementation for login task with steps
|
||||||
|
// taskData contains: { method: 'qr_code' | 'password', credentials: {...}, timeout: 300000, retry_count: 3 }
|
||||||
|
|
||||||
|
// Example: Update task data during execution
|
||||||
|
await this.updateTaskData({
|
||||||
|
start_time: Date.now(),
|
||||||
|
attempts: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, message: 'Login task completed' };
|
||||||
|
},
|
||||||
|
|
||||||
|
async executeGetOrderHistoryTask(task) {
|
||||||
|
const taskData = this.getCurrentTaskData();
|
||||||
|
TL.log('TASK', 'Executing get order history task', taskData);
|
||||||
|
|
||||||
|
// Implementation for get order history task
|
||||||
|
// taskData contains: { limit: 100, offset: 0, date_range: {...}, order_types: [...], status: [...] }
|
||||||
|
|
||||||
|
return { success: true, order_history: [] };
|
||||||
|
},
|
||||||
|
|
||||||
|
async executeGetBalanceTask(task) {
|
||||||
|
const taskData = this.getCurrentTaskData();
|
||||||
|
TL.log('TASK', 'Executing get balance task', taskData);
|
||||||
|
|
||||||
|
// Implementation for get balance task
|
||||||
|
// taskData contains: { currencies: ['USDT', 'BTC'], include_zero: false, include_locked: true, format: 'json' }
|
||||||
|
|
||||||
|
return { success: true, balance: {} };
|
||||||
|
},
|
||||||
|
|
||||||
|
async executeSwapTask(task) {
|
||||||
|
const taskData = this.getCurrentTaskData();
|
||||||
|
TL.log('TASK', 'Executing swap task', taskData);
|
||||||
|
|
||||||
|
// Implementation for swap task
|
||||||
|
// taskData contains: { from_token: 'USDT', to_token: 'BTC', amount: 100, slippage: 0.5, gas_fee: 'auto', deadline: 300, auto_confirm: true }
|
||||||
|
|
||||||
|
return { success: true, swap_result: {} };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ====== STATE OBSERVERS ======
|
||||||
|
const StateWatchers = {
|
||||||
|
// Storage observer - saves state changes to storage
|
||||||
|
async onServerModeChange(oldValue, newValue, data) {
|
||||||
|
TL.debug('OBSERVER', `Server mode changed: ${oldValue} -> ${newValue}`);
|
||||||
|
await STORAGE.setServerMode(newValue);
|
||||||
|
|
||||||
|
// Update server info
|
||||||
|
await APP_STATE.update({
|
||||||
|
server: CONFIG.servers[newValue] || CONFIG.servers.prod
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reload page after a short delay
|
||||||
|
setTimeout(() => location.reload(), 500);
|
||||||
|
},
|
||||||
|
|
||||||
|
async onBotStatusChange(oldValue, newValue, data) {
|
||||||
|
TL.debug('OBSERVER', `Bot status changed: ${oldValue} -> ${newValue}`);
|
||||||
|
await STORAGE.setBotStatus(newValue);
|
||||||
|
|
||||||
|
// Handle status-specific logic
|
||||||
|
if (newValue === BOT_STATUS.READY_FOR_NEW_TASKS) {
|
||||||
|
// Start checking for new tasks
|
||||||
|
TaskRunner.checkForNewTasks();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async onCurrentPageChange(oldValue, newValue, data) {
|
||||||
|
TL.debug('OBSERVER', `Page changed: ${oldValue} -> ${newValue}`);
|
||||||
|
|
||||||
|
// Handle page-specific logic for current task
|
||||||
|
if (data.current_task && data.current_step) {
|
||||||
|
// Continue with current step based on page change
|
||||||
|
await TaskRunner.continueCurrentStep();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async onLoginStateChange(oldValue, newValue, data) {
|
||||||
|
TL.debug('OBSERVER', `Login state changed: ${oldValue} -> ${newValue}`);
|
||||||
|
|
||||||
|
// Handle login state change for current task
|
||||||
|
if (data.current_task && data.current_step) {
|
||||||
|
await TaskRunner.continueCurrentStep();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ====== MENU SYSTEM ======
|
||||||
|
async function createGM_Menu() {
|
||||||
|
const data = APP_STATE.getData();
|
||||||
|
const curSrv = data.server;
|
||||||
|
|
||||||
|
GM_registerMenuCommand(`Server: ${curSrv.label} (${curSrv.url})`, async () => {
|
||||||
|
try {
|
||||||
|
const next = (data.server_mode === 'local') ? 'prod' : 'local';
|
||||||
|
await APP_STATE.update({ server_mode: next });
|
||||||
|
|
||||||
|
const nsv = CONFIG.servers[next];
|
||||||
|
const msg = `Switched to ${nsv.label} (${nsv.url})`;
|
||||||
|
TL.debug(`BAF`, msg);
|
||||||
|
TL.noti('BAF Server Switched', msg);
|
||||||
|
} catch (e) {
|
||||||
|
TL.error('BAF', 'switch server error', e);
|
||||||
|
TL.noti('BAF Server Switched', `Switch server error: ${e.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
GM_registerMenuCommand('Token', async () => {
|
||||||
|
try {
|
||||||
|
const curToken = await STORAGE.getToken();
|
||||||
|
const input = prompt('Bearer token:', curToken || '');
|
||||||
|
if (input !== null && input.trim() && input.trim() !== curToken) {
|
||||||
|
await STORAGE.setToken(input);
|
||||||
|
}
|
||||||
|
const s = await BAF.getServer();
|
||||||
|
const res = await BAF.ping();
|
||||||
|
const resStr =
|
||||||
|
`Server: ${s.label} (${s.url})\n` +
|
||||||
|
`Status: ${res.ok ? 'Connected ✅' : 'Failed ❌'} (${res.status})`;
|
||||||
|
TL.debug(`BAF`, resStr);
|
||||||
|
TL.noti('BAF Server', resStr);
|
||||||
|
} catch (e) {
|
||||||
|
const resStr = `ping error: ${e.message}`;
|
||||||
|
TL.error('BAF', resStr);
|
||||||
|
TL.noti('BAF Server', resStr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
GM_registerMenuCommand('Bot Status', async () => {
|
||||||
|
const data = APP_STATE.getData();
|
||||||
|
const statusOptions = [
|
||||||
|
BOT_STATUS.READY_FOR_NEW_TASKS,
|
||||||
|
BOT_STATUS.PAUSE_AUTOMATION,
|
||||||
|
BOT_STATUS.PERFORMING_TASKS
|
||||||
|
];
|
||||||
|
const currentIndex = statusOptions.indexOf(data.bot_status);
|
||||||
|
const nextIndex = (currentIndex + 1) % statusOptions.length;
|
||||||
|
const nextStatus = statusOptions[nextIndex];
|
||||||
|
|
||||||
|
await APP_STATE.update({ bot_status: nextStatus });
|
||||||
|
TL.noti('Bot Status', `Changed to: ${nextStatus}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
GM_registerMenuCommand('Get Tasks', async () => {
|
||||||
|
await TaskRunner.checkForNewTasks();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====== Heartbeat ======
|
// ====== HEARTBEAT ======
|
||||||
async function heartbeat_report() {
|
async function heartbeat_report() {
|
||||||
try {
|
try {
|
||||||
const isLoggedIn = await BINANCE.isLoggedIn();
|
const data = APP_STATE.getData();
|
||||||
|
|
||||||
const status = {
|
const status = {
|
||||||
logged_in: isLoggedIn
|
logged_in: data.is_logged_in,
|
||||||
|
current_page: data.current_page,
|
||||||
|
bot_status: data.bot_status,
|
||||||
|
current_task: data.current_task?.type || null,
|
||||||
|
task_data: data.task_data || null,
|
||||||
|
current_step: data.current_step || null,
|
||||||
|
has_current_task: !!data.current_task
|
||||||
};
|
};
|
||||||
|
|
||||||
// Log heartbeat
|
TL.debug(`HEARTBEAT`, `${JSON.stringify(status, null, 2)}`);
|
||||||
TL.debug(`HEARTBEAT`,`${JSON.stringify(status, null, 2)}`);
|
|
||||||
await BAF.ping(status);
|
await BAF.ping(status);
|
||||||
return status;
|
return status;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -283,9 +785,58 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ====== Khởi tạo ======
|
// ====== PAGE MONITOR ======
|
||||||
await welcome_message();
|
async function monitorPageChanges() {
|
||||||
await createGM_Menu();
|
let lastPage = APP_STATE.getData().current_page;
|
||||||
setInterval(heartbeat_report, CONFIG.heartbeat_interval);
|
|
||||||
|
setInterval(async () => {
|
||||||
|
const currentPage = BINANCE.detectPage();
|
||||||
|
if (currentPage !== lastPage) {
|
||||||
|
await APP_STATE.update({ current_page: currentPage });
|
||||||
|
lastPage = currentPage;
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== INITIALIZATION ======
|
||||||
|
const APP_STATE = new AppState();
|
||||||
|
|
||||||
|
async function initialize() {
|
||||||
|
// Initialize state
|
||||||
|
await APP_STATE.initialize();
|
||||||
|
|
||||||
|
// Register observers
|
||||||
|
APP_STATE.subscribe('server_mode', StateWatchers.onServerModeChange);
|
||||||
|
APP_STATE.subscribe('bot_status', StateWatchers.onBotStatusChange);
|
||||||
|
APP_STATE.subscribe('current_page', StateWatchers.onCurrentPageChange);
|
||||||
|
APP_STATE.subscribe('is_logged_in', StateWatchers.onLoginStateChange);
|
||||||
|
|
||||||
|
// Create menu
|
||||||
|
await createGM_Menu();
|
||||||
|
|
||||||
|
// Start monitoring
|
||||||
|
monitorPageChanges();
|
||||||
|
|
||||||
|
// Start heartbeat
|
||||||
|
setInterval(heartbeat_report, CONFIG.heartbeat_interval);
|
||||||
|
|
||||||
|
// Welcome message
|
||||||
|
const ping_res = await BAF.ping();
|
||||||
|
const data = APP_STATE.getData();
|
||||||
|
const res =
|
||||||
|
`Server: ${data.server.label} (${data.server.url})\n` +
|
||||||
|
`Status: ${ping_res.ok ? 'Connected ✅' : 'Failed ❌'} (${ping_res.status})\n` +
|
||||||
|
`Page: ${data.current_page || 'unknown'} (${window.location.href})\n` +
|
||||||
|
`Bot Status: ${data.bot_status}`;
|
||||||
|
|
||||||
|
TL.log(`BAF`, res);
|
||||||
|
|
||||||
|
// Start initial task checking if ready
|
||||||
|
if (data.bot_status === BOT_STATUS.READY_FOR_NEW_TASKS) {
|
||||||
|
TaskRunner.checkForNewTasks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the application
|
||||||
|
await initialize();
|
||||||
})();
|
})();
|
||||||
Reference in New Issue
Block a user