111 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
		
		
			
		
	
	
			111 lines
		
	
	
		
			3.3 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
|   | import json | ||
|  | import sys | ||
|  | from typing import Any, Collection, Dict, Iterable, Optional | ||
|  | from websockets import ConnectionClosed | ||
|  | from websockets.sync.client import connect, ClientConnection | ||
|  | from threading import Thread | ||
|  | 
 | ||
|  | 
 | ||
|  | __all__ = [ | ||
|  |     "Client" | ||
|  | ] | ||
|  | 
 | ||
|  | 
 | ||
|  | class Client: | ||
|  |     """Incomplete, minimalistic sync test client for AP network protocol""" | ||
|  | 
 | ||
|  |     recv_timeout = 1.0 | ||
|  | 
 | ||
|  |     host: str | ||
|  |     game: str | ||
|  |     slot: str | ||
|  |     password: Optional[str] | ||
|  | 
 | ||
|  |     _ws: Optional[ClientConnection] | ||
|  | 
 | ||
|  |     games: Iterable[str] | ||
|  |     data_package_checksums: Dict[str, Any] | ||
|  |     games_packages: Dict[str, Any] | ||
|  |     missing_locations: Collection[int] | ||
|  |     checked_locations: Collection[int] | ||
|  | 
 | ||
|  |     def __init__(self, host: str, game: str, slot: str, password: Optional[str] = None) -> None: | ||
|  |         self.host = host | ||
|  |         self.game = game | ||
|  |         self.slot = slot | ||
|  |         self.password = password | ||
|  |         self._ws = None | ||
|  |         self.games = [] | ||
|  |         self.data_package_checksums = {} | ||
|  |         self.games_packages = {} | ||
|  |         self.missing_locations = [] | ||
|  |         self.checked_locations = [] | ||
|  | 
 | ||
|  |     def __enter__(self) -> "Client": | ||
|  |         try: | ||
|  |             self.connect() | ||
|  |         except BaseException: | ||
|  |             self.__exit__(*sys.exc_info()) | ||
|  |             raise | ||
|  |         return self | ||
|  | 
 | ||
|  |     def __exit__(self, exc_type, exc_val, exc_tb) -> None:  # type: ignore | ||
|  |         self.close() | ||
|  | 
 | ||
|  |     def _poll(self) -> None: | ||
|  |         assert self._ws | ||
|  |         try: | ||
|  |             while True: | ||
|  |                 self._ws.recv() | ||
|  |         except (TimeoutError, ConnectionClosed, KeyboardInterrupt, SystemExit): | ||
|  |             pass | ||
|  | 
 | ||
|  |     def connect(self) -> None: | ||
|  |         self._ws = connect(f"ws://{self.host}") | ||
|  |         room_info = json.loads(self._ws.recv(self.recv_timeout))[0] | ||
|  |         self.games = sorted(room_info["games"]) | ||
|  |         self.data_package_checksums = room_info["datapackage_checksums"] | ||
|  |         self._ws.send(json.dumps([{ | ||
|  |             "cmd": "GetDataPackage", | ||
|  |             "games": list(self.games), | ||
|  |         }])) | ||
|  |         data_package_msg = json.loads(self._ws.recv(self.recv_timeout))[0] | ||
|  |         self.games_packages = data_package_msg["data"]["games"] | ||
|  |         self._ws.send(json.dumps([{ | ||
|  |             "cmd": "Connect", | ||
|  |             "game": self.game, | ||
|  |             "name": self.slot, | ||
|  |             "password": self.password, | ||
|  |             "uuid": "", | ||
|  |             "version": { | ||
|  |                 "class": "Version", | ||
|  |                 "major": 0, | ||
|  |                 "minor": 4, | ||
|  |                 "build": 6, | ||
|  |             }, | ||
|  |             "items_handling": 0, | ||
|  |             "tags": [], | ||
|  |             "slot_data": False, | ||
|  |         }])) | ||
|  |         connect_result_msg = json.loads(self._ws.recv(self.recv_timeout))[0] | ||
|  |         if connect_result_msg["cmd"] != "Connected": | ||
|  |             raise ConnectionError(", ".join(connect_result_msg.get("errors", [connect_result_msg["cmd"]]))) | ||
|  |         self.missing_locations = connect_result_msg["missing_locations"] | ||
|  |         self.checked_locations = connect_result_msg["checked_locations"] | ||
|  | 
 | ||
|  |     def close(self) -> None: | ||
|  |         if self._ws: | ||
|  |             Thread(target=self._poll).start() | ||
|  |             self._ws.close() | ||
|  | 
 | ||
|  |     def collect(self, locations: Iterable[int]) -> None: | ||
|  |         if not self._ws: | ||
|  |             raise ValueError("Not connected") | ||
|  |         self._ws.send(json.dumps([{ | ||
|  |             "cmd": "LocationChecks", | ||
|  |             "locations": locations, | ||
|  |         }])) | ||
|  | 
 | ||
|  |     def collect_any(self) -> None: | ||
|  |         self.collect([next(iter(self.missing_locations))]) |