@@ -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