python ircd using asyncio
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

65 lines
1.8 KiB

  1. from dataclasses import dataclass
  2. from typing import List, Optional
  3. MAX_MESSAGE_SIZE = 512
  4. EXPECTED_ARG_COUNT = {
  5. "NICK": 1,
  6. "USER": 4,
  7. "JOIN": 1,
  8. "PRIVMSG": 2,
  9. }
  10. class IRCParsingError(Exception):
  11. message: str
  12. def __init__(self, message: Optional[str] = None) -> None:
  13. self.message = message
  14. @dataclass
  15. class IRCMessage:
  16. cmd: str
  17. args: List[str]
  18. prefix: str = ""
  19. def encode(self) -> bytes:
  20. prefix = self.prefix
  21. if prefix != "":
  22. prefix = f":{prefix} "
  23. # TODO: Raise exception if formatted message exceeds 512 bytes
  24. return f"{prefix}{self.cmd} {' '.join(self.args)}\r\n".encode("utf-8")
  25. @staticmethod
  26. def parse(raw: bytes) -> "IRCMessage":
  27. if len(raw) > MAX_MESSAGE_SIZE:
  28. raise IRCParsingError(
  29. f"Message is {len(raw)} bytes, larger than allowed {MAX_MESSAGE_SIZE}"
  30. )
  31. if not raw.endswith(b"\r\n"):
  32. raise IRCParsingError("Message does not terminate in CRLF")
  33. tokens: List[str] = []
  34. raw_tokens: List[str] = raw.decode("utf-8").split(" ")
  35. for i, token in enumerate(raw_tokens):
  36. if token.startswith(":"):
  37. trailing = token[1:] + " " + " ".join(raw_tokens[i + 1 :])
  38. tokens.append(trailing)
  39. break
  40. tokens.append(token)
  41. if len(tokens) == 0:
  42. raise IRCParsingError("Message has no command")
  43. cmd = tokens[0]
  44. if cmd in EXPECTED_ARG_COUNT and EXPECTED_ARG_COUNT[cmd] != len(tokens) - 1:
  45. raise IRCParsingError(
  46. f"{cmd} had {len(tokens)-1} arguments, expected {EXPECTED_ARG_COUNT[cmd]}"
  47. )
  48. tokens[-1] = tokens[-1].strip()
  49. return IRCMessage(cmd=tokens[0], args=tokens[1:])