types.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. from __future__ import annotations
  2. from dataclasses import dataclass, field
  3. from datetime import datetime
  4. from collections.abc import Callable, Coroutine, Container
  5. from typing import Union, Any, Optional
  6. from aiosqlite.core import Connection
  7. __all__ = [
  8. "Attachment",
  9. "Message",
  10. "Command",
  11. "Response",
  12. "Context",
  13. "CommandType",
  14. "StartupShutdownType",
  15. "CommandConfiguration",
  16. ]
  17. @dataclass
  18. class Attachment:
  19. name: str
  20. body: Union[str, bytes]
  21. @dataclass
  22. class Message:
  23. origin_id: str
  24. channel_id: str
  25. sender_id: str
  26. timestamp: datetime
  27. origin_admin: bool
  28. channel_admin: bool
  29. text: Optional[str] = None
  30. attachments: list[Attachment] = field(default_factory=list)
  31. def __post_init__(self):
  32. self.command = None
  33. @dataclass
  34. class Command:
  35. bang: str
  36. name: str
  37. args: str
  38. @staticmethod
  39. def from_text(text: str) -> Optional[Command]:
  40. cleaned = text.lstrip()
  41. if len(cleaned) < 2:
  42. return None
  43. parts = cleaned[1:].lstrip().split(maxsplit=1)
  44. if len(parts) == 0:
  45. return None
  46. return Command(
  47. bang=cleaned[0],
  48. name=parts[0],
  49. args=parts[1] if len(parts) > 1 else "",
  50. )
  51. @dataclass
  52. class Response:
  53. origin_id: str
  54. channel_id: str
  55. text: Optional[str] = None
  56. attachments: Optional[list[Attachment]] = None
  57. @staticmethod
  58. def from_message(
  59. msg: Message, text: Optional[str] = None, attachments: list[Attachment] = None
  60. ) -> Response:
  61. return Response(
  62. origin_id=msg.origin_id,
  63. channel_id=msg.channel_id,
  64. text=text,
  65. attachments=attachments or [],
  66. )
  67. @dataclass
  68. class Context:
  69. config: Callable[[str], Any]
  70. respond: Callable[[], Coroutine[None, None, None]]
  71. database: Callable[[], Coroutine[None, None, Connection]]
  72. CommandType = Callable[[Message, Context], Coroutine[None, None, None]]
  73. StartupShutdownType = Callable[[Context], Coroutine[None, None, None]]
  74. @dataclass
  75. class CommandConfiguration:
  76. commands: dict[str, CommandType] = field(default_factory=dict)
  77. call_and_response: dict[str, str] = field(default_factory=dict)
  78. aliases: dict[str, str] = field(default_factory=dict)
  79. bangs: Container[str] = ("!",)
  80. startup: list[StartupShutdownType] = field(default_factory=list)
  81. shutdown: list[StartupShutdownType] = field(default_factory=list)
  82. def extend(self, other: CommandConfiguration) -> CommandConfiguration:
  83. return CommandConfiguration(
  84. commands={
  85. **self.commands,
  86. **other.commands,
  87. },
  88. call_and_response={
  89. **self.call_and_response,
  90. **other.call_and_response,
  91. },
  92. aliases={
  93. **self.aliases,
  94. **other.aliases,
  95. },
  96. bangs=(*self.bangs, *other.bangs),
  97. startup=[*self.startup, *other.startup],
  98. shutdown=[*self.shutdown, *other.shutdown],
  99. )