vs_cloud_api.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. # toolkit/vs_cloud_api.py
  2. import requests
  3. import json
  4. import time
  5. import urllib.parse
  6. from typing import Dict, Any, Optional
  7. from vs_log_macros import VSC_ERROR, VSC_INFO, VSC_DEBUG
  8. class VSCloudApi:
  9. """
  10. @brief VSCloudApi 的 Python 实现
  11. 用于对接云端服务 (打码、邮件、Session存储、任务调度)
  12. """
  13. _instance = None
  14. def __new__(cls, *args, **kwargs):
  15. if cls._instance is None:
  16. cls._instance = super(VSCloudApi, cls).__new__(cls)
  17. # 初始化默认配置
  18. cls._instance.base_url = "http://45.137.220.138:8888"
  19. cls._instance.api_token = "Bearer tok_8cb26cf337cb405784eb346dfafb7f54"
  20. cls._instance.session = requests.Session()
  21. return cls._instance
  22. @staticmethod
  23. def Instance():
  24. return VSCloudApi()
  25. def _get_headers(self, content_type: str = "application/json") -> Dict[str, str]:
  26. return {
  27. "Authorization": self.api_token,
  28. "Content-Type": content_type,
  29. "Accept": "application/json, text/plain, */*"
  30. }
  31. # =========================================================================
  32. # VAS Task Management (新增 API)
  33. # =========================================================================
  34. def get_vas_task_pop(self, routing_key: str) -> Optional[Dict]:
  35. """
  36. 获取任务列表
  37. API: GET /api/vas/task/list
  38. """
  39. url = f"{self.base_url}/api/vas/task/pop"
  40. params = {
  41. "queue_name": routing_key,
  42. }
  43. headers = self._get_headers()
  44. try:
  45. resp = self.session.get(url, params=params, headers=headers, timeout=30)
  46. if resp.status_code == 200:
  47. result = resp.json()
  48. if result.get("code") == 0:
  49. return result.get("data", {})
  50. else:
  51. VSC_ERROR("vs_cloud", f"get_vas_task_pop biz error: {result.get('message')}")
  52. else:
  53. VSC_ERROR("vs_cloud", f"get_vas_task_pop failed: {resp.status_code} {resp.text[:100]}")
  54. except Exception as e:
  55. VSC_ERROR("vs_cloud", f"get_vas_task_pop exception: {e}")
  56. return False
  57. def update_vas_task(self,
  58. task_id: int,
  59. update_data: Dict[str, Any]) -> Optional[Dict]:
  60. """
  61. 更新任务
  62. API: POST /api/vas/task/update?id=1
  63. Body: update_data (json)
  64. """
  65. url = f"{self.base_url}/api/vas/task/update"
  66. params = {"id": task_id}
  67. headers = self._get_headers()
  68. try:
  69. resp = self.session.post(url, params=params, json=update_data, headers=headers, timeout=30)
  70. if resp.status_code == 200:
  71. result = resp.json()
  72. if result.get("code") == 0:
  73. return result.get("data", {})
  74. else:
  75. VSC_ERROR("vs_cloud", f"update_vas_task biz error: {result.get('message')}")
  76. else:
  77. VSC_ERROR("vs_cloud", f"update_vas_task failed: {resp.status_code} {resp.text[:100]}")
  78. except Exception as e:
  79. VSC_ERROR("vs_cloud", f"update_vas_task exception: {e}")
  80. return None
  81. def submit_anti_turnstile_task(self, proxy: str, website_url: str) -> Optional[Dict]:
  82. """
  83. 提交反 Turnstile 任务
  84. """
  85. url = f"{self.base_url}/api/tasks"
  86. headers = self._get_headers()
  87. args = {
  88. "proxy": proxy,
  89. "websiteUrl": website_url
  90. }
  91. payload = {
  92. "command": "AntiCloudflareTurnstileTask",
  93. "args": json.dumps(args),
  94. "status": 0
  95. }
  96. try:
  97. resp = self.session.post(url, headers=headers, json=payload, timeout=30)
  98. if resp.status_code == 200:
  99. result = resp.json()
  100. if result.get("code") == 0:
  101. return result.get("data", {})
  102. else:
  103. VSC_ERROR("vs_cloud", f"update_vas_task biz error: {result.get('message')}")
  104. else:
  105. VSC_ERROR("vs_cloud", f"submit_anti_turnstile_task failed: {resp.status_code} {resp.text[:100]}")
  106. except Exception as e:
  107. VSC_ERROR("vs_cloud", f"submit_anti_turnstile_task exception: {e}")
  108. return None
  109. def get_anti_turnstile_result(self, task_id: str) -> Optional[Dict]:
  110. """获取反 Turnstile 结果"""
  111. url = f"{self.base_url}/api/tasks/{task_id}"
  112. headers = self._get_headers()
  113. try:
  114. resp = self.session.get(url, headers=headers, timeout=30)
  115. if resp.status_code == 200:
  116. result = resp.json()
  117. if result.get("code") == 0:
  118. return result.get("data", {})
  119. else:
  120. VSC_ERROR("vs_cloud", f"update_vas_task biz error: {result.get('message')}")
  121. else:
  122. VSC_ERROR("vs_cloud", f"get_anti_turnstile_result failed: {resp.status_code} {resp.text[:100]}")
  123. except Exception as e:
  124. VSC_ERROR("vs_cloud", f"get_anti_turnstile_result exception: {e}")
  125. return None
  126. def submit_anticloudflare_task(self, proxy: str, website_url: str) -> Optional[Dict]:
  127. """
  128. 提交 AntiCloudflareTask (用于 TLSContact 5s 盾)
  129. """
  130. url = f"{self.base_url}/api/tasks"
  131. args = {
  132. 'proxy': proxy,
  133. 'websiteUrl': website_url
  134. }
  135. data = {
  136. "command": "AntiCloudflareTask",
  137. "args": json.dumps(args),
  138. "status": 0
  139. }
  140. headers = self._get_headers()
  141. try:
  142. response = self.session.post(url, headers=headers, json=data, timeout=30)
  143. if response.status_code == 200:
  144. result = response.json()
  145. if result.get("code") == 0:
  146. return result.get("data", {})
  147. else:
  148. VSC_ERROR("vs_cloud", f"update_vas_task biz error: {result.get('message')}")
  149. else:
  150. VSC_ERROR("vs_cloud", f"get_anti_turnstile_result failed: {response.status_code} {response.text[:100]}")
  151. except Exception as e:
  152. VSC_ERROR("vs_cloud", f"submit_anticloudflare_task exception: {e}")
  153. return None
  154. def get_anticloudflare_result(self, task_id, retry_interval=5, max_retries=20) -> Optional[Dict]:
  155. """
  156. 获取 AntiCloudflareTask 结果 (带轮询)
  157. """
  158. url = f"{self.base_url}/api/tasks/{task_id}"
  159. headers = self._get_headers()
  160. try:
  161. for attempt in range(max_retries):
  162. response = self.session.get(url, headers=headers, timeout=30)
  163. if response.status_code == 200:
  164. result = response.json()
  165. if result.get("code") == 0:
  166. data = result.get("data", {})
  167. # status 2 表示成功
  168. if data.get("status") == 2:
  169. return data
  170. elif data.get("status") == 3:
  171. VSC_ERROR("vs_cloud", f"AntiCloudflareTask failed: {data.get('result')}")
  172. return None
  173. else:
  174. time.sleep(retry_interval)
  175. else:
  176. VSC_ERROR("vs_cloud", f"update_vas_task biz error: {result.get('message')}")
  177. else:
  178. VSC_ERROR("vs_cloud", f"get_anti_turnstile_result failed: {response.status_code} {response.text[:100]}")
  179. VSC_ERROR("vs_cloud", "Max retries reached, AntiCloudflareTask not completed.")
  180. except Exception as e:
  181. VSC_ERROR("vs_cloud", f"get_anticloudflare_result exception: {e}")
  182. return None
  183. def create_http_session(
  184. self,
  185. session_id: str,
  186. cookies: str,
  187. local_storage: str,
  188. user_agent: str,
  189. proxy: str,
  190. page: str
  191. ) -> Optional[Dict]:
  192. """创建 http session"""
  193. url = f"{self.base_url}/api/http-session"
  194. headers = self._get_headers()
  195. payload = {
  196. "local_storage": local_storage,
  197. "cookies": cookies,
  198. "user_agent": user_agent,
  199. "proxy": proxy,
  200. "page": page,
  201. "session_id": session_id
  202. }
  203. try:
  204. resp = self.session.post(url, headers=headers, json=payload, timeout=30)
  205. if resp.status_code == 200:
  206. result = resp.json()
  207. if result.get("code") == 0:
  208. return result.get("data", {})
  209. else:
  210. VSC_ERROR("vs_cloud", f"update_vas_task biz error: {result.get('message')}")
  211. else:
  212. VSC_ERROR("vs_cloud", f"create_http_session failed: {resp.status_code} {resp.text[:100]}")
  213. except Exception as e:
  214. VSC_ERROR("vs_cloud", f"create_http_session exception: {e}")
  215. return None
  216. def fetch_mail_content(
  217. self,
  218. email: str,
  219. sender: str,
  220. recipient: str,
  221. subject_keywords: str,
  222. body_keywords: str,
  223. sent_date: str,
  224. expiry: int
  225. ) -> Optional[str]:
  226. """
  227. 获取邮件内容
  228. """
  229. params = {
  230. "email": email,
  231. "sender": sender,
  232. "recipient": recipient,
  233. "subjectKeywords": subject_keywords,
  234. "bodyKeywords": body_keywords,
  235. "sentDate": sent_date,
  236. "expiry": str(expiry)
  237. }
  238. url = f"{self.base_url}/api/email-authorizations/fetch"
  239. headers = self._get_headers()
  240. try:
  241. resp = self.session.post(url, headers=headers, params=params, data="", timeout=30)
  242. if resp.status_code == 200:
  243. result = resp.json()
  244. if result.get('code', 0) == 0:
  245. data = result.get('data', {})
  246. return data.get('body', "")
  247. else:
  248. VSC_ERROR("vs_cloud", f"fetch_mail_content biz error: {result.get('message')}")
  249. else:
  250. VSC_ERROR("vs_cloud", f"fetch_mail_content failed: {resp.status_code} {resp.text[:100]}")
  251. except Exception as e:
  252. VSC_ERROR("vs_cloud", f"fetch_mail_content exception: {e}")
  253. return None
  254. def fetch_mail_content_from_top(
  255. self,
  256. email: str,
  257. sender: str,
  258. recipient: str,
  259. subject_keywords: str,
  260. body_keywords: str,
  261. top: int
  262. ) -> Optional[str]:
  263. """从顶部获取邮件内容"""
  264. params = {
  265. "email": email,
  266. "sender": sender,
  267. "recipient": recipient,
  268. "subjectKeywords": subject_keywords,
  269. "bodyKeywords": body_keywords,
  270. "top": str(top)
  271. }
  272. url = f"{self.base_url}/api/email-authorizations/fetch-top"
  273. headers = self._get_headers()
  274. try:
  275. resp = self.session.post(url, headers=headers, params=params, data="", timeout=30)
  276. if resp.status_code == 200:
  277. result = resp.json()
  278. if result.get('code', 0) == 0:
  279. data = result.get('data', {})
  280. return data.get('body', "")
  281. else:
  282. VSC_ERROR("vs_cloud", f"fetch_mail_content_from_top biz error: {result.get('message')}")
  283. else:
  284. VSC_ERROR("vs_cloud", f"fetch_mail_content_from_top failed: {resp.status_code} {resp.text[:100]}")
  285. except Exception as e:
  286. VSC_ERROR("vs_cloud", f"fetch_mail_content_from_top exception: {e}")
  287. return None