├── .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 |
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 |
--------------------------------------------------------------------------------