├── uid.txt
├── proxy.txt
├── requirements.txt
├── README.md
└── main.py
/uid.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/proxy.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | aiohttp
2 | loguru
3 | pyfiglet
4 | websockets==12.0
5 | websockets_proxy
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # xGrassBot
2 | This bot connects via multiple HTTP proxies to farm Grass Airdrop Season 2 using a single account. It automatically removes faulty proxies (on/off) and uses the Grass Desktop Node to double the farming points (x2.0).
3 |
4 | ## Installation
5 |
6 | 1. Install ENV
7 | ```bash
8 | sudo apt update -y && apt install -y python3 python3-venv pip
9 | ```
10 |
11 | 2. Setup resources:
12 | ```bash
13 | git clone https://github.com/officialputuid/xGrassBot && cd xGrassBot
14 | python3 -m venv venv && source venv/bin/activate && pip install -r requirements.txt
15 | python3 main.py
16 | ```
17 |
18 | ## User ID
19 | - If you haven't registered, feel free to use my referral link: ([REGISTER GRASS](https://app.getgrass.io/register/?referralCode=rjztRGaBttAB6Cx)).
20 | - How to Get Your User ID?
21 | - Login and open https://app.getgrass.io/dashboard
22 | - Open Developer Tools in your browser (F12) / Inspect Element.
23 | - In the "Console" tab, type:
24 | `localStorage.getItem('userId');`
25 | - Copy the result without "" or '' and paste it into `uid.txt`.
26 | - For multiple accounts, add each uid on a new line, for example:
27 | ```
28 | uid1
29 | uid2
30 | uid2
31 | ```
32 |
33 | ## Proxy
34 | - Fill in `proxy.txt` with the format `protocol://user:pass@host:port`.
35 | - Adjust the number of proxies to use on the following line `36 "ONETIME_PROXY = 100"`
36 |
37 | ## Need Proxy?
38 | 1. Sign up at [Proxies.fo](https://app.proxies.fo/ref/849ec384-ecb5-1151-b4a7-c99276bff848).
39 | 2. Go to [Plans](https://app.proxies.fo/plans) and only purchase the "ISP plan" (Residential plans don’t work).
40 | 3. Top up your balance, or you can directly buy a plan and pay with Crypto!
41 | 4. Go to the Dashboard, select your ISP plan, and click "Generate Proxy."
42 | 5. Set the proxy format to `protocol://username:password@hostname:port`
43 | 6. Choose any number for the proxy count, and paste the proxies into `proxy.txt`.
44 |
45 | ## Donations
46 | - **PayPal**: [Paypal.me/IPJAP](https://www.paypal.com/paypalme/IPJAP)
47 | - **Trakteer**: [Trakteer.id/officialputuid](https://trakteer.id/officialputuid) (ID)
48 |
--------------------------------------------------------------------------------
/main.py:
--------------------------------------------------------------------------------
1 | # Copyright (C) 2024 officialputuid
2 |
3 | import aiohttp
4 | import asyncio
5 | import base64
6 | import datetime
7 | import json
8 | import random
9 | import re
10 | import ssl
11 | import time
12 | import uuid
13 | import websockets
14 |
15 | from loguru import logger
16 | import pyfiglet
17 | from websockets_proxy import Proxy, proxy_connect
18 |
19 | logger.remove()
20 | logger.add(
21 | sink=lambda msg: print(msg, end=''),
22 | format=(
23 | "{time:DD/MM/YY HH:mm:ss} | "
24 | "{level:8} | {message}"
25 | ),
26 | colorize=True
27 | )
28 |
29 | # main.py
30 | def print_header():
31 | cn = pyfiglet.figlet_format("xGrassBot")
32 | print(cn)
33 | print("🌱 Season 2")
34 | print("🎨 by \033]8;;https://github.com/officialputuid\033\\officialputuid\033]8;;\033\\")
35 | print('🎁 \033]8;;https://paypal.me/IPJAP\033\\Paypal.me/IPJAP\033]8;;\033\\ — \033]8;;https://trakteer.id/officialputuid\033\\Trakteer.id/officialputuid\033]8;;\033\\')
36 |
37 | # Initialize the header
38 | print_header()
39 |
40 | # Number of proxies to use /uid
41 | ONETIME_PROXY = 100
42 | DELAY_INTERVAL = 0.5
43 | MAX_RETRIES = 3
44 | FILE_UID = "uid.txt"
45 | FILE_PROXY = "proxy.txt"
46 | USERAGENTS = [
47 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.2365.57",
48 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.2365.52",
49 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 Edg/122.0.2365.46",
50 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.2277.128",
51 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.2277.112",
52 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.2277.98",
53 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.2277.83",
54 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.2210.133",
55 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.2210.121",
56 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.2210.91"
57 | ]
58 | HTTP_STATUS_CODES = {
59 | 200: "OK",
60 | 201: "Created",
61 | 202: "Accepted",
62 | 204: "No Content",
63 | 400: "Bad Request",
64 | 401: "Unauthorized",
65 | 403: "Forbidden",
66 | 404: "Not Found",
67 | 500: "Internal Server Error",
68 | 502: "Bad Gateway",
69 | 503: "Service Unavailable",
70 | 504: "Gateway Timeout"
71 | }
72 |
73 | # Read UID and Proxy count
74 | def read_uid_and_proxy():
75 | with open(FILE_UID, 'r') as file:
76 | uid_count = sum(1 for line in file)
77 |
78 | with open(FILE_PROXY, 'r') as file:
79 | proxy_count = sum(1 for line in file)
80 |
81 | return uid_count, proxy_count
82 |
83 | uid_count, proxy_count = read_uid_and_proxy()
84 |
85 | print()
86 | print(f"🔑 UID: {uid_count}. from {FILE_UID}.")
87 | print(f"🌐 Loaded {proxy_count} proxies. from {FILE_PROXY}.")
88 | print(f"🌐 Active proxy loaded per-task: {ONETIME_PROXY} proxies.")
89 | print()
90 |
91 | # Get User input for proxy failure handling
92 | def get_user_input():
93 | user_input = ""
94 | while user_input not in ['yes', 'no']:
95 | user_input = input("🔵 Do you want to remove the proxy if there is a specific failure (yes/no)? ").strip().lower()
96 | if user_input not in ['yes', 'no']:
97 | print("🔴 Invalid input. Please enter 'yes' or 'no'.")
98 | return user_input == 'yes'
99 |
100 | remove_on_all_errors = get_user_input()
101 | print(f"🔵 You selected: {'Yes' if remove_on_all_errors else 'No'}, ENJOY!\n")
102 |
103 | # Ask user for node type
104 | def get_node_type():
105 | node_type = ""
106 | while node_type not in ['desktop', 'extension', 'grasslite']:
107 | print(f"🧩 Desktop Node (2.0x Reward), Extension (1.25x Reward), GrassLite (1.0x Reward)")
108 | node_type = input("🔵 Choose node type (desktop/extension/grasslite): ").strip().lower()
109 | if node_type not in ['desktop', 'extension', 'grasslite']:
110 | print("🔴 Invalid input. Please enter 'desktop' / 'extension' / 'grasslite'.")
111 | return node_type
112 |
113 | node_type = get_node_type()
114 | print(f"🔵 You selected: {node_type.capitalize()} node. ENJOY!\n")
115 |
116 | def truncate_userid(user_id):
117 | return f"{user_id[:3]}--{user_id[-3:]}"
118 |
119 | def truncate_proxy(proxy):
120 | pattern = r'([a-zA-Z0-9.-]+(?:\.[a-zA-Z]{2,})|(?:\d{1,3}\.){3}\d{1,3})'
121 | match = re.search(pattern, proxy)
122 | if match:
123 | return match.group(0)
124 | return 'Undefined'
125 |
126 | def count_proxies(FILE_PROXY):
127 | try:
128 | with open(FILE_PROXY, 'r') as file:
129 | proxies = file.readlines()
130 | return len(proxies)
131 | except FileNotFoundError:
132 | logger.error(f"File {FILE_PROXY} not found!")
133 | return 0
134 |
135 | async def connect_to_wss(protocol_proxy, user_id):
136 | device_id = str(uuid.uuid3(uuid.NAMESPACE_DNS, protocol_proxy))
137 | random_user_agent = random.choice(USERAGENTS)
138 | logger.info(f"UID: {truncate_userid(user_id)} | {node_type} | Generate Device ID: {device_id} | Proxy: {truncate_proxy(protocol_proxy)}")
139 |
140 | has_received_action = False
141 | is_authenticated = False
142 |
143 | total_proxies = count_proxies(FILE_PROXY)
144 |
145 | while True:
146 | try:
147 | await asyncio.sleep(random.randint(1, 10) / 10)
148 | custom_headers = {
149 | "User-Agent": random_user_agent
150 | }
151 |
152 | if node_type == "extension":
153 | custom_headers["Origin"] = "chrome-extension://lkbnfiajjmbhnfledhphioinpickokdi"
154 | elif node_type == "grasslite":
155 | custom_headers["Origin"] = "chrome-extension://ilehaonighjijnmpnagapkhpcdbhclfg"
156 |
157 | ssl_context = ssl.create_default_context()
158 | ssl_context.check_hostname = False
159 | ssl_context.verify_mode = ssl.CERT_NONE
160 | urilist = [
161 | "wss://proxy2.wynd.network:4444",
162 | "wss://proxy2.wynd.network:4650"
163 | ]
164 | uri = random.choice(urilist)
165 | server_hostname = uri.split("://")[1].split(":")[0]
166 | proxy = Proxy.from_url(protocol_proxy)
167 |
168 | async with proxy_connect(
169 | uri,
170 | proxy=proxy,
171 | ssl=ssl_context,
172 | server_hostname=server_hostname,
173 | extra_headers=custom_headers
174 | ) as websocket:
175 | logger.success(f"UID: {truncate_userid(user_id)} | {node_type} | Success connect to WS | uri: {uri} | Headers: {custom_headers} | Device ID: {device_id} | Proxy: {truncate_proxy(protocol_proxy)} | Remaining Proxy: {total_proxies}")
176 |
177 | async def send_ping():
178 | while True:
179 | if has_received_action:
180 | send_message = json.dumps({
181 | "id": str(uuid.uuid4()),
182 | "version": "1.0.0",
183 | "action": "PING",
184 | "data": {}
185 | })
186 | logger.debug(f"UID: {truncate_userid(user_id)} | {node_type} | Send PING message | data: {send_message}")
187 | await asyncio.sleep(DELAY_INTERVAL)
188 | await websocket.send(send_message)
189 | logger.info(f"UID: {truncate_userid(user_id)} | {node_type} | Done sent PING | data: {send_message}")
190 |
191 | rand_sleep = random.uniform(10, 30)
192 | logger.info(f"UID: {truncate_userid(user_id)} | {node_type} | Next PING in {rand_sleep:.2f} seconds, ENJOY!")
193 | await asyncio.sleep(rand_sleep)
194 |
195 | await asyncio.sleep(DELAY_INTERVAL)
196 | send_ping_task = asyncio.create_task(send_ping())
197 |
198 | try:
199 | while True:
200 | if is_authenticated and not has_received_action:
201 | logger.info(f"UID: {truncate_userid(user_id)} | {node_type} | Authenticated | Wait for PING Gate to Open for {'HTTP_REQUEST' if node_type in ['extension', 'grasslite'] else 'OPEN_TUNNEL'}")
202 |
203 | response = await websocket.recv()
204 | message = json.loads(response)
205 | logger.info(f"UID: {truncate_userid(user_id)} | {node_type} | Received message | data: {message}")
206 |
207 | if message.get("action") == "AUTH":
208 | extension_ids = {
209 | "extension": "lkbnfiajjmbhnfledhphioinpickokdi",
210 | "grasslite": "ilehaonighjijnmpnagapkhpcdbhclfg"
211 | }
212 |
213 | auth_response = {
214 | "id": message["id"],
215 | "origin_action": "AUTH",
216 | "result": {
217 | "browser_id": device_id,
218 | "user_id": user_id,
219 | "user_agent": random_user_agent,
220 | "timestamp": int(time.time()),
221 | "device_type": "extension" if node_type in ["extension", "grasslite"] else "desktop",
222 | "version": "4.26.2" if node_type == "extension" else "4.30.0"
223 | }
224 | }
225 |
226 | if node_type in extension_ids:
227 | auth_response["result"]["extension_id"] = extension_ids[node_type]
228 |
229 | logger.debug(f"UID: {truncate_userid(user_id)} | {node_type} | Send AUTH | data: {auth_response}")
230 | await asyncio.sleep(DELAY_INTERVAL)
231 | await websocket.send(json.dumps(auth_response))
232 | logger.success(f"UID: {truncate_userid(user_id)} | {node_type} | Done sent AUTH | data: {auth_response}")
233 | is_authenticated = True
234 |
235 | elif message.get("action") in ["HTTP_REQUEST", "OPEN_TUNNEL"]:
236 | has_received_action = True
237 | request_data = message["data"]
238 |
239 | headers = {
240 | "User-Agent": custom_headers["User-Agent"],
241 | "Content-Type": "application/json; charset=utf-8"
242 | }
243 |
244 | async with aiohttp.ClientSession() as session:
245 | async with session.get(request_data["url"], headers=headers) as api_response:
246 | content = await api_response.text()
247 | encoded_body = base64.b64encode(content.encode()).decode()
248 |
249 | status_text = HTTP_STATUS_CODES.get(api_response.status, "")
250 |
251 | http_response = {
252 | "id": message["id"],
253 | "origin_action": message["action"],
254 | "result": {
255 | "url": request_data["url"],
256 | "status": api_response.status,
257 | "status_text": status_text,
258 | "headers": dict(api_response.headers),
259 | "body": encoded_body
260 | }
261 | }
262 |
263 | logger.info(f"UID: {truncate_userid(user_id)} | {node_type} | Open PING Access | data: {http_response}")
264 | await asyncio.sleep(DELAY_INTERVAL)
265 | await websocket.send(json.dumps(http_response))
266 | logger.success(f"UID: {truncate_userid(user_id)} | {node_type} | Done sent PING Access | data: {http_response}")
267 |
268 | elif message.get("action") == "PONG":
269 | pong_response = {"id": message["id"], "origin_action": "PONG"}
270 | logger.debug(f"UID: {truncate_userid(user_id)} | {node_type} | Send PONG | data: {pong_response}")
271 | await asyncio.sleep(DELAY_INTERVAL)
272 | await websocket.send(json.dumps(pong_response))
273 | logger.success(f"UID: {truncate_userid(user_id)} | {node_type} | Done sent PONG | data: {pong_response}")
274 |
275 | except websockets.exceptions.ConnectionClosedError as e:
276 | logger.error(f"UID: {truncate_userid(user_id)} | {node_type} | Connection closed error | Proxy: {truncate_proxy(protocol_proxy)} | Error: {str(e)} | Remaining Proxy: {total_proxies}")
277 | await asyncio.sleep(DELAY_INTERVAL)
278 | finally:
279 | await websocket.close()
280 | logger.warning(f"UID: {truncate_userid(user_id)} | {node_type} | WebSocket connection closed | Proxy: {truncate_proxy(protocol_proxy)} | Remaining Proxy: {total_proxies}")
281 | send_ping_task.cancel()
282 | await asyncio.sleep(DELAY_INTERVAL)
283 | break
284 |
285 | except Exception as e:
286 | logger.error(f"UID: {truncate_userid(user_id)} | {node_type} | Error with proxy {truncate_proxy(protocol_proxy)} ➜ {str(e)} | Remaining Proxy: {total_proxies}")
287 | error_conditions = [
288 | "403 Forbidden",
289 | "Host unreachable",
290 | "Empty host component",
291 | "Invalid scheme component",
292 | "[SSL: WRONG_VERSION_NUMBER]",
293 | "invalid length of packed IP address string",
294 | "Empty connect reply",
295 | "Device creation limit exceeded",
296 | "[Errno 111] Could not connect to proxy",
297 | "sent 1011 (internal error) keepalive ping timeout; no close frame received"
298 | ]
299 | skip_proxy = [
300 | "Proxy connection timed out: 60",
301 | "407 Proxy Authentication Required",
302 | "Invalid port component"
303 | ]
304 |
305 | if any(error_msg in str(e) for error_msg in skip_proxy):
306 | logger.warning(f"UID: {truncate_userid(user_id)} | {node_type} | Skipping proxy due to error ➜ {truncate_proxy(protocol_proxy)} | Remaining Proxy: {total_proxies}")
307 | return "skip"
308 |
309 | if remove_on_all_errors:
310 | if any(error_msg in str(e) for error_msg in error_conditions):
311 | logger.warning(f"UID: {truncate_userid(user_id)} | {node_type} | Removing error proxy due to error ➜ {truncate_proxy(protocol_proxy)} | Remaining Proxy: {total_proxies}")
312 | remove_proxy_from_list(protocol_proxy)
313 | return None
314 | else:
315 | if "Device creation limit exceeded" in str(e):
316 | logger.warning(f"UID: {truncate_userid(user_id)} | {node_type} | Removing error proxy due to error ➜ {truncate_proxy(protocol_proxy)} | Remaining Proxy: {total_proxies}")
317 | remove_proxy_from_list(protocol_proxy)
318 | return None
319 |
320 | await asyncio.sleep(DELAY_INTERVAL)
321 | continue
322 |
323 | async def main():
324 | with open(FILE_UID, 'r') as file:
325 | user_ids = file.read().splitlines()
326 |
327 | with open(FILE_PROXY, 'r') as file:
328 | all_proxies = file.read().splitlines()
329 |
330 | if len(all_proxies) < ONETIME_PROXY * len(user_ids):
331 | logger.error(f"The number of proxies is insufficient to provide {ONETIME_PROXY} proxies per User ID.")
332 | return
333 |
334 | random.shuffle(all_proxies)
335 | proxy_allocation = {
336 | user_id: all_proxies[i * ONETIME_PROXY: (i + 1) * ONETIME_PROXY]
337 | for i, user_id in enumerate(user_ids)
338 | }
339 |
340 | retry_count = {}
341 |
342 | for user_id, proxies in proxy_allocation.items():
343 | logger.warning(f"UID: {truncate_userid(user_id)} | {node_type} | Total proxies to be used: {len(proxies)}")
344 | await asyncio.sleep(DELAY_INTERVAL)
345 |
346 | tasks = {}
347 |
348 | for user_id, proxies in proxy_allocation.items():
349 | for proxy in proxies:
350 | retry_count[(proxy, user_id)] = 0
351 | await asyncio.sleep(DELAY_INTERVAL)
352 | task = asyncio.create_task(connect_to_wss(proxy, user_id))
353 | tasks[task] = (proxy, user_id)
354 |
355 | while True:
356 | done, pending = await asyncio.wait(tasks.keys(), return_when=asyncio.FIRST_COMPLETED)
357 |
358 | for task in done:
359 | try:
360 | result = task.result()
361 |
362 | failed_proxy, user_id = tasks[task]
363 |
364 | if result == "skip":
365 | retry_count[(failed_proxy, user_id)] += 1
366 |
367 | if retry_count[(failed_proxy, user_id)] > MAX_RETRIES:
368 | logger.warning(f"UID: {truncate_userid(user_id)} | {node_type} | Max retries (skipping proxy) reached for proxy: {truncate_proxy(failed_proxy)}.")
369 | continue
370 |
371 | logger.warning(f"UID: {truncate_userid(user_id)} | {node_type} | Skipping proxy: {truncate_proxy(failed_proxy)}")
372 |
373 | available_proxies = list(set(all_proxies) - set(proxy_allocation[user_id]))
374 | if available_proxies:
375 | new_proxy = random.choice(available_proxies)
376 | proxy_allocation[user_id].append(new_proxy)
377 |
378 | retry_count[(new_proxy, user_id)] = retry_count[(failed_proxy, user_id)]
379 | await asyncio.sleep(DELAY_INTERVAL)
380 | new_task = asyncio.create_task(connect_to_wss(new_proxy, user_id))
381 | tasks[new_task] = (new_proxy, user_id)
382 | logger.success(f"UID: {truncate_userid(user_id)} | {node_type} | Replaced skipping proxy {truncate_proxy(failed_proxy)} with {truncate_proxy(new_proxy)}")
383 | else:
384 | logger.warning(f"UID: {truncate_userid(user_id)} | {node_type} | No available proxies left for replacement.")
385 |
386 | elif result is None:
387 | retry_count[(failed_proxy, user_id)] += 1
388 |
389 | if retry_count[(failed_proxy, user_id)] > MAX_RETRIES:
390 | logger.warning(f"UID: {truncate_userid(user_id)} | {node_type} | Max retries (error proxy) reached for proxy: {truncate_proxy(failed_proxy)}.")
391 | proxy_allocation[user_id].remove(failed_proxy)
392 | continue
393 |
394 | logger.warning(f"UID: {truncate_userid(user_id)} | {node_type} | Removing and replacing failed proxy: {truncate_proxy(failed_proxy)}")
395 | proxy_allocation[user_id].remove(failed_proxy)
396 |
397 | available_proxies = list(set(all_proxies) - set(proxy_allocation[user_id]))
398 | if available_proxies:
399 | new_proxy = random.choice(available_proxies)
400 | proxy_allocation[user_id].append(new_proxy)
401 |
402 | retry_count[(new_proxy, user_id)] = retry_count[(failed_proxy, user_id)]
403 | await asyncio.sleep(DELAY_INTERVAL)
404 | new_task = asyncio.create_task(connect_to_wss(new_proxy, user_id))
405 | tasks[new_task] = (new_proxy, user_id)
406 | logger.success(f"UID: {truncate_userid(user_id)} | {node_type} | Replaced failed proxy: {truncate_proxy(failed_proxy)} with: {truncate_proxy(new_proxy)}")
407 | else:
408 | logger.warning(f"UID: {truncate_userid(user_id)} | {node_type} | No available proxies left for replacement.")
409 |
410 | except Exception as e:
411 | logger.error(f"UID: {truncate_userid(user_id)} | {node_type} | Error handling task: {str(e)}")
412 | finally:
413 | tasks.pop(task)
414 |
415 | active_proxies = [proxy for _, proxy in tasks.values()]
416 | for user_id, proxies in proxy_allocation.items():
417 | for proxy in set(proxies) - set(active_proxies):
418 | new_task = asyncio.create_task(connect_to_wss(proxy, user_id))
419 | tasks[new_task] = (proxy, user_id)
420 |
421 | def remove_proxy_from_list(proxy):
422 | with open(FILE_PROXY, "r+") as file:
423 | lines = file.readlines()
424 | file.seek(0)
425 | for line in lines:
426 | if line.strip() != proxy:
427 | file.write(line)
428 | file.truncate()
429 |
430 | if __name__ == '__main__':
431 | try:
432 | asyncio.run(main())
433 | except (KeyboardInterrupt, SystemExit):
434 | logger.info(f"Program terminated by user. ENJOY!\n")
435 |
--------------------------------------------------------------------------------