3
\$\begingroup\$

I am looking for a better way to minimize nested if statements in my MessageHandler.handleMessage method. I am looking to adhere to SRP and a function should do one thing and one thing well!

Any input would be greatly appreciated.


class MessageHandler():
 """ MessageHandler - Handles twitch IRC messages and emits events based on Commands and msgid tags """
 def __init__(self):
 self.COMMANDS: COMMANDS = COMMANDS()
 def handleMessage(self, IrcMessage: str)->tuple:
 """ MessageHandler.handleMessage - breaks down data string from irc server returns tuple (event: str, message: Message) or
 returns tuple of (None, None)
 :param IrcMessage: a irc recieved message for server
 :type IrcMessage: str
 :raises TypeError: IrcMessage needs to be of type str 
 :return: a tuple with event string and Message data type populasted with all parsed message data
 :rtype: tuple
 """
 try:
 if not isinstance(IrcMessage, str):
 raise TypeError("MessageHandler.handleMessage requires input of type str")
 message = self._parse(IrcMessage)
 # Populate message values
 message.channel: str = message.params[0] if len(message.params) > 0 else None
 message.text: str = message.params[1] if len(message.params) > 1 else None
 message.id: str = message.tags.get("msg-id")
 message.raw: str = IrcMessage
 message.username: str = message.tags.get("display-name")
 # Parse badges and emotes
 message.tags = self._badges(self._emotes(message.tags))
 # Transform IRCv3 Tags
 message = self._TransformIRCv3Tags(message)
 except AttributeError:
 return(None, None)
 # Handle message with prefix "tmi.twitch.tv"
 if message.prefix == self.COMMANDS.TMI_TWITCH_TV:
 # Handle command bot Username
 if message.command == self.COMMANDS.USERNAME:
 botUsername = message.params[0]
 # Chatroom NOTICE check msgid tag
 elif message.command == self.COMMANDS.NOTICE:
 if message.id in self.COMMANDS.MESSAGEIDS.__dict__.values():
 return (self.COMMANDS.NOTICE, message)
 else:
 if message.raw.replace(":tmi.twitch.tv NOTICE * :",'') in ("Login unsuccessful", "Login authentication failed", "Error logging in", "Invalid NICK"): 
 return (self.COMMANDS.LOGIN_UNSUCCESSFUL, \
 message.raw.replace(":tmi.twitch.tv NOTICE * :",''))
 else:
 return (message.command, message)
 # Handle message with prefix jtv ??????? unsure it is still required
 elif message.prefix == "jtv":
 print(message.params)#still testing 
 else:
 if message.command == self.COMMANDS.MESSAGE:
 message.username: str = message.prefix[:message.prefix.find("!")]
 return (self.COMMANDS.MESSAGE, message)
 elif message.command == self.COMMANDS.WHISPER:
 return (self.COMMANDS.WHISPER, message)
 elif message.command == self.COMMANDS.NAMES:
 return (self.COMMANDS.NAMES, message)
 return (None, None) # invalid message
 @staticmethod
 def _TransformIRCv3Tags(message: Message)->Message:
 """ MessageHandler._TransformIRCv3Tags reformats message tags
 :param message: message object
 :type message: Message
 :return: message with updated tags
 :rtype: Message
 """
 if message.tags:
 for key in message.tags:
 if key not in ("emote-sets", "ban-duration", "bits"):
 if isinstance(message.tags[key], bool):
 message.tags[key] = None
 elif message.tags[key] in ('0', '1'):
 message.tags[key] = bool(int(message.tags[key]))
 return message
 @staticmethod
 def _badges(tags: dict)->dict:
 """ MessageHandler._badges - Parse tags['badges'] from str to dict and update tags['badges']
 :param tags: tags from parsed IRC message
 :type event: dict
 :return: tags
 :rtype: dict
 """
 if ("badges" in tags and isinstance(tags.get("badges"), str)):
 badges = tags.get("badges").split(",")
 tags["badges"]: dict = {}
 for badge in badges:
 key, value = badge.split("/")
 if value is None:
 return tags
 tags["badges"][key] = value
 return tags
 @staticmethod
 def _emotes(tags: dict)->dict:
 """ MessageHandler._emotes - Parse tags['emotes'] from str to list and update tags['emotes']
 :param tags: tags from parsed IRC message
 :type event: dict
 :return: tags
 :rtype: dict
 """
 if ("emotes" in tags and isinstance(tags.get("emotes"), str)):
 emotes: dict = {}
 emoticons = tags.get("emotes").split("/")
 for emoticon in emoticons:
 key, value = emoticon.split(":")
 if value is None:
 return tags
 emotes[key] = value.split(",")
 tags["emotes"] = emotes
 return tags
 @staticmethod
 def _parse(data: str)->Message:
 """ MessageHandler._parse - Parses IRC messages to Message type
 :param data: string from IRC server
 :type event: str
 :return: message
 :rtype: Message
 """
 message = Message()
 if not isinstance(data, str):
 raise TypeError("MessageHandler._parse requires input of type str")
 position: int = 0
 nextspace: int = 0
 if len(data) < 1:
 return None
 if data.startswith("@"): 
 nextspace = data.find(" ")
 if nextspace == -1:
 return None # invalid message form
 tags = data[1:nextspace].split(";")
 for tag in tags:
 key, value = tag.split("=")
 message.tags[key] = value or True
 position = nextspace + 1
 while data[position] == " ":
 position += 1
 if data[position] == ":":
 nextspace = data.find(" ", position)
 if nextspace == -1: 
 return None # invalid message form
 message.prefix = data[position + 1:nextspace]
 position = nextspace + 1
 while data[position] == " ":
 position += 1
 nextspace = data.find(" ", position)
 if nextspace == -1:
 if len(data) > position:
 message.command = data[position:]
 return message
 return None # invalid message form
 message.command = data[position:nextspace]
 position = nextspace + 1
 while data[position] == " ":
 position += 1
 dataLen = len(data)
 while position < dataLen:
 nextspace = data.find(" ", position)
 if data[position] == ":": 
 message.params.append(data[position + 1:])
 break
 if nextspace != -1:
 message.params.append(data[position:nextspace])
 position = nextspace + 1
 while data[position] == " ":
 position += 1
 continue
 if nextspace == -1:
 message.params.append(data[position:])
 break
 return message
Ben A
10.7k5 gold badges37 silver badges101 bronze badges
asked Jan 29, 2020 at 3:29
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

I don't know how are twitch messages formes, but here is just a quick thought after a first look at your code: regex might be helpful, especially for _parse method. It seems that there are known delimiters. You will be able to capture all relevant data in a single match, and for example drop all of your space skipping loops.

You might want to take your _parse method outside of your handler class, and create a specific MessageParser class. Parsing the raw message is different from handling parsed message.

In the handleMessage method, there's no return for the case of a "tmi.twitch.tv" when message.command == self.COMMANDS.USERNAME, so it ends up to return(None, None). Is it wanted? All other cases return something.

Concerning the nested if, by now I don't see a lot of options... However, you can use intermediate variables to have only one exit point, instead of all the returns. This will greatly help debugging by having, for example, only one breakpoint.

My 2 cents.

answered Jan 30, 2020 at 17:34
\$\endgroup\$
1
  • \$\begingroup\$ twitch messages use IRC formats, and I do like the idea of a single return statement and will have to edit that section. the username is just who you logged in as so no event necessary. Having the _parse method in the same module as messageHandler my thought was they would need to change at the same time which makes them bound together. i will also try out the regex method too ,thanks for you input \$\endgroup\$ Commented Jan 31, 2020 at 2:07

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.