You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

256 lines
8.7 KiB

import asyncio
import json
import random
import sys
import time
import traceback
import uuid
import aiohttp
from fake_useragent import UserAgent
from tenacity import stop_after_attempt, retry, retry_if_not_exception_type, wait_random, retry_if_exception_type
# 配置文件路径
ACCOUNTS_DETAILS_FILE_PATH = "account_details.txt"
ACCOUNTS_FILE_PATH = "accounts.txt"
PROXIES_FILE_PATH = "proxies.txt"
MIN_PROXY_SCORE = 50
# 日志记录
class Logger:
@staticmethod
def info(message):
print(f"[INFO] {message}")
@staticmethod
def error(message):
print(f"[ERROR] {message}")
@staticmethod
def success(message):
print(f"[SUCCESS] {message}")
logger = Logger()
# 异常类
class ProxyForbiddenException(Exception):
pass
class LowProxyScoreException(Exception):
pass
class ProxyScoreNotFoundException(Exception):
pass
class WebsocketClosedException(Exception):
pass
# Grass 类,负责登录、检测代理分数和持久连接
class Grass:
def __init__(self, _id: int, email: str, user_id: str, proxy: str = None):
self.proxy = f"socks5://{proxy}" if proxy else None # 添加协议
self.email = email
self.user_id = user_id
self.user_agent = UserAgent().random
self.proxy_score = None
self.id = _id
self.session = aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(ssl=False))
self.all_proxies_name = []
async def start(self):
try:
user_id = self.user_id
browser_id = str(self.email) # 使用邮箱作为浏览器ID
await self.run(browser_id, user_id)
except Exception as e:
logger.error(f"{self.id} | Error: {e}")
finally:
await self.close()
async def run(self, browser_id: str, user_id: str):
while True:
try:
await self.connection_handler()
await self.auth_to_extension(browser_id, user_id)
if self.proxy_score is None:
await asyncio.sleep(1)
await self.handle_proxy_score(MIN_PROXY_SCORE)
while True:
await self.send_ping()
await self.send_pong()
logger.info(f"{self.id} | Mined grass.")
await asyncio.sleep(19.9)
except WebsocketClosedException as e:
logger.info(f"Websocket closed: {e}. Retrying...")
except ConnectionResetError as e:
logger.info(f"Connection reset: {e}. Retrying...")
except TypeError as e:
logger.info(f"Type error: {e}. Retrying...")
await asyncio.sleep(1)
@retry(stop=stop_after_attempt(30),
retry=(retry_if_exception_type(ConnectionError) | retry_if_not_exception_type(ProxyForbiddenException)),
wait=wait_random(0.5, 1),
reraise=True)
async def connection_handler(self):
logger.info(f"{self.id} | Connecting...")
await self.connect()
logger.info(f"{self.id} | Connected")
@retry(stop=stop_after_attempt(10),
retry=retry_if_not_exception_type(LowProxyScoreException),
before_sleep=lambda retry_state, **kwargs: logger.info(f"{retry_state.outcome.exception()}"),
wait=wait_random(5, 7),
reraise=True)
async def handle_proxy_score(self, min_score: int):
if (proxy_score := await self.get_proxy_score_by_device_id()) is None:
raise ProxyScoreNotFoundException(f"{self.id} | Proxy score not found for {self.proxy}. Guess Bad proxies!")
elif proxy_score >= min_score:
self.proxy_score = proxy_score
logger.success(f"{self.id} | Proxy score: {self.proxy_score}")
return True
else:
raise LowProxyScoreException(f"{self.id} | Too low proxy score: {proxy_score} for {self.proxy}. Exit...")
async def auth_to_extension(self, browser_id: str, user_id: str):
connection_id = await self.get_connection_id()
message = json.dumps(
{
"id": connection_id,
"origin_action": "AUTH",
"result": {
"browser_id": browser_id,
"user_id": user_id,
"user_agent": self.user_agent,
"timestamp": int(time.time()),
"device_type": "extension",
"version": "4.26.2"
}
}
)
print(message)
await self.send_message(message)
async def connect(self):
uri = "wss://proxy.wynd.network"
headers = {
'Pragma': 'no-cache',
'Origin': 'chrome-extension://ilehaonighjijnmpnagapkhpcdbhclfg',
'Accept-Language': 'uk-UA,uk;q=0.9,en-US;q=0.8,en;q=0.7',
'User-Agent': self.user_agent,
'Upgrade': 'websocket',
'Cache-Control': 'no-cache',
'Connection': 'Upgrade',
'Sec-WebSocket-Version': '13',
'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits',
}
try:
self.websocket = await self.session.ws_connect(uri, proxy_headers=headers, proxy=self.proxy)
except Exception as e:
if 'status' in dir(e) and e.status == 403:
raise ProxyForbiddenException(f"Low proxy score. Can't connect. Error: {e}")
raise e
async def get_proxy_score_by_device_id(self):
url = 'https://api.getgrass.io/extension/user-score'
headers = {
'authority': 'api.getgrass.io',
'accept': 'application/json, text/plain, */*',
'accept-language': 'uk-UA,uk;q=0.9,en-US;q=0.8,en;q=0.7',
'content-type': 'application/json',
'origin': 'https://app.getgrass.io',
'referer': 'https://app.getgrass.io/',
'sec-ch-ua': '"Not(A:Brand";v="99", "Brave";v="133", "Chromium";v="133"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none',
'user-agent': self.user_agent,
}
response = await self.session.get(url, headers=headers, proxy=self.proxy)
res_json = await response.json()
print(res_json)
if not (isinstance(res_json, dict) and res_json.get("data", None) is not None):
return
devices = res_json['data']['currentDeviceData']
self.ip = await self.get_ip()
logger.info(f"当前代理IP: {self.ip}")
return next((device['final_score'] for device in devices
if device['device_ip'] == self.ip), None)
async def send_message(self, message):
await self.websocket.send_str(message)
async def receive_message(self):
msg = await self.websocket.receive()
if msg.type == aiohttp.WSMsgType.CLOSED:
raise WebsocketClosedException(f"Websocket closed: {msg}")
return msg.data
async def get_connection_id(self):
msg = await self.receive_message()
return json.loads(msg)['id']
async def send_ping(self):
message = json.dumps(
{"id": str(uuid.uuid4()), "version": "1.0.0", "action": "PING", "data": {}}
)
await self.send_message(message)
async def send_pong(self):
connection_id = await self.get_connection_id()
message = json.dumps(
{"id": connection_id, "origin_action": "PONG"}
)
await self.send_message(message)
async def get_ip(self):
return await (await self.session.get('https://api.ipify.org', proxy=self.proxy)).text()
async def close(self):
if self.session:
await self.session.close()
# 主函数
async def main():
account_details = []
with open(ACCOUNTS_DETAILS_FILE_PATH, 'r') as f:
account_details = f.readlines()
grass_instances = []
tasks = []
for i, account in enumerate(account_details):
email, user_id, proxy_web, proxy_api = account.strip().split("|||")
grass = Grass(i, email, user_id, proxy_api)
grass_instances.append(grass)
tasks.append(grass.start())
try:
await asyncio.gather(*tasks)
except Exception as e:
logger.error(f"An error occurred: {e}")
finally:
# 确保所有会话都被关闭
for grass in grass_instances:
await grass.close()
if __name__ == "__main__":
if sys.platform == 'win32':
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
loop = asyncio.ProactorEventLoop()
asyncio.set_event_loop(loop)
loop.run_until_complete(main())
else:
asyncio.run(main())