33Backported from async-irc (https://github.com/snoonetIRC/async-irc.git)
44"""
55
6+ import datetime
67import re
8+ import warnings
79from abc import ABCMeta , abstractmethod
810from collections .abc import Iterable , Iterator , Sequence
911from typing import Final , Literal , Optional , Union , cast
5658}
5759
5860
61+ def parse_server_time (
62+ value : Optional [str ], default : datetime .datetime
63+ ) -> datetime .datetime :
64+ if value :
65+ return datetime .datetime .strptime (
66+ value , "%Y-%m-%dT%H:%M:%S.%fZ"
67+ ).replace (tzinfo = datetime .timezone .utc )
68+
69+ return default
70+
71+
5972class Parseable (metaclass = ABCMeta ):
6073 """Abstract class for parseable objects."""
6174
@@ -597,13 +610,59 @@ def __init__(
597610 prefix : Union [str , Prefix , None , Iterable [str ]],
598611 command : str ,
599612 * parameters : Union [str , list [str ], ParamList ],
613+ time : Optional [datetime .datetime ] = None ,
600614 ) -> None :
601615 """Construct message object."""
602616 self ._tags = _parse_tags (tags )
603617 self ._prefix = _parse_prefix (prefix )
604618 self ._command = command
605619 self ._parameters = _parse_params (parameters )
606620
621+ if time is None :
622+ time = datetime .datetime .now (datetime .timezone .utc )
623+
624+ if time .tzinfo is None :
625+ time = time .replace (tzinfo = datetime .timezone .utc )
626+ warnings .warn (
627+ "Timezone-naive datetime is deprecated for 'time'." ,
628+ DeprecationWarning ,
629+ stacklevel = 2 ,
630+ )
631+
632+ self ._time = time
633+
634+ def has_tag (self , name : str ) -> bool :
635+ """Return whether this message has a particular message tag."""
636+ if not self .tags :
637+ return False
638+
639+ return name in self .tags
640+
641+ def get_tag_value (self , name : str ) -> Optional [str ]:
642+ """Get value for a message tag, or None if not set."""
643+ if self .tags and name in self .tags :
644+ return self .tags [name ].value
645+
646+ return None
647+
648+ @property
649+ def time (self ) -> datetime .datetime :
650+ """Time the message was sent/received.
651+
652+ Uses server-time tag if available.
653+ """
654+ return parse_server_time (self .get_tag_value ("time" ), self ._time )
655+
656+ @property
657+ def message_id (self ) -> Optional [str ]:
658+ """Unique message ID provided by server."""
659+ return self .get_tag_value ("msgid" )
660+
661+ @property
662+ def batch_id (self ) -> Optional [str ]:
663+ """Batch ID provided by server."""
664+ return self .get_tag_value ("batch" )
665+
607666 @property
608667 def tags (self ) -> MsgTagList :
609668 """IRC tag list."""
@@ -629,7 +688,12 @@ def as_tuple(self) -> MessageTuple:
629688 return self .tags , self .prefix , self .command , self .parameters
630689
631690 @classmethod
632- def parse (cls , text : Union [str , bytes ]) -> Self :
691+ def parse (
692+ cls ,
693+ text : Union [str , bytes ],
694+ * ,
695+ time : Optional [datetime .datetime ] = None ,
696+ ) -> Self :
633697 """Parse an IRC message in to objects."""
634698 if isinstance (text , memoryview ):
635699 text = text .tobytes ().decode (errors = "ignore" )
@@ -652,7 +716,7 @@ def parse(cls, text: Union[str, bytes]) -> Self:
652716 prefix_obj = Prefix .parse (prefix [1 :]) if prefix else None
653717 command = command .upper ()
654718 param_obj = ParamList .parse (params )
655- return cls (tags_obj , prefix_obj , command , param_obj )
719+ return cls (tags_obj , prefix_obj , command , param_obj , time = time )
656720
657721 def __eq__ (self , other : object ) -> bool :
658722 """Compare to another message which can be str, bytes, or a Message object."""
0 commit comments