├── .github └── workflows │ └── docker-image.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── entrypoint.sh ├── main.py ├── proxies.example.txt ├── proxies └── .gitignore ├── requirements.txt └── tokens.example.txt /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Log in to Docker Hub 18 | uses: docker/login-action@v3.3.0 19 | with: 20 | username: ${{ secrets.DOCKER_USERNAME }} 21 | password: ${{ secrets.DOCKER_PASSWORD }} 22 | - name: Set up QEMU 23 | uses: docker/setup-qemu-action@v3 24 | - name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v2 26 | - name: Build and push Docker image 27 | id: push 28 | uses: docker/build-push-action@v6.7.0 29 | with: 30 | platform: linux/amd64,linux/arm64 31 | context: . 32 | file: ./Dockerfile 33 | push: true 34 | tags: overtrue/nodepay-bot 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | /logs 4 | /web/users/* 5 | proxies.txt 6 | tokens.txt 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3 2 | 3 | ENV USER_ID= 4 | 5 | WORKDIR /app 6 | 7 | ADD . /app/ 8 | 9 | # install dependencies 10 | RUN pip install -r requirements.txt 11 | RUN chmod +x /app/entrypoint.sh 12 | 13 | CMD ["/bin/bash", "/app/entrypoint.sh"] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 overtrue 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [NodePay bot](https://app.nodepay.ai/register?ref=ffWdlWvILxU2eSW) 2 | 3 | 4 | image 5 | 6 | 7 | Auto ping bot for [NodePay](https://app.nodepay.ai/register?ref=ffWdlWvILxU2eSW), with multi accounts and multi proxies support. 8 | 9 | [https://app.nodepay.ai/](https://app.nodepay.ai/register?ref=ffWdlWvILxU2eSW) 10 | 11 | [手把手中文使用教程](https://mirror.xyz/0xe8224b3E9C8d35b34D088BB5A216B733a5A6D9EA/S5Pjq_SlQoJvLwyV0Q0wSpqo7h_2-V0uPwaiyhDG-r0) | TG Channel: 12 | 13 | ## Features 14 | 15 | - Multi accounts support. 16 | - Multi proxies support. 17 | - Auto ping every 6 minutes. 18 | - Parallel ping with multi accounts and multi proxies. 19 | - Auto retry when the connection is lost. 20 | 21 | ## Usage 22 | 23 | The `nodepay-bot` can be run using Docker or manually. 24 | 25 | ### Get your user ID 26 | 27 | you can obtain your user ID from the nodepay website: 28 | 29 | - Visit [https://app.nodepay.ai](https://app.nodepay.ai/register?ref=ffWdlWvILxU2eSW) 30 | - Open the browser's developer tools (usually by pressing F12 or right-clicking and selecting "Inspect"). 31 | - Go to the "Console" tab. 32 | - Paste the following command and press Enter: 33 | 34 | ```javascript 35 | copy(localStorage.getItem('np_token')); 36 | ``` 37 | 38 | - Copy the value returned, which is your user ID. 39 | 40 | > [!WARNING] 41 | > if you see the following error: 42 | > 43 | > ```json 44 | > {"success":false,"code":403,"msg":"Unauthorized","data":null} 45 | > ``` 46 | > 47 | > in the console, please copy the value from the `np_token` in the `Application` tab and paste it into the `tokens.txt` file. 48 | 49 | ### Prepare proxies 50 | 51 | You can buy proxies from [ProxyCheap](https://app.proxy-cheap.com/r/ksvW8Z) or any other proxy provider. 52 | 53 | > It seems that the official has restricted that a maximum of 10 nodes are valid for one account. So please don't configure too many proxies for each account. It is recommended to have a maximum of 10. 54 | 55 | ### Running the Bot with Docker (not ready) 56 | 57 | 1. Create a text file named `tokens.txt` with your copied `np_token`. 58 | 1. Create a text file named `proxies.txt` with the desired proxy URLs. Ensure each URL is in the format: 59 | 60 | ```plaintext 61 | http://username:password@hostname1:port 62 | http://username:password@hostname2:port 63 | // or 64 | socks5://username:password@hostname1:port 65 | socks5://username:password@hostname2:port 66 | ``` 67 | 68 | > Note: You can use HTTP or SOCKS5 proxies, and you can config with multiple proxies in the `proxies.txt` file (one proxy per line). 69 | 70 | 1. Run the `nodepay-bot` using Docker: 71 | 72 | #### Single Accounts 73 | 74 | ```bash 75 | docker run -d \ 76 | -v $(pwd)/tokens.txt:/app/tokens.txt \ 77 | -v $(pwd)/proxies.txt:/app/proxies.txt \ 78 | overtrue/nodepay-bot 79 | ``` 80 | 81 | #### Multi Accounts 82 | 83 | If you have multiple accounts, you can create a `tokens.txt` file with the following format: 84 | 85 | ```plaintext 86 | eyJh... 87 | eyJi... 88 | ``` 89 | 90 | and create a dir named `proxies/N.txt` with the following structure, `N` is the account index, starting from 1, 91 | 92 | for example, if you have 3 accounts, you should create 3 proxies files: 93 | 94 | ```plaintext 95 | proxies/1.txt 96 | proxies/2.txt 97 | proxies/3.txt 98 | ``` 99 | 100 | > Note: You can use HTTP or SOCKS5 proxies, and you can config with multiple proxies in the `proxies/N.txt` file (one proxy per line). 101 | > 102 | > It seems that the official has restricted that a maximum of 10 nodes are valid for one account. So please don't configure too many proxies for each account. It is recommended to have a maximum of 10. 103 | 104 | and run the bot with the following command: 105 | 106 | ```bash 107 | docker run -d \ 108 | -v $(pwd)/tokens.txt:/app/tokens.txt \ 109 | -v $(pwd)/proxies:/app/proxies \ 110 | overtrue/nodepay-bot 111 | ``` 112 | 113 | ### Check running stats 114 | 115 | ```bash 116 | # get containerid 117 | docker ps 118 | 119 | # show logs of containerid 120 | docker logs -f 121 | ``` 122 | 123 | ### Manual Installation 124 | 125 | > You need to have Node.js installed on your machine to run the bot manually. 126 | 127 | 1. Git clone this repository to your local machine. 128 | 129 | ```bash 130 | git clone git@github.com:web3bothub/nodepay-bot.git 131 | ``` 132 | 133 | 1. Navigate to the project directory. 134 | 135 | ```bash 136 | cd nodepay-bot 137 | ``` 138 | 139 | 1. Create a text file named `tokens.txt` with your copied `np_token`. 140 | 1. Create the `proxies.txt` file with the desired proxy URLs. Ensure each URL is in the format: 141 | 142 | ```plaintext 143 | http://username:password@hostname1:port 144 | http://username:password@hostname2:port 145 | // or 146 | socks5://username:password@hostname1:port 147 | socks5://username:password@hostname2:port 148 | ``` 149 | 150 | > Note: You can use HTTP or SOCKS5 proxies, You can config with multiple proxies in the `proxies.txt` file (one proxy per line). 151 | > 152 | > It seems that the official has restricted that a maximum of 10 nodes are valid for one account. So please don't configure too many proxies for each account. It is recommended to have a maximum of 10. 153 | 154 | 1. Run the `nodepay-bot` by executing the following command: 155 | 156 | ```bash 157 | pip install -r requirements.txt 158 | ``` 159 | 160 | 1. If you want to run the bot in the background, you can use the `pm2` package: 161 | 162 | ```bash 163 | python3 main.py 164 | ``` 165 | 166 | ## Note 167 | 168 | - Run this bot, I don't guarantee you will get the reward, it depends on the nodepay website. 169 | - If you dont familiar with the python, you can use the docker to run this bot. 170 | - You can just run this bot at your own risk, I'm not responsible for any loss or damage caused by this bot. This bot is for educational purposes only. 171 | 172 | ## Contribution 173 | 174 | Feel free to contribute to this project by creating a pull request. 175 | 176 | ## Support Me 177 | 178 | if you want to support me, you can donate to my address: 179 | 180 | - TRC20: `TMwJhT5iCsQAfmRRKmAfasAXRaUhPWTSCE` 181 | - ERC20: `0xa2f5b8d9689d20d452c5340745a9a2c0104c40de` 182 | - SOLANA: `HCbbrqD9Xvfqx7nWjNPaejYDtXFp4iY8PT7F4i8PpE5K` 183 | - TON: `UQBD-ms1jA9cmoo8O39BXI6jqh8zwRSoBMUAl4yjEPKD6ata` 184 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | python3 main.py 4 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aiohttp 3 | import logging 4 | import random 5 | import os 6 | import time 7 | import sys 8 | import json 9 | from pathlib import Path 10 | import cloudscraper 11 | 12 | # Global constants 13 | DOMAIN_API = { 14 | 'SESSION': 'http://api.nodepay.ai/api/auth/session', 15 | 'PING': [ 16 | "https://nw.nodepay.org/api/network/ping", 17 | ] 18 | } 19 | 20 | PING_INTERVAL = 180 # seconds 21 | 22 | # Connection states 23 | from enum import Enum 24 | 25 | scraper = cloudscraper.create_scraper( 26 | browser={ 27 | 'browser': 'chrome', 28 | 'platform': 'windows', 29 | 'desktop': True 30 | } 31 | ) 32 | 33 | 34 | class ConnectionStates(Enum): 35 | CONNECTED = 1 36 | DISCONNECTED = 2 37 | NONE_CONNECTION = 3 38 | 39 | 40 | # Logger configuration function to add an account prefix 41 | def create_logger(account_identifier): 42 | logger = logging.getLogger(f'token:{account_identifier}') 43 | logger.setLevel(logging.INFO) 44 | handler = logging.StreamHandler() 45 | formatter = logging.Formatter( 46 | '%(asctime)s | [%(name)s] %(levelname)s: %(message)s', 47 | datefmt='%Y-%m-%d %H:%M:%S') 48 | handler.setFormatter(formatter) 49 | logger.addHandler(handler) 50 | return logger 51 | 52 | 53 | def get_random_user_agent(): 54 | user_agents = [ 55 | # A list of common user agents 56 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)' 57 | ' Chrome/58.0.3029.110 Safari/537.36', 58 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko)' 59 | ' Chrome/61.0.3163.100 Safari/537.36', 60 | 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko)' 61 | ' Chrome/41.0.2228.0 Safari/537.36', 62 | # Add more user agents here 63 | ] 64 | return random.choice(user_agents) 65 | 66 | 67 | async def get_ip_address(proxy): 68 | try: 69 | async with aiohttp.ClientSession() as session: 70 | async with session.get('https://api.ipify.org?format=json', proxy=proxy, timeout=5) as resp: 71 | if resp.status == 200: 72 | data = await resp.json() 73 | return data.get('ip') 74 | except Exception as e: 75 | return None 76 | 77 | 78 | class AccountSession: 79 | def __init__(self, token, account_id): 80 | self.account_id = account_id 81 | self.token = token 82 | self.browser_ids = [] 83 | self.account_info = {} 84 | self.proxy_auth_status = False 85 | self.status_connect = ConnectionStates.NONE_CONNECTION 86 | self.retries = 0 87 | self.last_ping_time = 0 88 | self.proxies = [] 89 | self.user_agent = get_random_user_agent() 90 | self.logger = create_logger(f'token:{account_id}') 91 | 92 | async def init(self): 93 | try: 94 | await self.get_proxies() 95 | self.format_stats() 96 | await self.authenticate() 97 | await self.ping() 98 | self.start_ping_loop() 99 | except Exception as error: 100 | self.logger.error(f"Initialization error: {error}") 101 | 102 | def format_stats(self): 103 | for index, proxy in enumerate(self.proxies): 104 | self.browser_ids.append({ 105 | 'ping_count': 0, 106 | 'successful_pings': 0, 107 | 'score': 0, 108 | 'start_time': time.time(), 109 | 'last_ping_time': None 110 | }) 111 | 112 | async def get_proxies(self): 113 | try: 114 | account_proxy_path = Path(f'./proxies/{self.account_id}.txt') 115 | proxy_data = '' 116 | if account_proxy_path.is_file(): 117 | async with aiofiles.open(account_proxy_path, 'r') as f: 118 | proxy_data = await f.read() 119 | else: 120 | root_proxy_path = Path('./proxies.txt') 121 | self.logger.info(f"Account-specific proxy file({account_proxy_path}) not found, trying {root_proxy_path} instead.") 122 | if root_proxy_path.is_file(): 123 | async with aiofiles.open(root_proxy_path, 'r') as f: 124 | proxy_data = await f.read() 125 | else: 126 | raise FileNotFoundError('No proxies found in either account-specific or root proxy file') 127 | self.proxies = [line.strip() for line in proxy_data.splitlines() if line.strip()] 128 | if not self.proxies: 129 | raise ValueError('No proxies found in either account-specific or root proxy file') 130 | self.logger.info(f"Loaded {len(self.proxies)} proxies for account token {self.account_id}.") 131 | except Exception as error: 132 | self.logger.error(f"Failed to load proxies: {error}") 133 | raise 134 | 135 | async def authenticate(self): 136 | for proxy in self.proxies: 137 | try: 138 | if not self.proxy_auth_status: 139 | self.logger.info(f"Authenticating with proxy {proxy}") 140 | ip_address = await get_ip_address(proxy) 141 | self.logger.info(f"IP address: {ip_address}") 142 | 143 | response = await self.perform_request(DOMAIN_API['SESSION'], {}, proxy) 144 | if not response: 145 | continue 146 | 147 | if response.get('code') != 0: 148 | self.logger.error(f"Failed to authenticate with proxy {proxy}: {response}, response.code is not 0") 149 | self.handle_logout(proxy) 150 | continue 151 | 152 | self.account_info = response.get('data', {}) 153 | if 'uid' in self.account_info: 154 | self.proxy_auth_status = True 155 | self.save_session_info() 156 | self.logger.info(f"Authenticated with proxy {proxy}") 157 | else: 158 | self.logger.error(f"Failed to authenticate with proxy {proxy}: {self.account_info}, response.data.uid is not found") 159 | self.handle_logout(proxy) 160 | continue 161 | except Exception as error: 162 | self.logger.error(f"Failed to authenticate with proxy: {proxy}: {error}") 163 | 164 | async def perform_request(self, url, data, proxy, max_retries=3): 165 | headers = { 166 | 'Authorization': f'Bearer {self.token}', 167 | 'Content-Type': 'application/json', 168 | 'Origin': 'chrome-extension://lgmpfmgeabnnlemejacfljbmonaomfmm', 169 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36", 170 | "Accept": "application/json, text/plain, */*", 171 | "Accept-Language": "en-US,en;q=0.5", 172 | "Sec-Ch-Ua": '"Chromium";v="130", "Google Chrome";v="130", "Not?A_Brand";v="99"', 173 | "Sec-Ch-Ua-Mobile": "?0", 174 | "Sec-Ch-Ua-Platform": "Windows", 175 | "sec-fetch-dest": "empty", 176 | "sec-fetch-mode": "cors", 177 | "sec-fetch-site": "cors-site", 178 | "Priority": "u=1, i", 179 | "Referer": "https://app.nodepay.ai/", 180 | } 181 | 182 | for attempt in range(max_retries): 183 | try: 184 | proxies = {"http": proxy, "https": proxy} if proxy else None 185 | 186 | self.logger.info(f"Using proxy {proxy} for request to {url}") 187 | self.logger.info(f"Performing request to {url} with data: {json.dumps(data)}") 188 | 189 | response = scraper.post(url, json=data, headers=headers, proxies=proxies, timeout=30) 190 | self.logger.info(f"Response: {response.text}") 191 | 192 | if response.status_code == 200: 193 | return response.json() 194 | else: 195 | self.logger.error(f"API call failed to {url} for proxy {proxy}: HTTP {response.status_code}") 196 | if response.status_code == 403: 197 | return None 198 | except Exception as error: 199 | self.logger.error(f"API call failed to {url} for proxy {proxy}: {error}") 200 | await asyncio.sleep(2 ** attempt) 201 | self.logger.error(f"API call failed to {url} after {max_retries} attempts for proxy {proxy}") 202 | return None 203 | 204 | def start_ping_loop(self): 205 | self.logger.info(f"Ping loop started with interval {PING_INTERVAL} seconds") 206 | asyncio.create_task(self.ping_loop()) 207 | 208 | async def ping_loop(self): 209 | while True: 210 | await self.ping() 211 | await asyncio.sleep(PING_INTERVAL) 212 | 213 | async def ping(self): 214 | current_time = time.time() 215 | if current_time - self.last_ping_time < PING_INTERVAL: 216 | self.logger.info(f"Skipping ping for account {self.account_id} as interval has not elapsed yet") 217 | return 218 | 219 | self.last_ping_time = current_time 220 | 221 | for index, proxy in enumerate(self.proxies): 222 | self.browser_ids[index]['last_ping_time'] = current_time 223 | try: 224 | data = { 225 | 'id': self.account_info.get('uid'), 226 | 'browser_id': self.browser_ids[index], 227 | 'timestamp': int(current_time), 228 | # 'version': '2.2.7' 229 | } 230 | ping_success = False 231 | for ping_api in DOMAIN_API['PING']: 232 | self.logger.info(f"Pinging [{ping_api}] for proxy {proxy}") 233 | response = await self.perform_request(ping_api, data, proxy) 234 | print(response) 235 | self.logger.info(f"Ping response: {response}") 236 | 237 | # Update ping stats 238 | self.browser_ids[index]['ping_count'] += 1 239 | 240 | if response and response.get('data') and response.get('code') == 0: 241 | self.retries = 0 242 | self.status_connect = ConnectionStates.CONNECTED 243 | ping_success = True 244 | self.browser_ids[index]['successful_pings'] += 1 245 | self.logger.info(f"Ping successful for proxy {proxy}, network score: {response['data'].get('ip_score', 0)}") 246 | break 247 | if not ping_success: 248 | self.logger.error(f"Ping failed for proxy {proxy}, tried all endpoints.") 249 | self.handle_ping_fail(proxy, None) 250 | except Exception as error: 251 | self.logger.error(f"Ping failed for proxy {proxy}: {error}") 252 | self.handle_ping_fail(proxy, None) 253 | 254 | def handle_ping_fail(self, proxy, response): 255 | self.retries += 1 256 | if response and response.get('code') == 403: 257 | self.handle_logout(proxy) 258 | elif self.retries >= 2: 259 | self.status_connect = ConnectionStates.DISCONNECTED 260 | 261 | def handle_logout(self, proxy): 262 | self.status_connect = ConnectionStates.NONE_CONNECTION 263 | self.account_info = {} 264 | self.proxy_auth_status = False 265 | self.logger.info(f"Logged out and cleared session info for proxy {proxy}") 266 | 267 | def save_session_info(self): 268 | # Placeholder for saving session info if needed 269 | pass 270 | 271 | 272 | async def load_tokens(): 273 | try: 274 | async with aiofiles.open('tokens.txt', 'r') as f: 275 | tokens_data = await f.read() 276 | tokens = [line.strip().strip("'\"") for line in tokens_data.splitlines() if line.strip()] 277 | return tokens 278 | except Exception as error: 279 | print(f"Failed to load tokens: {error}") 280 | raise 281 | 282 | 283 | async def main(): 284 | print(""" 285 | _ __ __ ___ ___ __ 286 | / |/ /__ ___/ /__ / _ \\___ ___ __/ _ )___ / /_ 287 | / / _ \\/ _ / -_) ___/ _ `/ // / _ / _ \\/ __/ 288 | /_/|_/\\___/\\_,_/\\__/_/ \\_,_/\\_, /____/\\___/\\__/ 289 | /___/ 290 | ----------------------------------------------------- 291 | | NodePay bot by @overtrue | 292 | | Telegram: https://t.me/+ntyApQYvrBowZTc1 | 293 | | GitHub: https://github.com/web3bothub/nodepay-bot | 294 | ------------------------------------------------------ 295 | """) 296 | print('Starting program...') 297 | await asyncio.sleep(3) 298 | try: 299 | tokens = await load_tokens() 300 | sessions = [] 301 | for index, token in enumerate(tokens, start=1): 302 | session = AccountSession(token, index) 303 | await session.init() 304 | sessions.append(session) 305 | await asyncio.sleep(10) 306 | # Keep the program running 307 | await asyncio.Event().wait() 308 | except Exception as error: 309 | print(f"Program terminated: {error}") 310 | 311 | 312 | if __name__ == '__main__': 313 | import aiofiles 314 | 315 | asyncio.run(main()) 316 | -------------------------------------------------------------------------------- /proxies.example.txt: -------------------------------------------------------------------------------- 1 | socks5://username:password@1.2.3.4:5678 2 | socks5://username:password@1.2.3.5:5678 3 | http://username:password@1.2.3.6:5678 4 | http://username:password@1.2.3.7:5678 5 | -------------------------------------------------------------------------------- /proxies/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cloudscraper 2 | aiohttp 3 | aiofiles 4 | PySocks 5 | -------------------------------------------------------------------------------- /tokens.example.txt: -------------------------------------------------------------------------------- 1 | eyJhbGciOiJ... 2 | --------------------------------------------------------------------------------