main.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. import asyncio
  2. import json
  3. import random
  4. import sys
  5. import time
  6. import traceback
  7. import uuid
  8. import aiohttp
  9. from fake_useragent import UserAgent
  10. from tenacity import stop_after_attempt, retry, retry_if_not_exception_type, wait_random, retry_if_exception_type
  11. # 配置文件路径
  12. ACCOUNTS_FILE_PATH = "accounts.txt"
  13. PROXIES_FILE_PATH = "proxies.txt"
  14. MIN_PROXY_SCORE = 50
  15. # 日志记录
  16. class Logger:
  17. @staticmethod
  18. def info(message):
  19. print(f"[INFO] {message}")
  20. @staticmethod
  21. def error(message):
  22. print(f"[ERROR] {message}")
  23. @staticmethod
  24. def success(message):
  25. print(f"[SUCCESS] {message}")
  26. logger = Logger()
  27. # 异常类
  28. class ProxyForbiddenException(Exception):
  29. pass
  30. class LowProxyScoreException(Exception):
  31. pass
  32. class ProxyScoreNotFoundException(Exception):
  33. pass
  34. class WebsocketClosedException(Exception):
  35. pass
  36. # Grass 类,负责登录、检测代理分数和持久连接
  37. class Grass:
  38. def __init__(self, _id: int, email: str, password: str, proxy: str = None):
  39. self.proxy = f"http://{proxy}" if proxy else None # 添加协议
  40. self.email = email
  41. self.password = password
  42. self.user_agent = UserAgent().random
  43. self.proxy_score = None
  44. self.id = _id
  45. self.session = aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(ssl=False))
  46. async def start(self):
  47. try:
  48. user_id = await self.login()
  49. browser_id = str(self.email) # 使用邮箱作为浏览器ID
  50. await self.run(browser_id, user_id)
  51. except Exception as e:
  52. logger.error(f"{self.id} | Error: {e}")
  53. finally:
  54. await self.close()
  55. async def run(self, browser_id: str, user_id: str):
  56. while True:
  57. try:
  58. await self.connection_handler()
  59. await self.auth_to_extension(browser_id, user_id)
  60. if self.proxy_score is None:
  61. await asyncio.sleep(1)
  62. await self.handle_proxy_score(MIN_PROXY_SCORE)
  63. while True:
  64. await self.send_ping()
  65. await self.send_pong()
  66. logger.info(f"{self.id} | Mined grass.")
  67. await asyncio.sleep(19.9)
  68. except WebsocketClosedException as e:
  69. logger.info(f"Websocket closed: {e}. Retrying...")
  70. except ConnectionResetError as e:
  71. logger.info(f"Connection reset: {e}. Retrying...")
  72. except TypeError as e:
  73. logger.info(f"Type error: {e}. Retrying...")
  74. await asyncio.sleep(1)
  75. @retry(stop=stop_after_attempt(30),
  76. retry=(retry_if_exception_type(ConnectionError) | retry_if_not_exception_type(ProxyForbiddenException)),
  77. wait=wait_random(0.5, 1),
  78. reraise=True)
  79. async def connection_handler(self):
  80. logger.info(f"{self.id} | Connecting...")
  81. await self.connect()
  82. logger.info(f"{self.id} | Connected")
  83. @retry(stop=stop_after_attempt(10),
  84. retry=retry_if_not_exception_type(LowProxyScoreException),
  85. before_sleep=lambda retry_state, **kwargs: logger.info(f"{retry_state.outcome.exception()}"),
  86. wait=wait_random(5, 7),
  87. reraise=True)
  88. async def handle_proxy_score(self, min_score: int):
  89. if (proxy_score := await self.get_proxy_score_by_device_id()) is None:
  90. raise ProxyScoreNotFoundException(f"{self.id} | Proxy score not found for {self.proxy}. Guess Bad proxies!")
  91. elif proxy_score >= min_score:
  92. self.proxy_score = proxy_score
  93. logger.success(f"{self.id} | Proxy score: {self.proxy_score}")
  94. return True
  95. else:
  96. raise LowProxyScoreException(f"{self.id} | Too low proxy score: {proxy_score} for {self.proxy}. Exit...")
  97. async def connect(self):
  98. uri = "wss://proxy.wynd.network:4444/"
  99. headers = {
  100. 'Pragma': 'no-cache',
  101. 'Origin': 'chrome-extension://ilehaonighjijnmpnagapkhpcdbhclfg',
  102. 'Accept-Language': 'uk-UA,uk;q=0.9,en-US;q=0.8,en;q=0.7',
  103. 'User-Agent': self.user_agent,
  104. 'Upgrade': 'websocket',
  105. 'Cache-Control': 'no-cache',
  106. 'Connection': 'Upgrade',
  107. 'Sec-WebSocket-Version': '13',
  108. 'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits',
  109. }
  110. try:
  111. self.websocket = await self.session.ws_connect(uri, proxy_headers=headers, proxy=self.proxy)
  112. except Exception as e:
  113. if 'status' in dir(e) and e.status == 403:
  114. raise ProxyForbiddenException(f"Low proxy score. Can't connect. Error: {e}")
  115. raise e
  116. async def send_message(self, message):
  117. await self.websocket.send_str(message)
  118. async def receive_message(self):
  119. msg = await self.websocket.receive()
  120. if msg.type == aiohttp.WSMsgType.CLOSED:
  121. raise WebsocketClosedException(f"Websocket closed: {msg}")
  122. return msg.data
  123. async def get_connection_id(self):
  124. msg = await self.receive_message()
  125. return json.loads(msg)['id']
  126. async def auth_to_extension(self, browser_id: str, user_id: str):
  127. connection_id = await self.get_connection_id()
  128. message = json.dumps(
  129. {
  130. "id": connection_id,
  131. "origin_action": "AUTH",
  132. "result": {
  133. "browser_id": browser_id,
  134. "user_id": user_id,
  135. "user_agent": self.user_agent,
  136. "timestamp": int(time.time()),
  137. "device_type": "extension",
  138. "version": "3.3.2"
  139. }
  140. }
  141. )
  142. await self.send_message(message)
  143. async def send_ping(self):
  144. message = json.dumps(
  145. {"id": str(uuid.uuid4()), "version": "1.0.0", "action": "PING", "data": {}}
  146. )
  147. await self.send_message(message)
  148. async def send_pong(self):
  149. connection_id = await self.get_connection_id()
  150. message = json.dumps(
  151. {"id": connection_id, "origin_action": "PONG"}
  152. )
  153. await self.send_message(message)
  154. async def login(self):
  155. url = 'https://api.getgrass.io/login'
  156. json_data = {
  157. 'password': self.password,
  158. 'username': self.email,
  159. }
  160. headers = {
  161. 'authority': 'api.getgrass.io',
  162. 'accept': 'application/json, text/plain, */*',
  163. 'accept-language': 'uk-UA,uk;q=0.9,en-US;q=0.8,en;q=0.7',
  164. 'content-type': 'application/json',
  165. 'origin': 'https://app.getgrass.io',
  166. 'referer': 'https://app.getgrass.io/',
  167. 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
  168. 'sec-ch-ua-mobile': '?0',
  169. 'sec-ch-ua-platform': '"Windows"',
  170. 'sec-fetch-dest': 'empty',
  171. 'sec-fetch-mode': 'cors',
  172. 'sec-fetch-site': 'same-site',
  173. 'user-agent': self.user_agent,
  174. }
  175. response = await self.session.post(url, headers=headers, json=json_data, proxy=self.proxy)
  176. if response.status != 200:
  177. raise aiohttp.ClientConnectionError(f"login | {await response.text()}")
  178. return await response.json()
  179. async def get_proxy_score_by_device_id(self):
  180. url = 'https://api.getgrass.io/extension/user-score'
  181. headers = {
  182. 'authority': 'api.getgrass.io',
  183. 'accept': 'application/json, text/plain, */*',
  184. 'accept-language': 'uk-UA,uk;q=0.9,en-US;q=0.8,en;q=0.7',
  185. 'content-type': 'application/json',
  186. 'origin': 'https://app.getgrass.io',
  187. 'referer': 'https://app.getgrass.io/',
  188. 'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
  189. 'sec-ch-ua-mobile': '?0',
  190. 'sec-ch-ua-platform': '"Windows"',
  191. 'sec-fetch-dest': 'empty',
  192. 'sec-fetch-mode': 'cors',
  193. 'sec-fetch-site': 'same-site',
  194. 'user-agent': self.user_agent,
  195. }
  196. response = await self.session.get(url, headers=headers, proxy=self.proxy)
  197. res_json = await response.json()
  198. if not (isinstance(res_json, dict) and res_json.get("data", None) is not None):
  199. return
  200. devices = res_json['data']['currentDeviceData']
  201. self.ip = await self.get_ip()
  202. return next((device['final_score'] for device in devices
  203. if device['device_ip'] == self.ip), None)
  204. async def get_ip(self):
  205. return await (await self.session.get('https://api.ipify.org', proxy=self.proxy)).text()
  206. async def close(self):
  207. if self.session:
  208. await self.session.close()
  209. # 主函数
  210. async def main():
  211. accounts = []
  212. with open(ACCOUNTS_FILE_PATH, 'r') as f:
  213. accounts = f.readlines()
  214. proxies = []
  215. with open(PROXIES_FILE_PATH, 'r') as f:
  216. proxies = f.readlines()
  217. grass_instances = []
  218. tasks = []
  219. for i, account in enumerate(accounts):
  220. email, password = account.strip().split(":")
  221. proxy = proxies[i % len(proxies)].strip() if proxies else None
  222. grass = Grass(i, email, password, proxy)
  223. grass_instances.append(grass)
  224. tasks.append(grass.start())
  225. try:
  226. await asyncio.gather(*tasks)
  227. except Exception as e:
  228. logger.error(f"An error occurred: {e}")
  229. finally:
  230. # 确保所有会话都被关闭
  231. for grass in grass_instances:
  232. await grass.close()
  233. if __name__ == "__main__":
  234. if sys.platform == 'win32':
  235. asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
  236. loop = asyncio.ProactorEventLoop()
  237. asyncio.set_event_loop(loop)
  238. loop.run_until_complete(main())
  239. else:
  240. asyncio.run(main())