Moontalk server and client (provided by many parties)
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

99 行
3.5KB

  1. import asyncio
  2. import re
  3. import typing
  4. server_message_regex = re.compile(r"^(?P<nickname>[\w\s]+):\s*(?P<content>.*)$")
  5. class MoonchatMessage(typing.NamedTuple):
  6. nickname: str
  7. content: str
  8. class MessageDecodeError(ValueError):
  9. pass
  10. class Moonchat:
  11. def __init__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter, encoding: str):
  12. self.reader = reader
  13. self.writer = writer
  14. self.encoding = encoding
  15. self.closed = False
  16. def close(self):
  17. if self.closed:
  18. return
  19. self.closed = True
  20. if not self.writer.is_closing():
  21. if self.writer.can_write_eof():
  22. self.writer.write_eof()
  23. self.writer.close()
  24. @staticmethod
  25. async def connect(ip: str, port: int, encoding='ascii', **kwargs):
  26. """Provide the hostname, port and optional arguments to open_connection."""
  27. streams = await asyncio.open_connection(ip, port, **kwargs)
  28. return Moonchat(*streams, encoding=encoding) if encoding else Moonchat(*streams)
  29. def encode_message(self, message: str) -> bytes:
  30. """Return encoded raw data with trailing newline if required."""
  31. return (message.removesuffix('\n')+'\n').encode(self.encoding)
  32. def decode_message(self, data: bytes) -> MoonchatMessage:
  33. """Return decoded raw data without trailing newlines."""
  34. unparsed = (data.decode(self.encoding)).strip()
  35. regex_match = server_message_regex.match(unparsed)
  36. if not regex_match:
  37. raise ValueError("cannot decode malformed message: " + unparsed)
  38. return MoonchatMessage(**regex_match.groupdict())
  39. async def send_message(self, message: str) -> bool:
  40. """Sends string to chat. Return whether successful."""
  41. encoded_message = self.encode_message(message)
  42. return await self.send_message_raw(encoded_message)
  43. async def send_message_raw(self, message: bytes | bytearray | memoryview) -> bool:
  44. """Send raw data straight to the server if you feel like it. Return True if successful."""
  45. if self.closed:
  46. return False
  47. if self.writer.is_closing():
  48. self.close()
  49. return False
  50. self.writer.write(message)
  51. await self.writer.drain()
  52. return True
  53. async def recieve_message_raw(self) -> bytes | None:
  54. """Retrieve the next line from the server, or None if there are no more messages."""
  55. if self.closed:
  56. return None
  57. line = await self.reader.readline()
  58. if b'\n' not in line: # partial reads mean we're out of data
  59. self.close()
  60. return None
  61. return line
  62. async def recieve_message(self) -> MoonchatMessage | None:
  63. """Retrieve the next message from the server."""
  64. raw_message = await self.recieve_message_raw()
  65. return self.decode_message(raw_message) if raw_message else None
  66. async def raw_messages(self):
  67. """Yield raw unencoded messages until connection is closed."""
  68. while not self.closed:
  69. if message := await self.recieve_message_raw():
  70. yield message
  71. async def messages(self, ignore_invalid=False):
  72. """Yield messages until the connection is closed"""
  73. while not self.closed:
  74. try:
  75. message = await self.recieve_message()
  76. except MessageDecodeError as err:
  77. if not ignore_invalid:
  78. raise err
  79. if message:
  80. yield message