1717import asyncio
1818import logging
1919import signal
20+ import socket
2021import ssl
2122import threading
2223from typing import Dict , List , Optional , Tuple , TYPE_CHECKING
@@ -56,7 +57,7 @@ def __init__(
5657 ):
5758 super ().__init__ (bot )
5859
59- def is_connected (self ) -> False :
60+ def is_connected (self ) -> bool :
6061 """Check if the backend is connected to an IRC server.
6162
6263 **Always returns False:** This backend type is never connected.
@@ -377,40 +378,124 @@ def get_connection_kwargs(self) -> Dict:
377378 'local_addr' : self ._source_address ,
378379 }
379380
380- async def _run_forever ( self ) -> None :
381- self . _loop = asyncio . get_running_loop ()
382-
383- # register signal handlers
384- for quit_signal in QUIT_SIGNALS :
385- self . _loop . add_signal_handler ( quit_signal , self . _signal_quit )
386- for restart_signal in RESTART_SIGNALS :
387- self . _loop . add_signal_handler ( restart_signal , self . _signal_restart )
381+ async def _connect_to_server (
382+ self , ** connection_kwargs
383+ ) -> Tuple [
384+ Optional [ asyncio . StreamReader ],
385+ Optional [ asyncio . StreamWriter ],
386+ ]:
387+ reader : Optional [ asyncio . StreamReader ] = None
388+ writer : Optional [ asyncio . StreamWriter ] = None
388389
389390 # open connection
390391 try :
391- self ._reader , self ._writer = await asyncio .open_connection (
392- ** self .get_connection_kwargs (),
392+ reader , writer = await asyncio .open_connection (
393+ ** connection_kwargs ,
394+ )
395+
396+ # SSL Errors (certificate verification and generic SSL errors)
397+ except ssl .SSLCertVerificationError as err :
398+ LOGGER .error (
399+ 'Unable to connect due to '
400+ 'SSL certificate verification failure: %s' ,
401+ err ,
393402 )
394- except ssl .SSLError :
395- LOGGER .exception ('Unable to connect due to SSL error.' )
403+ self .log_exception ()
396404 # tell the bot to quit without restart
397405 self .bot .hasquit = True
398406 self .bot .wantsrestart = False
399- return
400- except Exception :
401- LOGGER .exception ('Unable to connect.' )
407+ except ssl .SSLError as err :
408+ LOGGER .error ('Unable to connect due to an SSL error: %s' , err )
409+ self .log_exception ()
410+ # tell the bot to quit without restart
411+ self .bot .hasquit = True
412+ self .bot .wantsrestart = False
413+
414+ # Specific connection error (invalid address and timeout)
415+ except socket .gaierror as err :
416+ LOGGER .error (
417+ 'Unable to connect due to invalid IRC server address: %s' ,
418+ err ,
419+ )
420+ LOGGER .error (
421+ 'You should verify that "%s:%s" is the correct address '
422+ 'to connect to the IRC server.' ,
423+ connection_kwargs .get ('host' ),
424+ connection_kwargs .get ('port' ),
425+ )
426+ self .log_exception ()
427+ # tell the bot to quit without restart
428+ self .bot .hasquit = True
429+ self .bot .wantsrestart = False
430+ except TimeoutError as err :
431+ LOGGER .error ('Unable to connect due to a timeout: %s' , err )
432+ self .log_exception ()
433+ # tell the bot to quit without restart
434+ self .bot .hasquit = True
435+ self .bot .wantsrestart = False
436+
437+ # Generic connection error
438+ except ConnectionError as err :
439+ LOGGER .error ('Unable to connect: %s' , err )
440+ self .log_exception ()
441+ # tell the bot to quit without restart
442+ self .bot .hasquit = True
443+ self .bot .wantsrestart = False
444+
445+ # Generic OSError (used for any unspecific connection error)
446+ except OSError as err :
447+ LOGGER .error ('Unable to connect: %s' , err )
448+ LOGGER .error (
449+ 'You should verify that "%s:%s" is the correct address '
450+ 'to connect to the IRC server.' ,
451+ connection_kwargs .get ('host' ),
452+ connection_kwargs .get ('port' ),
453+ )
454+ self .log_exception ()
455+ # tell the bot to quit without restart
456+ self .bot .hasquit = True
457+ self .bot .wantsrestart = False
458+
459+ # Unexpected error
460+ except Exception as err :
461+ LOGGER .error (
462+ 'Unable to connect due to an unexpected error: %s' ,
463+ err ,
464+ )
465+ self .log_exception ()
402466 # until there is a way to prevent an infinite loop of connection
403467 # error and reconnect, we have to tell the bot to quit here
404468 # TODO: prevent infinite connection failure loop
405469 self .bot .hasquit = True
406470 self .bot .wantsrestart = False
407- return
408471
409- self . _connected = True
472+ return reader , writer
410473
474+ async def _run_forever (self ) -> None :
475+ self ._loop = asyncio .get_running_loop ()
476+ connection_kwargs = self .get_connection_kwargs ()
477+
478+ # register signal handlers
479+ for quit_signal in QUIT_SIGNALS :
480+ self ._loop .add_signal_handler (quit_signal , self ._signal_quit )
481+ for restart_signal in RESTART_SIGNALS :
482+ self ._loop .add_signal_handler (restart_signal , self ._signal_restart )
483+
484+ # connect to socket
485+ LOGGER .debug ('Attempt connection.' )
486+ self ._reader , self ._writer = await self ._connect_to_server (
487+ ** connection_kwargs
488+ )
489+ if not self ._reader or not self ._writer :
490+ LOGGER .debug ('Connection attempt failed.' )
491+ return
492+
493+ # on socket connection
411494 LOGGER .debug ('Connection registered.' )
495+ self ._connected = True
412496 self .bot .on_connect ()
413497
498+ # read forever
414499 LOGGER .debug ('Waiting for messages...' )
415500 self ._read_task = asyncio .create_task (self .read_forever ())
416501 try :
@@ -420,6 +505,7 @@ async def _run_forever(self) -> None:
420505 else :
421506 LOGGER .debug ('Reader received EOF.' )
422507
508+ # on socket disconnection
423509 self ._connected = False
424510
425511 # cancel timeout tasks
0 commit comments