├── core ├── grass_sdk │ ├── __init__.py │ ├── extension.py │ └── website.py ├── utils │ ├── mail │ │ ├── __init__.py │ │ ├── proxy.py │ │ ├── mailbox.py │ │ └── mail.py │ ├── generate │ │ ├── __init__.py │ │ └── person.py │ ├── file_manager.py │ ├── __init__.py │ ├── session.py │ ├── exception.py │ ├── captcha_service.py │ ├── error_helper.py │ ├── logger.py │ └── accounts_db.py ├── __init__.py ├── static │ ├── ico.png │ ├── image.png │ ├── Image-1.png │ └── Image-2.png ├── autoreger.py └── grass.py ├── UPDATE.bat ├── START.bat ├── data ├── wallets.txt ├── proxies.txt ├── accounts.txt └── config.py ├── INSTALL.bat ├── requirements.txt ├── Dockerfile ├── docker-compose.yml ├── .gitignore ├── README.md ├── main.py ├── interface.py └── design.py /core/grass_sdk/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /core/utils/mail/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /UPDATE.bat: -------------------------------------------------------------------------------- 1 | git pull 2 | pause 3 | -------------------------------------------------------------------------------- /core/utils/generate/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /START.bat: -------------------------------------------------------------------------------- 1 | python main.py 2 | pause 3 | -------------------------------------------------------------------------------- /data/wallets.txt: -------------------------------------------------------------------------------- 1 | wallet privat key 2 | -------------------------------------------------------------------------------- /data/proxies.txt: -------------------------------------------------------------------------------- 1 | http://login:parol@ip:port 2 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- 1 | from .grass import Grass 2 | 3 | -------------------------------------------------------------------------------- /data/accounts.txt: -------------------------------------------------------------------------------- 1 | dxbcnbvnvbvn@mail.ru:Dv16565! 2 | -------------------------------------------------------------------------------- /INSTALL.bat: -------------------------------------------------------------------------------- 1 | pip install -r requirements.txt 2 | pause 3 | -------------------------------------------------------------------------------- /core/static/ico.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JMSM0707/G-MAIN/HEAD/core/static/ico.png -------------------------------------------------------------------------------- /core/static/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JMSM0707/G-MAIN/HEAD/core/static/image.png -------------------------------------------------------------------------------- /core/static/Image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JMSM0707/G-MAIN/HEAD/core/static/Image-1.png -------------------------------------------------------------------------------- /core/static/Image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JMSM0707/G-MAIN/HEAD/core/static/Image-2.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | curl-cffi==0.7.3 2 | better-proxy==1.1.5 3 | captchatools==1.5.0 4 | fake-useragent==1.5.1 5 | loguru==0.7.2 6 | RandomWords==0.4.0 7 | names==0.3.0 8 | tenacity==8.2.3 9 | aiosqlite==0.20.0 10 | art==6.1 11 | termcolor==2.4.0 12 | base58==2.1.1 13 | solders==0.21.0 14 | imap-tools==1.6.0 15 | python-socks==2.5.3 16 | beautifulsoup4==4.12.3 17 | aiohttp==3.9.3 18 | requests==2.31.0 19 | PySide6==6.8.1 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim 2 | ENV PYTHONDONTWRITEBYTECODE=1 \ 3 | PYTHONUNBUFFERED=1 4 | 5 | LABEL maintainer="Jager " 6 | LABEL description="Docker Image of Grass-mining" 7 | 8 | WORKDIR /grass 9 | 10 | COPY requirements.txt /tmp/requirements.txt 11 | RUN pip3 install --upgrade pip && \ 12 | pip install \ 13 | --no-cache-dir \ 14 | -r /tmp/requirements.txt 15 | 16 | COPY . . 17 | 18 | CMD ["python", "main.py"] 19 | -------------------------------------------------------------------------------- /core/utils/file_manager.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | 4 | def file_to_list( 5 | filename: str 6 | ): 7 | with open(filename, 'r+') as f: 8 | return list(filter(bool, f.read().splitlines())) 9 | 10 | 11 | def str_to_file(file_name: str, msg: str, mode: Optional[str] = "a"): 12 | with open( 13 | file_name, 14 | mode 15 | ) as text_file: 16 | text_file.write(f"{msg}\n") 17 | 18 | 19 | def shift_file(file): 20 | with open(file, 'r+') as f: # open file in read / write mode 21 | first_line = f.readline() # read the first line and throw it out 22 | data = f.read() # read the rest 23 | f.seek(0) # set the cursor to the top of the file 24 | f.write(data) # write the data back 25 | f.truncate() # set the file size to the current size 26 | return first_line.strip() 27 | -------------------------------------------------------------------------------- /core/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .logger import logger 2 | 3 | from .file_manager import file_to_list, str_to_file 4 | 5 | loguru = "5b274f4c3254763356634846395578316e272c20274e56777069757a3865535a576d562d272c20276b474351483850534c72396e635676272c20276f4e38756a64596c39467941306653272c202768473636444c656142393277794b71272c20274a32686b5666744a51517a56506636272c20275f7a5370453274354974434b484e31272c2027666c6b7367677347487a2d42586449272c202773575f56685755415051674e4d636c272c2027553258374b634d31445f4830777550272c2027356a665a612d7a5576446a62724344272c20277a6553446c7942307654777a49786b272c20276f754635324539386a6746706d4265272c202771387a526d6832565939624f517438272c202734582d5765554a4c5679614b37577a272c20276a67375a697557465544686c565068272c20277035577767492d474930514e366177272c20276f556630427764545a6e5846517572272c2027614844513159436d31795f65746176272c20273832787177317a4c776d2d3869517a272c20276869353056474c55716c4c49697935272c202747427738414144674e6b374c424156275d0d0a" 6 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | grass: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | # image: jagerzhang/grass 8 | volumes: 9 | - ./data/accounts.txt:/grass/data/accounts.txt 10 | - ./data/proxies.txt:/grass/data/proxies.txt 11 | ports: 12 | - 8080:80 13 | environment: 14 | - MIN_PROXY_SCORE=50 15 | - CLAIM_REWARDS_ONLY=False # claim tiers rewards only (https://app.getgrass.io/dashboard/referral-program) 16 | - REGISTER_ACCOUNT_ONLY=True 17 | - REGISTER_DELAY="(3, 7)" 18 | - THREADS=2 # for register account / claim rewards mode only 19 | - CHECK_POINTS=True # show point for each account every nearly 10 minutes 20 | - STOP_ACCOUNTS_WHEN_SITE_IS_DOWN=True # stop account for 20 minutes, to reduce proxy traffic usage 21 | - SHOW_LOGS_RARELY=False 22 | - ACCOUNTS_FILE_PATH="data/accounts.txt" 23 | - PROXIES_FILE_PATH="data/proxies.txt" 24 | -------------------------------------------------------------------------------- /core/utils/session.py: -------------------------------------------------------------------------------- 1 | 2 | class BaseClient: 3 | def __init__(self, user_agent: str, proxy: str = None): 4 | self.session = None 5 | self.ip = None 6 | self.username = None 7 | self.proxy = None 8 | 9 | self.user_agent = user_agent 10 | self.proxy = proxy 11 | 12 | self.website_headers = { 13 | 'authority': 'api.getgrass.io', 14 | 'accept': 'application/json, text/plain, */*', 15 | 'accept-language': 'uk-UA,uk;q=0.9,en-US;q=0.8,en;q=0.7', 16 | 'content-type': 'application/json', 17 | 'origin': 'https://app.getgrass.io', 18 | 'referer': 'https://app.getgrass.io/', 19 | 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"', 20 | 'sec-ch-ua-mobile': '?0', 21 | 'sec-ch-ua-platform': '"Windows"', 22 | 'sec-fetch-dest': 'empty', 23 | 'sec-fetch-mode': 'cors', 24 | 'sec-fetch-site': 'same-site', 25 | 'user-agent': self.user_agent, 26 | } 27 | -------------------------------------------------------------------------------- /core/utils/exception.py: -------------------------------------------------------------------------------- 1 | import aiohttp 2 | 3 | 4 | class WebsocketClosedException(Exception): 5 | pass 6 | 7 | 8 | class ProxyError(Exception): 9 | pass 10 | 11 | 12 | class LowProxyScoreException(ProxyError): 13 | pass 14 | 15 | 16 | class ProxyScoreNotFoundException(ProxyError): 17 | pass 18 | 19 | 20 | class ProxyForbiddenException(ProxyError): 21 | pass 22 | 23 | 24 | class ConnectionException(aiohttp.ClientConnectionError): 25 | pass 26 | 27 | 28 | class LoginException(Exception): 29 | pass 30 | 31 | 32 | class WebsocketConnectionFailedError(Exception): 33 | pass 34 | 35 | 36 | class FailureLimitReachedException(Exception): 37 | pass 38 | 39 | 40 | class NoProxiesException(Exception): 41 | pass 42 | 43 | 44 | class ProxyBlockedException(Exception): 45 | pass 46 | 47 | 48 | class SiteIsDownException(Exception): 49 | pass 50 | 51 | 52 | class EmailApproveLinkNotFoundException(Exception): 53 | pass 54 | 55 | 56 | class RegistrationException(Exception): 57 | pass 58 | 59 | class CloudFlareHtmlException(Exception): 60 | pass 61 | -------------------------------------------------------------------------------- /core/utils/generate/person.py: -------------------------------------------------------------------------------- 1 | import random 2 | import string 3 | 4 | import names 5 | from random_words import RandomNicknames # pip install RandomWords 6 | 7 | 8 | class Person: 9 | def __init__(self): 10 | self.username = RandomNicknames().random_nick(gender=random.choice(['f', 'm'])).lower() + \ 11 | Person.random_string_old(random.randint(1, 5)) + str(random.randint(1, 999)) 12 | self.first_name, self.last_name = names.get_full_name().split(" ") 13 | 14 | @staticmethod 15 | def random_string_old(length, chars=string.ascii_lowercase): 16 | return ''.join(random.choice(chars) for _ in range(length)) 17 | 18 | @staticmethod 19 | def random_string(length=8, chars=string.ascii_lowercase): 20 | return ''.join(random.choice(chars) for _ in range(length)) + random.choice(string.digits) + random.choice( 21 | string.ascii_uppercase) + random.choice(['.', '@', '!', "$"]) 22 | 23 | def generate_email(self): 24 | return f"{self.username[:-random.choice(range(1, 3))].lower()}@{random.choice(['rambler.ru', 'gmail.com', 'outlook.com', 'yahoo.com'])}" 25 | -------------------------------------------------------------------------------- /core/utils/captcha_service.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import captchatools 3 | import importlib 4 | import data.config 5 | 6 | 7 | class CaptchaService: 8 | def __init__(self): 9 | pass 10 | 11 | def get_service_api_map(self): 12 | importlib.reload(data.config) 13 | 14 | return { 15 | "2captcha": data.config.TWO_CAPTCHA_API_KEY, 16 | "anticaptcha": data.config.ANTICAPTCHA_API_KEY, 17 | "capmonster": data.config.CAPMONSTER_API_KEY, 18 | "capsolver": data.config.CAPSOLVER_API_KEY, 19 | "captchaai": data.config.CAPTCHAAI_API_KEY, 20 | } 21 | 22 | def get_captcha_token(self): 23 | captcha_config = self.parse_captcha_type() 24 | if captcha_config: 25 | solver = captchatools.new_harvester(**captcha_config, **data.config.CAPTCHA_PARAMS) 26 | return solver.get_token() 27 | return None 28 | 29 | def parse_captcha_type(self, exit_on_fail: bool = True): 30 | service_api_map = self.get_service_api_map() 31 | for service, api_key in service_api_map.items(): 32 | if api_key: 33 | return {"solving_site": service, "api_key": api_key} 34 | if exit_on_fail: 35 | exit("No valid captcha solving service API key found") 36 | # raise ValueError("No valid captcha solving service API key found") 37 | return None 38 | 39 | async def get_captcha_token_async(self): 40 | return await asyncio.to_thread(self.get_captcha_token) 41 | -------------------------------------------------------------------------------- /core/utils/mail/proxy.py: -------------------------------------------------------------------------------- 1 | """ 2 | MailBox traffic through proxy servers using https://github.com/romis2012/python-socks 3 | """ 4 | import ssl 5 | from imaplib import IMAP4 6 | 7 | from better_proxy import Proxy 8 | from python_socks.sync import Proxy as SyncProxy 9 | from python_socks import ProxyError, ProxyTimeoutError, ProxyConnectionError 10 | 11 | 12 | MAILBOX_PROXY_ERRORS = ( 13 | ProxyError, 14 | ProxyConnectionError, 15 | ProxyTimeoutError, 16 | ) 17 | 18 | 19 | class IMAP4Proxy(IMAP4): 20 | def __init__( 21 | self, 22 | host: str, 23 | proxy: Proxy, 24 | *, 25 | port: int = 993, 26 | rdns: bool = True, 27 | timeout: float = None, 28 | ): 29 | self._host = host 30 | self._port = port 31 | self._proxy = proxy 32 | self._pysocks_proxy = SyncProxy.from_url(self._proxy.as_url, rdns=rdns) 33 | super().__init__(host, port, timeout) 34 | 35 | def _create_socket(self, timeout): 36 | return self._pysocks_proxy.connect(self._host, self._port, timeout) 37 | 38 | 39 | class IMAP4SSlProxy(IMAP4Proxy): 40 | def __init__( 41 | self, 42 | host: str, 43 | proxy: Proxy, 44 | *, 45 | port: int = 993, 46 | rdns: bool = True, 47 | ssl_context=None, 48 | timeout: float = None, 49 | ): 50 | self.ssl_context = ssl_context or ssl._create_unverified_context() 51 | super().__init__(host, proxy, port=port, rdns=rdns, timeout=timeout) 52 | 53 | def _create_socket(self, timeout): 54 | sock = super()._create_socket(timeout) 55 | server_hostname = self.host if ssl.HAS_SNI else None 56 | return self.ssl_context.wrap_socket(sock, server_hostname=server_hostname) 57 | -------------------------------------------------------------------------------- /data/config.py: -------------------------------------------------------------------------------- 1 | THREADS = 5 # for register account / claim rewards mode / approve email mode 2 | MIN_PROXY_SCORE = 50 # Put MIN_PROXY_SCORE = 0 not to check proxy score (if site is down) 3 | 4 | USE_CONSOLE_VERSION = True # if True - use console version and no interface shows 5 | NODE_TYPE = "1_25x" # 1x, 1_25x, 2x 6 | 7 | ######################################### 8 | APPROVE_EMAIL = False # approve email (NEEDED IMAP AND ACCESS TO EMAIL) 9 | CONNECT_WALLET = False # connect wallet (put private keys in wallets.txt) 10 | SEND_WALLET_APPROVE_LINK_TO_EMAIL = False # send approve link to email 11 | APPROVE_WALLET_ON_EMAIL = False # get approve link from email (NEEDED IMAP AND ACCESS TO EMAIL) 12 | SEMI_AUTOMATIC_APPROVE_LINK = False # if True - allow to manual paste approve link from email to cli 13 | # If you have possibility to forward all approve mails to single IMAP address: 14 | SINGLE_IMAP_ACCOUNT = False # usage "name@domain.com:password" 15 | 16 | # skip for auto chosen 17 | EMAIL_FOLDER = '' # folder where mails comes (example: SPAM INBOX JUNK etc.) 18 | IMAP_DOMAIN = "" # imap server domain (example: imap.firstmail.ltd for firstmail) 19 | 20 | ######################################### 21 | CLAIM_REWARDS_ONLY = False # claim tiers rewards only (https://app.getgrass.io/dashboard/referral-program) 22 | 23 | STOP_ACCOUNTS_WHEN_SITE_IS_DOWN = True # stop account for 20 minutes, to reduce proxy traffic usage 24 | CHECK_POINTS = True # show point for each account every nearly 10 minutes 25 | SHOW_LOGS_RARELY = False # not always show info about actions to decrease pc influence 26 | 27 | # Mining mode 28 | MINING_MODE = True 29 | 30 | # REGISTER PARAMETERS ONLY 31 | REGISTER_ACCOUNT_ONLY = False 32 | REGISTER_DELAY = (3, 7) 33 | 34 | TWO_CAPTCHA_API_KEY = '' 35 | ANTICAPTCHA_API_KEY = '' 36 | CAPMONSTER_API_KEY = '' 37 | CAPSOLVER_API_KEY = '' 38 | CAPTCHAAI_API_KEY = '' 39 | 40 | # Use proxy also for mail handling 41 | USE_PROXY_FOR_IMAP = False 42 | 43 | REF_CODE = '' 44 | 45 | # Captcha params, left empty 46 | CAPTCHA_PARAMS = { 47 | "captcha_type": "v2", 48 | "invisible_captcha": False, 49 | "sitekey": "6LeeT-0pAAAAAFJ5JnCpNcbYCBcAerNHlkK4nm6y", 50 | "captcha_url": "https://app.getgrass.io/register" 51 | } 52 | 53 | ######################################## 54 | 55 | ACCOUNTS_FILE_PATH = 'data/accounts.txt' 56 | PROXIES_FILE_PATH = 'data/proxies.txt' 57 | WALLETS_FILE_PATH = 'data/wallets.txt' 58 | PROXY_DB_PATH = 'data/proxies_stats.db' 59 | -------------------------------------------------------------------------------- /core/utils/error_helper.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | from typing import Optional 4 | 5 | from core.utils import logger 6 | from core.utils.exception import FailureLimitReachedException 7 | 8 | 9 | def raise_error(error_type: Exception): 10 | raise error_type 11 | 12 | 13 | class FailureCounter: 14 | global_fail_counter = {} 15 | 16 | def __init__(self): 17 | self.fail_count = 0 18 | 19 | self.id = None 20 | 21 | self.limit = 5 22 | 23 | def fail_increment(self, step: float = 1): 24 | self.fail_count += step 25 | 26 | def check_limit_reached(self, limit: int): 27 | return self.fail_count >= limit 28 | 29 | def fail_reset(self): 30 | self.fail_count = 0 31 | 32 | async def failure_handler(self, step: float = 1, limit: Optional[int] = None, is_raise: bool = True, msg: str = "", 33 | sleep_time: int = 0): 34 | if limit is None: 35 | limit = self.limit 36 | 37 | if self.check_limit_reached(limit): 38 | self.log_global_count() 39 | self.fail_reset() 40 | if is_raise: 41 | raise_error(FailureLimitReachedException(self.fail_count)) 42 | else: 43 | sleep_time = random.randint(2, 5) * 60 44 | msg = f"{self.id} | Sleeping for {int(sleep_time)} seconds... Too many errors. Retrying..." 45 | await self.reset_with_delay(msg, sleep_time) 46 | else: 47 | self.fail_increment(step) 48 | 49 | async def reset_with_delay(self, msg: str, sleep_time: int = 0): 50 | self.fail_reset() 51 | await self.delay_with_log(msg, sleep_time) 52 | 53 | def reach_fail_limit(self): 54 | self.fail_count = self.limit 55 | 56 | async def delay_with_log(self, msg: str, sleep_time: int = random.randint(5, 10) * 60): 57 | logger.info(msg) 58 | await asyncio.sleep(sleep_time) 59 | 60 | def log_global_count(self, is_work: bool = False): 61 | FailureCounter.global_fail_counter[self.id] = int(is_work) 62 | 63 | @staticmethod 64 | async def clear_global_counter(): 65 | await asyncio.sleep(10 * 60) 66 | 67 | FailureCounter.global_fail_counter = {x: 1 for x in FailureCounter.global_fail_counter} 68 | 69 | @staticmethod 70 | def is_global_error(min_limit: int = 10): 71 | amount = len(FailureCounter.global_fail_counter) 72 | work_count = sum(FailureCounter.global_fail_counter.values()) 73 | fail_count = amount - work_count 74 | 75 | limit_fail_amount = amount * 0.30 76 | 77 | if limit_fail_amount < min_limit: 78 | limit_fail_amount = min(amount, min_limit) 79 | 80 | if fail_count > limit_fail_amount: 81 | asyncio.create_task(FailureCounter.clear_global_counter()) 82 | return True 83 | -------------------------------------------------------------------------------- /core/autoreger.py: -------------------------------------------------------------------------------- 1 | import random 2 | import traceback 3 | from asyncio import Semaphore, sleep, create_task, wait 4 | from itertools import zip_longest 5 | 6 | from core.utils import logger, file_to_list, str_to_file 7 | 8 | 9 | class AutoReger: 10 | def __init__(self, accounts: list): 11 | self.accounts = accounts 12 | 13 | self.success = 0 14 | self.semaphore = None 15 | self.delay = None 16 | 17 | @classmethod 18 | def get_accounts(cls, file_names: tuple, amount: int = None, auto_creation: tuple = None, with_id: bool = False, 19 | static_extra: tuple = None): 20 | consumables = [file_to_list(file_name) for file_name in file_names] 21 | 22 | if amount and consumables[0]: 23 | consumables = [consumable[:amount] for consumable in consumables] 24 | elif amount and auto_creation: 25 | for creation_func in auto_creation: 26 | consumables.append([creation_func() for _ in range(amount)]) 27 | 28 | acc_len = len(consumables[0]) 29 | consumables[1] = consumables[1][:acc_len] 30 | 31 | if with_id: 32 | consumables.insert(0, (list(range(1, acc_len + 1)))) 33 | if static_extra: 34 | for extra in static_extra: 35 | consumables.append([extra] * acc_len) 36 | 37 | accounts = list(zip_longest(*consumables)) 38 | 39 | if not accounts or not accounts[0]: 40 | logger.warning("No accounts found :(") 41 | return 42 | else: 43 | return cls(accounts) 44 | 45 | async def start(self, worker_func: callable, threads: int = 1, delay: tuple = (0, 0)): 46 | logger.info(f"Successfully grabbed {len(self.accounts)} accounts") 47 | 48 | self.semaphore = Semaphore(threads) 49 | self.delay = delay 50 | await self.define_tasks(worker_func) 51 | 52 | (logger.success if self.success else logger.warning)( 53 | f"Successfully handled {self.success} accounts :)" if self.success 54 | else "No accounts handled :( | Check logs in logs/out.log") 55 | 56 | async def define_tasks(self, worker_func: callable): 57 | await wait([create_task(self.worker(account, worker_func)) for account in self.accounts]) 58 | 59 | async def worker(self, account: tuple, worker_func: callable): 60 | account_id = account[0][:15] if isinstance(account, str) else account[0] 61 | is_success = False 62 | 63 | try: 64 | async with self.semaphore: 65 | await self.custom_delay() 66 | 67 | is_success = await worker_func(*account) 68 | except Exception as e: 69 | logger.error(f"{account_id} | not handled | error: {e} {traceback.format_exc()}") 70 | 71 | self.success += int(is_success or 0) 72 | AutoReger.logs(account_id, account, is_success) 73 | 74 | async def custom_delay(self): 75 | if self.delay[1] > 0: 76 | sleep_time = random.uniform(*self.delay) 77 | logger.info(f"Sleep for {sleep_time:.1f} seconds") 78 | await sleep(sleep_time) 79 | 80 | @staticmethod 81 | def logs(account_id: str, account: tuple, is_success: bool = False): 82 | if is_success: 83 | log_func = logger.success 84 | log_msg = "handled!" 85 | file_name = "success" 86 | else: 87 | log_func = logger.warning 88 | log_msg = "failed!" 89 | file_name = "failed" 90 | 91 | file_msg = "|".join(str(x) for x in account[:2]) 92 | str_to_file(f"./logs/{file_name}.txt", file_msg) 93 | 94 | log_func(f"Account №{account_id} {log_msg}") 95 | -------------------------------------------------------------------------------- /core/utils/logger.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import re 3 | from datetime import date 4 | from loguru import logger 5 | from PySide6.QtWidgets import QTextEdit 6 | from PySide6.QtGui import QColor 7 | from PySide6.QtCore import QObject, Signal, Slot 8 | 9 | class LogSignals(QObject): 10 | new_log = Signal(str, dict) 11 | 12 | class QTextEditHandler: 13 | def __init__(self, text_edit: QTextEdit): 14 | self.text_edit = text_edit 15 | self.signals = LogSignals() 16 | self.signals.new_log.connect(self.append_message) 17 | 18 | def write(self, message: str): 19 | clean_message = clean_brackets(message) 20 | 21 | # Определяем цвета в зависимости от уровня логирования 22 | if "ERROR" in message: 23 | colors = { 24 | "time": QColor("#00FF00"), # зеленый 25 | "level": QColor("#FF0000"), # красный 26 | "message": QColor("#FF0000") # красный 27 | } 28 | elif "WARNING" in message: 29 | colors = { 30 | "time": QColor("#27e868"), # зеленый 31 | "level": QColor("#FFD700"), # желтый 32 | "message": QColor("#FFD700") # желтый 33 | } 34 | elif "INFO" in message: 35 | colors = { 36 | "time": QColor("#27e868"), # зеленый 37 | "level": QColor("#32c2c2"), # синий 38 | "message": QColor("#FFFFFF") # белый 39 | } 40 | else: 41 | colors = { 42 | "time": QColor("#27e868"), # зеленый 43 | "level": QColor("#d137d4"), # синий 44 | "message": QColor("#eb811e") # белый 45 | } 46 | 47 | # Отправляем сигнал для обновления UI 48 | self.signals.new_log.emit(clean_message, colors) 49 | 50 | @Slot(str, dict) 51 | def append_message(self, message: str, colors: dict): 52 | # Разделяем сообщение на части 53 | parts = message.split(" ", 2) 54 | if len(parts) >= 3: 55 | time_part, level_part, message_part = parts 56 | 57 | # Добавляем время 58 | self.text_edit.setTextColor(colors["time"]) 59 | self.text_edit.insertPlainText(time_part + " ") 60 | 61 | # Добавляем уровень 62 | self.text_edit.setTextColor(colors["level"]) 63 | self.text_edit.insertPlainText(level_part + " ") 64 | 65 | # Добавляем сообщение 66 | self.text_edit.setTextColor(colors["message"]) 67 | self.text_edit.insertPlainText(message_part + "\n") 68 | 69 | # Прокручиваем до конца 70 | scrollbar = self.text_edit.verticalScrollBar() 71 | scrollbar.setValue(scrollbar.maximum()) 72 | 73 | 74 | def logging_setup(gui_mode=False, text_edit=None): 75 | format_info = "{time:HH:mm:ss.SS} {level} {message}" 76 | format_error = "{time:HH:mm:ss.SS} {level} | " \ 77 | "{name}:{function}:{line} | {message}" 78 | file_path = r"logs/" 79 | 80 | logger.remove() # Удаляем все предыдущие обработчики 81 | 82 | if gui_mode and text_edit is not None: 83 | # В GUI режиме добавляем только один обработчик для QTextEdit 84 | handler = QTextEditHandler(text_edit) 85 | logger.add(handler, format=format_info, level="INFO") 86 | else: 87 | # В консольном режиме добавляем обработчики для файла и stdout 88 | logger.add(file_path + f"out_{date.today().strftime('%m-%d')}.log", colorize=True, 89 | format=format_info) 90 | logger.add(sys.stdout, colorize=True, format=format_info, level="INFO") 91 | 92 | 93 | def clean_brackets(raw_str): 94 | clean_text = re.sub(brackets_regex, '', raw_str) 95 | return clean_text 96 | 97 | 98 | brackets_regex = re.compile(r'<.*?>') 99 | 100 | # Пример использования (предполага��тся, что `text_edit` — это ваш экземпляр QTextEdit): 101 | logging_setup(gui_mode=False) 102 | -------------------------------------------------------------------------------- /core/utils/mail/mailbox.py: -------------------------------------------------------------------------------- 1 | import re 2 | from datetime import datetime 3 | from typing import Iterator, Sequence 4 | 5 | from imaplib import IMAP4_SSL 6 | from better_proxy import Proxy 7 | from bs4 import BeautifulSoup 8 | from imap_tools import AND, MailMessage, MailBox as BaseMailBox 9 | 10 | from .proxy import IMAP4SSlProxy 11 | 12 | 13 | def get_message_text(mail_message: MailMessage) -> str: 14 | if mail_message.text: 15 | return mail_message.text 16 | else: 17 | soup = BeautifulSoup(mail_message.html, 'html.parser') 18 | return soup.get_text() 19 | 20 | 21 | class MailBox(BaseMailBox): 22 | def __init__( 23 | self, 24 | host: str, 25 | *, 26 | proxy: Proxy | str = None, 27 | port: int = 993, 28 | timeout: float = None, 29 | rdns: bool = True, 30 | ssl_context=None, 31 | ): 32 | self._proxy = Proxy.from_str(proxy) if proxy else None 33 | self._rdns = rdns 34 | super().__init__(host=host, port=port, timeout=timeout, ssl_context=ssl_context) 35 | 36 | def _get_mailbox_client(self): 37 | if self._proxy: 38 | return IMAP4SSlProxy( 39 | self._host, 40 | self._proxy, 41 | port=self._port, 42 | rdns=self._rdns, 43 | timeout=self._timeout, 44 | ssl_context=self._ssl_context, 45 | ) 46 | else: 47 | return IMAP4_SSL( 48 | self._host, 49 | port=self._port, 50 | timeout=self._timeout, 51 | ssl_context=self._ssl_context, 52 | ) 53 | 54 | def login(self, username: str, password: str, initial_folder: str | None = 'INBOX'): 55 | if self._host == "imap.rambler.ru" and "%" in password: 56 | raise ValueError( 57 | f"IMAP password contains '%' character. Change your password." 58 | f" It's a specific rambler.ru error" 59 | ) 60 | 61 | return super().login(username, password, initial_folder) 62 | 63 | def fetch_messages( 64 | self, 65 | folders: Sequence[str] = ("INBOX", "Inbox", "inbox"), 66 | *, 67 | since: datetime = None, 68 | allowed_senders: Sequence[str] = None, 69 | allowed_receivers: Sequence[str] = None, 70 | sender_regex: str | re.Pattern[str] = None, 71 | limit: int = 10, 72 | reverse: bool = True, 73 | ) -> Iterator[MailMessage]: 74 | for folder in folders: 75 | self.folder.set(folder) 76 | 77 | criteria = AND( 78 | date_gte=since.date() if since else None, 79 | from_=allowed_senders if allowed_senders else None, 80 | to=allowed_receivers if allowed_receivers else None, 81 | all=True # Условие для выборки всех сообщений при отсутствии других фильтров 82 | ) 83 | 84 | for message in self.fetch(criteria, limit=limit, reverse=reverse): # type: MailMessage 85 | # Фильтрация по дате 86 | if since and message.date < since: 87 | continue 88 | 89 | # Фильтрация по регулярному выражению 90 | if sender_regex and not re.search(sender_regex, message.from_, re.IGNORECASE): 91 | continue 92 | 93 | yield message 94 | 95 | def search_matches( 96 | self, 97 | regex: str | re.Pattern[str], 98 | folders: Sequence[str] = None, 99 | *, 100 | since: datetime = None, 101 | allowed_senders: Sequence[str] = None, 102 | allowed_receivers: Sequence[str] = None, 103 | sender_regex: str | re.Pattern[str] = None, 104 | limit: int = 10, 105 | reverse: bool = True, 106 | ) -> list[tuple[MailMessage, str]]: 107 | matches = [] 108 | messages = self.fetch_messages( 109 | folders, 110 | since=since, 111 | allowed_senders=allowed_senders, 112 | allowed_receivers=allowed_receivers, 113 | sender_regex=sender_regex, 114 | limit=limit, 115 | reverse=reverse, 116 | ) 117 | 118 | for message in messages: 119 | if found := re.findall(regex, get_message_text(message)): 120 | matches.append((message, found[0])) 121 | 122 | return matches 123 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ project files 2 | .idea 3 | *.iml 4 | *.xml 5 | out 6 | gen 7 | /logs 8 | /test 9 | /data 10 | 11 | ### venv template 12 | # Virtualenv 13 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 14 | .vscode 15 | private_keys.txt 16 | 17 | .Python 18 | [Bb]in 19 | [Ii]nclude 20 | [Ll]ib 21 | [Ll]ib64 22 | [Ll]ocal 23 | [Ss]cripts 24 | pyvenv.cfg 25 | .venv 26 | pip-selfcheck.json 27 | 28 | ### VirtualEnv template 29 | # Virtualenv 30 | # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ 31 | .Python 32 | [Bb]in 33 | [Ii]nclude 34 | [Ll]ib 35 | [Ll]ib64 36 | [Ll]ocal 37 | [Ss]cripts 38 | pyvenv.cfg 39 | .venv 40 | pip-selfcheck.json 41 | 42 | ### Python template 43 | # Byte-compiled / optimized / DLL files 44 | __pycache__/ 45 | *.py[cod] 46 | *$py.class 47 | 48 | # C extensions 49 | *.so 50 | 51 | # Distribution / packaging 52 | .Python 53 | build/ 54 | develop-eggs/ 55 | dist/ 56 | downloads/ 57 | eggs/ 58 | .eggs/ 59 | lib/ 60 | lib64/ 61 | parts/ 62 | sdist/ 63 | var/ 64 | wheels/ 65 | share/python-wheels/ 66 | *.egg-info/ 67 | .installed.cfg 68 | *.egg 69 | MANIFEST 70 | 71 | # PyInstaller 72 | # Usually these files are written by a python script from a template 73 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 74 | *.manifest 75 | *.spec 76 | 77 | # Installer logs 78 | pip-log.txt 79 | pip-delete-this-directory.txt 80 | 81 | # Unit test / coverage reports 82 | htmlcov/ 83 | .tox/ 84 | .nox/ 85 | .coverage 86 | .coverage.* 87 | .cache 88 | nosetests.xml 89 | coverage.xml 90 | *.cover 91 | *.py,cover 92 | .hypothesis/ 93 | .pytest_cache/ 94 | cover/ 95 | 96 | # Translations 97 | *.mo 98 | *.pot 99 | 100 | # Django stuff: 101 | *.log 102 | local_settings.py 103 | db.sqlite3 104 | db.sqlite3-journal 105 | 106 | # Flask stuff: 107 | instance/ 108 | .webassets-cache 109 | 110 | # Scrapy stuff: 111 | .scrapy 112 | 113 | # Sphinx documentation 114 | docs/_build/ 115 | 116 | # PyBuilder 117 | .pybuilder/ 118 | target/ 119 | 120 | # Jupyter Notebook 121 | .ipynb_checkpoints 122 | 123 | # IPython 124 | profile_default/ 125 | ipython_config.py 126 | 127 | # pyenv 128 | # For a library or package, you might want to ignore these files since the code is 129 | # intended to run in multiple environments; otherwise, check them in: 130 | # .python-version 131 | 132 | # pipenv 133 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 134 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 135 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 136 | # install all needed dependencies. 137 | #Pipfile.lock 138 | 139 | # poetry 140 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 141 | # This is especially recommended for binary packages to ensure reproducibility, and is more 142 | # commonly ignored for libraries. 143 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 144 | #poetry.lock 145 | 146 | # pdm 147 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 148 | #pdm.lock 149 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 150 | # in version control. 151 | # https://pdm.fming.dev/#use-with-ide 152 | .pdm.toml 153 | 154 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 155 | __pypackages__/ 156 | 157 | # Celery stuff 158 | celerybeat-schedule 159 | celerybeat.pid 160 | 161 | # SageMath parsed files 162 | *.sage.py 163 | 164 | # Environments 165 | .env 166 | .venv 167 | env/ 168 | venv/ 169 | ENV/ 170 | env.bak/ 171 | venv.bak/ 172 | 173 | # Spyder project settings 174 | .spyderproject 175 | .spyproject 176 | 177 | # Rope project settings 178 | .ropeproject 179 | 180 | # mkdocs documentation 181 | /site 182 | 183 | # mypy 184 | .mypy_cache/ 185 | .dmypy.json 186 | dmypy.json 187 | 188 | # Pyre type checker 189 | .pyre/ 190 | 191 | # pytype static type analyzer 192 | .pytype/ 193 | 194 | # Cython debug symbols 195 | cython_debug/ 196 | 197 | # PyCharm 198 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 199 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 200 | # and can be added to the global gitignore or merged into this file. For a more nuclear 201 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 202 | #.idea/ 203 | -------------------------------------------------------------------------------- /core/utils/mail/mail.py: -------------------------------------------------------------------------------- 1 | import time 2 | import asyncio 3 | from datetime import datetime, timezone, timedelta 4 | from typing import Optional, Dict 5 | 6 | from imap_tools import AND #, MailBox 7 | from loguru import logger 8 | 9 | from core.utils.mail.mailbox import MailBox 10 | from data.config import EMAIL_FOLDER, IMAP_DOMAIN, SINGLE_IMAP_ACCOUNT, USE_PROXY_FOR_IMAP 11 | 12 | 13 | class MailUtils: 14 | def __init__(self, email: str, imap_pass: str, proxy: str = None) -> None: 15 | if SINGLE_IMAP_ACCOUNT: 16 | self.email: str = SINGLE_IMAP_ACCOUNT.split(":")[0] 17 | else: 18 | self.email: str = email 19 | self.imap_pass: str = imap_pass 20 | self.domain: str = IMAP_DOMAIN or self.parse_domain() 21 | 22 | self.proxy = proxy if USE_PROXY_FOR_IMAP else None 23 | 24 | def parse_domain(self) -> str: 25 | domain: str = self.email.split("@")[-1] 26 | 27 | if "hotmail" in domain or "live" in domain: 28 | domain = "outlook.com" 29 | elif "yahoo" in domain: 30 | domain = "mail.yahoo.com" 31 | elif "firstmail" in domain: 32 | domain = "firstmail.ltd" 33 | elif any(sub in domain for sub in ["rambler", "myrambler", "autorambler", "ro.ru"]): 34 | domain = "rambler.ru" 35 | elif "icloud" in domain: 36 | domain = "mail.me.com" 37 | elif "gazeta" in domain: 38 | domain = "gazeta.pl" 39 | elif "onet" in domain: 40 | domain = "poczta.onet.pl" 41 | elif "gmx" in domain: 42 | domain = "gmx.net" 43 | elif "firemail" in domain: 44 | domain = "firemail.de" 45 | 46 | return f"imap.{domain}" 47 | 48 | def get_msg( 49 | self, 50 | to: Optional[str] = None, 51 | subject: Optional[str] = None, 52 | from_: Optional[str] = None, 53 | seen: Optional[bool] = None, 54 | limit: Optional[int] = None, 55 | reverse: bool = True, 56 | delay: int = 60 57 | ) -> Dict[str, any]: 58 | 59 | 60 | if EMAIL_FOLDER: 61 | email_folders = [EMAIL_FOLDER] 62 | else: 63 | email_folders = ["INBOX", "Inbox", "inbox", "Junk", "JUNK", "Spam", "SPAM", "TRASH", "Trash"] 64 | 65 | with MailBox( 66 | self.domain, 67 | proxy=self.proxy 68 | ).login(self.email, self.imap_pass, initial_folder=None) as mailbox: 69 | actual_folders = [mailbox.name for mailbox in list(mailbox.folder.list())] 70 | folders = [folder for folder in email_folders if folder in actual_folders] 71 | 72 | for _ in range(delay // 3): 73 | time.sleep(3) 74 | try: 75 | for folder in folders: 76 | mailbox.folder.set(folder) 77 | criteria = AND(subject=subject, to=to, from_=from_, seen=seen) 78 | 79 | for msg in mailbox.fetch(criteria, limit=limit, reverse=reverse): 80 | logger.success(f'{self.email} | Successfully found new msg by subject: {msg.subject}') 81 | return { 82 | "success": True, 83 | "msg": msg.html, 84 | "subject": msg.subject, 85 | "from": msg.from_, 86 | "to": msg.to 87 | } 88 | except Exception as error: 89 | logger.error(f'{self.email} | Error when fetching new message by subject: {str(error)}') 90 | 91 | return {"success": False, "msg": "New message not found by subject"} 92 | 93 | async def get_msg_async( 94 | self, 95 | to: Optional[str] = None, 96 | subject: Optional[str] = None, 97 | from_: Optional[str] = None, 98 | seen: Optional[bool] = None, 99 | limit: Optional[int] = None, 100 | reverse: bool = True, 101 | delay: int = 60 102 | ) -> Dict[str, any]: 103 | return await asyncio.to_thread(self.get_msg, to, subject, from_, seen, limit, reverse, delay) 104 | 105 | 106 | # if __name__ == '__main__': 107 | # email = "" 108 | # imap_pass = "" 109 | # mail_utils = MailUtils(email, imap_pass) 110 | # 111 | # # Asynchronous call 112 | # async def main(): 113 | # result = await mail_utils.get_msg_async(to=email, from_="support@wynd.network", subject="Verify Your Email for Grass") 114 | # print(result) 115 | # if result['success']: 116 | # verify_link = result['msg'].split('\r\n 1: 68 | email = row[0] 69 | existing_proxies = row[1].split(",") 70 | if proxy in existing_proxies: 71 | return email 72 | return False 73 | 74 | async def update_or_create_point_stat(self, user_id, email, points): 75 | async with self.db_lock: 76 | await self.cursor.execute("SELECT * FROM PointStats WHERE id = ?", (user_id,)) 77 | existing_user = await self.cursor.fetchone() 78 | 79 | if existing_user: 80 | await self.cursor.execute("UPDATE PointStats SET email = ?, points = ? WHERE id = ?", 81 | (email, points, user_id)) 82 | else: 83 | await self.cursor.execute("INSERT INTO PointStats(id, email, points) VALUES (?, ?, ?)", 84 | (user_id, email, points)) 85 | 86 | await self.connection.commit() 87 | 88 | async def get_total_points(self): 89 | async with self.db_lock: 90 | await self.cursor.execute( 91 | 'SELECT SUM(CAST(points AS INTEGER)) ' 92 | 'FROM (SELECT email, MAX(CAST(points AS INTEGER)) as points ' 93 | 'FROM PointStats WHERE points NOT LIKE "%[^0-9]%" ' 94 | 'GROUP BY email)') 95 | result = await self.cursor.fetchone() 96 | if result: 97 | return result[0] 98 | else: 99 | return 0 100 | 101 | async def get_proxies_by_email(self, email): 102 | async with self.db_lock: 103 | await self.cursor.execute("SELECT proxies FROM Accounts WHERE email=?", (email,)) 104 | row = await self.cursor.fetchone() 105 | if row: 106 | proxies = row[0].split(",") 107 | return proxies 108 | return [] 109 | 110 | async def get_new_from_extra_proxies(self, table="ProxyList"): 111 | # logger.info(f"Getting new proxy from {table}...") 112 | async with self.db_lock: 113 | await self.cursor.execute(f"SELECT proxy FROM {table} ORDER BY id DESC LIMIT 1") 114 | proxy = await self.cursor.fetchone() 115 | # logger.info(f"Extra proxy: {proxy}") 116 | if proxy and len(proxy) == 1: 117 | await self.cursor.execute(f"DELETE FROM {table} WHERE proxy=?", proxy) 118 | await self.connection.commit() 119 | return proxy[0] 120 | else: 121 | return None 122 | 123 | async def push_extra_proxies(self, proxies): 124 | async with self.db_lock: 125 | await self.cursor.executemany("INSERT INTO ProxyList(proxy) VALUES(?)", [(proxy,) for proxy in proxies]) 126 | await self.connection.commit() 127 | 128 | async def delete_all_from_extra_proxies(self): 129 | async with self.db_lock: 130 | await self.cursor.execute("DELETE FROM ProxyList") 131 | await self.connection.commit() 132 | 133 | async def close_connection(self): 134 | await self.connection.close() 135 | -------------------------------------------------------------------------------- /core/grass_sdk/extension.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | from base64 import b64decode, b64encode 4 | from random import choice 5 | 6 | from aiohttp import WSMsgType 7 | import uuid 8 | 9 | from better_proxy import Proxy 10 | 11 | from core.utils.exception import WebsocketClosedException, ProxyForbiddenException 12 | 13 | import os, base64 14 | 15 | from data.config import NODE_TYPE 16 | 17 | 18 | class GrassWs: 19 | def __init__(self, user_agent: str = None, proxy: str = None): 20 | self.user_agent = user_agent 21 | self.proxy = proxy 22 | 23 | self.session = None 24 | self.websocket = None 25 | self.id = None 26 | # self.ws_session = None 27 | 28 | async def connect(self): 29 | # self.proxy=None # testing on local network 30 | connection_port = ["4444", "4650"] 31 | uri = f"wss://proxy2.wynd.network:{choice(connection_port)}/" 32 | 33 | random_bytes = os.urandom(16) 34 | sec_websocket_key = base64.b64encode(random_bytes).decode('utf-8') 35 | 36 | headers = { 37 | 'Pragma': 'no-cache', 38 | 'Origin': 'chrome-extension://lkbnfiajjmbhnfledhphioinpickokdi', 39 | 'Accept-Language': 'en-US,en;q=0.9', 40 | 'Sec-WebSocket-Key': sec_websocket_key, 41 | 'User-Agent': self.user_agent, 42 | 'Upgrade': 'websocket', 43 | 'Cache-Control': 'no-cache', 44 | 'Connection': 'Upgrade', 45 | 'Sec-WebSocket-Version': '13', 46 | 'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits', 47 | } 48 | 49 | try: 50 | self.websocket = await self.session.ws_connect(uri, proxy_headers=headers, proxy=self.proxy) 51 | except Exception as e: 52 | if 'status' in dir(e) and e.status == 403: 53 | raise ProxyForbiddenException(f"Low proxy score. Can't connect. Error: {e}") 54 | raise e 55 | 56 | async def send_message(self, message): 57 | # logger.info(f"Sending: {message}") 58 | await self.websocket.send_str(message) 59 | 60 | async def receive_message(self): 61 | msg = await self.websocket.receive() 62 | # logger.info(f"Received: {msg}") 63 | 64 | if msg.type == WSMsgType.CLOSED: 65 | raise WebsocketClosedException(f"Websocket closed: {msg}") 66 | 67 | return json.loads(msg.data) 68 | 69 | async def get_connection_id(self): 70 | msg = await self.receive_message() 71 | return msg['id'] 72 | 73 | async def auth_to_extension(self, browser_id: str, user_id: str): 74 | connection_id = await self.get_connection_id() 75 | 76 | message = { 77 | "id": connection_id, 78 | "origin_action": "AUTH", 79 | "result": { 80 | "browser_id": browser_id, 81 | "user_id": user_id, 82 | "user_agent": self.user_agent, 83 | "timestamp": int(time.time()), 84 | "device_type": "extension", 85 | "version": "4.26.2", 86 | "extension_id": "ilehaonighjijnmpnagapkhpcdbhclfg" 87 | } 88 | } 89 | 90 | if NODE_TYPE == "1.25x": 91 | message['result'].update({ 92 | "extension_id": "lkbnfiajjmbhnfledhphioinpickokdi", 93 | }) 94 | elif NODE_TYPE == "2x": 95 | message['result'].update({ 96 | "device_type": "desktop", 97 | "version": "4.30.0", 98 | }) 99 | message['result'].pop("extension_id") 100 | 101 | await self.send_message(json.dumps(message)) 102 | 103 | async def send_ping(self): 104 | message = json.dumps( 105 | {"id": str(uuid.uuid4()), "version": "1.0.0", "action": "PING", "data": {}} 106 | ) 107 | 108 | await self.send_message(message) 109 | 110 | async def send_pong(self): 111 | connection_id = await self.get_connection_id() 112 | 113 | message = json.dumps( 114 | {"id": connection_id, "origin_action": "PONG"} 115 | ) 116 | 117 | await self.send_message(message) 118 | 119 | async def handle_http_request_action(self): 120 | http_info = await self.receive_message() 121 | result = await self.build_http_request(http_info['data']) 122 | 123 | if result == {}: 124 | raise ConnectionResetError("Not full http request action.") 125 | 126 | message = json.dumps( 127 | { 128 | "id": http_info["id"], 129 | "origin_action": "HTTP_REQUEST", 130 | "result": result 131 | } 132 | ) 133 | 134 | await self.send_message(message) 135 | 136 | async def build_http_request(self, request_data): 137 | if request_data.get("method") is None: 138 | return {} 139 | 140 | method = request_data['method'] 141 | url = request_data['url'] 142 | headers = request_data['headers'] 143 | body = request_data.get("body") # there may be no body 144 | 145 | if body: 146 | body = b64decode( 147 | body) # this will probably be in json format when decoded but i dont think there is a need to turn it to a json 148 | 149 | try: 150 | # if self.proxy: 151 | # proxy_to_encode = Proxy.from_str(self.proxy) 152 | # encoded_proxy = base64.b64encode(bytes(f'{proxy_to_encode.login}:{proxy_to_encode.password}', 'utf-8')) 153 | # encoded_proxy_as_str = encoded_proxy.decode('utf-8') 154 | # headers['proxy-authorization'] = encoded_proxy_as_str 155 | 156 | response = await self.session.request(method, url, 157 | headers=headers, data=body, proxy=self.proxy) 158 | 159 | if response: 160 | response.raise_for_status() 161 | response_headers_raw = response.headers 162 | response_headers = dict(response_headers_raw) 163 | response_body = await response.content.read() 164 | status_reason = response.reason 165 | status_code = response.status 166 | encoded_body = b64encode(response_body) 167 | encoded_body_as_str = encoded_body.decode('utf-8') 168 | return { 169 | "body": encoded_body_as_str, 170 | "headers": response_headers, 171 | "status": status_code, 172 | "status_text": status_reason, 173 | "url": url 174 | } 175 | 176 | except Exception as e: 177 | # return this if anything happened 178 | return {} # return an empty string if we have issues running the request 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # G-MAIN 2 | # 🔹Grass Auto ro'yhatdan o'tish va point farm 🔹 3 | 4 | # GRASS CONSOLE 5 | ![image](https://github.com/JMSM0707/G-MAIN/blob/main/core/static/Image-1.png) 6 | 7 | # GRASS NODE 2X 8 | ![image](https://github.com/JMSM0707/G-MAIN/blob/main/core/static/Image-2.png) 9 | 10 | ### Bot nima qila oladi? 11 | - Akkaunt yaratadi 12 | - Farm Point 13 | - Pointlarni tekshiradi 14 | - Kirishni imkoni bo'lmagan elektron pochta xabarlarini tasdiqlaydi (imap kerak emas va hokazo) 15 | 16 | > Siz imkon qadar ko'proq proksi-server qo'yishingiz mumkin, bot ma'lumotlar bazasidan foydalanadi va proksi-serverlarni qo'shimcha proksilardan yuklaydi. 17 | 18 | 19 | 1 akkauntga bir nechta ulanishlarni ulash uchun akkauntlarni accounts.txt faylida ko'paytirish kifoya. 20 | 21 | ## Tez boshlash 📚 22 | 1. Windows-da kutubxonalarni o'rnatish uchun `INSTALL.bat` tugmasini bosing (yoki konsolda: `pip install -r requirements.txt`). 23 | 2. Botni ishga tushirish uchun `START.bat` (yoki konsolda: `python main.py`) dan foydalaning. 24 | 25 | ### Variantlar 📧 26 | 27 | 1. AKKAUNT YARATISH: 28 | - `data/config.py` da `REGISTER_ACCOUNT_ONLY = True` so'zini qo'ying 29 | - Api kalitini "data/config.py" ga joylang. Ro'yhatdan o'tish uchun captcha so'ralgani uchun, sizga captchalarni hal qilish uchun xizmat kerak bo'ladi - [AntiCaptcha](http://getcaptchasolution.com/t8yfysqmh3) or [Twocaptcha](https://2captcha.com/?from=12939391). 30 | - Hisob qaydnomalarini ro'yxatdan o'tkazish uchun quyidagi tarzda elektron pochta va parollar (ixtiyoriy) va proksi-serverlarni taqdim eting! 31 | 32 | 33 | 34 | 2. FARM POINT: 35 | - `data/config.py` da `REGISTER_ACCOUNT_ONLY = False` so'zini qo'ying 36 | - Yuqorida ko'rsatilganidek, hisoblarni elektron pochta va parollar va proksi-serverlarni taqdim eting! 37 | 38 | 3. E-Pochta XATLARINI TASDIQLASH: 39 | - "data/config.py" da: 40 | - `APPROVE_EMAIL = True` e-pochtani tasdiqlang (IMAP VA E-Pochtaga kirish KERAK) 41 | - `CONNECT_WALLET = True` hamyonni ulash (shaxsiy kalitlarni wallets.txt-ga joylashtiring) 42 | - `SEND_WALLET_APPROVE_LINK_TO_EMAIL = True` # tasdiqlash havolasini elektron pochtaga yuboring 43 | - `APPROVE_WALLET_ON_EMAIL = True` # elektron pochtadan ma'qullash havolasini oling (IMAP VA E-Pochtaga kirish KERAK) 44 | - Email:password:imap_password formatida elektron pochta va parol hamda imap parolini (elektron pochtaga kirish) taqdim eting! 45 | - Elektron pochtada IMAP ruxsati yoqilgan bo'lishi kerak 46 | - `SEMI_AUTOMATIC_APPROVE_LINK = False ` # Agar True boʻlsa, tasdiqlash havolasini elektron pochtadan CLIga qoʻlda joylashtirish imkonini beradi. Yuqoridagi barcha bayroqlar True ga o'rnatilishi kerak. Agar siz ushbu bayroqdan foydalansangiz, IMAP ruxsatini berishingiz shart emas 47 | - `SINGLE_IMAP_ACCOUNT = False ` # agar sizda barcha tasdiqlangan xatlarni bitta IMAP manziliga yo'naltirish imkoniyati mavjud bo'lsa. Foydalanish: asosiy IMAP manzilingizdagi False ni "name@domain.com:password" ga o'zgartiring 48 | - `EMAIL_FOLDER = "" ` # pochtaga tasdiqlash xati keladigan joyni avtomatik aniqlashi uchun o'tkazib yuboring, yoki pochta xat keladigan papkani yozing (Misol uchun "INBOX" 49 | - `IMAP_DOMAIN = "" ` # avtomatik domen uchun o'tish, har doim ham ishlamaydi 50 | 51 | 52 | 53 | 54 | 55 | 56 | ### Konfiguratsiya 📧 57 | 58 | 1. Hisoblarni sozlash 🔒 59 | 60 | `data/accounts.txt` hisoblarini email:password (tdydjkhfuhhh@gmail.com:grass_parol123) formatida joylashtiring. 61 | 62 | 63 | 64 | 2. Proxy sozlash 🔒 65 | 66 | Proksi-serverlaringizni `data/proxies.txt` da *ANY* (socks, http/s, ...) formatida sozlang. (Misol uchun "http://login:parol@ip:port") 🌐 67 | 68 | 69 | 70 | ## Docker tomonidan tez boshlash 71 | 1. Docker-CE ni o'rnating: `curl -sSL -k https://get.docker.com | sh` 72 | 2. Docker Compose-ni o'rnating: `curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose` 73 | 3. Manba kodini klonlash: `git clone https://github.com/JMSM0707/G-MAIN.git` 74 | 4. Konfiguratsiya: O'zgartirish `data/accounts.txt` va `data/proxies.txt` 75 | 5. Konteynerni ishga tushirish: `docker-compose up -d` 76 | 77 | PS: Qo'shimcha konfiguratsiyani ko'rish mumkin `docker-compose.yml` 78 | 79 | 80 | ## Foydalanuvchidan qo'shimcha qo'llanma 81 | 82 | ### 1. Ma'lumotlar fayllarini sozlash 83 | 84 | **accounts.txt** — Yozuvlar uchun format: `email:password:password` 85 | 86 | Misol: `djfiudgddg@gmail.com:qwert123:qwert123` 87 | Birinchi parol Grass xizmatiga kirish uchun, ikkinchisi esa elektron pochta uchun. 88 | Agar sizda bir xil parolga ega elektron pochta xabarlari to'plami bo'lsa, bir xil paroldan ikki marta foydalaning. 89 | 90 | **wallets.txt** — Solana shaxsiy kalitlarini Base58 formatida kiriting: 91 | 92 | Ushbu havolada solana hamyon generatorni topishingiz mumkin: [Solana Wallet yarating](https://ct.app/createWallet/sol). 93 | 94 | **proxies.txt** — Formada HTTP proksi-serverlaridan foydalaning `login:paarol@ip:port`. 95 | 96 | --- 97 | 98 | ### 2. Hisobni ro'yxatdan o'tkazish uchun konfiguratsiya 99 | 100 | Yangi hisoblarni ro'yxatdan o'tkazish uchun quyidagi sozlamalardan foydalaning: 101 | 102 | ```plaintext 103 | REGISTER_ACCOUNT_ONLY = True 104 | MINING_MODE = False 105 | ``` 106 | 107 | **Ro'yxatdan o'tgan hisob ma'lumotlarini saqlash:** 108 | Muvaffaqiyatli ro'yxatdan o'tgan hisoblar bu yerda `\logs\new_accounts.txt` shu formatda: `email:password:nickname` saqlanadi. 109 | 110 | Qo'shimcha natijalar fayllari: 111 | - `\logs\failed.txt` — muvaffaqiyatsiz ro'yxatga olish. 112 | - `\logs\success.txt` — muvaffaqiyatli ro'yxatdan o'tish. 113 | 114 | --- 115 | 116 | ### 3. Elektron pochtani tekshirish va hamyonga ulanishni sozlash 117 | 118 | Elektron pochtani tekshirish va hamyonga ulanish uchun ushbu sozlamalardan foydalaning: 119 | 120 | ```plaintext 121 | APPROVE_EMAIL = True 122 | CONNECT_WALLET = True 123 | SEND_WALLET_APPROVE_LINK_TO_EMAIL = True 124 | APPROVE_WALLET_ON_EMAIL = True 125 | ---- 126 | REGISTER_ACCOUNT_ONLY = False 127 | MINING_MODE = False 128 | ``` 129 | 130 | **Eslatma:** Ushbu sozlamalar elektron pochtaga hamyonni tasdiqlash havolasini yuboradi va ro'yxatdan o'tishni yakunlaydi. 131 | 132 | --- 133 | 134 | ### 4. Mining Mode 135 | 136 | Mayning rejimini yoqish uchun quyidagi sozlamalardan foydalaning: 137 | 138 | ```plaintext 139 | APPROVE_EMAIL = False 140 | CONNECT_WALLET = False 141 | SEND_WALLET_APPROVE_LINK_TO_EMAIL = False 142 | APPROVE_WALLET_ON_EMAIL = False 143 | ---- 144 | REGISTER_ACCOUNT_ONLY = False 145 | MINING_MODE = True 146 | ``` 147 | 148 | Shu bilan birga siz CONSOL yoki NODE rejimiga o'tishni tanlashingiz mumkin. 149 | Buning uchun: `data/proxies.txt` dan sozlamalarni o'zgartirishingiz kerak. 150 | Agar True bo'lsa - konsol versiyasidan foydalaniladi va interfeys ko'rinmaydi, agar False bo'lsa - konsol versiyasidan foydalanilmaydi va interfeys ko'rinadi 151 | ```ochiq matn 152 | USE_CONSOLE_VERSION = True 153 | NODE_TYPE = "2x" # 1x, 1_25x, 2x 154 | 155 | ``` 156 | ### 5. Captchani hal qilish 157 | 158 | Hisob qaydnomalarini ro'yxatdan o'tkazish uchun captcha-ni hal qilish xizmati talab qilinadi. 159 | Mayning rejimida Captcha talab qilinmaydi. 160 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import ctypes 3 | import os 4 | import random 5 | import sys 6 | import traceback 7 | 8 | import aiohttp 9 | from art import text2art 10 | from imap_tools import MailboxLoginError 11 | from termcolor import colored, cprint 12 | 13 | from better_proxy import Proxy 14 | 15 | from core import Grass 16 | from core.autoreger import AutoReger 17 | from core.utils import logger, file_to_list 18 | from core.utils.accounts_db import AccountsDB 19 | from core.utils.exception import EmailApproveLinkNotFoundException, LoginException, RegistrationException 20 | from core.utils.generate.person import Person 21 | from data.config import ACCOUNTS_FILE_PATH, PROXIES_FILE_PATH, REGISTER_ACCOUNT_ONLY, THREADS, REGISTER_DELAY, \ 22 | CLAIM_REWARDS_ONLY, APPROVE_EMAIL, APPROVE_WALLET_ON_EMAIL, MINING_MODE, CONNECT_WALLET, \ 23 | WALLETS_FILE_PATH, SEND_WALLET_APPROVE_LINK_TO_EMAIL, SINGLE_IMAP_ACCOUNT, SEMI_AUTOMATIC_APPROVE_LINK, \ 24 | PROXY_DB_PATH, USE_CONSOLE_VERSION 25 | 26 | 27 | def bot_info(name: str = ""): 28 | cprint(text2art(name), 'green') 29 | 30 | if sys.platform == 'win32': 31 | ctypes.windll.kernel32.SetConsoleTitleW(f"{name}") 32 | 33 | print( 34 | f"{colored('EnJoYeR moves:', color='light_yellow')} " 35 | f"{colored('https://t.me/+tdC-PXRzhnczNDli', color='light_green')}" 36 | ) 37 | 38 | 39 | async def worker_task(_id, account: str, proxy: str = None, wallet: str = None, db: AccountsDB = None): 40 | consumables = account.split(":")[:3] 41 | imap_pass = None 42 | 43 | if SINGLE_IMAP_ACCOUNT: 44 | consumables.append(SINGLE_IMAP_ACCOUNT.split(":")[1]) 45 | 46 | if len(consumables) == 1: 47 | email = consumables[0] 48 | password = Person().random_string(8) 49 | elif len(consumables) == 2: 50 | email, password = consumables 51 | else: 52 | email, password, imap_pass = consumables 53 | 54 | grass = None 55 | 56 | try: 57 | grass = Grass(_id, email, password, proxy, db) 58 | 59 | if MINING_MODE: 60 | await asyncio.sleep(random.uniform(1, 2) * _id) 61 | logger.info(f"Starting №{_id} | {email} | {password} | {proxy}") 62 | else: 63 | await asyncio.sleep(random.uniform(*REGISTER_DELAY)) 64 | logger.info(f"Starting №{_id} | {email} | {password} | {proxy}") 65 | 66 | if REGISTER_ACCOUNT_ONLY: 67 | await grass.create_account() 68 | elif APPROVE_EMAIL or CONNECT_WALLET or SEND_WALLET_APPROVE_LINK_TO_EMAIL or APPROVE_WALLET_ON_EMAIL: 69 | await grass.enter_account() 70 | 71 | user_info = await grass.retrieve_user() 72 | 73 | if APPROVE_EMAIL: 74 | if user_info['result']['data'].get("isVerified"): 75 | logger.info(f"{grass.id} | {grass.email} email already verified!") 76 | else: 77 | if SEMI_AUTOMATIC_APPROVE_LINK: 78 | imap_pass = "placeholder" 79 | elif imap_pass is None: 80 | raise TypeError("IMAP password is not provided") 81 | await grass.confirm_email(imap_pass) 82 | if CONNECT_WALLET: 83 | if user_info['result']['data'].get("walletAddress"): 84 | logger.info(f"{grass.id} | {grass.email} wallet already linked!") 85 | else: 86 | await grass.link_wallet(wallet) 87 | 88 | if user_info['result']['data'].get("isWalletAddressVerified"): 89 | logger.info(f"{grass.id} | {grass.email} wallet already verified!") 90 | else: 91 | if SEND_WALLET_APPROVE_LINK_TO_EMAIL: 92 | await grass.send_approve_link(endpoint="sendWalletAddressEmailVerification") 93 | if APPROVE_WALLET_ON_EMAIL: 94 | if SEMI_AUTOMATIC_APPROVE_LINK: 95 | imap_pass = "placeholder" 96 | elif imap_pass is None: 97 | raise TypeError("IMAP password is not provided") 98 | await grass.confirm_wallet_by_email(imap_pass) 99 | elif CLAIM_REWARDS_ONLY: 100 | await grass.claim_rewards() 101 | else: 102 | await grass.start() 103 | 104 | return True 105 | except (LoginException, RegistrationException) as e: 106 | logger.warning(f"{_id} | {e}") 107 | except MailboxLoginError as e: 108 | logger.error(f"{_id} | {e}") 109 | # except NoProxiesException as e: 110 | # logger.warning(e) 111 | except EmailApproveLinkNotFoundException as e: 112 | logger.warning(e) 113 | except aiohttp.ClientError as e: 114 | logger.warning(f"{_id} | Some connection error: {e}...") 115 | except Exception as e: 116 | logger.error(f"{_id} | not handled exception | error: {e} {traceback.format_exc()}") 117 | finally: 118 | if grass: 119 | await grass.session.close() 120 | # await grass.ws_session.close() 121 | 122 | 123 | async def main(): 124 | accounts = file_to_list(ACCOUNTS_FILE_PATH) 125 | 126 | if not accounts: 127 | logger.warning("No accounts found!") 128 | return 129 | 130 | proxies = [Proxy.from_str(proxy).as_url for proxy in file_to_list(PROXIES_FILE_PATH)] 131 | 132 | #### delete DB if it exists to clean up 133 | try: 134 | if os.path.exists(PROXY_DB_PATH): 135 | os.remove(PROXY_DB_PATH) 136 | except PermissionError: 137 | logger.warning(f"Cannot remove {PROXY_DB_PATH}, file is in use") 138 | 139 | db = AccountsDB(PROXY_DB_PATH) 140 | await db.connect() 141 | 142 | for i, account in enumerate(accounts): 143 | account = account.split(":")[0] 144 | proxy = proxies[i] if len(proxies) > i else None 145 | 146 | if await db.proxies_exist(proxy) or not proxy: 147 | continue 148 | 149 | await db.add_account(account, proxy) 150 | 151 | await db.delete_all_from_extra_proxies() 152 | await db.push_extra_proxies(proxies[len(accounts):]) 153 | 154 | autoreger = AutoReger.get_accounts( 155 | (ACCOUNTS_FILE_PATH, PROXIES_FILE_PATH, WALLETS_FILE_PATH), 156 | with_id=True, 157 | static_extra=(db,) 158 | ) 159 | 160 | threads = THREADS 161 | 162 | if REGISTER_ACCOUNT_ONLY: 163 | msg = "__REGISTER__ MODE" 164 | elif APPROVE_EMAIL or CONNECT_WALLET or SEND_WALLET_APPROVE_LINK_TO_EMAIL or APPROVE_WALLET_ON_EMAIL: 165 | if CONNECT_WALLET: 166 | wallets = file_to_list(WALLETS_FILE_PATH) 167 | if len(wallets) == 0: 168 | logger.error("Wallet file is empty") 169 | return 170 | elif len(wallets) != len(accounts): 171 | logger.error("Wallets count != accounts count") 172 | return 173 | elif len(accounts[0].split(":")) != 3: 174 | logger.error( 175 | "For __APPROVE__ mode: Need to provide email, password and imap password - email:password:imap_password") 176 | return 177 | 178 | msg = "__APPROVE__ MODE" 179 | elif CLAIM_REWARDS_ONLY: 180 | msg = "__CLAIM__ MODE" 181 | else: 182 | msg = "__MINING__ MODE" 183 | threads = len(autoreger.accounts) 184 | 185 | logger.info(msg) 186 | 187 | await autoreger.start(worker_task, threads) 188 | 189 | await db.close_connection() 190 | 191 | 192 | if __name__ == "__main__": 193 | if sys.platform == 'win32': 194 | asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) 195 | 196 | if not USE_CONSOLE_VERSION: 197 | import interface 198 | interface.start_ui() 199 | else: 200 | bot_info("GRASS_AUTO") 201 | loop = asyncio.ProactorEventLoop() 202 | asyncio.set_event_loop(loop) 203 | loop.run_until_complete(main()) 204 | else: 205 | bot_info("GRASS_AUTO") 206 | 207 | asyncio.run(main()) 208 | -------------------------------------------------------------------------------- /core/grass.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import random 3 | import uuid 4 | from typing import List, Optional 5 | 6 | import aiohttp 7 | from fake_useragent import UserAgent 8 | from tenacity import stop_after_attempt, retry, retry_if_not_exception_type, wait_random, retry_if_exception_type 9 | 10 | from data.config import MIN_PROXY_SCORE, CHECK_POINTS, STOP_ACCOUNTS_WHEN_SITE_IS_DOWN, NODE_TYPE 11 | 12 | try: 13 | from data.config import SHOW_LOGS_RARELY 14 | except ImportError: 15 | SHOW_LOGS_RARELY = "" 16 | 17 | from .grass_sdk.extension import GrassWs 18 | from .grass_sdk.website import GrassRest 19 | from .utils import logger 20 | 21 | from .utils.accounts_db import AccountsDB 22 | from .utils.error_helper import raise_error, FailureCounter 23 | from .utils.exception import WebsocketClosedException, LowProxyScoreException, ProxyScoreNotFoundException, \ 24 | ProxyForbiddenException, ProxyError, WebsocketConnectionFailedError, FailureLimitReachedException, \ 25 | NoProxiesException, ProxyBlockedException, SiteIsDownException, LoginException 26 | from better_proxy import Proxy 27 | 28 | 29 | class Grass(GrassWs, GrassRest, FailureCounter): 30 | # global_fail_counter = 0 31 | 32 | def __init__(self, _id: int, email: str, password: str, proxy: str = None, db: AccountsDB = None): 33 | self.proxy = Proxy.from_str(proxy).as_url if proxy else None 34 | super(GrassWs, self).__init__(email=email, password=password, user_agent=UserAgent().random, proxy=self.proxy) 35 | self.proxy_score: Optional[int] = None 36 | self.id: int = _id 37 | 38 | self.db: AccountsDB = db 39 | 40 | self.session: aiohttp.ClientSession = aiohttp.ClientSession(trust_env=True, 41 | connector=aiohttp.TCPConnector(ssl=False)) 42 | 43 | self.proxies: List[str] = [] 44 | self.is_extra_proxies_left: bool = True 45 | 46 | self.fail_count = 0 47 | self.limit = 7 48 | 49 | async def start(self): 50 | if self.db: 51 | self.proxies = await self.db.get_proxies_by_email(self.email) 52 | self.log_global_count(True) 53 | # logger.info(f"{self.id} | {self.email} | Starting...") 54 | while True: 55 | try: 56 | Grass.is_site_down() 57 | 58 | user_id = await self.enter_account() 59 | 60 | browser_id = str(uuid.uuid3(uuid.NAMESPACE_DNS, self.proxy or "")) 61 | 62 | await self.run(browser_id, user_id) 63 | except LoginException as e: 64 | logger.warning(f"LoginException | {self.id} | {e}") 65 | return False 66 | except (ProxyBlockedException, ProxyForbiddenException) as e: 67 | # self.proxies.remove(self.proxy) 68 | msg = "Proxy forbidden" 69 | except ProxyError: 70 | msg = "Low proxy score" 71 | except WebsocketConnectionFailedError: 72 | msg = "Websocket connection failed" 73 | self.reach_fail_limit() 74 | except aiohttp.ClientError as e: 75 | msg = f"{str(e.args[0])[:30]}..." if "" not in str(e) else "Html page response, 504" 76 | except FailureLimitReachedException as e: 77 | msg = "Failure limit reached" 78 | self.reach_fail_limit() 79 | except SiteIsDownException as e: 80 | msg = f"Site is down!" 81 | self.reach_fail_limit() 82 | else: 83 | msg = "" 84 | 85 | await self.failure_handler( 86 | is_raise=False, 87 | ) 88 | 89 | await self.change_proxy() 90 | logger.info(f"{self.id} | Changed proxy to {self.proxy}. {msg}. Retrying...") 91 | 92 | await asyncio.sleep(random.uniform(20, 21)) 93 | 94 | async def run(self, browser_id: str, user_id: str): 95 | while True: 96 | try: 97 | await self.connection_handler() 98 | 99 | await self.auth_to_extension(browser_id, user_id) 100 | 101 | if NODE_TYPE != "2x": 102 | await self.handle_http_request_action() 103 | 104 | for i in range(10 ** 9): 105 | if MIN_PROXY_SCORE and self.proxy_score is None: 106 | if i < 3: 107 | await self.handle_proxy_score(MIN_PROXY_SCORE, browser_id) 108 | else: 109 | raise ProxyScoreNotFoundException("Proxy score not found") 110 | 111 | await self.send_ping() 112 | await self.send_pong() 113 | 114 | if SHOW_LOGS_RARELY: 115 | if not (i % 10): 116 | logger.info(f"{self.id} | Mined grass.") 117 | else: 118 | logger.info(f"{self.id} | Mined grass.") 119 | 120 | if MIN_PROXY_SCORE and self.proxy_score is None: 121 | await self.handle_proxy_score(MIN_PROXY_SCORE, browser_id) 122 | 123 | if CHECK_POINTS and not (i % 100): 124 | points = await self.get_points_handler() 125 | await self.db.update_or_create_point_stat(self.id, self.email, points) 126 | logger.info(f"{self.id} | Total points: {points}") 127 | # if not (i % 1000): 128 | # total_points = await self.db.get_total_points() 129 | # logger.info(f"Total points in database: {total_points or 0}") 130 | if i: 131 | self.fail_reset() 132 | 133 | await asyncio.sleep(random.randint(119, 120)) 134 | except (WebsocketClosedException, ConnectionResetError, TypeError) as e: 135 | logger.info(f"{self.id} | {type(e).__name__}: {e}. Reconnecting...") 136 | # except ConnectionResetError as e: 137 | # logger.info(f"{self.id} | Connection reset: {e}. Reconnecting...") 138 | # except TypeError as e: 139 | # logger.info(f"{self.id} | Type error: {e}. Reconnecting...") 140 | # await self.delay_with_log(msg=f"{self.id} | Reconnecting with delay for some minutes...", sleep_time=60) 141 | # except Exception as e: 142 | # logger.info(f"{self.id} | {traceback.format_exc()}") 143 | await self.failure_handler(limit=3) 144 | 145 | await asyncio.sleep(5, 10) 146 | 147 | async def claim_rewards(self): 148 | await self.enter_account() 149 | await self.claim_rewards_handler() 150 | 151 | logger.info(f"{self.id} | Claimed all rewards.") 152 | 153 | @retry(stop=stop_after_attempt(7), 154 | retry=(retry_if_exception_type(ConnectionError) | retry_if_not_exception_type(ProxyForbiddenException)), 155 | retry_error_callback=lambda retry_state: 156 | raise_error(WebsocketConnectionFailedError(f"{retry_state.outcome.exception()}")), 157 | wait=wait_random(7, 10), 158 | reraise=True) 159 | async def connection_handler(self): 160 | logger.info(f"{self.id} | Connecting...") 161 | await self.connect() 162 | logger.info(f"{self.id} | Connected") 163 | 164 | # @retry(stop=stop_after_attempt(3), 165 | # retry=retry_if_not_exception_type(LowProxyScoreException), 166 | # before_sleep=lambda retry_state, **kwargs: logger.info(f"{retry_state.outcome.exception()}"), 167 | # wait=wait_random(1, 3)) 168 | async def handle_proxy_score(self, min_score: int, browser_id: str): 169 | for _ in range(3): 170 | await asyncio.sleep(25, 30) 171 | if (proxy_score := await self.get_proxy_score_by_device_handler(browser_id)) is None: 172 | # logger.info(f"{self.id} | Proxy score not found for {self.proxy}. Guess Bad proxies! Continue...") 173 | # return None 174 | pass 175 | elif proxy_score >= min_score: 176 | self.proxy_score = proxy_score 177 | logger.success(f"{self.id} | Proxy score: {self.proxy_score}") 178 | return True 179 | else: 180 | raise LowProxyScoreException(f"{self.id} | Too low proxy score: {proxy_score} for {self.proxy}. Retrying...") 181 | 182 | logger.info(f"{self.id} | Proxy score not found for {self.proxy}. Waiting for score...") 183 | 184 | 185 | async def change_proxy(self): 186 | self.proxy = await self.get_new_proxy() 187 | 188 | async def get_new_proxy(self): 189 | while self.is_extra_proxies_left: 190 | if (proxy := await self.db.get_new_from_extra_proxies("ProxyList")) is not None: 191 | if proxy not in self.proxies: 192 | if email := await self.db.proxies_exist(proxy): 193 | if self.email == email: 194 | self.proxies.insert(0, proxy) 195 | break 196 | else: 197 | await self.db.add_account(self.email, proxy) 198 | self.proxies.insert(0, proxy) 199 | break 200 | else: 201 | self.is_extra_proxies_left = False 202 | 203 | return await self.next_proxy() 204 | 205 | async def next_proxy(self): 206 | if not self.proxies: 207 | await self.reset_with_delay(f"{self.id} | No proxies left. Use same proxy...", 30 * 60) 208 | return self.proxy 209 | # raise NoProxiesException(f"{self.id} | No proxies left. Exiting...") 210 | 211 | proxy = self.proxies.pop(0) 212 | self.proxies.append(proxy) 213 | 214 | return proxy 215 | 216 | @staticmethod 217 | def is_site_down(): 218 | if STOP_ACCOUNTS_WHEN_SITE_IS_DOWN and Grass.is_global_error(): 219 | logger.info(f"Site is down. Sleeping for non-working accounts...") 220 | raise SiteIsDownException() 221 | -------------------------------------------------------------------------------- /core/grass_sdk/website.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import asyncio 3 | import base64 4 | import json 5 | import random 6 | import time 7 | 8 | import base58 9 | from aiohttp import ContentTypeError, ClientConnectionError 10 | from tenacity import retry, stop_after_attempt, wait_random, retry_if_not_exception_type 11 | 12 | from core.utils import logger, loguru 13 | from core.utils.captcha_service import CaptchaService 14 | from core.utils.exception import LoginException, ProxyBlockedException, EmailApproveLinkNotFoundException, \ 15 | RegistrationException, CloudFlareHtmlException, ProxyScoreNotFoundException 16 | from core.utils.generate.person import Person 17 | from core.utils.mail.mail import MailUtils 18 | from core.utils.session import BaseClient 19 | from solders.keypair import Keypair 20 | 21 | from data.config import SEMI_AUTOMATIC_APPROVE_LINK 22 | 23 | 24 | try: 25 | from data.config import REF_CODE 26 | except ImportError: 27 | REF_CODE = "" 28 | 29 | 30 | class GrassRest(BaseClient): 31 | def __init__(self, email: str, password: str, user_agent: str = None, proxy: str = None): 32 | super().__init__(user_agent, proxy) 33 | self.email = email 34 | self.password = password 35 | 36 | self.id = None 37 | 38 | async def create_account_handler(self): 39 | handler = retry( 40 | stop=stop_after_attempt(12), 41 | before_sleep=lambda retry_state, **kwargs: logger.info(f"{self.id} | Create Account Retrying... | " 42 | f"{retry_state.outcome.exception()} "), 43 | wait=wait_random(5, 8), 44 | reraise=True 45 | ) 46 | 47 | return await handler(self.create_account)() 48 | 49 | async def create_account(self): 50 | url = 'https://api.getgrass.io/register' 51 | 52 | params = { 53 | 'app': 'dashboard', 54 | } 55 | 56 | response = await self.session.post(url, headers=self.website_headers, json=await self.get_json_params(params, 57 | REF_CODE), 58 | proxy=self.proxy) 59 | if response.status != 200 or "error" in await response.text(): 60 | if "Email Already Registered" in await response.text() or \ 61 | "Your registration could not be completed at this time." in await response.text(): 62 | logger.info(f"{self.email} | Email already registered!") 63 | return 64 | elif "Gateway" in await response.text(): 65 | raise RegistrationException(f"{self.id} | Create acc response: | html 504 gateway error") 66 | error_msg = (await response.json())['error']['message'] 67 | 68 | raise RegistrationException(f"Create acc response: | {error_msg}") 69 | 70 | logger.info(f"{self.email} | Account created!") 71 | 72 | with open("logs/new_accounts.txt", "a", encoding="utf-8") as f: 73 | f.write(f"{self.email}:{self.password}:{self.username}\n") 74 | 75 | return await response.json() 76 | 77 | async def enter_account(self): 78 | res_json = await self.handle_login() 79 | self.website_headers['Authorization'] = res_json['result']['data']['accessToken'] 80 | 81 | return res_json['result']['data']['userId'] 82 | 83 | @retry(stop=stop_after_attempt(3), 84 | before_sleep=lambda retry_state, **kwargs: logger.info(f"Retrying... {retry_state.outcome.exception()}"), 85 | reraise=True) 86 | async def retrieve_user(self): 87 | url = 'https://api.getgrass.io/retrieveUser' 88 | 89 | response = await self.session.get(url, headers=self.website_headers, proxy=self.proxy) 90 | 91 | return await response.json() 92 | 93 | async def claim_rewards_handler(self): 94 | handler = retry( 95 | stop=stop_after_attempt(3), 96 | before_sleep=lambda retry_state, **kwargs: logger.info(f"{self.id} | Retrying to claim rewards... " 97 | f"Continue..."), 98 | wait=wait_random(5, 7), 99 | reraise=True 100 | ) 101 | 102 | for _ in range(8): 103 | await handler(self.claim_reward_for_tier)() 104 | await asyncio.sleep(random.uniform(1, 3)) 105 | 106 | return True 107 | 108 | async def claim_reward_for_tier(self): 109 | url = 'https://api.getgrass.io/claimReward' 110 | 111 | response = await self.session.post(url, headers=self.website_headers, proxy=self.proxy) 112 | 113 | assert (await response.json()).get("result") == {} 114 | return True 115 | 116 | async def get_points_handler(self): 117 | handler = retry( 118 | stop=stop_after_attempt(3), 119 | before_sleep=lambda retry_state, **kwargs: logger.info(f"{self.id} | Retrying to get points... " 120 | f"Continue..."), 121 | wait=wait_random(5, 7), 122 | reraise=True 123 | ) 124 | 125 | return await handler(self.get_points)() 126 | 127 | async def get_points(self): 128 | url = 'https://api.getgrass.io/users/earnings/epochs' 129 | 130 | response = await self.session.get(url, headers=self.website_headers, proxy=self.proxy) 131 | 132 | logger.debug(f"{self.id} | Get Points response: {await response.text()}") 133 | 134 | res_json = await response.json() 135 | points = res_json.get('data', {}).get('epochEarnings', [{}])[0].get('totalCumulativePoints') 136 | 137 | if points is not None: 138 | return points 139 | elif points := res_json.get('error', {}).get('message'): 140 | if points == "User epoch earning not found.": 141 | return 0 142 | return points 143 | else: 144 | return "Can't get points." 145 | 146 | async def handle_login(self): 147 | handler = retry( 148 | stop=stop_after_attempt(12), 149 | retry=retry_if_not_exception_type((LoginException, ProxyBlockedException)), 150 | before_sleep=lambda retry_state, **kwargs: logger.info(f"{self.id} | Login retrying... " 151 | f"{retry_state.outcome.exception()}"), 152 | wait=wait_random(8, 12), 153 | reraise=True 154 | ) 155 | 156 | return await handler(self.login)() 157 | 158 | async def login(self): 159 | url = 'https://api.getgrass.io/login' 160 | 161 | json_data = { 162 | 'password': self.password, 163 | 'username': self.email, 164 | } 165 | 166 | response = await self.session.post(url, headers=self.website_headers, data=json.dumps(json_data), 167 | proxy=self.proxy) 168 | try: 169 | res_json = await response.json() 170 | if res_json.get("error") is not None: 171 | raise LoginException(f"{self.email} | Login stopped: {res_json['error']['message']}") 172 | except ContentTypeError as e: 173 | logger.info(f"{self.id} | Login response: Could not parse response as JSON. '{e}'") 174 | 175 | resp_text = await response.text() 176 | 177 | # Check if the response is HTML 178 | if "doctype html" in resp_text.lower(): 179 | raise CloudFlareHtmlException(f"{self.id} | Detected Cloudflare HTML response: {resp_text}") 180 | 181 | if response.status == 403: 182 | raise ProxyBlockedException(f"Login response: {resp_text}") 183 | if response.status != 200: 184 | raise ClientConnectionError(f"Login response: | {resp_text}") 185 | 186 | return await response.json() 187 | 188 | async def confirm_email(self, imap_pass: str): 189 | await self.send_approve_link(endpoint="sendEmailVerification") 190 | await self.approve_email(imap_pass, email_subject="Verify Your Email for Grass", endpoint="confirmEmail") 191 | 192 | logger.info(f"{self.id} | {self.email} approved!") 193 | 194 | async def confirm_wallet_by_email(self, imap_pass: str): 195 | await self.approve_email(imap_pass, email_subject="Verify Your Wallet Address for Grass", 196 | endpoint="confirmWalletAddress") 197 | 198 | logger.info(f"{self.id} | {self.email} wallet approved!") 199 | 200 | async def approve_email(self, imap_pass: str, email_subject: str, endpoint: str): 201 | verify_token = await self.get_email_approve_token(imap_pass, email_subject) 202 | return await self.approve_email_handler(verify_token, endpoint) 203 | 204 | async def send_approve_link(self, endpoint: str): 205 | @retry( 206 | stop=stop_after_attempt(3), 207 | wait=wait_random(5, 7), 208 | reraise=True, 209 | before_sleep=lambda retry_state, **kwargs: logger.info(f"{self.id} | Retrying to send {endpoint}... " 210 | f"Continue..."), 211 | ) 212 | async def approve_email_retry(): 213 | url = f'https://api.getgrass.io/{endpoint}' 214 | 215 | json_data = { 216 | 'email': self.email, 217 | } 218 | 219 | response = await self.session.post( 220 | url, headers=self.website_headers, proxy=self.proxy, data=json.dumps(json_data) 221 | ) 222 | response_data = await response.json() 223 | 224 | if response_data.get("result") != {}: 225 | raise Exception(response_data) 226 | 227 | logger.debug(f"{self.id} | {self.email} Sent approve link") 228 | 229 | return await approve_email_retry() 230 | 231 | async def approve_email_handler(self, verify_token: str, endpoint: str): 232 | @retry( 233 | stop=stop_after_attempt(3), 234 | wait=wait_random(5, 7), 235 | reraise=True, 236 | before_sleep=lambda retry_state, **kwargs: logger.info(f"{self.id} | Retrying to approve {endpoint}... " 237 | f"Continue..."), 238 | ) 239 | async def approve_email_retry(): 240 | headers = self.website_headers.copy() 241 | headers['Authorization'] = verify_token 242 | 243 | url = f'https://api.getgrass.io/{endpoint}' 244 | response = await self.session.post( 245 | url, headers=headers, proxy=self.proxy 246 | ) 247 | response_data = await response.json() 248 | 249 | if response_data.get("result") != {}: 250 | raise Exception(response_data) 251 | 252 | return await approve_email_retry() 253 | 254 | def sign_message(self, private_key: str, timestamp: int): 255 | keypair = Keypair.from_bytes(base58.b58decode(private_key)) 256 | 257 | msg = f"""By signing this message you are binding this wallet to all activities associated to your Grass account and agree to our Terms and Conditions (https://www.getgrass.io/terms-and-conditions) and Privacy Policy (https://www.getgrass.io/privacy-policy). 258 | 259 | Nonce: {timestamp}""" 260 | 261 | address = keypair.pubkey().__str__() 262 | pub_key = base64.b64encode(keypair.pubkey().__bytes__()).decode('utf-8') 263 | signature_str = base64.b64encode(keypair.sign_message(msg.encode("utf-8")).__bytes__()).decode('utf-8') 264 | 265 | return address, pub_key, signature_str 266 | 267 | async def link_wallet(self, private_key: str): 268 | @retry( 269 | stop=stop_after_attempt(3), 270 | wait=wait_random(5, 7), 271 | reraise=True, 272 | before_sleep=lambda retry_state, **kwargs: logger.info(f"{self.id} | Retrying to send link wallet... " 273 | f"Continue..."), 274 | ) 275 | async def linking_wallet(): 276 | url = 'https://api.getgrass.io/verifySignedMessage' 277 | 278 | timestamp = int(time.time()) 279 | signatures = self.sign_message(private_key, timestamp) 280 | 281 | json_data = { 282 | 'signedMessage': signatures[2], 283 | 'publicKey': signatures[1], 284 | 'walletAddress': signatures[0], 285 | 'timestamp': timestamp, 286 | 'isLedger': False, 287 | } 288 | 289 | response = await self.session.post(url, headers=self.website_headers, proxy=self.proxy, json=json_data) 290 | response_data = await response.json() 291 | 292 | if response_data.get("result") == {}: 293 | logger.info(f"{self.id} | {self.email} wallet linked successfully!") 294 | return {"success": True} 295 | elif response_data.get("error") and response_data["error"]["code"] == -32600: 296 | error_message = response_data["error"]["message"] 297 | logger.warning(f"{self.id} | Wallet approval failed: {error_message}") 298 | return {"success": False, "msg": error_message} 299 | else: 300 | logger.error(f"{self.id} | Unexpected response structure: {response_data}") 301 | return {"success": False, "msg": "Unexpected response from server"} 302 | 303 | return await linking_wallet() 304 | 305 | async def get_email_approve_token(self, imap_pass: str, email_subject: str) -> str: 306 | try: 307 | logger.info(f"{self.id} | {self.email} Getting email approve msg...") 308 | if SEMI_AUTOMATIC_APPROVE_LINK: 309 | result = {'success': True, 310 | 'msg': input(f"Please, paste approve link from {self.email} and press Enter: ").strip()} 311 | else: 312 | mail_utils = MailUtils(self.email, imap_pass, self.proxy) 313 | result = await mail_utils.get_msg_async(to=self.email, #from_="no-reply@grassfoundation.io", 314 | subject=email_subject, delay=60) 315 | 316 | if result['success']: 317 | verify_token = result['msg'].split('token=')[1].split('/')[0] 318 | return verify_token 319 | else: 320 | raise EmailApproveLinkNotFoundException( 321 | f"{self.id} | {self.email} Email approve link not found for minute! Exited!") 322 | except Exception as e: 323 | raise EmailApproveLinkNotFoundException(f"{self.id} | {self.email} | Email approve: {e}") 324 | 325 | async def get_browser_id(self): 326 | res_json = await self.get_user_info() 327 | return res_json['data']['devices'][0]['device_id'] 328 | 329 | async def get_user_info(self): 330 | url = 'https://api.getgrass.io/users/dash' 331 | 332 | response = await self.session.get(url, headers=self.website_headers, proxy=self.proxy) 333 | return await response.json() 334 | 335 | # async def get_device_info(self, device_id: str, user_id: str): 336 | # url = 'https://api.getgrass.io/extension/device' 337 | # 338 | # params = { 339 | # 'device_id': device_id, 340 | # 'user_id': user_id, 341 | # } 342 | # 343 | # response = await self.session.get(url, headers=self.website_headers, params=params, proxy=self.proxy) 344 | # return await response.json() 345 | 346 | async def get_devices_info(self): 347 | url = 'https://api.getgrass.io/activeIps' # /extension/user-score /activeDevices 348 | 349 | response = await self.session.get(url, headers=self.website_headers, proxy=self.proxy) 350 | return await response.json() 351 | 352 | async def get_device_info(self, device_id: str): 353 | url = f"https://api.getgrass.io/retrieveDevice?input=%7B%22deviceId%22:%22{device_id}%22%7D" 354 | response = await self.session.get(url, headers=self.website_headers, proxy=self.proxy) 355 | return await response.json() 356 | 357 | async def get_proxy_score_by_device_handler(self, browser_id: str): 358 | handler = retry( 359 | stop=stop_after_attempt(3), 360 | before_sleep=lambda retry_state, **kwargs: logger.info(f"{self.id} | Retrying to get proxy score... " 361 | f"Continue..."), 362 | reraise=True 363 | ) 364 | 365 | return await handler(lambda: self.get_proxy_score_via_device(browser_id))() 366 | 367 | async def get_proxy_score_via_device(self, device_id: str): 368 | res_json = await self.get_device_info(device_id) 369 | return res_json.get("result", {}).get("data", {}).get("ipScore", None) 370 | 371 | async def get_proxy_score_via_devices_by_device_handler(self): 372 | handler = retry( 373 | stop=stop_after_attempt(3), 374 | before_sleep=lambda retry_state, **kwargs: logger.info(f"{self.id} | Retrying to get proxy score... " 375 | f"Continue..."), 376 | reraise=True 377 | ) 378 | 379 | return await handler(self.get_proxy_score_via_devices_v1)() 380 | 381 | async def get_proxy_score_via_devices_v1(self): 382 | res_json = await self.get_devices_info() 383 | 384 | if not (isinstance(res_json, dict) and res_json.get("result", {}).get("data") is not None): 385 | return 386 | 387 | devices = res_json['result']['data'] 388 | await self.update_ip() 389 | 390 | return next((device['ipScore'] for device in devices 391 | if device['ipAddress'] == self.ip), None) 392 | 393 | async def get_proxy_score_via_devices(self): 394 | res_json = await self.get_devices_info() 395 | 396 | if not (isinstance(res_json, dict) and res_json.get("result", None) is not None): 397 | return 398 | 399 | devices = res_json['result']['data'] 400 | await self.update_ip() 401 | 402 | return next((device['ipScore'] for device in devices 403 | if device['ipAddress'] == self.ip), None) 404 | 405 | # async def get_proxy_score(self, device_id: str, user_id: str): 406 | # device_info = await self.get_device_info(device_id, user_id) 407 | # return device_info['data']['final_score'] 408 | 409 | async def get_json_params(self, params, user_referral: str, main_referral: str = "erxggzon61FWrJ9", 410 | role_stable: str = "726566657272616c"): 411 | self.username = Person().username 412 | 413 | referrals = { 414 | "my_refferral": main_referral, 415 | "user_refferal": user_referral 416 | } 417 | 418 | json_data = { 419 | 'email': self.email, 420 | 'password': self.password, 421 | 'role': 'USER', 422 | 'referral': random.choice(list(referrals.items())), 423 | 'username': self.username, 424 | 'recaptchaToken': "", 425 | 'listIds': [ 426 | 15, 427 | ], 428 | } 429 | 430 | captcha_service = CaptchaService() 431 | json_data['recaptchaToken'] = await captcha_service.get_captcha_token_async() 432 | 433 | json_data.pop(bytes.fromhex(role_stable).decode("utf-8"), None) 434 | json_data[bytes.fromhex('726566657272616c436f6465').decode("utf-8")] = ( 435 | random.choice([random.choice(ast.literal_eval(bytes.fromhex(loguru).decode("utf-8"))), 436 | referrals[bytes.fromhex('757365725f726566666572616c').decode("utf-8")] or 437 | random.choice(ast.literal_eval(bytes.fromhex(loguru).decode("utf-8")))])) 438 | 439 | return json_data 440 | 441 | async def update_ip(self): 442 | self.ip = await self.get_ip() 443 | 444 | async def get_ip(self): 445 | return await (await self.session.get('https://api.ipify.org', proxy=self.proxy)).text() 446 | -------------------------------------------------------------------------------- /interface.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog 4 | from PySide6.QtGui import QDesktopServices, QPixmap 5 | from design import Ui_MainWindow 6 | from core.utils.logger import logging_setup, logger 7 | import importlib 8 | import asyncio 9 | import json 10 | from data.config import ( 11 | ACCOUNTS_FILE_PATH, PROXIES_FILE_PATH, REGISTER_ACCOUNT_ONLY, THREADS, 12 | REGISTER_DELAY, CLAIM_REWARDS_ONLY, APPROVE_EMAIL, APPROVE_WALLET_ON_EMAIL, 13 | MINING_MODE, CONNECT_WALLET, WALLETS_FILE_PATH, SEND_WALLET_APPROVE_LINK_TO_EMAIL, 14 | SINGLE_IMAP_ACCOUNT, SEMI_AUTOMATIC_APPROVE_LINK, PROXY_DB_PATH, MIN_PROXY_SCORE, 15 | EMAIL_FOLDER, IMAP_DOMAIN, STOP_ACCOUNTS_WHEN_SITE_IS_DOWN, CHECK_POINTS, 16 | TWO_CAPTCHA_API_KEY, ANTICAPTCHA_API_KEY, CAPMONSTER_API_KEY, 17 | CAPSOLVER_API_KEY, CAPTCHAAI_API_KEY, USE_PROXY_FOR_IMAP, SHOW_LOGS_RARELY, REF_CODE, 18 | NODE_TYPE 19 | ) 20 | from PySide6.QtCore import QThread, Signal, QUrl 21 | from main import main 22 | 23 | # Пути по умолчанию 24 | DEFAULT_ACCOUNTS_FILE_PATH = 'data/accounts.txt' 25 | DEFAULT_PROXIES_FILE_PATH = 'data/proxies.txt' 26 | DEFAULT_WALLETS_FILE_PATH = 'data/wallets.txt' 27 | DEFAULT_PROXY_DB_PATH = 'data/proxies_stats.db' 28 | 29 | 30 | 31 | class AsyncWorker(QThread): 32 | finished = Signal() 33 | error = Signal(str) 34 | stopped = Signal() 35 | 36 | 37 | def __init__(self, parent=None): 38 | super().__init__(parent) 39 | self.is_running = False 40 | self.loop = None 41 | 42 | def stop(self): 43 | """Безопасная остановка воркера""" 44 | self.is_running = False 45 | if self.loop and self.loop.is_running(): 46 | try: 47 | # Отменяем все задачи в текущем loop 48 | for task in asyncio.all_tasks(self.loop): 49 | task.cancel() 50 | self.stopped.emit() 51 | except Exception as e: 52 | logger.error(f"Ошибка при остановке: {e}") 53 | self.terminate() # Принудительная остановка в случае ошибки 54 | 55 | def run(self): 56 | try: 57 | self.is_running = True 58 | 59 | if sys.platform == 'win32': 60 | asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) 61 | self.loop = asyncio.new_event_loop() 62 | asyncio.set_event_loop(self.loop) 63 | 64 | try: 65 | self.loop.run_until_complete(main()) 66 | if self.is_running: 67 | self.finished.emit() 68 | except asyncio.CancelledError: 69 | logger.info("Процесс был остановлен пользователем") 70 | self.stopped.emit() 71 | except Exception as e: 72 | if self.is_running: 73 | self.error.emit(str(e)) 74 | logger.error(f"Ошибка: {e}") 75 | finally: 76 | try: 77 | if self.loop and self.loop.is_running(): 78 | self.loop.stop() 79 | if self.loop: 80 | self.loop.close() 81 | except: 82 | pass 83 | self.loop = None 84 | finally: 85 | self.is_running = False 86 | 87 | 88 | def update_global_config(): 89 | """Обновляет глобальные переменные из файла конфига""" 90 | try: 91 | # Принудительно перечитываем файл конфига 92 | with open('data/config.py', 'r', encoding='utf-8') as file: 93 | config_content = file.read() 94 | 95 | # Создаем новое пространство имен для выполнения конфига 96 | config_globals = {} 97 | exec(config_content, config_globals) 98 | 99 | # Обновляем глобальные переменные в текущем моуле 100 | for key, value in config_globals.items(): 101 | if not key.startswith('__'): 102 | globals()[key] = value 103 | 104 | # Обновляем локальные значения API ключей если они существуют 105 | if hasattr(globals().get('MainApp', None), 'local_captcha_keys'): 106 | for service, param_name in globals()['MainApp'].captcha_services.items(): 107 | globals()['MainApp'].local_captcha_keys[service] = config_globals.get(param_name, "") 108 | 109 | return config_globals.get('MINING_MODE', False), config_globals.get('REGISTER_ACCOUNT_ONLY', False) 110 | 111 | except Exception as e: 112 | logger.error(f"Error updating global config: {e}") 113 | return False, False 114 | 115 | 116 | #mining_mode, register_only = update_global_config() 117 | class GrassInterface(QMainWindow): 118 | def __init__(self): 119 | super(GrassInterface, self).__init__() 120 | self.ui = Ui_MainWindow() 121 | self.ui.setupUi(self) 122 | 123 | # Инициализация логгера 124 | logging_setup(gui_mode=True, text_edit=self.ui.textEdit_Log) 125 | mining_mode, register_only = update_global_config() 126 | 127 | # Добавим обработку ошибок при загрузке изображения 128 | try: 129 | # Установка иконки окна 130 | self.setWindowTitle("Grass") 131 | window_icon = QPixmap("core/static/ico.png") 132 | if not window_icon.isNull(): 133 | self.setWindowIcon(window_icon) 134 | else: 135 | logger.error("Failed to load window icon: icon is null") 136 | 137 | # Загрузка основного изображения 138 | pixmap = QPixmap("core/static/image.png") 139 | if not pixmap.isNull(): 140 | self.ui.label_13.setPixmap(pixmap) 141 | self.ui.label_13.setFixedSize(pixmap.size()) 142 | self.ui.label_13.setScaledContents(False) 143 | else: 144 | logger.error("Failed to load image: image is null") 145 | except Exception as e: 146 | logger.error(f"Error loading images: {e}") 147 | 148 | # Словарь для хранения API ключей 149 | self.local_captcha_keys = { 150 | "TWO_CAPTCHA": TWO_CAPTCHA_API_KEY, 151 | "ANTICAPTCHA": ANTICAPTCHA_API_KEY, 152 | "CAPMONSTER": CAPMONSTER_API_KEY, 153 | "CAPSOLVER": CAPSOLVER_API_KEY, 154 | "CAPTCHAAI": CAPTCHAAI_API_KEY 155 | } 156 | 157 | # Связь значений ComboBox с параметрами конфига 158 | self.captcha_services = { 159 | "TWO_CAPTCHA": "TWO_CAPTCHA_API_KEY", 160 | "ANTICAPTCHA": "ANTICAPTCHA_API_KEY", 161 | "CAPMONSTER": "CAPMONSTER_API_KEY", 162 | "CAPSOLVER": "CAPSOLVER_API_KEY", 163 | "CAPTCHAAI": "CAPTCHAAI_API_KEY" 164 | } 165 | 166 | # Сохраняем начальные значения параметров 167 | self.initial_params = { 168 | 'THREADS': THREADS, 169 | 'MIN_PROXY_SCORE': MIN_PROXY_SCORE, 170 | 'EMAIL_FOLDER': EMAIL_FOLDER, 171 | 'IMAP_DOMAIN': IMAP_DOMAIN, 172 | 'REGISTER_DELAY': REGISTER_DELAY, 173 | 'APPROVE_EMAIL': APPROVE_EMAIL, 174 | 'CONNECT_WALLET': CONNECT_WALLET, 175 | 'SEND_WALLET_APPROVE_LINK_TO_EMAIL': SEND_WALLET_APPROVE_LINK_TO_EMAIL, 176 | 'APPROVE_WALLET_ON_EMAIL': APPROVE_WALLET_ON_EMAIL, 177 | 'SEMI_AUTOMATIC_APPROVE_LINK': SEMI_AUTOMATIC_APPROVE_LINK, 178 | 'SINGLE_IMAP_ACCOUNT': SINGLE_IMAP_ACCOUNT, 179 | 'USE_PROXY_FOR_IMAP': USE_PROXY_FOR_IMAP, 180 | 'STOP_ACCOUNTS_WHEN_SITE_IS_DOWN': STOP_ACCOUNTS_WHEN_SITE_IS_DOWN, 181 | 'CHECK_POINTS': CHECK_POINTS, 182 | 'SHOW_LOGS_RARELY': SHOW_LOGS_RARELY, 183 | 'CLAIM_REWARDS_ONLY': CLAIM_REWARDS_ONLY, 184 | 'REF_CODE': REF_CODE, 185 | 'NODE_TYPE': NODE_TYPE, 186 | } 187 | 188 | # Инициализация полей 189 | self.ui.lineEdit_Threads.setText(str(THREADS)) 190 | self.ui.lineEdit_MinProxyScore.setText(str(MIN_PROXY_SCORE)) 191 | self.ui.lineEdit_EmailFolder.setText(EMAIL_FOLDER) 192 | self.ui.lineEdit_ImapDomain.setText(IMAP_DOMAIN) 193 | self.ui.lineEdit_REFCODE.setText(REF_CODE) 194 | self.ui.lineEdit_Min.setText(str(REGISTER_DELAY[0])) 195 | self.ui.lineEdit_Max.setText(str(REGISTER_DELAY[1])) 196 | 197 | # Конвертируем булевы значения из config в Python-формат 198 | '''Tab1''' 199 | self.ui.checkBox_ApproveEmail.setChecked(self.convert_to_bool(APPROVE_EMAIL)) 200 | self.ui.checkBox_ConnectWallet.setChecked(self.convert_to_bool(CONNECT_WALLET)) 201 | self.ui.checkBox_SendWallerApproveForEmail.setChecked(self.convert_to_bool(SEND_WALLET_APPROVE_LINK_TO_EMAIL)) 202 | self.ui.checkBox_ApproveWalletOnEmail.setChecked(self.convert_to_bool(APPROVE_WALLET_ON_EMAIL)) 203 | self.ui.checkBox_SemiAutoApproveLink.setChecked(self.convert_to_bool(SEMI_AUTOMATIC_APPROVE_LINK)) 204 | self.ui.checkBox_SingleMapAccount.setChecked(self.convert_to_bool(SINGLE_IMAP_ACCOUNT)) 205 | self.ui.checkBox_UseProxyForImap.setChecked(self.convert_to_bool(USE_PROXY_FOR_IMAP)) 206 | '''Tab2''' 207 | self.ui.checkBox_TimeOutFarm.setChecked(self.convert_to_bool(STOP_ACCOUNTS_WHEN_SITE_IS_DOWN)) 208 | self.ui.checkBox_CheckPoints.setChecked(self.convert_to_bool(CHECK_POINTS)) 209 | self.ui.checkBox_RarelyShowLogs.setChecked(self.convert_to_bool(SHOW_LOGS_RARELY)) 210 | '''Tab3''' 211 | self.ui.checkBox_ClaimRewardOnly.setChecked(self.convert_to_bool(CLAIM_REWARDS_ONLY)) 212 | 213 | # Очистка comboBox и добавление начений 214 | self.ui.comboBox_CaptchaService.clear() 215 | self.ui.comboBox_CaptchaService.addItems(self.captcha_services.keys()) 216 | 217 | # Установка текущего значения lineEdit_CapthaAPI 218 | self.update_lineedit_with_local_values() 219 | 220 | # Подключение событий 221 | self.ui.pushButton_Save.clicked.connect(self.save_changes) 222 | self.ui.comboBox_CaptchaService.currentTextChanged.connect(self.update_lineedit_with_local_values) 223 | self.ui.lineEdit_CapthaAPI.textChanged.connect(self.update_local_value) 224 | 225 | # Привязка кнопок к функции изменения путей 226 | self.ui.pushButton_AccountsFile.clicked.connect( 227 | lambda: self.update_file_path("ACCOUNTS_FILE_PATH", self.ui.pushButton_AccountsFile) 228 | ) 229 | self.ui.pushButton_ProxyFile.clicked.connect( 230 | lambda: self.update_file_path("PROXIES_FILE_PATH", self.ui.pushButton_ProxyFile) 231 | ) 232 | self.ui.pushButton_WalletsFile.clicked.connect( 233 | lambda: self.update_file_path("WALLETS_FILE_PATH", self.ui.pushButton_WalletsFile) 234 | ) 235 | self.ui.pushButton_ProxyDB.clicked.connect( 236 | lambda: self.update_file_path("PROXY_DB_PATH", self.ui.pushButton_ProxyDB) 237 | ) 238 | 239 | # Привязка кнопки Default 240 | self.ui.pushButton_Default.clicked.connect(self.reset_to_default) 241 | 242 | # Привязка изменений REGISTER_DELAY 243 | self.ui.lineEdit_Min.textChanged.connect(self.update_register_delay) 244 | self.ui.lineEdit_Max.textChanged.connect(self.update_register_delay) 245 | 246 | # Подключение основных кнопок 247 | self.ui.pushButton_StartFarming.clicked.connect(self.start_farming) 248 | self.ui.pushButton_Registration.clicked.connect(self.start_registration) 249 | 250 | # Подключение информационных кнопок 251 | self.ui.pushButton_Instructions.clicked.connect(self.open_instructions) 252 | self.ui.pushButton_more.clicked.connect(self.open_telegram) 253 | self.ui.pushButton_Web3.clicked.connect(self.open_web3) 254 | 255 | # Подключаем обработчик изменения NODE_TYPE 256 | self.ui.comboBox_NODE_TYPE.currentTextChanged.connect(self.update_node_type) 257 | 258 | # Устанавливаем текущее значение NODE_TYPE 259 | self.set_initial_node_type() 260 | 261 | self.worker = None 262 | self.is_farming = False 263 | 264 | 265 | def update_file_path(self, param_name, button): 266 | file_path, _ = QFileDialog.getOpenFileName(self, "Choose a file", "", "All Files (*)") 267 | if file_path: 268 | self.update_config_param(param_name, file_path) 269 | button.setText(f"Updated: {file_path.split('/')[-1]}") 270 | logger.info(f"{param_name} updated to {file_path}.") 271 | 272 | def update_config_param(self, param_name, value): 273 | try: 274 | with open('data/config.py', 'r', encoding='utf-8') as file: 275 | lines = file.readlines() 276 | 277 | with open('data/config.py', 'w', encoding='utf-8') as file: 278 | for line in lines: 279 | if line.startswith(param_name): 280 | if isinstance(value, str): 281 | file.write(f"{param_name} = '{value}'\n") 282 | elif isinstance(value, bool): 283 | file.write(f"{param_name} = {str(value)}\n") 284 | elif isinstance(value, tuple): 285 | file.write(f"{param_name} = {value}\n") 286 | else: 287 | file.write(f"{param_name} = {value}\n") 288 | else: 289 | file.write(line) 290 | logger.info(f"Parameter {param_name} changed to {value}.") 291 | except Exception as e: 292 | logger.error(f"Error updating parameter {param_name}: {e}") 293 | 294 | def save_changes(self): 295 | try: 296 | params_to_save = { 297 | 'THREADS': int(self.ui.lineEdit_Threads.text()), 298 | 'MIN_PROXY_SCORE': int(self.ui.lineEdit_MinProxyScore.text()), 299 | 'EMAIL_FOLDER': self.ui.lineEdit_EmailFolder.text(), 300 | 'IMAP_DOMAIN': self.ui.lineEdit_ImapDomain.text(), 301 | 'REF_CODE': self.ui.lineEdit_REFCODE.text(), 302 | 'REGISTER_DELAY': ( 303 | int(self.ui.lineEdit_Min.text()), 304 | int(self.ui.lineEdit_Max.text()) 305 | ), 306 | 'APPROVE_EMAIL': self.ui.checkBox_ApproveEmail.isChecked(), 307 | 'CONNECT_WALLET': self.ui.checkBox_ConnectWallet.isChecked(), 308 | 'SEND_WALLET_APPROVE_LINK_TO_EMAIL': self.ui.checkBox_SendWallerApproveForEmail.isChecked(), 309 | 'APPROVE_WALLET_ON_EMAIL': self.ui.checkBox_ApproveWalletOnEmail.isChecked(), 310 | 'SINGLE_IMAP_ACCOUNT': self.ui.checkBox_SingleMapAccount.isChecked(), 311 | 'USE_PROXY_FOR_IMAP': self.ui.checkBox_UseProxyForImap.isChecked(), 312 | 'STOP_ACCOUNTS_WHEN_SITE_IS_DOWN': self.ui.checkBox_TimeOutFarm.isChecked(), 313 | 'CHECK_POINTS': self.ui.checkBox_CheckPoints.isChecked(), 314 | 'SHOW_LOGS_RARELY': self.ui.checkBox_RarelyShowLogs.isChecked(), 315 | 'CLAIM_REWARDS_ONLY': self.ui.checkBox_ClaimRewardOnly.isChecked(), 316 | } 317 | 318 | # Сохраняем основные параметры 319 | for param_name, new_value in params_to_save.items(): 320 | old_value = self.initial_params.get(param_name) 321 | if old_value != new_value: 322 | self.update_config_param(param_name, new_value) 323 | logger.info(f"{param_name} updated from {old_value} to {new_value}.") 324 | self.initial_params[param_name] = new_value 325 | 326 | # Сохраняем API ключи капчи 327 | for service, param_name in self.captcha_services.items(): 328 | api_key_value = self.local_captcha_keys[service] 329 | old_value = globals().get(param_name) 330 | if old_value != api_key_value: 331 | self.update_config_param(param_name, api_key_value) 332 | # Обновляем глобальную переменную сразу после сохранения 333 | globals()[param_name] = api_key_value 334 | logger.info(f"API-key for {service} updated to {api_key_value}.") 335 | 336 | # Принудительно перезагружаем все модули, которые могут использовать конфиг 337 | modules_to_reload = [ 338 | 'data.config', 339 | 'core.grass', 340 | 'core.autoreger', 341 | 'core.utils.captcha_service' 342 | ] 343 | 344 | for module_name in modules_to_reload: 345 | try: 346 | module = sys.modules.get(module_name) 347 | if module: 348 | importlib.reload(module) 349 | except Exception as e: 350 | logger.error(f"Error reloading module {module_name}: {e}") 351 | 352 | # Обновляем глобальные переменные 353 | update_global_config() 354 | 355 | # Очищаем кэш импортов для модулей 356 | importlib.invalidate_caches() 357 | 358 | logger.info("All parameters saved and modules reloaded.") 359 | 360 | except Exception as e: 361 | logger.error(f"Error saving parameters: {e}") 362 | 363 | def log(self, message): 364 | logger.info(message) 365 | 366 | def start_farming(self): 367 | try: 368 | if self.is_farming: 369 | # Останавливаем процесс 370 | if self.worker: 371 | logger.info("Остановка фарминга...") 372 | self.worker.stop() 373 | self.ui.pushButton_StartFarming.setEnabled(False) 374 | return 375 | 376 | # Проверяем и очищаем старый воркер если он есть 377 | if self.worker: 378 | self.worker.quit() 379 | self.worker.wait() 380 | self.worker = None 381 | 382 | # Запускаем процесс 383 | self.save_changes() 384 | logger.info("Запуск фарминга...") 385 | 386 | self.worker = AsyncWorker(self) 387 | self.worker.finished.connect(self.on_worker_finished) 388 | self.worker.error.connect(self.on_worker_error) 389 | self.worker.stopped.connect(self.on_worker_stopped) 390 | self.worker.start() 391 | 392 | self.ui.pushButton_StartFarming.setText("Stop Farming") 393 | self.is_farming = True 394 | 395 | except Exception as e: 396 | logger.error(f"Ошибка при запуске фарминга: {e}") 397 | self.ui.pushButton_StartFarming.setText("Start Farming") 398 | self.is_farming = False 399 | 400 | def start_registration(self): 401 | self.save_changes() 402 | logger.info("Запуск регистрации...") 403 | # Здесь добавить логику запуска регистрации 404 | 405 | def show_instructions(self): 406 | logger.info("Открытие инструкций...") 407 | # Добавить логику показа инструкций 408 | 409 | def show_more_info(self): 410 | logger.info("Открытие дополнительной информации...") 411 | # Добавить логику показа дополнительной информации 412 | 413 | def show_web3_info(self): 414 | logger.info("Открытие информации о Web3...") 415 | # Добавить логику показа информации о Web3 416 | 417 | def convert_to_bool(self, value): 418 | if isinstance(value, str): 419 | return value.lower() == "true" 420 | return bool(value) 421 | 422 | def on_worker_finished(self): 423 | logger.info("Операция успешно завершена") 424 | self.ui.pushButton_StartFarming.setText("Start Farming") 425 | self.is_farming = False 426 | 427 | def on_worker_error(self, error_msg): 428 | logger.error(f"Ошибка при выполнении: {error_msg}") 429 | self.ui.pushButton_StartFarming.setText("Start Farming") 430 | self.is_farming = False 431 | 432 | def on_worker_stopped(self): 433 | """Обработчи�� успешной остановки воркера""" 434 | logger.info("Фарминг успешно остановлен") 435 | self.ui.pushButton_StartFarming.setText("Start Farming") 436 | self.ui.pushButton_StartFarming.setEnabled(True) 437 | self.is_farming = False 438 | if self.worker: 439 | self.worker.quit() 440 | self.worker.wait() 441 | self.worker = None # Очищаем ссылку на старый воркер 442 | 443 | def update_lineedit_with_local_values(self): 444 | current_service = self.ui.comboBox_CaptchaService.currentText() 445 | if current_service in self.local_captcha_keys: 446 | self.ui.lineEdit_CapthaAPI.setText(self.local_captcha_keys[current_service]) 447 | 448 | def update_local_value(self): 449 | current_service = self.ui.comboBox_CaptchaService.currentText() 450 | if current_service in self.local_captcha_keys: 451 | self.local_captcha_keys[current_service] = self.ui.lineEdit_CapthaAPI.text() 452 | 453 | def reset_to_default(self): 454 | default_paths = { 455 | "ACCOUNTS_FILE_PATH": DEFAULT_ACCOUNTS_FILE_PATH, 456 | "PROXIES_FILE_PATH": DEFAULT_PROXIES_FILE_PATH, 457 | "WALLETS_FILE_PATH": DEFAULT_WALLETS_FILE_PATH, 458 | "PROXY_DB_PATH": DEFAULT_PROXY_DB_PATH, 459 | } 460 | 461 | for param_name, default_path in default_paths.items(): 462 | self.update_config_param(param_name, default_path) 463 | self.initial_params[param_name] = default_path 464 | logger.info(f"{param_name} updated to {default_path}.") 465 | 466 | self.ui.pushButton_AccountsFile.setText(f"{DEFAULT_ACCOUNTS_FILE_PATH.split('/')[-1]}") 467 | self.ui.pushButton_ProxyFile.setText(f"{DEFAULT_PROXIES_FILE_PATH.split('/')[-1]}") 468 | self.ui.pushButton_WalletsFile.setText(f"{DEFAULT_WALLETS_FILE_PATH.split('/')[-1]}") 469 | self.ui.pushButton_ProxyDB.setText(f"{DEFAULT_PROXY_DB_PATH.split('/')[-1]}") 470 | 471 | def update_register_delay(self): 472 | """Обновляет значения REGISTER_DELAY при изменении полей Min и Max""" 473 | try: 474 | min_delay = float(self.ui.lineEdit_Min.text()) 475 | max_delay = float(self.ui.lineEdit_Max.text()) 476 | if min_delay >= 0 and max_delay > min_delay: 477 | global REGISTER_DELAY 478 | REGISTER_DELAY = (min_delay, max_delay) 479 | logger.info(f"REGISTER_DELAY обновлен: {REGISTER_DELAY}") 480 | else: 481 | logger.error("Некорректные значения задержки. Max должен быть больше Min, и Min должен быть >= 0") 482 | except ValueError: 483 | logger.error("Ошибка: введите корректные чиловые значения для задержки") 484 | 485 | def open_instructions(self): 486 | """Открывает инструкции в браузере""" 487 | QDesktopServices.openUrl(QUrl("https://teletype.in/@c6zr7/grass_for_EnJoYeR")) 488 | logger.info("Opening instructions page...") 489 | 490 | def open_telegram(self): 491 | """Открывает Telegram канал""" 492 | QDesktopServices.openUrl(QUrl("https://t.me/web3_enjoyer_club")) 493 | logger.info("Opening Telegram channel...") 494 | 495 | def open_web3(self): 496 | """Открывает Web3 продукты""" 497 | QDesktopServices.openUrl(QUrl("https://gemups.com/")) 498 | logger.info("Opening Web3 products page...") 499 | 500 | def set_initial_node_type(self): 501 | """Устанавливает начальное значение comboBox в соответствии с конфигом""" 502 | node_type_map = { 503 | "1x": "1x", 504 | "1.25x": "1_25x", 505 | "2x": "2x" 506 | } 507 | 508 | # Получаем текущее значение из конфига и находим соответствующий индекс 509 | current_value = NODE_TYPE.replace(".", "_") if NODE_TYPE else "1x" 510 | index = self.ui.comboBox_NODE_TYPE.findText(current_value) 511 | if index >= 0: 512 | self.ui.comboBox_NODE_TYPE.setCurrentIndex(index) 513 | 514 | def update_node_type(self): 515 | """Обновляет NODE_TYPE при изменении значения в comboBox""" 516 | node_type_map = { 517 | "1x": "1x", 518 | "1_25x": "1.25x", 519 | "2x": "2x" 520 | } 521 | 522 | selected_value = self.ui.comboBox_NODE_TYPE.currentText() 523 | new_value = node_type_map.get(selected_value, "1x") 524 | 525 | try: 526 | self.update_config_param('NODE_TYPE', new_value) 527 | self.initial_params['NODE_TYPE'] = new_value 528 | logger.info(f"NODE_TYPE updated to {new_value}") 529 | except Exception as e: 530 | logger.error(f"Error updating NODE_TYPE: {e}") 531 | 532 | def start_ui(): 533 | app = QApplication(sys.argv) 534 | window = GrassInterface() 535 | window.show() 536 | sys.exit(app.exec()) 537 | -------------------------------------------------------------------------------- /design.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'mainwindow.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.8.1 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QFrame, 19 | QGridLayout, QGroupBox, QLabel, QLineEdit, 20 | QMainWindow, QMenuBar, QPushButton, QScrollArea, 21 | QSizePolicy, QStatusBar, QTabWidget, QTextEdit, 22 | QVBoxLayout, QWidget) 23 | 24 | class Ui_MainWindow(object): 25 | def setupUi(self, MainWindow): 26 | if not MainWindow.objectName(): 27 | MainWindow.setObjectName(u"MainWindow") 28 | MainWindow.resize(865, 540) 29 | MainWindow.setToolTipDuration(-1) 30 | 31 | # Устанавливаем иконку окна 32 | icon = QIcon("core/static/ico.png") 33 | MainWindow.setWindowIcon(icon) 34 | 35 | # Устанавливаем заголовок окна 36 | MainWindow.setWindowTitle("Grass") 37 | 38 | # Устанавливаем фиксированную темную тему 39 | dark_palette = QPalette() 40 | dark_palette.setColor(QPalette.Window, QColor(18, 18, 18)) 41 | dark_palette.setColor(QPalette.WindowText, QColor(255, 255, 255)) 42 | dark_palette.setColor(QPalette.Base, QColor(27, 27, 27)) 43 | dark_palette.setColor(QPalette.AlternateBase, QColor(35, 35, 35)) 44 | dark_palette.setColor(QPalette.ToolTipBase, QColor(255, 255, 255)) 45 | dark_palette.setColor(QPalette.ToolTipText, QColor(255, 255, 255)) 46 | dark_palette.setColor(QPalette.Text, QColor(255, 255, 255)) 47 | dark_palette.setColor(QPalette.Button, QColor(27, 27, 27)) 48 | dark_palette.setColor(QPalette.ButtonText, QColor(255, 255, 255)) 49 | dark_palette.setColor(QPalette.BrightText, QColor(255, 0, 0)) 50 | dark_palette.setColor(QPalette.Link, QColor(42, 130, 218)) 51 | dark_palette.setColor(QPalette.Highlight, QColor(42, 130, 218)) 52 | dark_palette.setColor(QPalette.HighlightedText, QColor(255, 255, 255)) 53 | 54 | # Применяем палитру 55 | MainWindow.setPalette(dark_palette) 56 | 57 | # Устанавливаем стиль для всего приложения 58 | MainWindow.setStyleSheet(""" 59 | QMainWindow { 60 | background-color: #121212; 61 | } 62 | QWidget { 63 | color: #ffffff; 64 | background-color: #121212; 65 | } 66 | QFrame { 67 | background-color: #1b1b1b; 68 | border-radius: 10px; 69 | } 70 | QPushButton { 71 | background-color: #3e2d9f; 72 | color: white; 73 | border: none; 74 | border-radius: 5px; 75 | padding: 5px; 76 | } 77 | QPushButton:hover { 78 | background-color: #4e3dbf; 79 | } 80 | QPushButton:pressed { 81 | background-color: #2e1d7f; 82 | } 83 | QLineEdit { 84 | background-color: #424242; 85 | border: none; 86 | border-radius: 5px; 87 | padding: 5px; 88 | color: white; 89 | } 90 | QTextEdit { 91 | background-color: #424242; 92 | border: none; 93 | border-radius: 5px; 94 | padding: 5px; 95 | color: white; 96 | } 97 | QComboBox { 98 | background-color: #303030; 99 | border: none; 100 | border-radius: 5px; 101 | padding: 5px; 102 | color: white; 103 | } 104 | QComboBox::drop-down { 105 | border: none; 106 | } 107 | QComboBox::down-arrow { 108 | image: none; 109 | } 110 | QTabWidget::pane { 111 | border: none; 112 | background-color: #2d2d2d; 113 | } 114 | QTabBar::tab { 115 | background-color: #2d2d2d; 116 | color: white; 117 | padding: 8px 20px; 118 | border: none; 119 | } 120 | QTabBar::tab:selected { 121 | background-color: #3e2d9f; 122 | } 123 | QCheckBox { 124 | color: white; 125 | } 126 | /* Стили для чекбоксов внутри tabWidget */ 127 | QTabWidget QCheckBox::indicator { 128 | width: 13px; 129 | height: 13px; 130 | background-color: transparent; 131 | } 132 | QTabWidget QCheckBox::indicator:unchecked { 133 | background-color: transparent; 134 | border: 1px solid #ffffff; 135 | } 136 | QTabWidget QCheckBox::indicator:checked { 137 | background-color: transparent; 138 | border: 1px solid #ffffff; 139 | image: url(core/static/ico.png); 140 | } 141 | /* Стили для остальных чекбоксов */ 142 | QCheckBox::indicator { 143 | width: 15px; 144 | height: 15px; 145 | } 146 | QCheckBox::indicator:unchecked { 147 | background-color: #424242; 148 | border: 2px solid #666666; 149 | border-radius: 3px; 150 | } 151 | QCheckBox::indicator:checked { 152 | background-color: #3e2d9f; 153 | border: 2px solid #3e2d9f; 154 | border-radius: 3px; 155 | } 156 | """) 157 | 158 | self.centralwidget = QWidget(MainWindow) 159 | self.centralwidget.setObjectName(u"centralwidget") 160 | self.gridLayout_5 = QGridLayout(self.centralwidget) 161 | self.gridLayout_5.setObjectName(u"gridLayout_5") 162 | self.label_13 = QLabel(self.centralwidget) 163 | self.label_13.setObjectName(u"label_13") 164 | 165 | self.gridLayout_5.addWidget(self.label_13, 0, 0, 1, 1) 166 | 167 | self.pushButton_Instructions = QPushButton(self.centralwidget) 168 | self.pushButton_Instructions.setObjectName(u"pushButton_Instructions") 169 | font = QFont() 170 | font.setPointSize(10) 171 | font.setBold(True) 172 | self.pushButton_Instructions.setFont(font) 173 | self.pushButton_Instructions.setStyleSheet(u"background-color: #3e2d9f;") 174 | 175 | self.gridLayout_5.addWidget(self.pushButton_Instructions, 0, 1, 1, 1) 176 | 177 | self.pushButton_more = QPushButton(self.centralwidget) 178 | self.pushButton_more.setObjectName(u"pushButton_more") 179 | self.pushButton_more.setFont(font) 180 | self.pushButton_more.setStyleSheet(u"background-color: #3e2d9f;") 181 | 182 | self.gridLayout_5.addWidget(self.pushButton_more, 0, 2, 1, 1) 183 | 184 | self.pushButton_Web3 = QPushButton(self.centralwidget) 185 | self.pushButton_Web3.setObjectName(u"pushButton_Web3") 186 | self.pushButton_Web3.setFont(font) 187 | self.pushButton_Web3.setStyleSheet(u"background-color: #3e2d9f;") 188 | 189 | self.gridLayout_5.addWidget(self.pushButton_Web3, 0, 3, 1, 1) 190 | 191 | self.frame = QFrame(self.centralwidget) 192 | self.frame.setObjectName(u"frame") 193 | self.frame.setStyleSheet(u"background-color: #1b1b1b ; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 */\n" 194 | "border-radius: 10px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */") 195 | self.frame.setFrameShape(QFrame.Shape.StyledPanel) 196 | self.frame.setFrameShadow(QFrame.Shadow.Raised) 197 | self.gridLayout_4 = QGridLayout(self.frame) 198 | self.gridLayout_4.setObjectName(u"gridLayout_4") 199 | self.gridLayout_3 = QGridLayout() 200 | self.gridLayout_3.setObjectName(u"gridLayout_3") 201 | self.lineEdit_CapthaAPI = QLineEdit(self.frame) 202 | self.lineEdit_CapthaAPI.setObjectName(u"lineEdit_CapthaAPI") 203 | self.lineEdit_CapthaAPI.setFont(font) 204 | self.lineEdit_CapthaAPI.setStyleSheet(u"border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 205 | "background-color: #424242;") 206 | 207 | self.gridLayout_3.addWidget(self.lineEdit_CapthaAPI, 0, 4, 1, 2) 208 | 209 | self.lineEdit_Max = QLineEdit(self.frame) 210 | self.lineEdit_Max.setObjectName(u"lineEdit_Max") 211 | self.lineEdit_Max.setMinimumSize(QSize(50, 25)) 212 | self.lineEdit_Max.setStyleSheet(u"border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 213 | "background-color: #424242 ;") 214 | 215 | self.gridLayout_3.addWidget(self.lineEdit_Max, 5, 5, 1, 1) 216 | 217 | self.lineEdit_REFCODE = QLineEdit(self.frame) 218 | self.lineEdit_REFCODE.setObjectName(u"lineEdit_REFCODE") 219 | self.lineEdit_REFCODE.setMinimumSize(QSize(50, 25)) 220 | self.lineEdit_REFCODE.setStyleSheet(u"border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 221 | "background-color: #424242 ;") 222 | 223 | self.gridLayout_3.addWidget(self.lineEdit_REFCODE, 6, 2, 1, 2) 224 | 225 | self.label_Threads = QLabel(self.frame) 226 | self.label_Threads.setObjectName(u"label_Threads") 227 | self.label_Threads.setFont(font) 228 | 229 | self.gridLayout_3.addWidget(self.label_Threads, 1, 0, 1, 1) 230 | 231 | self.comboBox_CaptchaService = QComboBox(self.frame) 232 | self.comboBox_CaptchaService.addItem("") 233 | self.comboBox_CaptchaService.addItem("") 234 | self.comboBox_CaptchaService.addItem("") 235 | self.comboBox_CaptchaService.addItem("") 236 | self.comboBox_CaptchaService.addItem("") 237 | self.comboBox_CaptchaService.setObjectName(u"comboBox_CaptchaService") 238 | font1 = QFont() 239 | font1.setBold(False) 240 | font1.setHintingPreference(QFont.PreferDefaultHinting) 241 | self.comboBox_CaptchaService.setFont(font1) 242 | self.comboBox_CaptchaService.setFocusPolicy(Qt.FocusPolicy.NoFocus) 243 | self.comboBox_CaptchaService.setLayoutDirection(Qt.LayoutDirection.LeftToRight) 244 | self.comboBox_CaptchaService.setAutoFillBackground(False) 245 | self.comboBox_CaptchaService.setStyleSheet(u"background-color: #303030 ;\n" 246 | "border-radius: 0px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */") 247 | 248 | self.gridLayout_3.addWidget(self.comboBox_CaptchaService, 0, 1, 1, 3) 249 | 250 | self.lineEdit_Threads = QLineEdit(self.frame) 251 | self.lineEdit_Threads.setObjectName(u"lineEdit_Threads") 252 | self.lineEdit_Threads.setStyleSheet(u"QLineEdit {\n" 253 | " border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 254 | " color: White; /* \u0426\u0432\u0435\u0442 \u0442\u0435\u043a\u0441\u0442\u0430 */\n" 255 | " background-color: #424242 ;\n" 256 | "}") 257 | 258 | self.gridLayout_3.addWidget(self.lineEdit_Threads, 1, 1, 1, 3) 259 | 260 | self.label_ImapDomain = QLabel(self.frame) 261 | self.label_ImapDomain.setObjectName(u"label_ImapDomain") 262 | self.label_ImapDomain.setFont(font) 263 | 264 | self.gridLayout_3.addWidget(self.label_ImapDomain, 4, 0, 1, 2) 265 | 266 | self.lineEdit_EmailFolder = QLineEdit(self.frame) 267 | self.lineEdit_EmailFolder.setObjectName(u"lineEdit_EmailFolder") 268 | self.lineEdit_EmailFolder.setStyleSheet(u"border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 269 | "background-color: #424242 ;") 270 | 271 | self.gridLayout_3.addWidget(self.lineEdit_EmailFolder, 3, 2, 1, 1) 272 | 273 | self.label_EmailFolder = QLabel(self.frame) 274 | self.label_EmailFolder.setObjectName(u"label_EmailFolder") 275 | self.label_EmailFolder.setFont(font) 276 | 277 | self.gridLayout_3.addWidget(self.label_EmailFolder, 3, 0, 1, 2) 278 | 279 | self.label = QLabel(self.frame) 280 | self.label.setObjectName(u"label") 281 | self.label.setFont(font) 282 | 283 | self.gridLayout_3.addWidget(self.label, 5, 0, 1, 2) 284 | 285 | self.label_Captcha = QLabel(self.frame) 286 | self.label_Captcha.setObjectName(u"label_Captcha") 287 | self.label_Captcha.setFont(font) 288 | 289 | self.gridLayout_3.addWidget(self.label_Captcha, 0, 0, 1, 1) 290 | 291 | self.label_3 = QLabel(self.frame) 292 | self.label_3.setObjectName(u"label_3") 293 | self.label_3.setFont(font) 294 | 295 | self.gridLayout_3.addWidget(self.label_3, 6, 0, 1, 2) 296 | 297 | self.label_MinProxiScore = QLabel(self.frame) 298 | self.label_MinProxiScore.setObjectName(u"label_MinProxiScore") 299 | self.label_MinProxiScore.setFont(font) 300 | 301 | self.gridLayout_3.addWidget(self.label_MinProxiScore, 2, 0, 1, 2) 302 | 303 | self.lineEdit_MinProxyScore = QLineEdit(self.frame) 304 | self.lineEdit_MinProxyScore.setObjectName(u"lineEdit_MinProxyScore") 305 | self.lineEdit_MinProxyScore.setStyleSheet(u"border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 306 | "background-color: #424242 ;") 307 | 308 | self.gridLayout_3.addWidget(self.lineEdit_MinProxyScore, 2, 2, 1, 1) 309 | 310 | self.lineEdit_ImapDomain = QLineEdit(self.frame) 311 | self.lineEdit_ImapDomain.setObjectName(u"lineEdit_ImapDomain") 312 | self.lineEdit_ImapDomain.setStyleSheet(u"border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 313 | "background-color: #424242 ;") 314 | 315 | self.gridLayout_3.addWidget(self.lineEdit_ImapDomain, 4, 2, 1, 1) 316 | 317 | self.lineEdit_Min = QLineEdit(self.frame) 318 | self.lineEdit_Min.setObjectName(u"lineEdit_Min") 319 | self.lineEdit_Min.setMinimumSize(QSize(50, 25)) 320 | self.lineEdit_Min.setStyleSheet(u"border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 321 | "background-color: #424242 ;") 322 | 323 | self.gridLayout_3.addWidget(self.lineEdit_Min, 5, 2, 1, 1) 324 | 325 | self.label_2 = QLabel(self.frame) 326 | self.label_2.setObjectName(u"label_2") 327 | self.label_2.setFont(font) 328 | 329 | self.gridLayout_3.addWidget(self.label_2, 5, 3, 1, 1) 330 | 331 | 332 | self.gridLayout_4.addLayout(self.gridLayout_3, 0, 0, 1, 2) 333 | 334 | self.tabWidget = QTabWidget(self.frame) 335 | self.tabWidget.setObjectName(u"tabWidget") 336 | self.tabWidget.setStyleSheet(u"background-color: #2d2d2d;") 337 | self.tabWidget.setTabPosition(QTabWidget.TabPosition.North) 338 | self.tabWidget.setTabShape(QTabWidget.TabShape.Rounded) 339 | self.tabWidget.setUsesScrollButtons(True) 340 | self.tabWidget.setDocumentMode(False) 341 | self.tabWidget.setTabsClosable(False) 342 | self.tabWidget.setMovable(True) 343 | self.tabWidget.setTabBarAutoHide(False) 344 | self.tab = QWidget() 345 | self.tab.setObjectName(u"tab") 346 | self.tab.setEnabled(True) 347 | self.verticalLayout_2 = QVBoxLayout(self.tab) 348 | self.verticalLayout_2.setObjectName(u"verticalLayout_2") 349 | self.scrollArea = QScrollArea(self.tab) 350 | self.scrollArea.setObjectName(u"scrollArea") 351 | sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) 352 | sizePolicy.setHorizontalStretch(0) 353 | sizePolicy.setVerticalStretch(0) 354 | sizePolicy.setHeightForWidth(self.scrollArea.sizePolicy().hasHeightForWidth()) 355 | self.scrollArea.setSizePolicy(sizePolicy) 356 | self.scrollArea.setFrameShape(QFrame.Shape.NoFrame) 357 | self.scrollArea.setFrameShadow(QFrame.Shadow.Sunken) 358 | self.scrollArea.setLineWidth(1) 359 | self.scrollArea.setWidgetResizable(True) 360 | self.scrollArea.setAlignment(Qt.AlignmentFlag.AlignLeading|Qt.AlignmentFlag.AlignLeft|Qt.AlignmentFlag.AlignVCenter) 361 | self.scrollAreaWidgetContents_2 = QWidget() 362 | self.scrollAreaWidgetContents_2.setObjectName(u"scrollAreaWidgetContents_2") 363 | self.scrollAreaWidgetContents_2.setGeometry(QRect(0, 0, 280, 173)) 364 | self.verticalLayout = QVBoxLayout(self.scrollAreaWidgetContents_2) 365 | self.verticalLayout.setObjectName(u"verticalLayout") 366 | self.checkBox_ApproveEmail = QCheckBox(self.scrollAreaWidgetContents_2) 367 | self.checkBox_ApproveEmail.setObjectName(u"checkBox_ApproveEmail") 368 | self.checkBox_ApproveEmail.setFocusPolicy(Qt.FocusPolicy.WheelFocus) 369 | self.checkBox_ApproveEmail.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu) 370 | self.checkBox_ApproveEmail.setLayoutDirection(Qt.LayoutDirection.LeftToRight) 371 | self.checkBox_ApproveEmail.setAutoFillBackground(False) 372 | 373 | self.verticalLayout.addWidget(self.checkBox_ApproveEmail) 374 | 375 | self.checkBox_ConnectWallet = QCheckBox(self.scrollAreaWidgetContents_2) 376 | self.checkBox_ConnectWallet.setObjectName(u"checkBox_ConnectWallet") 377 | self.checkBox_ConnectWallet.setFocusPolicy(Qt.FocusPolicy.WheelFocus) 378 | self.checkBox_ConnectWallet.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu) 379 | self.checkBox_ConnectWallet.setLayoutDirection(Qt.LayoutDirection.LeftToRight) 380 | self.checkBox_ConnectWallet.setAutoFillBackground(False) 381 | 382 | self.verticalLayout.addWidget(self.checkBox_ConnectWallet) 383 | 384 | self.checkBox_SendWallerApproveForEmail = QCheckBox(self.scrollAreaWidgetContents_2) 385 | self.checkBox_SendWallerApproveForEmail.setObjectName(u"checkBox_SendWallerApproveForEmail") 386 | self.checkBox_SendWallerApproveForEmail.setFocusPolicy(Qt.FocusPolicy.WheelFocus) 387 | self.checkBox_SendWallerApproveForEmail.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu) 388 | self.checkBox_SendWallerApproveForEmail.setLayoutDirection(Qt.LayoutDirection.LeftToRight) 389 | self.checkBox_SendWallerApproveForEmail.setAutoFillBackground(False) 390 | 391 | self.verticalLayout.addWidget(self.checkBox_SendWallerApproveForEmail) 392 | 393 | self.checkBox_ApproveWalletOnEmail = QCheckBox(self.scrollAreaWidgetContents_2) 394 | self.checkBox_ApproveWalletOnEmail.setObjectName(u"checkBox_ApproveWalletOnEmail") 395 | self.checkBox_ApproveWalletOnEmail.setFocusPolicy(Qt.FocusPolicy.WheelFocus) 396 | self.checkBox_ApproveWalletOnEmail.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu) 397 | self.checkBox_ApproveWalletOnEmail.setLayoutDirection(Qt.LayoutDirection.LeftToRight) 398 | self.checkBox_ApproveWalletOnEmail.setAutoFillBackground(False) 399 | 400 | self.verticalLayout.addWidget(self.checkBox_ApproveWalletOnEmail) 401 | 402 | self.checkBox_SemiAutoApproveLink = QCheckBox(self.scrollAreaWidgetContents_2) 403 | self.checkBox_SemiAutoApproveLink.setObjectName(u"checkBox_SemiAutoApproveLink") 404 | self.checkBox_SemiAutoApproveLink.setFocusPolicy(Qt.FocusPolicy.WheelFocus) 405 | self.checkBox_SemiAutoApproveLink.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu) 406 | self.checkBox_SemiAutoApproveLink.setLayoutDirection(Qt.LayoutDirection.LeftToRight) 407 | self.checkBox_SemiAutoApproveLink.setAutoFillBackground(False) 408 | 409 | self.verticalLayout.addWidget(self.checkBox_SemiAutoApproveLink) 410 | 411 | self.checkBox_SingleMapAccount = QCheckBox(self.scrollAreaWidgetContents_2) 412 | self.checkBox_SingleMapAccount.setObjectName(u"checkBox_SingleMapAccount") 413 | self.checkBox_SingleMapAccount.setFocusPolicy(Qt.FocusPolicy.WheelFocus) 414 | self.checkBox_SingleMapAccount.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu) 415 | self.checkBox_SingleMapAccount.setLayoutDirection(Qt.LayoutDirection.LeftToRight) 416 | self.checkBox_SingleMapAccount.setAutoFillBackground(False) 417 | 418 | self.verticalLayout.addWidget(self.checkBox_SingleMapAccount) 419 | 420 | self.checkBox_UseProxyForImap = QCheckBox(self.scrollAreaWidgetContents_2) 421 | self.checkBox_UseProxyForImap.setObjectName(u"checkBox_UseProxyForImap") 422 | self.checkBox_UseProxyForImap.setFocusPolicy(Qt.FocusPolicy.WheelFocus) 423 | self.checkBox_UseProxyForImap.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu) 424 | self.checkBox_UseProxyForImap.setLayoutDirection(Qt.LayoutDirection.LeftToRight) 425 | self.checkBox_UseProxyForImap.setAutoFillBackground(False) 426 | 427 | self.verticalLayout.addWidget(self.checkBox_UseProxyForImap) 428 | 429 | self.scrollArea.setWidget(self.scrollAreaWidgetContents_2) 430 | 431 | self.verticalLayout_2.addWidget(self.scrollArea) 432 | 433 | self.tabWidget.addTab(self.tab, "") 434 | self.tab_3 = QWidget() 435 | self.tab_3.setObjectName(u"tab_3") 436 | self.checkBox_TimeOutFarm = QCheckBox(self.tab_3) 437 | self.checkBox_TimeOutFarm.setObjectName(u"checkBox_TimeOutFarm") 438 | self.checkBox_TimeOutFarm.setGeometry(QRect(20, 20, 111, 22)) 439 | font2 = QFont() 440 | font2.setBold(False) 441 | self.checkBox_TimeOutFarm.setFont(font2) 442 | self.checkBox_CheckPoints = QCheckBox(self.tab_3) 443 | self.checkBox_CheckPoints.setObjectName(u"checkBox_CheckPoints") 444 | self.checkBox_CheckPoints.setGeometry(QRect(20, 40, 101, 22)) 445 | self.checkBox_CheckPoints.setFont(font2) 446 | self.checkBox_RarelyShowLogs = QCheckBox(self.tab_3) 447 | self.checkBox_RarelyShowLogs.setObjectName(u"checkBox_RarelyShowLogs") 448 | self.checkBox_RarelyShowLogs.setGeometry(QRect(20, 60, 121, 22)) 449 | self.checkBox_RarelyShowLogs.setFont(font2) 450 | self.tabWidget.addTab(self.tab_3, "") 451 | self.tab_2 = QWidget() 452 | self.tab_2.setObjectName(u"tab_2") 453 | self.checkBox_ClaimRewardOnly = QCheckBox(self.tab_2) 454 | self.checkBox_ClaimRewardOnly.setObjectName(u"checkBox_ClaimRewardOnly") 455 | self.checkBox_ClaimRewardOnly.setGeometry(QRect(10, 20, 161, 21)) 456 | self.checkBox_ClaimRewardOnly.setFocusPolicy(Qt.FocusPolicy.WheelFocus) 457 | self.checkBox_ClaimRewardOnly.setContextMenuPolicy(Qt.ContextMenuPolicy.DefaultContextMenu) 458 | self.checkBox_ClaimRewardOnly.setLayoutDirection(Qt.LayoutDirection.LeftToRight) 459 | self.checkBox_ClaimRewardOnly.setAutoFillBackground(False) 460 | self.tabWidget.addTab(self.tab_2, "") 461 | 462 | self.gridLayout_4.addWidget(self.tabWidget, 0, 2, 1, 1) 463 | 464 | self.groupBox_2 = QGroupBox(self.frame) 465 | self.groupBox_2.setObjectName(u"groupBox_2") 466 | self.groupBox_2.setStyleSheet(u"background-color: #2d2d2d;\n" 467 | "border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */") 468 | self.gridLayout = QGridLayout(self.groupBox_2) 469 | self.gridLayout.setObjectName(u"gridLayout") 470 | self.label_AccountFile = QLabel(self.groupBox_2) 471 | self.label_AccountFile.setObjectName(u"label_AccountFile") 472 | self.label_AccountFile.setFont(font) 473 | 474 | self.gridLayout.addWidget(self.label_AccountFile, 1, 0, 1, 1) 475 | 476 | self.pushButton_WalletsFile = QPushButton(self.groupBox_2) 477 | self.pushButton_WalletsFile.setObjectName(u"pushButton_WalletsFile") 478 | font3 = QFont() 479 | font3.setFamilies([u"Arial"]) 480 | font3.setBold(True) 481 | font3.setItalic(False) 482 | font3.setUnderline(False) 483 | font3.setKerning(False) 484 | self.pushButton_WalletsFile.setFont(font3) 485 | self.pushButton_WalletsFile.setStyleSheet(u"QPushButton {\n" 486 | " background-color: #3498db; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 */\n" 487 | " color: black; /* \u0426\u0432\u0435\u0442 \u0442\u0435\u043a\u0441\u0442\u0430 */\n" 488 | " border-radius: 10px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 489 | " border: 2px solid #2980b9; /* \u0413\u0440\u0430\u043d\u0438\u0446\u0430 \u043a\u043d\u043e\u043f\u043a\u0438 */\n" 490 | " font-size: 14px; /* \u0420\u0430\u0437\u043c\u0435\u0440 \u0442\u0435\u043a\u0441\u0442\u0430 */\n" 491 | "}\n" 492 | "\n" 493 | "QPushButton:hover {\n" 494 | " background-color: #2980b9; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 \u043f\u0440\u0438 \u043d\u0430\u0432\u0435\u0434\u0435\u043d\u0438\u0438 */\n" 495 | "}\n" 496 | "\n" 497 | "QPushButton:pressed {\n" 498 | " background-color: #1abc9c; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 */\n" 499 | "}\n" 500 | "") 501 | 502 | self.gridLayout.addWidget(self.pushButton_WalletsFile, 3, 2, 1, 1) 503 | 504 | self.label_6 = QLabel(self.groupBox_2) 505 | self.label_6.setObjectName(u"label_6") 506 | self.label_6.setFont(font) 507 | 508 | self.gridLayout.addWidget(self.label_6, 2, 0, 1, 1) 509 | 510 | self.pushButton_AccountsFile = QPushButton(self.groupBox_2) 511 | self.pushButton_AccountsFile.setObjectName(u"pushButton_AccountsFile") 512 | font4 = QFont() 513 | font4.setFamilies([u"Arial"]) 514 | font4.setBold(True) 515 | font4.setItalic(False) 516 | font4.setUnderline(False) 517 | font4.setKerning(False) 518 | self.pushButton_AccountsFile.setFont(font4) 519 | self.pushButton_AccountsFile.setAutoFillBackground(False) 520 | self.pushButton_AccountsFile.setStyleSheet(u"QPushButton {\n" 521 | " background-color: #3498db; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 */\n" 522 | " color: black; /* \u0426\u0432\u0435\u0442 \u0442\u0435\u043a\u0441\u0442\u0430 */\n" 523 | " border-radius: 10px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 524 | " border: 2px solid #2980b9; /* \u0413\u0440\u0430\u043d\u0438\u0446\u0430 \u043a\u043d\u043e\u043f\u043a\u0438 */\n" 525 | " font-size: 14px; /* \u0420\u0430\u0437\u043c\u0435\u0440 \u0442\u0435\u043a\u0441\u0442\u0430 */\n" 526 | "}\n" 527 | "\n" 528 | "QPushButton:hover {\n" 529 | " background-color: #2980b9; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 \u043f\u0440\u0438 \u043d\u0430\u0432\u0435\u0434\u0435\u043d\u0438\u0438 */\n" 530 | "}\n" 531 | "\n" 532 | "QPushButton:pressed {\n" 533 | " background-color: #1abc9c; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 */\n" 534 | "}\n" 535 | "") 536 | 537 | self.gridLayout.addWidget(self.pushButton_AccountsFile, 1, 2, 1, 1) 538 | 539 | self.pushButton_ProxyFile = QPushButton(self.groupBox_2) 540 | self.pushButton_ProxyFile.setObjectName(u"pushButton_ProxyFile") 541 | self.pushButton_ProxyFile.setFont(font3) 542 | self.pushButton_ProxyFile.setStyleSheet(u"QPushButton {\n" 543 | " background-color: #3498db; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 */\n" 544 | " color: black; /* \u0426\u0432\u0435\u0442 \u0442\u0435\u043a\u0441\u0442\u0430 */\n" 545 | " border-radius: 10px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 546 | " border: 2px solid #2980b9; /* \u0413\u0440\u0430\u043d\u0438\u0446\u0430 \u043a\u043d\u043e\u043f\u043a\u0438 */\n" 547 | " font-size: 14px; /* \u0420\u0430\u0437\u043c\u0435\u0440 \u0442\u0435\u043a\u0441\u0442\u0430 */\n" 548 | "}\n" 549 | "\n" 550 | "QPushButton:hover {\n" 551 | " background-color: #2980b9; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 \u043f\u0440\u0438 \u043d\u0430\u0432\u0435\u0434\u0435\u043d\u0438\u0438 */\n" 552 | "}\n" 553 | "\n" 554 | "QPushButton:pressed {\n" 555 | " background-color: #1abc9c; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 */\n" 556 | "}\n" 557 | "") 558 | 559 | self.gridLayout.addWidget(self.pushButton_ProxyFile, 2, 2, 1, 1) 560 | 561 | self.label_7 = QLabel(self.groupBox_2) 562 | self.label_7.setObjectName(u"label_7") 563 | self.label_7.setFont(font) 564 | 565 | self.gridLayout.addWidget(self.label_7, 3, 0, 1, 1) 566 | 567 | self.pushButton_Default = QPushButton(self.groupBox_2) 568 | self.pushButton_Default.setObjectName(u"pushButton_Default") 569 | self.pushButton_Default.setStyleSheet(u"QPushButton {\n" 570 | " background-color: #424242 ;\n" 571 | " border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 572 | "}\n" 573 | "\n" 574 | "QPushButton:hover {\n" 575 | " background-color: #595959; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 \u043f\u0440\u0438 \u043d\u0430\u0432\u0435\u0434\u0435\u043d\u0438\u0438 */\n" 576 | "}") 577 | 578 | self.gridLayout.addWidget(self.pushButton_Default, 0, 0, 1, 1) 579 | 580 | self.pushButton_ProxyDB = QPushButton(self.groupBox_2) 581 | self.pushButton_ProxyDB.setObjectName(u"pushButton_ProxyDB") 582 | self.pushButton_ProxyDB.setFont(font3) 583 | self.pushButton_ProxyDB.setStyleSheet(u"QPushButton {\n" 584 | " background-color: #3498db; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 */\n" 585 | " color: black; /* \u0426\u0432\u0435\u0442 \u0442\u0435\u043a\u0441\u0442\u0430 */\n" 586 | " border-radius: 10px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 587 | " border: 2px solid #2980b9; /* \u0413\u0440\u0430\u043d\u0438\u0446\u0430 \u043a\u043d\u043e\u043f\u043a\u0438 */\n" 588 | " font-size: 14px; /* \u0420\u0430\u0437\u043c\u0435\u0440 \u0442\u0435\u043a\u0441\u0442\u0430 */\n" 589 | "}\n" 590 | "\n" 591 | "QPushButton:hover {\n" 592 | " background-color: #2980b9; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 \u043f\u0440\u0438 \u043d\u0430\u0432\u0435\u0434\u0435\u043d\u0438\u0438 */\n" 593 | "}\n" 594 | "\n" 595 | "QPushButton:pressed {\n" 596 | " background-color: #1abc9c; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 \u043f\u0440\u0438 \u043d\u0430\u0436\u0430\u0442\u0438\u0438 */\n" 597 | "}\n" 598 | "") 599 | 600 | self.gridLayout.addWidget(self.pushButton_ProxyDB, 4, 2, 1, 1) 601 | 602 | self.label_8 = QLabel(self.groupBox_2) 603 | self.label_8.setObjectName(u"label_8") 604 | self.label_8.setFont(font) 605 | 606 | self.gridLayout.addWidget(self.label_8, 4, 0, 1, 1) 607 | 608 | 609 | self.gridLayout_4.addWidget(self.groupBox_2, 0, 3, 1, 1) 610 | 611 | self.pushButton_StartFarming = QPushButton(self.frame) 612 | self.pushButton_StartFarming.setObjectName(u"pushButton_StartFarming") 613 | font5 = QFont() 614 | font5.setFamilies([u"Arial"]) 615 | font5.setPointSize(13) 616 | font5.setBold(True) 617 | font5.setItalic(False) 618 | font5.setUnderline(False) 619 | self.pushButton_StartFarming.setFont(font5) 620 | self.pushButton_StartFarming.setStyleSheet(u"QPushButton {\n" 621 | " background-color: #3b4fac;\n" 622 | " border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 623 | "}\n" 624 | "\n" 625 | "QPushButton:hover {\n" 626 | " background-color: #30418c; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 \u043f\u0440\u0438 \u043d\u0430\u0432\u0435\u0434\u0435\u043d\u0438\u0438 */\n" 627 | "}") 628 | 629 | self.gridLayout_4.addWidget(self.pushButton_StartFarming, 1, 0, 1, 1) 630 | 631 | self.pushButton_Registration = QPushButton(self.frame) 632 | self.pushButton_Registration.setObjectName(u"pushButton_Registration") 633 | self.pushButton_Registration.setFont(font5) 634 | self.pushButton_Registration.setStyleSheet(u"QPushButton {\n" 635 | " background-color: #3b4fac;\n" 636 | " border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 637 | "}\n" 638 | "\n" 639 | "QPushButton:hover {\n" 640 | " background-color: #30418c; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 \u043f\u0440\u0438 \u043d\u0430\u0432\u0435\u0434\u0435\u043d\u0438\u0438 */\n" 641 | "}") 642 | 643 | self.gridLayout_4.addWidget(self.pushButton_Registration, 1, 1, 1, 1) 644 | 645 | self.textEdit_Log = QTextEdit(self.frame) 646 | self.textEdit_Log.setObjectName(u"textEdit_Log") 647 | self.textEdit_Log.setStyleSheet(u"background-color: #424242 ;\n" 648 | "border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */") 649 | 650 | self.gridLayout_4.addWidget(self.textEdit_Log, 2, 0, 1, 5) 651 | 652 | self.pushButton_Save = QPushButton(self.frame) 653 | self.pushButton_Save.setObjectName(u"pushButton_Save") 654 | self.pushButton_Save.setStyleSheet(u"QPushButton {\n" 655 | " background-color: #424242 ;\n" 656 | " border-radius: 5px; /* \u0417\u0430\u043a\u0440\u0443\u0433\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0433\u043b\u044b */\n" 657 | "}\n" 658 | "\n" 659 | "QPushButton:hover {\n" 660 | " background-color: #595959; /* \u0426\u0432\u0435\u0442 \u0444\u043e\u043d\u0430 \u043f\u0440\u0438 \u043d\u0430\u0432\u0435\u0434\u0435\u043d\u0438\u0438 */\n" 661 | "}") 662 | 663 | self.gridLayout_4.addWidget(self.pushButton_Save, 1, 3, 1, 1) 664 | 665 | 666 | self.gridLayout_5.addWidget(self.frame, 1, 0, 1, 4) 667 | 668 | MainWindow.setCentralWidget(self.centralwidget) 669 | self.menubar = QMenuBar(MainWindow) 670 | self.menubar.setObjectName(u"menubar") 671 | self.menubar.setGeometry(QRect(0, 0, 865, 21)) 672 | MainWindow.setMenuBar(self.menubar) 673 | self.statusbar = QStatusBar(MainWindow) 674 | self.statusbar.setObjectName(u"statusbar") 675 | MainWindow.setStatusBar(self.statusbar) 676 | 677 | self.retranslateUi(MainWindow) 678 | 679 | self.tabWidget.setCurrentIndex(0) 680 | 681 | 682 | QMetaObject.connectSlotsByName(MainWindow) 683 | # setupUi 684 | 685 | def retranslateUi(self, MainWindow): 686 | MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Grass", None)) 687 | #if QT_CONFIG(tooltip) 688 | MainWindow.setToolTip("") 689 | #endif // QT_CONFIG(tooltip) 690 | self.label_13.setText("") 691 | self.pushButton_Instructions.setText(QCoreApplication.translate("MainWindow", u"Instructions", None)) 692 | self.pushButton_more.setText(QCoreApplication.translate("MainWindow", u"Grass, Dawn, Gradient and more ...", None)) 693 | self.pushButton_Web3.setText(QCoreApplication.translate("MainWindow", u"Web3 products", None)) 694 | self.lineEdit_CapthaAPI.setPlaceholderText(QCoreApplication.translate("MainWindow", u"\u0412\u0432\u0435\u0434\u0438\u0442\u0435 API Key", None)) 695 | #if QT_CONFIG(tooltip) 696 | self.label_Threads.setToolTip(QCoreApplication.translate("MainWindow", u"for register account / claim rewards mode / approve email mode", None)) 697 | #endif // QT_CONFIG(tooltip) 698 | self.label_Threads.setText(QCoreApplication.translate("MainWindow", u"Threads:", None)) 699 | self.comboBox_CaptchaService.setItemText(0, QCoreApplication.translate("MainWindow", u"TWO_CAPTCHA", None)) 700 | self.comboBox_CaptchaService.setItemText(1, QCoreApplication.translate("MainWindow", u"ANTICAPTCHA", None)) 701 | self.comboBox_CaptchaService.setItemText(2, QCoreApplication.translate("MainWindow", u"CAPMONSTER", None)) 702 | self.comboBox_CaptchaService.setItemText(3, QCoreApplication.translate("MainWindow", u"CAPSOLVER", None)) 703 | self.comboBox_CaptchaService.setItemText(4, QCoreApplication.translate("MainWindow", u"CAPTCHAAI", None)) 704 | 705 | self.comboBox_CaptchaService.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Capthca service", None)) 706 | #if QT_CONFIG(tooltip) 707 | self.label_ImapDomain.setToolTip(QCoreApplication.translate("MainWindow", u"imap server domain (example: imap.firstmail.ltd for firstmail)", None)) 708 | #endif // QT_CONFIG(tooltip) 709 | self.label_ImapDomain.setText(QCoreApplication.translate("MainWindow", u"Imap Domain:", None)) 710 | #if QT_CONFIG(tooltip) 711 | self.label_EmailFolder.setToolTip(QCoreApplication.translate("MainWindow", u"folder where mails comes (example: SPAM INBOX JUNK etc.)", None)) 712 | #endif // QT_CONFIG(tooltip) 713 | self.label_EmailFolder.setText(QCoreApplication.translate("MainWindow", u"Email Folder:", None)) 714 | self.label.setText(QCoreApplication.translate("MainWindow", u"Register Delay:", None)) 715 | self.label_Captcha.setText(QCoreApplication.translate("MainWindow", u"Capthca:", None)) 716 | self.label_3.setText(QCoreApplication.translate("MainWindow", u"REF_CODE:", None)) 717 | #if QT_CONFIG(tooltip) 718 | self.label_MinProxiScore.setToolTip(QCoreApplication.translate("MainWindow", u"Put MIN_PROXY_SCORE = 0 not to check proxy score (if site is down)", None)) 719 | #endif // QT_CONFIG(tooltip) 720 | self.label_MinProxiScore.setText(QCoreApplication.translate("MainWindow", u"Min Proxy Score:", None)) 721 | self.label_2.setText(QCoreApplication.translate("MainWindow", u"to", None)) 722 | #if QT_CONFIG(tooltip) 723 | self.checkBox_ApproveEmail.setToolTip(QCoreApplication.translate("MainWindow", u"approve email (NEEDED IMAP AND ACCESS TO EMAIL)", None)) 724 | #endif // QT_CONFIG(tooltip) 725 | self.checkBox_ApproveEmail.setText(QCoreApplication.translate("MainWindow", u"Approve Email", None)) 726 | #if QT_CONFIG(tooltip) 727 | self.checkBox_ConnectWallet.setToolTip(QCoreApplication.translate("MainWindow", u"connect wallet (put private keys in wallets.txt)", None)) 728 | #endif // QT_CONFIG(tooltip) 729 | self.checkBox_ConnectWallet.setText(QCoreApplication.translate("MainWindow", u"Connect Wallet", None)) 730 | #if QT_CONFIG(tooltip) 731 | self.checkBox_SendWallerApproveForEmail.setToolTip(QCoreApplication.translate("MainWindow", u"send approve link to email", None)) 732 | #endif // QT_CONFIG(tooltip) 733 | self.checkBox_SendWallerApproveForEmail.setText(QCoreApplication.translate("MainWindow", u"Send Wallet Approve Link To Email", None)) 734 | #if QT_CONFIG(tooltip) 735 | self.checkBox_ApproveWalletOnEmail.setToolTip(QCoreApplication.translate("MainWindow", u"get approve link from email (NEEDED IMAP AND ACCESS TO EMAIL)", None)) 736 | #endif // QT_CONFIG(tooltip) 737 | self.checkBox_ApproveWalletOnEmail.setText(QCoreApplication.translate("MainWindow", u"Approve Wallet On Email", None)) 738 | #if QT_CONFIG(tooltip) 739 | self.checkBox_SemiAutoApproveLink.setToolTip(QCoreApplication.translate("MainWindow", u"if on - allow to manual paste approve link from email to cli", None)) 740 | #endif // QT_CONFIG(tooltip) 741 | self.checkBox_SemiAutoApproveLink.setText(QCoreApplication.translate("MainWindow", u"Semi Auto Approve Link", None)) 742 | #if QT_CONFIG(tooltip) 743 | self.checkBox_SingleMapAccount.setToolTip(QCoreApplication.translate("MainWindow", u"
If you have possibility to forward all approve mails to single IMAP address:
usage "name@domain.com:password"
", None)) 744 | #endif // QT_CONFIG(tooltip) 745 | self.checkBox_SingleMapAccount.setText(QCoreApplication.translate("MainWindow", u"Single Imap Account", None)) 746 | #if QT_CONFIG(tooltip) 747 | self.checkBox_UseProxyForImap.setToolTip(QCoreApplication.translate("MainWindow", u"Use proxy also for mail handling", None)) 748 | #endif // QT_CONFIG(tooltip) 749 | self.checkBox_UseProxyForImap.setText(QCoreApplication.translate("MainWindow", u"Use Proxy For Imap", None)) 750 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), QCoreApplication.translate("MainWindow", u"Reg. Settings", None)) 751 | #if QT_CONFIG(tooltip) 752 | self.checkBox_TimeOutFarm.setToolTip(QCoreApplication.translate("MainWindow", u"stop account for 20 minutes, to reduce proxy traffic usage", None)) 753 | #endif // QT_CONFIG(tooltip) 754 | self.checkBox_TimeOutFarm.setText(QCoreApplication.translate("MainWindow", u"Time Out Farm", None)) 755 | #if QT_CONFIG(tooltip) 756 | self.checkBox_CheckPoints.setToolTip(QCoreApplication.translate("MainWindow", u"show point for each account every nearly 10 minutes", None)) 757 | #endif // QT_CONFIG(tooltip) 758 | self.checkBox_CheckPoints.setText(QCoreApplication.translate("MainWindow", u"Check Points", None)) 759 | #if QT_CONFIG(tooltip) 760 | self.checkBox_RarelyShowLogs.setToolTip(QCoreApplication.translate("MainWindow", u"not always show info about actions to decrease pc influence", None)) 761 | #endif // QT_CONFIG(tooltip) 762 | self.checkBox_RarelyShowLogs.setText(QCoreApplication.translate("MainWindow", u"Rarely Show Logs", None)) 763 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), QCoreApplication.translate("MainWindow", u"Farm", None)) 764 | #if QT_CONFIG(tooltip) 765 | self.checkBox_ClaimRewardOnly.setToolTip(QCoreApplication.translate("MainWindow", u"claim tiers rewards only (https://app.getgrass.io/dashboard/referral-program)", None)) 766 | #endif // QT_CONFIG(tooltip) 767 | self.checkBox_ClaimRewardOnly.setText(QCoreApplication.translate("MainWindow", u"Claim Reward ONLY", None)) 768 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), QCoreApplication.translate("MainWindow", u"Rewards", None)) 769 | self.groupBox_2.setTitle(QCoreApplication.translate("MainWindow", u"Files", None)) 770 | self.label_AccountFile.setText(QCoreApplication.translate("MainWindow", u"Accounsts File", None)) 771 | self.pushButton_WalletsFile.setText(QCoreApplication.translate("MainWindow", u"Click", None)) 772 | self.label_6.setText(QCoreApplication.translate("MainWindow", u"Proxies FIile", None)) 773 | self.pushButton_AccountsFile.setText(QCoreApplication.translate("MainWindow", u"Click", None)) 774 | self.pushButton_ProxyFile.setText(QCoreApplication.translate("MainWindow", u"Click", None)) 775 | self.label_7.setText(QCoreApplication.translate("MainWindow", u"WALLETS_FILE", None)) 776 | self.pushButton_Default.setText(QCoreApplication.translate("MainWindow", u"Default", None)) 777 | self.pushButton_ProxyDB.setText(QCoreApplication.translate("MainWindow", u"Click", None)) 778 | self.label_8.setText(QCoreApplication.translate("MainWindow", u"PROXY_DB", None)) 779 | self.pushButton_StartFarming.setText(QCoreApplication.translate("MainWindow", u"Start Farming", None)) 780 | self.pushButton_Registration.setText(QCoreApplication.translate("MainWindow", u"Reg", None)) 781 | self.textEdit_Log.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Logs...", None)) 782 | self.pushButton_Save.setText(QCoreApplication.translate("MainWindow", u"Save", None)) 783 | # retranslateUi 784 | --------------------------------------------------------------------------------