Skip to content
This repository was archived by the owner on Apr 6, 2026. It is now read-only.

Commit 647b30d

Browse files
committed
add reconnection
1 parent 3ab18a0 commit 647b30d

1 file changed

Lines changed: 76 additions & 5 deletions

File tree

MaxBridge/max_api.py

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ def __init__(self, auth_token: str = None, on_event=None):
5454
}
5555
self.user = None
5656
self.chats = {}
57+
self.subscribed_chats = set()
5758

5859
self.ws = None
5960
self.ioloop = None
@@ -144,21 +145,87 @@ def _connect_and_run(self):
144145

145146
@tornado.gen.coroutine
146147
def _listener_loop_async(self):
147-
"""Asynchronously listens for all incoming messages."""
148+
"""Asynchronously listens for all incoming messages. Auto-reconnects on server close."""
148149
try:
149150
while self.is_running:
150151
message = yield self.ws.read_message()
151152
if message is None:
152153
if self.is_running:
153-
self.logger.warning("Connection closed by server.")
154+
self.logger.warning("Connection closed by server. Attempting to reconnect...")
155+
# Trigger reconnect in async context
156+
yield self._reconnect_async()
154157
break
155158
self._process_message(message)
156159
except tornado.websocket.WebSocketClosedError:
157-
if self.is_running: self.logger.warning("Listener loop terminated: WebSocket closed.")
160+
if self.is_running:
161+
self.logger.warning("Listener loop terminated: WebSocket closed by server. Reconnecting...")
162+
yield self._reconnect_async()
158163
except Exception as e:
159-
if self.is_running: self.logger.error(f"An error occurred in the listener loop: {e}")
164+
if self.is_running:
165+
self.logger.error(f"An error occurred in the listener loop: {e}")
166+
yield self._reconnect_async()
160167
finally:
161168
self.is_running = False
169+
170+
@tornado.gen.coroutine
171+
def _reconnect_async(self):
172+
"""Async reconnect logic triggered internally after connection loss."""
173+
self.is_running = False
174+
if self.heartbeat_callback:
175+
self.heartbeat_callback.stop()
176+
177+
# Close existing WS if still open
178+
if self.ws:
179+
try:
180+
self.ws.close()
181+
except:
182+
pass
183+
184+
# Wait a moment before reconnecting
185+
yield tornado.gen.sleep(2)
186+
187+
# Re-initialize connection
188+
try:
189+
self.logger.info('Reconnecting...')
190+
request = HTTPRequest(
191+
url=self.ws_url,
192+
headers={
193+
"Origin": "https://web.max.ru",
194+
"User-Agent": self.user_agent["headerUserAgent"],
195+
"Sec-Fetch-Dest": "empty",
196+
"Sec-Fetch-Mode": "websocket",
197+
"Sec-Fetch-Site": "cross-site",
198+
}
199+
)
200+
self.ws = yield tornado.websocket.websocket_connect(request)
201+
self.is_running = True
202+
self.logger.info("Reconnected to WebSocket.")
203+
yield self._handshake_async()
204+
205+
# Re-authenticate if token exists
206+
if self.token:
207+
yield self._authenticate_async()
208+
self.logger.info("Re-authenticated successfully.")
209+
210+
# Restart heartbeat
211+
if self.heartbeat_callback:
212+
self.heartbeat_callback.start()
213+
214+
for chat_id in self.subscribed_chats:
215+
try:
216+
yield self.send_command_async(self.OPCODE_MAP['SUBSCRIBE_TO_CHAT'], {"chatId": chat_id, "subscribe": True})
217+
self.logger.debug(f"Resubscribed to chat {chat_id}")
218+
except Exception as e:
219+
self.logger.warning(f"Failed to resubscribe to chat {chat_id}: {e}")
220+
221+
self.logger.info("Reconnection and re-authentication complete.")
222+
except Exception as e:
223+
self.logger.error(f"Reconnection failed: {e}. Will retry in 5 seconds...")
224+
yield tornado.gen.sleep(5)
225+
# Try again recursively (limited by caller's logic or external control)
226+
# Or you can raise and let parent handle
227+
if self.is_running:
228+
yield self._reconnect_async() # Recursive retry
162229

163230
def _process_message(self, message):
164231
"""Processes a raw message, dispatching to sync/async waiters or event handlers."""
@@ -188,7 +255,7 @@ def _process_message(self, message):
188255
def close(self):
189256
if not self.is_running and self.ioloop is None: return
190257
self.logger.info("Closing connection...")
191-
self.is_running = False
258+
self.is_running = False # Prevent reconnect attempts
192259

193260
if self.ioloop:
194261
self.ioloop.add_callback(self._shutdown_async)
@@ -351,6 +418,10 @@ def subscribe_to_chat(self, chat_id: int, subscribe: bool = True):
351418
status = "Subscribed to" if subscribe else "Unsubscribed from"
352419
response = self.send_command(self.OPCODE_MAP['SUBSCRIBE_TO_CHAT'], payload)
353420
self.logger.info(f"{status} chat {chat_id}")
421+
if subscribe:
422+
self.subscribed_chats.add(chat_id)
423+
else:
424+
self.subscribed_chats.discard(chat_id)
354425
return response
355426

356427
def mark_as_read(self, chat_id: int, message_id: str):

0 commit comments

Comments
 (0)