7
7
import traceback
8
8
from functools import partial
9
9
from pathlib import Path
10
+ from typing import Mapping , Optional
10
11
11
12
from irclib .parser import Message
12
13
@@ -34,6 +35,36 @@ def irc_clean(dirty):
34
35
"NOTICE" : EventType .notice
35
36
}
36
37
38
+ content_params = {
39
+ "PRIVMSG" : 1 ,
40
+ "NOTICE" : 1 ,
41
+ "PART" : 1 ,
42
+ "KICK" : 2 ,
43
+ "TOPIC" : 1 ,
44
+ "NICK" : 0 ,
45
+ "QUIT" : 0 ,
46
+ }
47
+
48
+ chan_params = {
49
+ "PRIVMSG" : 0 ,
50
+ "NOTICE" : 0 ,
51
+ "JOIN" : 0 ,
52
+ "PART" : 0 ,
53
+ "TOPIC" : 0 ,
54
+ "MODE" : 0 ,
55
+ "KICK" : 0 ,
56
+ "INVITE" : 1 ,
57
+ "353" : 2 ,
58
+ "366" : 1 ,
59
+ "324" : 1 ,
60
+ }
61
+
62
+ target_params = {
63
+ "KICK" : 1 ,
64
+ "INVITE" : 0 ,
65
+ "MODE" : 0 ,
66
+ }
67
+
37
68
38
69
def decode (bytestring ):
39
70
"""
@@ -47,6 +78,15 @@ def decode(bytestring):
47
78
return bytestring .decode ('utf-8' , errors = 'ignore' )
48
79
49
80
81
+ def _get_param (msg : Message , index_map : Mapping [str , int ]) -> Optional [str ]:
82
+ if msg .command in index_map :
83
+ idx = index_map [msg .command ]
84
+ if idx < len (msg .parameters ):
85
+ return msg .parameters [idx ]
86
+
87
+ return None
88
+
89
+
50
90
@client ("irc" )
51
91
class IrcClient (Client ):
52
92
"""
@@ -417,114 +457,97 @@ def data_received(self, data):
417
457
line = decode (line_data )
418
458
419
459
try :
420
- message = Message . parse (line )
460
+ event = self . parse_line (line )
421
461
except Exception :
422
462
logger .exception (
423
463
"[%s] Error occurred while parsing IRC line '%s' from %s" ,
424
464
self .conn .name , line , self .conn .describe_server ()
425
465
)
426
- continue
427
-
428
- command = message .command
429
- command_params = message .parameters
430
-
431
- # Reply to pings immediately
432
-
433
- if command == "PING" :
434
- self .conn .send ("PONG " + command_params [- 1 ], log = False )
435
-
436
- # Parse the command and params
437
-
438
- # Content
439
- if command_params .has_trail :
440
- content_raw = command_params [- 1 ]
441
- content = irc_clean (content_raw )
442
466
else :
443
- content_raw = None
444
- content = None
445
-
446
- # Event type
447
- event_type = irc_command_to_event_type .get (
448
- command , EventType .other
449
- )
467
+ # handle the message, async
468
+ async_util .wrap_future (self .bot .process (event ), loop = self .loop )
469
+
470
+ def parse_line (self , line : str ) -> Event :
471
+ message = Message .parse (line )
472
+ command = message .command
473
+ command_params = message .parameters
474
+
475
+ # Reply to pings immediately
476
+ if command == "PING" :
477
+ self .conn .send ("PONG " + command_params [- 1 ], log = False )
478
+
479
+ # Parse the command and params
480
+ # Content
481
+ content_raw = _get_param (message , content_params )
482
+ if content_raw is not None :
483
+ content = irc_clean (content_raw )
484
+ else :
485
+ content = None
450
486
451
- # Target (for KICK, INVITE)
452
- if event_type is EventType .kick :
453
- target = command_params [1 ]
454
- elif command in ("INVITE" , "MODE" ):
455
- target = command_params [0 ]
456
- else :
457
- # TODO: Find more commands which give a target
458
- target = None
459
-
460
- # Parse for CTCP
461
- if event_type is EventType .message and content_raw .startswith ("\x01 " ):
462
- possible_ctcp = content_raw [1 :]
463
- if content_raw .endswith ('\x01 ' ):
464
- possible_ctcp = possible_ctcp [:- 1 ]
465
-
466
- if '\x01 ' in possible_ctcp :
467
- logger .debug (
468
- "[%s] Invalid CTCP message received, "
469
- "treating it as a mornal message" ,
470
- self .conn .name
471
- )
472
- ctcp_text = None
473
- else :
474
- ctcp_text = possible_ctcp
475
- ctcp_text_split = ctcp_text .split (None , 1 )
476
- if ctcp_text_split [0 ] == "ACTION" :
477
- # this is a CTCP ACTION, set event_type and content accordingly
478
- event_type = EventType .action
479
- content = irc_clean (ctcp_text_split [1 ])
480
- else :
481
- # this shouldn't be considered a regular message
482
- event_type = EventType .other
483
- else :
487
+ # Event type
488
+ event_type = irc_command_to_event_type .get (
489
+ command , EventType .other
490
+ )
491
+ target = _get_param (message , target_params )
492
+
493
+ # Parse for CTCP
494
+ if event_type is EventType .message and content_raw .startswith ("\x01 " ):
495
+ possible_ctcp = content_raw [1 :]
496
+ if content_raw .endswith ('\x01 ' ):
497
+ possible_ctcp = possible_ctcp [:- 1 ]
498
+
499
+ if '\x01 ' in possible_ctcp :
500
+ logger .debug (
501
+ "[%s] Invalid CTCP message received, "
502
+ "treating it as a mornal message" ,
503
+ self .conn .name
504
+ )
484
505
ctcp_text = None
485
-
486
- # Channel
487
- channel = None
488
- if command_params :
489
- if command in ["NOTICE" , "PRIVMSG" , "KICK" , "JOIN" , "PART" , "MODE" ]:
490
- channel = command_params [0 ]
491
- elif command == "INVITE" :
492
- channel = command_params [1 ]
493
- elif len (command_params ) > 2 or not (command_params .has_trail and len (command_params ) == 1 ):
494
- channel = command_params [0 ]
495
-
496
- prefix = message .prefix
497
-
498
- if prefix is None :
499
- nick = None
500
- user = None
501
- host = None
502
- mask = None
503
506
else :
504
- nick = prefix .nick
505
- user = prefix .user
506
- host = prefix .host
507
- mask = prefix .mask
508
-
509
- if channel :
510
- # TODO Migrate plugins to accept the original case of the channel
511
- channel = channel .lower ()
512
-
513
- channel = channel .split ()[0 ] # Just in case there is more data
514
-
515
- if channel == self .conn .nick .lower ():
516
- channel = nick .lower ()
517
-
518
- # Set up parsed message
519
- # TODO: Do we really want to send the raw `prefix` and `command_params` here?
520
- event = Event (
521
- bot = self .bot , conn = self .conn , event_type = event_type , content_raw = content_raw , content = content ,
522
- target = target , channel = channel , nick = nick , user = user , host = host , mask = mask , irc_raw = line ,
523
- irc_prefix = mask , irc_command = command , irc_paramlist = command_params , irc_ctcp_text = ctcp_text
524
- )
525
-
526
- # handle the message, async
527
- async_util .wrap_future (self .bot .process (event ), loop = self .loop )
507
+ ctcp_text = possible_ctcp
508
+ ctcp_text_split = ctcp_text .split (None , 1 )
509
+ if ctcp_text_split [0 ] == "ACTION" :
510
+ # this is a CTCP ACTION, set event_type and content accordingly
511
+ event_type = EventType .action
512
+ content = irc_clean (ctcp_text_split [1 ])
513
+ else :
514
+ # this shouldn't be considered a regular message
515
+ event_type = EventType .other
516
+ else :
517
+ ctcp_text = None
518
+
519
+ # Channel
520
+ channel = _get_param (message , chan_params )
521
+ prefix = message .prefix
522
+ if prefix is None :
523
+ nick = None
524
+ user = None
525
+ host = None
526
+ mask = None
527
+ else :
528
+ nick = prefix .nick
529
+ user = prefix .user
530
+ host = prefix .host
531
+ mask = prefix .mask
532
+
533
+ if channel :
534
+ # TODO Migrate plugins to accept the original case of the channel
535
+ channel = channel .lower ()
536
+
537
+ channel = channel .split ()[0 ] # Just in case there is more data
538
+
539
+ # Channel for a PM is the sending user
540
+ if channel == self .conn .nick .lower ():
541
+ channel = nick .lower ()
542
+
543
+ # Set up parsed message
544
+ # TODO: Do we really want to send the raw `prefix` and `command_params` here?
545
+ event = Event (
546
+ bot = self .bot , conn = self .conn , event_type = event_type , content_raw = content_raw , content = content ,
547
+ target = target , channel = channel , nick = nick , user = user , host = host , mask = mask , irc_raw = line ,
548
+ irc_prefix = mask , irc_command = command , irc_paramlist = command_params , irc_ctcp_text = ctcp_text
549
+ )
550
+ return event
528
551
529
552
@property
530
553
def connected (self ):
0 commit comments