diff --git a/agent.user.js b/agent.user.js index 7263c6f..64488f1 100644 --- a/agent.user.js +++ b/agent.user.js @@ -1,12 +1,17 @@ // ==UserScript== // @name Binance Alpha Farm Agent // @namespace http://baf.thuanle.me -// @version 2025.07.30 +// @version 2025.07.31 // @author TL // @match https://www.binance.com/en/alpha/bsc/* // @run-at document-idle // @grant GM_setValue // @grant GM_getValue +// @grant GM_registerMenuCommand +// @grant GM_notification +// @grant GM_xmlhttpRequest +// @connect baf.thuanle.me +// @connect localhost // @downloadURL https://git.thuanle.me/public/binance-alpha-farm-agent/raw/branch/main/agent.user.js // @updateURL https://git.thuanle.me/public/binance-alpha-farm-agent/raw/branch/main/agent.user.js // ==/UserScript== @@ -14,140 +19,148 @@ (async () => { 'use strict'; - // Utility functions + // ====== Storage ====== + const STORAGE = { + key_token: 'baf-agent-token', + getToken: () => GM_getValue(STORAGE.key_token, ''), + 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'), + setServerMode: (mode) => GM_setValue(STORAGE.key_server_mode, mode), + }; + + // ====== Servers (có icon) ====== + const BAF_SERVERS = { + local: { label: '🏠 Local', url: 'http://localhost:3000' }, + prod: { label: '🌐 Prod', url: 'https://baf.thuanle.me' }, + }; + + + + // ====== Utility ====== const TL = { log: (msg, ...args) => console.log(`[TL] ${msg}`, ...args), - delay: ms => new Promise(resolve => setTimeout(resolve, ms)), - getEl: sel => document.querySelector(sel), - waitForEl: async (sel, timeout = 10000) => { - const start = Date.now(); - while (Date.now() - start < timeout) { - const el = document.querySelector(sel); - if (el) return el; - await TL.delay(100); - } - throw new Error(`waitForEl: Timeout waiting for ${sel}`); - }, + error: (msg, ...args) => console.error(`[TL] ${msg}`, ...args), + }; - eventFillFloatInputById(id, value) { - const input = document.getElementById(id); - if (!input) return; - const current = parseFloat(input.value); - const target = parseFloat(value); - if (Math.abs(current - target) < 1e-8) return; - - const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set; - setter.call(input, value); - input.dispatchEvent(new Event('input', { bubbles: true })); + // ====== Network helpers (GM_xmlhttpRequest) ====== + TL.net = { + gmRequest(url, init = {}) { + const headersToObject = (h) => { + const o = {}; + (h || new Headers()).forEach((v, k) => { o[k] = v; }); + return o; + }; + return new Promise((resolve, reject) => { + GM_xmlhttpRequest({ + url, + method: (init.method || 'GET').toUpperCase(), + headers: headersToObject(init.headers), + data: init.body, + onload: (resp) => { + const text = resp.responseText || ''; + let data = text; + const isJSON = /content-type:\s*application\/json/i.test(resp.responseHeaders || ''); + if (isJSON) { try { data = JSON.parse(text); } catch { } } + TL.log(`[GM] ${resp.status} ${url}`, data); + resolve({ + status: resp.status, + ok: resp.status >= 200 && resp.status < 300, + data, + rawText: text, + headers: null + }); + }, + onerror: reject, + ontimeout: () => reject(new Error('GM_xmlhttpRequest timeout')), + }); + }); } }; - // POP UP HELPER - const PopupHelper = { - defaultStyle: ` - position: fixed; - bottom: 20px; - background: white; - color: #000; - border: 1px solid #ccc; - padding: 12px 16px; - border-radius: 6px; - box-shadow: 0 4px 12px rgba(0,0,0,0.2); - z-index: 999999; - font-family: sans-serif; - font-size: 12px; - display: block !important; - visibility: visible !important; - opacity: 1 !important; - `, - - createOrUpdatePopup(id, options = {}) { - try { - const { - html = '', - position = 'bottom-right', - customStyle = '', - onClick = null - } = options; - - TL.log(`🔍 Creating/updating popup with ID: ${id}`); - - let popup = document.getElementById(id); - if (!popup) { - popup = document.createElement('div'); - popup.id = id; - document.body.appendChild(popup); - TL.log(`🔍 Created new popup element with ID: ${id}`); - } else { - TL.log(`🔍 Found existing popup with ID: ${id}`); - } - - // Set position based on position parameter - let positionStyle = ''; - switch (position) { - case 'bottom-right': - positionStyle = 'right: 20px;'; - break; - case 'bottom-left': - positionStyle = 'left: 20px;'; - break; - case 'top-right': - positionStyle = 'top: 20px; right: 20px;'; - break; - case 'top-left': - positionStyle = 'top: 20px; left: 20px;'; - break; - case 'center': - positionStyle = 'top: 50%; left: 50%; transform: translate(-50%, -50%);'; - break; - default: - positionStyle = 'right: 20px;'; - } - - // Apply styles - const finalStyle = this.defaultStyle + positionStyle + customStyle; - popup.style.cssText = finalStyle; - TL.log(`🔍 Applied styles to popup: ${finalStyle.substring(0, 100)}...`); - - // Set content - popup.innerHTML = html; - TL.log(`🔍 Set HTML content to popup (length: ${html.length})`); - - // Add click handler if provided - if (onClick && typeof onClick === 'function') { - popup.onclick = onClick; - } - - TL.log(`✅ Popup ${id} created/updated successfully`); - return popup; - } catch (error) { - TL.log(`❌ Error in createOrUpdatePopup: ${error.message}`); - return null; - } + // ====== BAF API ====== + const BAF = { + // Trả về object {label, url} theo mode hiện tại + getServer: async () => { + const mode = await STORAGE.getServerMode(); + return BAF_SERVERS[mode] || BAF_SERVERS.prod; }, - removePopup(id) { - const popup = document.getElementById(id); - if (popup) { - popup.remove(); - } + // Trả về URL host hiện tại + getHost: async () => { + const s = await BAF.getServer(); + return s.url; }, - + // Wrapper GMXHR + request: async (method, path, { params, body, headers } = {}) => { + const base = await BAF.getHost(); + const url = new URL(path, base); + if (params) for (const [k, v] of Object.entries(params)) url.searchParams.append(k, v); + + const h = new Headers(headers || {}); + const token = await STORAGE.getToken(); + if (token && !h.has('Authorization')) h.set('Authorization', `Bearer ${token}`); + + const init = { method, headers: h }; + if (body !== undefined && body !== null) { + if (!(body instanceof FormData) && !h.has('Content-Type')) h.set('Content-Type', 'application/json'); + init.body = (h.get('Content-Type') || '').includes('application/json') && typeof body !== 'string' + ? JSON.stringify(body) + : body; + } + return TL.net.gmRequest(url.toString(), init); + }, + + get: (path, params, init = {}) => BAF.request('GET', path, { params, headers: init.headers }), + post: (path, body, init = {}) => BAF.request('POST', path, { body, headers: init.headers }), + + ping: () => BAF.post('/agent/ping'), }; - // Main script logic - // show welcome popup - PopupHelper.createOrUpdatePopup('welcome-popup', { - html: ` -
-

Welcome to Binance Alpha Farm Agent

-

This script will help you manage your Binance Alpha Farm tasks more efficiently.

-

Click here to learn more.

- -
- `, onClick: () => { - PopupHelper.removePopup('welcome-popup'); - } - }); + // ====== Cấu hình menu ====== + async function createGM_Menu() { + const curSrv = await BAF.getServer(); + GM_registerMenuCommand(`Server: ${curSrv.label} (${curSrv.url})`, async () => { + try { + const cur = await STORAGE.getServerMode(); + const next = (cur === 'local') ? 'prod' : 'local'; + await STORAGE.setServerMode(next); + const nsv = await BAF.getServer(); + const msg = `Switched to ${nsv.label} (${nsv.url})`; + TL.log(msg); + GM_notification?.({ title: 'BAF Server Switched', text: msg, timeout: 2500 }); + alert(msg); + } catch (e) { + TL.error('switch server error', e); + alert('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.log(resStr); + alert(resStr); + } catch (e) { + const resStr = `ping error: ${e.message}`; + TL.error(resStr); + alert(resStr); + } + }); + + } + + // ====== Khởi tạo ====== + await createGM_Menu(); })(); \ No newline at end of file