├── .gitignore ├── README.md ├── index.js ├── package.json ├── proxy.txt ├── src ├── Bot.js ├── Config.js ├── ProxyManager.js └── utils.js └── uid.txt /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | uid.txt 4 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @GetGrass-Bot 2 | 这是一个专为小草空投第二季设计的机器人 3 | 4 | ## 功能 5 | ✅ 支持多个 UID 6 | 7 | ✅ 支持自定义代理 8 | 9 | ## 安装步骤 10 | 将此仓库克隆到你的本地环境: 11 | 12 | ```bash 13 | git clone https://github.com/ziqing888/getgrass-bot.git 14 | 15 | 16 | ``` 17 | 进入项目目录: 18 | ```bash 19 | cd getgrass-bot 20 | ``` 21 | ## 安装所需依赖项: 22 | ```bash 23 | npm install 24 | ``` 25 | 26 | ## 使用说明 27 | 获取用户 ID: 28 | 29 | 登录到 GetGrass 网站: https://app.getgrass.io/dashboard 30 | 31 | 打开浏览器开发者工具(通常按 F12 或右键选择“检查”)。 32 | 33 | 切换到“控制台”选项卡。 34 | 35 | 输入以下命令并按回车: 36 | 37 | ```bash 38 | localStorage.getItem('userId'); 39 | ``` 40 | 复制返回的值,这就是你的用户 ID。 41 | 42 | 创建用户 ID 文件: 43 | 44 | 在项目目录中创建一个名为 uid.txt 的文件,每行写一个用户 ID,例如: 45 | 46 | ```bash 47 | 123123213 48 | 12313123 49 | ``` 50 | 添加代理信息: 51 | 52 | 创建一个名为 proxy.txt 的文件,并按行添加你的代理 URL,格式如下: 53 | ```bash 54 | 55 | http://username:password@hostname:port 56 | socks5://username:password@hostname:port 57 | ``` 58 | 启动机器人: 59 | 60 | 在终端中执行以下命令运行 GetGrass-Bot: 61 | ```bash 62 | npm start 63 | 64 | ``` 65 | 推荐代理: 66 | ▫️ 廉价代理 67 | https://iproyal.com/?r=645571 68 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('colors'); 2 | const inquirer = require('inquirer'); 3 | const Bot = require('./src/Bot'); 4 | const Config = require('./src/Config'); 5 | const { 6 | fetchProxies, 7 | readLines, 8 | selectProxySource, 9 | } = require('./src/ProxyManager'); 10 | const { delay, displayHeader } = require('./src/utils'); 11 | 12 | async function main() { 13 | displayHeader(); 14 | console.log(`请稍候...\n`.yellow); 15 | 16 | await delay(1000); 17 | 18 | const config = new Config(); 19 | const bot = new Bot(config); 20 | 21 | const proxySource = await selectProxySource(inquirer); 22 | 23 | let proxies = []; 24 | if (proxySource.type === 'file') { 25 | proxies = await readLines(proxySource.source); 26 | } else if (proxySource.type === 'url') { 27 | proxies = await fetchProxies(proxySource.source); 28 | } else if (proxySource.type === 'none') { 29 | console.log('未选择代理。直接连接。'.cyan); 30 | } 31 | 32 | if (proxySource.type !== 'none' && proxies.length === 0) { 33 | console.error('未找到代理。退出...'.red); 34 | return; 35 | } 36 | 37 | console.log( 38 | proxySource.type !== 'none' 39 | ? `已加载 ${proxies.length} 个代理`.green 40 | : '启用直接连接模式。'.green 41 | ); 42 | 43 | const userIDs = await readLines('uid.txt'); 44 | if (userIDs.length === 0) { 45 | console.error('uid.txt 中未找到用户 ID。退出...'.red); 46 | return; 47 | } 48 | 49 | console.log(`已加载 ${userIDs.length} 个用户 ID\n`.green); 50 | 51 | const connectionPromises = userIDs.flatMap((userID) => 52 | proxySource.type !== 'none' 53 | ? proxies.map((proxy) => bot.connectToProxy(proxy, userID)) 54 | : [bot.connectDirectly(userID)] 55 | ); 56 | 57 | await Promise.all(connectionPromises); 58 | } 59 | 60 | main().catch(console.error); 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getgrass-bot", 3 | "version": "2..0", 4 | "description": "一个用于通过多种 HTTP 和 SOCKS 代理建立 WebSocket 连接的挂机小草空投第2季的机器人。", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node index.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/ziqing888/getgrass-bot.git" 13 | }, 14 | "keywords": [ 15 | "WebSocket", 16 | "proxy", 17 | "HTTP", 18 | "SOCKS", 19 | "bot", 20 | "getgrass", 21 | "airdrops", 22 | "farming" 23 | ], 24 | "author": "qklxsqf", 25 | "license": "MIT", 26 | "dependencies": { 27 | "axios": "^1.7.7", 28 | "colors": "^1.4.0", 29 | "https-proxy-agent": "^7.0.4", 30 | "inquirer": "^8.2.6", 31 | "socks-proxy-agent": "^8.0.2", 32 | "uuid": "^9.0.1", 33 | "ws": "^8.16.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /proxy.txt: -------------------------------------------------------------------------------- 1 | #示例 2 | http://username:password@hostname:port 3 | socks5://username:password@hostname:port 4 | -------------------------------------------------------------------------------- /src/Bot.js: -------------------------------------------------------------------------------- 1 | require('colors'); 2 | const WebSocket = require('ws'); 3 | const axios = require('axios'); 4 | const { v4: uuidv4 } = require('uuid'); 5 | const { SocksProxyAgent } = require('socks-proxy-agent'); 6 | const { HttpsProxyAgent } = require('https-proxy-agent'); 7 | 8 | const MAX_RETRIES = 5; // 最大重试次数 9 | const RETRY_INTERVAL = 10000; // 重试间隔 10 秒 10 | const PING_INTERVAL = 45000; // 心跳间隔设为 45 秒 11 | 12 | class Bot { 13 | constructor(config) { 14 | this.config = config; 15 | } 16 | 17 | async getProxyIP(proxy) { 18 | const agent = proxy.startsWith('http') 19 | ? new HttpsProxyAgent(proxy) 20 | : new SocksProxyAgent(proxy); 21 | try { 22 | const response = await axios.get(this.config.ipCheckURL, { 23 | httpsAgent: agent, 24 | }); 25 | console.log(`已通过代理 ${proxy} 连接`.green); 26 | return response.data; 27 | } catch (error) { 28 | console.error( 29 | `跳过代理 ${proxy},因连接错误:${error.message}`.yellow 30 | ); 31 | return null; 32 | } 33 | } 34 | 35 | async connectWithRetry(proxy, userID) { 36 | let attempts = 0; 37 | while (attempts < MAX_RETRIES) { 38 | try { 39 | await this.connectToProxy(proxy, userID); 40 | console.log(`成功连接到代理 ${proxy} 的用户 ID: ${userID}`); 41 | return; 42 | } catch (error) { 43 | console.error(`在代理 ${proxy} 上连接出错:${error.message}`); 44 | attempts++; 45 | console.log(`重试连接 (${attempts}/${MAX_RETRIES})...`); 46 | await this.delay(RETRY_INTERVAL); 47 | } 48 | } 49 | console.error(`无法连接到代理 ${proxy},已达到最大重试次数。`); 50 | } 51 | 52 | async connectToProxy(proxy, userID) { 53 | const formattedProxy = proxy.startsWith('socks5://') 54 | ? proxy 55 | : proxy.startsWith('http') 56 | ? proxy 57 | : `socks5://${proxy}`; 58 | const proxyInfo = await this.getProxyIP(formattedProxy); 59 | 60 | if (!proxyInfo) { 61 | return; 62 | } 63 | 64 | try { 65 | const agent = formattedProxy.startsWith('http') 66 | ? new HttpsProxyAgent(formattedProxy) 67 | : new SocksProxyAgent(formattedProxy); 68 | const wsURL = `wss://${this.config.wssHost}`; 69 | const ws = new WebSocket(wsURL, { 70 | agent, 71 | headers: { 72 | 'User-Agent': 73 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0', 74 | Pragma: 'no-cache', 75 | 'Accept-Language': 'uk-UA,uk;q=0.9,en-US;q=0.8,en;q=0.7', 76 | 'Cache-Control': 'no-cache', 77 | OS: 'Windows', 78 | Platform: 'Desktop', 79 | Browser: 'Mozilla', 80 | }, 81 | }); 82 | 83 | ws.on('open', () => { 84 | console.log(`已连接到代理 ${proxy}`.cyan); 85 | console.log(`代理 IP 信息: ${JSON.stringify(proxyInfo)}`.magenta); 86 | this.sendPing(ws, proxyInfo.ip); 87 | }); 88 | 89 | ws.on('message', (message) => { 90 | const msg = JSON.parse(message); 91 | console.log(`收到消息: ${JSON.stringify(msg)}`.blue); 92 | 93 | if (msg.action === 'AUTH') { 94 | const authResponse = { 95 | id: msg.id, 96 | origin_action: 'AUTH', 97 | result: { 98 | browser_id: uuidv4(), 99 | user_id: userID, 100 | user_agent: 'Mozilla/5.0', 101 | timestamp: Math.floor(Date.now() / 1000), 102 | device_type: 'desktop', 103 | version: '4.28.2', 104 | }, 105 | }; 106 | ws.send(JSON.stringify(authResponse)); 107 | console.log(`发送认证响应: ${JSON.stringify(authResponse)}`.green); 108 | } else if (msg.action === 'PONG') { 109 | console.log(`收到 PONG: ${JSON.stringify(msg)}`.blue); 110 | } 111 | }); 112 | 113 | ws.on('close', (code, reason) => { 114 | console.log(`WebSocket 已关闭,代码: ${code},原因: ${reason}`.yellow); 115 | setTimeout(() => this.connectToProxy(proxy, userID), this.config.retryInterval); 116 | }); 117 | 118 | ws.on('error', (error) => { 119 | console.error(`WebSocket 在代理 ${proxy} 上出错:${error.message}`.red); 120 | if (error.message.includes('TLS') || error.message.includes('ECONNRESET')) { 121 | console.log('请检查代理的网络稳定性,或尝试更换代理服务器。'.yellow); 122 | } 123 | ws.terminate(); 124 | }); 125 | } catch (error) { 126 | console.error(`无法使用代理 ${proxy} 连接:${error.message}`.red); 127 | } 128 | } 129 | 130 | async connectDirectly(userID) { 131 | try { 132 | const wsURL = `wss://${this.config.wssHost}`; 133 | const ws = new WebSocket(wsURL, { 134 | headers: { 135 | 'User-Agent': 136 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0', 137 | Pragma: 'no-cache', 138 | 'Accept-Language': 'uk-UA,uk;q=0.9,en-US;q=0.8,en;q=0.7', 139 | 'Cache-Control': 'no-cache', 140 | OS: 'Windows', 141 | Platform: 'Desktop', 142 | Browser: 'Mozilla', 143 | }, 144 | }); 145 | 146 | ws.on('open', () => { 147 | console.log(`已直接连接,无需代理`.cyan); 148 | this.sendPing(ws, 'Direct IP'); 149 | }); 150 | 151 | ws.on('message', (message) => { 152 | const msg = JSON.parse(message); 153 | console.log(`收到消息: ${JSON.stringify(msg)}`.blue); 154 | 155 | if (msg.action === 'AUTH') { 156 | const authResponse = { 157 | id: msg.id, 158 | origin_action: 'AUTH', 159 | result: { 160 | browser_id: uuidv4(), 161 | user_id: userID, 162 | user_agent: 'Mozilla/5.0', 163 | timestamp: Math.floor(Date.now() / 1000), 164 | device_type: 'desktop', 165 | version: '4.28.2', 166 | }, 167 | }; 168 | ws.send(JSON.stringify(authResponse)); 169 | console.log(`发送认证响应: ${JSON.stringify(authResponse)}`.green); 170 | } else if (msg.action === 'PONG') { 171 | console.log(`收到 PONG: ${JSON.stringify(msg)}`.blue); 172 | } 173 | }); 174 | 175 | ws.on('close', (code, reason) => { 176 | console.log(`WebSocket 已关闭,代码: ${code},原因: ${reason}`.yellow); 177 | setTimeout(() => this.connectDirectly(userID), this.config.retryInterval); 178 | }); 179 | 180 | ws.on('error', (error) => { 181 | console.error(`WebSocket 出错:${error.message}`.red); 182 | ws.terminate(); 183 | }); 184 | } catch (error) { 185 | console.error(`无法直接连接:${error.message}`.red); 186 | } 187 | } 188 | 189 | sendPing(ws, proxyIP) { 190 | setInterval(() => { 191 | const pingMessage = { 192 | id: uuidv4(), 193 | version: '1.0.0', 194 | action: 'PING', 195 | data: {}, 196 | }; 197 | ws.send(JSON.stringify(pingMessage)); 198 | console.log(`发送 ping - IP: ${proxyIP},消息: ${JSON.stringify(pingMessage)}`.cyan); 199 | }, PING_INTERVAL); 200 | } 201 | 202 | delay(ms) { 203 | return new Promise((resolve) => setTimeout(resolve, ms)); 204 | } 205 | } 206 | 207 | module.exports = Bot; 208 | 209 | -------------------------------------------------------------------------------- /src/Config.js: -------------------------------------------------------------------------------- 1 | class Config { 2 | constructor() { 3 | this.ipCheckURL = 'https://ipinfo.io/json'; 4 | this.wssList = ['proxy.wynd.network:4444', 'proxy.wynd.network:4650']; 5 | this.retryInterval = 20000; 6 | this.wssHost = 7 | this.wssList[Math.floor(Math.random() * this.wssList.length)]; 8 | } 9 | } 10 | 11 | module.exports = Config; 12 | -------------------------------------------------------------------------------- /src/ProxyManager.js: -------------------------------------------------------------------------------- 1 | require('colors'); 2 | const axios = require('axios'); 3 | const fs = require('fs'); 4 | 5 | const PROXY_SOURCES = { 6 | '服务器 1': 'https://files.ramanode.top/airdrop/grass/server_1.txt', 7 | '服务器 2': 'https://files.ramanode.top/airdrop/grass/server_2.txt', 8 | '服务器 3': 'https://files.ramanode.top/airdrop/grass/server_3.txt', 9 | '服务器 4': 'https://files.ramanode.top/airdrop/grass/server_4.txt', 10 | '服务器 5': 'https://files.ramanode.top/airdrop/grass/server_5.txt', 11 | '服务器 6': 'https://files.ramanode.top/airdrop/grass/server_6.txt', 12 | }; 13 | 14 | async function fetchProxies(url) { 15 | try { 16 | const response = await axios.get(url); 17 | console.log(`\n已从 ${url} 获取代理`.green); 18 | return response.data.split('\n').filter(Boolean); 19 | } catch (error) { 20 | console.error(`无法从 ${url} 获取代理:${error.message}`.red); 21 | return []; 22 | } 23 | } 24 | 25 | async function readLines(filename) { 26 | try { 27 | const data = await fs.promises.readFile(filename, 'utf-8'); 28 | console.log(`已从 ${filename} 加载数据`.green); 29 | return data.split('\n').filter(Boolean); 30 | } catch (error) { 31 | console.error(`读取 ${filename} 失败:${error.message}`.red); 32 | return []; 33 | } 34 | } 35 | 36 | async function selectProxySource(inquirer) { 37 | const choices = [...Object.keys(PROXY_SOURCES), 'CUSTOM', 'NO PROXY']; 38 | const { source } = await inquirer.prompt([ 39 | { 40 | type: 'list', 41 | name: 'source', 42 | message: '选择代理来源:'.cyan, 43 | choices, 44 | }, 45 | ]); 46 | 47 | if (source === 'CUSTOM') { 48 | const { filename } = await inquirer.prompt([ 49 | { 50 | type: 'input', 51 | name: 'filename', 52 | message: '输入您的 proxy.txt 文件路径:'.cyan, 53 | default: 'proxy.txt', 54 | }, 55 | ]); 56 | return { type: 'file', source: filename }; 57 | } else if (source === '无代理') { 58 | return { type: 'none' }; 59 | } 60 | 61 | return { type: 'url', source: PROXY_SOURCES[source] }; 62 | } 63 | 64 | module.exports = { fetchProxies, readLines, selectProxySource }; 65 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | require('colors'); 2 | const chalk = require('chalk'); 3 | 4 | const delay = async (ms) => new Promise((resolve) => setTimeout(resolve, ms)); 5 | 6 | function displayHeader() { 7 | process.stdout.write('\x1Bc'); 8 | console.log(chalk.yellow('╔════════════════════════════════════════╗')); 9 | console.log(chalk.yellow('║ 🚀 小草空投机器人 🚀 ║')); 10 | console.log(chalk.yellow('║ 👤 脚本编写:子清 ║')); 11 | console.log(chalk.yellow('║ 📢 电报频道:https://t.me/zqbot ║')); 12 | console.log(chalk.yellow('╚════════════════════════════════════════╝')); 13 | console.log(); 14 | } 15 | 16 | module.exports = { delay, displayHeader }; 17 | -------------------------------------------------------------------------------- /uid.txt: -------------------------------------------------------------------------------- 1 | #每行写一个用户 ID 2 | --------------------------------------------------------------------------------