platform_config.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. """
  2. Chatto Platform Config
  3. """
  4. from dataclasses import dataclass
  5. from email.policy import default
  6. import os
  7. from typing import Any, Optional
  8. from gateway.config import PlatformConfig
  9. import utils
  10. from vendor.chattolib.client import ChattoClient
  11. class ChattoConstants:
  12. """
  13. Chatto Platform Constants
  14. """
  15. PLATFORM_ID: str = "CHATTO"
  16. PLATFORM_NAME: str = "chatto-platform"
  17. PLATFORM_LABEL: str = "Chatto"
  18. def _get_env_or_extra_str(env_var: str, extra_val: Optional[str]) -> Optional[str]:
  19. """Get a value from environment variable or extra config."""
  20. env_value = os.getenv(env_var)
  21. if env_value is not None:
  22. return env_value.strip()
  23. if extra_val is not None:
  24. return extra_val.strip()
  25. return None
  26. def _get_env_or_extra_truthy(env_var: str, extra_val: Optional[str], default: bool = False) -> bool:
  27. """Get a boolean value from environment variable or extra config."""
  28. return utils.is_truthy_value(_get_env_or_extra_str(env_var, extra_val), default)
  29. def _split_str_to_list(mystring: str) -> list:
  30. return list(c.strip() for c in mystring.split(","))
  31. def _get_env_or_extra_list(env_var: str, extra_val: Optional[set]) -> list[str]:
  32. """Get a list of values from environment variable or extra config."""
  33. env_value = os.getenv(env_var)
  34. if env_value is not None:
  35. return _split_str_to_list(env_value)
  36. if extra_val is not None:
  37. if isinstance(extra_val, list):
  38. return list(
  39. c.strip() for c in extra_val
  40. )
  41. if isinstance(extra_val, str):
  42. return _split_str_to_list(extra_val)
  43. return []
  44. @dataclass
  45. class ChattoConfiguration:
  46. """
  47. Chatto Platform Config
  48. Some constants and getting from environment/config.yaml
  49. """
  50. _token: Optional[str] = None
  51. def __init__(self, pconfig: PlatformConfig):
  52. """PlatformConfig from Hermes provides our own configuration within the "extra"
  53. object. But here, we allow overriding via environment variables again.
  54. We take our Configuration from this typed Class, because it is easier access than using extra.get["base_url"].
  55. """
  56. self._base_url = _get_env_or_extra_str("CHATTO_BASE_URL", pconfig.extra.get("base_url")) or ChattoClient.DEFAULT_BASE_URL
  57. self._token = _get_env_or_extra_str("CHATTO_TOKEN", pconfig.extra.get("token"))
  58. self._login = _get_env_or_extra_str("CHATTO_LOGIN", pconfig.extra.get("login"))
  59. self._password = _get_env_or_extra_str("CHATTO_PASSWORD", pconfig.extra.get("password"))
  60. self._channels_str = _get_env_or_extra_str("CHATTO_CHANNELS", pconfig.extra.get("channels"))
  61. self._channels_list = _get_env_or_extra_list("CHATTO_CHANNELS", pconfig.extra.get("channels"))
  62. self._home_channel = _get_env_or_extra_str("CHATTO_HOME_CHANNEL", pconfig.extra.get("home_channel"))
  63. self._require_mention = _get_env_or_extra_truthy("CHATTO_REQUIRE_MENTION", pconfig.extra.get("require_mention"))
  64. # free_response_channels: room IDs where the bot responds without being mentioned via "@botname"
  65. self._free_response_channels_list = _get_env_or_extra_list("CHATTO_FREE_RESPONSE_CHANNELS", pconfig.extra.get("free_response_channels"))
  66. # Auto-thread: by default, Chatto creates a thread for replies to room
  67. # messages (not DMs, not already in a thread). This keeps conversations
  68. # organized in the room. Can be disabled via extra.auto_thread=false.
  69. self._auto_thread = _get_env_or_extra_truthy("CHATTO_AUTO_THREAD", pconfig.extra.get("auto_thread"))
  70. def _add_env_to_seed(self, seed: dict, our_key: str) -> dict
  71. env_value = os.getenv("CHATTO_" + our_key.upper())
  72. if env_value:
  73. seed[our_key.lower()] = env_value
  74. return seed
  75. def env_only_to_seed_extra(self) -> Optional[dict]:
  76. """Seed PlatformConfig.extra from env vars.
  77. Returns a dict compatible with the PlatformConfig merge hook (or None
  78. when no env-provided values are present).
  79. Called by the platform registry during load_gateway_config().
  80. Return None when the platform isn't minimally configured — the
  81. caller then skips auto-enabling. Return a dict to seed extras.
  82. The special 'home_channel' key is extracted and becomes a proper
  83. HomeChannel dataclass on the PlatformConfig; every other key is
  84. merged into PlatformConfig.extra.
  85. """
  86. seed = {}
  87. seed["base_url"] = (os.getenv("CHATTO_BASE_URL") or ChattoClient.DEFAULT_BASE_URL).strip()
  88. seed = self._add_env_to_seed(seed, "TOKEN")
  89. seed = self._add_env_to_seed(seed, "LOGIN")
  90. seed = self._add_env_to_seed(seed, "PASSWORD")
  91. seed = self._add_env_to_seed(seed, "CHANNELS")
  92. seed = self._add_env_to_seed(seed, "HOME_CHANNEL")
  93. seed = self._add_env_to_seed(seed, "REQUIRE_MENTION")
  94. seed = self._add_env_to_seed(seed, "FREE_RESPONSE_CHANNELS")
  95. seed = self._add_env_to_seed(seed, "AUTO_THREAD")
  96. return seed
  97. def _config_key_not_set(self, config_key: str) -> ValueError:
  98. envname_upper: str = "CHATTO_" + config_key.upper()
  99. keyname_lower: str = "chatto.extra." + config_key.lower()
  100. valueErrorString: str = "Environment variable " + envname_upper + " or config.yaml YAML key " + keyname_lower + " is not set."
  101. return ValueError(valueErrorString)
  102. def _ensure_configuration(self) -> None:
  103. """Ensure that the Chatto configuration is valid."""
  104. if not self._base_url:
  105. raise self._config_key_not_set("base_url")
  106. if not self._login:
  107. raise self._config_key_not_set("login")
  108. if not self._password:
  109. raise self._config_key_not_set("password")
  110. return None