// ==UserScript== // @name Binance Alpha Farm Agent // @namespace http://baf.thuanle.me // @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== (async () => { 'use strict'; // ====== 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), error: (msg, ...args) => console.error(`[TL] ${msg}`, ...args), }; // ====== 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')), }); }); } }; // ====== 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; }, // 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'), }; // ====== 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(); })();