from __future__ import annotations
from dataclasses import dataclass
from typing import Generic, TypeVar, Union, cast
import weechat
from slack.api import SlackWorkspace
from slack.log import print_error
from slack.shared import shared
from slack.util import get_callback_name
class WeeChatColor(str):
pass
@dataclass
class WeeChatConfig:
name: str
def __post_init__(self):
self.pointer = weechat.config_new(self.name, "", "")
@dataclass
class WeeChatSection:
weechat_config: WeeChatConfig
name: str
user_can_add_options: bool = False
user_can_delete_options: bool = False
callback_read: str = ""
callback_write: str = ""
def __post_init__(self):
self.pointer = weechat.config_new_section(
self.weechat_config.pointer,
self.name,
self.user_can_add_options,
self.user_can_delete_options,
self.callback_read,
"",
self.callback_write,
"",
"",
"",
"",
"",
"",
"",
)
WeeChatOptionType = TypeVar("WeeChatOptionType", bound=Union[int, str])
@dataclass
class WeeChatOption(Generic[WeeChatOptionType]):
section: WeeChatSection
name: str
description: str
default_value: WeeChatOptionType
min_value: Union[int, None] = None
max_value: Union[int, None] = None
string_values: Union[str, None] = None
parent_option: Union[WeeChatOption[WeeChatOptionType], None] = None
def __post_init__(self):
self._pointer = self._create_weechat_option()
@property
def value(self) -> WeeChatOptionType:
if weechat.config_option_is_null(self._pointer):
if self.parent_option:
return self.parent_option.value
return self.default_value
if isinstance(self.default_value, bool):
return cast(WeeChatOptionType, weechat.config_boolean(self._pointer) == 1)
if isinstance(self.default_value, int):
return cast(WeeChatOptionType, weechat.config_integer(self._pointer))
if isinstance(self.default_value, WeeChatColor):
color = weechat.config_color(self._pointer)
return cast(WeeChatOptionType, WeeChatColor(color))
return cast(WeeChatOptionType, weechat.config_string(self._pointer))
@value.setter
def value(self, value: WeeChatOptionType):
rc = self.value_set_as_str(str(value))
if rc == weechat.WEECHAT_CONFIG_OPTION_SET_ERROR:
raise Exception(f"Failed to value for option: {self.name}")
def value_set_as_str(self, value: str) -> int:
return weechat.config_option_set(self._pointer, value, 1)
def value_set_null(self) -> int:
if not self.parent_option:
raise Exception(
f"Can't set null value for option without parent: {self.name}"
)
return weechat.config_option_set_null(self._pointer, 1)
@property
def weechat_type(self) -> str:
if self.string_values:
return "integer"
if isinstance(self.default_value, bool):
return "boolean"
if isinstance(self.default_value, int):
return "integer"
if isinstance(self.default_value, WeeChatColor):
return "color"
return "string"
def _create_weechat_option(self) -> str:
if self.parent_option:
parent_option_name = (
f"{self.parent_option.section.weechat_config.name}"
f".{self.parent_option.section.name}"
f".{self.parent_option.name}"
)
name = f"{self.name} << {parent_option_name}"
default_value = None
null_value_allowed = True
else:
name = self.name
default_value = str(self.default_value)
null_value_allowed = False
value = None
if shared.weechat_version < 0x3050000:
default_value = str(self.default_value)
value = default_value
return weechat.config_new_option(
self.section.weechat_config.pointer,
self.section.pointer,
name,
self.weechat_type,
self.description,
self.string_values or "",
self.min_value or -(2**31),
self.max_value or 2**31 - 1,
default_value,
value,
null_value_allowed,
"",
"",
"",
"",
"",
"",
)
class SlackConfigSectionColor:
def __init__(self, weechat_config: WeeChatConfig):
self._section = WeeChatSection(weechat_config, "color")
self.reaction_suffix = WeeChatOption(
self._section,
"reaction_suffix",
"Color to use for the [:wave:(@user)] suffix on messages that have "
"reactions attached to them.",
WeeChatColor("darkgray"),
)
class SlackConfigSectionWorkspace:
def __init__(
self,
section: WeeChatSection,
workspace_name: Union[str, None],
parent_config: Union[SlackConfigSectionWorkspace, None],
):
self._section = section
self._workspace_name = workspace_name
self._parent_config = parent_config
self.api_token = self._create_option(
"api_token",
"",
"",
)
self.api_cookies = self._create_option(
"api_cookies",
"",
"",
)
self.autoconnect = self._create_option(
"autoconnect",
"automatically connect to workspace when WeeChat is starting",
False,
)
self.slack_timeout = self._create_option(
"slack_timeout",
"timeout (in seconds) for network requests",
30,
)
def _create_option(
self,
name: str,
description: str,
default_value: WeeChatOptionType,
min_value: Union[int, None] = None,
max_value: Union[int, None] = None,
string_values: Union[str, None] = None,
) -> WeeChatOption[WeeChatOptionType]:
if self._workspace_name:
option_name = f"{self._workspace_name}.{name}"
else:
option_name = name
if self._parent_config:
parent_option = getattr(self._parent_config, name, None)
else:
parent_option = None
return WeeChatOption(
self._section,
option_name,
description,
default_value,
min_value,
max_value,
string_values,
parent_option,
)
def config_section_workspace_read_cb(
data: str, config_file: str, section: str, option_name: str, value: Union[str, None]
) -> int:
option_split = option_name.split(".", 1)
if len(option_split) < 2:
return weechat.WEECHAT_CONFIG_OPTION_SET_ERROR
workspace_name, name = option_split
if not workspace_name or not name:
return weechat.WEECHAT_CONFIG_OPTION_SET_ERROR
if workspace_name not in shared.workspaces:
shared.workspaces[workspace_name] = SlackWorkspace(workspace_name)
option = getattr(shared.workspaces[workspace_name].config, name, None)
if option is None:
return weechat.WEECHAT_CONFIG_OPTION_SET_OPTION_NOT_FOUND
if not isinstance(option, WeeChatOption):
return weechat.WEECHAT_CONFIG_OPTION_SET_ERROR
if value is None or (
shared.weechat_version < 0x3080000
and value == ""
and option.weechat_type != "string"
):
rc = option.value_set_null()
else:
rc = option.value_set_as_str(value)
if rc == weechat.WEECHAT_CONFIG_OPTION_SET_ERROR:
print_error(f'error creating workspace option "{option_name}"')
return rc
def config_section_workspace_write_for_old_weechat_cb(
data: str, config_file: str, section_name: str
) -> int:
if not weechat.config_write_line(config_file, section_name, ""):
return weechat.WEECHAT_CONFIG_WRITE_ERROR
for workspace in shared.workspaces.values():
for option in vars(workspace.config).values():
if isinstance(option, WeeChatOption):
if (
option.weechat_type != "string"
or not weechat.config_option_is_null(
option._pointer # pyright: ignore [reportPrivateUsage]
)
):
if not weechat.config_write_option(
config_file,
option._pointer, # pyright: ignore [reportPrivateUsage]
):
return weechat.WEECHAT_CONFIG_WRITE_ERROR
return weechat.WEECHAT_CONFIG_WRITE_OK
class SlackConfig:
def __init__(self):
self.weechat_config = WeeChatConfig("slack")
self.color = SlackConfigSectionColor(self.weechat_config)
self._section_workspace_default = WeeChatSection(
self.weechat_config, "workspace_default"
)
# WeeChat < 3.8 sends null as an empty string to callback_read, so in
# order to distinguish them, don't write the null values to the config
# See https://github.com/weechat/weechat/pull/1843
callback_write = (
get_callback_name(config_section_workspace_write_for_old_weechat_cb)
if shared.weechat_version < 0x3080000
else ""
)
self._section_workspace = WeeChatSection(
self.weechat_config,
"workspace",
callback_read=get_callback_name(config_section_workspace_read_cb),
callback_write=callback_write,
)
self._workspace_default = SlackConfigSectionWorkspace(
self._section_workspace_default, None, None
)
def config_read(self):
weechat.config_read(self.weechat_config.pointer)
def create_workspace_config(self, workspace_name: str):
if workspace_name in shared.workspaces:
raise Exception(
f"Failed to create workspace config, already exists: {workspace_name}"
)
return SlackConfigSectionWorkspace(
self._section_workspace, workspace_name, self._workspace_default
)