diff options
-rw-r--r-- | slack/slack_api.py | 20 | ||||
-rw-r--r-- | slack/slack_buffer.py | 20 | ||||
-rw-r--r-- | slack/slack_conversation.py | 7 | ||||
-rw-r--r-- | slack/slack_message.py | 6 | ||||
-rw-r--r-- | slack/slack_thread.py | 8 | ||||
-rw-r--r-- | slack/slack_workspace.py | 9 | ||||
-rw-r--r-- | slack/util.py | 11 |
7 files changed, 66 insertions, 15 deletions
diff --git a/slack/slack_api.py b/slack/slack_api.py index be74c2c..7fff321 100644 --- a/slack/slack_api.py +++ b/slack/slack_api.py @@ -297,3 +297,23 @@ class SlackApi(SlackApiCommon): if response["ok"] is False: raise SlackApiError(self.workspace, method, response, params) return response + + async def chat_post_message( + self, + conversation: SlackConversation, + text: str, + thread_ts: Optional[SlackTs] = None, + ): + method = "chat.postMessage" + params: Params = { + "channel": conversation.id, + "text": text, + "as_user": True, + "link_names": True, + } + if thread_ts is not None: + params["thread_ts"] = thread_ts + response: SlackGenericResponse = await self._fetch(method, params) + if response["ok"] is False: + raise SlackApiError(self.workspace, method, response, params) + return response diff --git a/slack/slack_buffer.py b/slack/slack_buffer.py index adad7b7..4fa5981 100644 --- a/slack/slack_buffer.py +++ b/slack/slack_buffer.py @@ -9,12 +9,14 @@ import weechat from slack.shared import shared from slack.slack_message import SlackMessage, SlackTs -from slack.util import get_callback_name +from slack.task import run_async +from slack.util import get_callback_name, htmlescape if TYPE_CHECKING: from typing_extensions import Literal from slack.slack_api import SlackApi + from slack.slack_conversation import SlackConversation from slack.slack_workspace import SlackWorkspace @@ -171,6 +173,11 @@ class SlackBuffer(ABC): @property @abstractmethod + def conversation(self) -> SlackConversation: + raise NotImplementedError() + + @property + @abstractmethod def context(self) -> Literal["conversation", "thread"]: raise NotImplementedError() @@ -304,8 +311,17 @@ class SlackBuffer(ABC): weechat.buffer_set(self.buffer_pointer, "hotlist", "-1") self.hotlist_tss.clear() + @abstractmethod + async def post_message(self, text: str) -> None: + raise NotImplementedError() + + async def process_input(self, input_data: str): + if input_data.startswith(("//", " ")): + input_data = input_data[1:] + await self.post_message(htmlescape(input_data)) + def _buffer_input_cb(self, data: str, buffer: str, input_data: str) -> int: - weechat.prnt(buffer, "Text: %s" % input_data) + run_async(self.process_input(input_data)) return weechat.WEECHAT_RC_OK def _buffer_close_cb(self, data: str, buffer: str) -> int: diff --git a/slack/slack_conversation.py b/slack/slack_conversation.py index abfafa5..c1db29e 100644 --- a/slack/slack_conversation.py +++ b/slack/slack_conversation.py @@ -140,6 +140,10 @@ class SlackConversation(SlackBuffer): return self._workspace @property + def conversation(self) -> SlackConversation: + return self + + @property def context(self) -> Literal["conversation", "thread"]: return "conversation" @@ -630,3 +634,6 @@ class SlackConversation(SlackBuffer): last_read_line_ts = self.last_read_line_ts() if last_read_line_ts and last_read_line_ts != self.last_read: await self._api.conversations_mark(self, last_read_line_ts) + + async def post_message(self, text: str) -> None: + await self._api.chat_post_message(self.conversation, text) diff --git a/slack/slack_message.py b/slack/slack_message.py index ddb2ceb..c986edd 100644 --- a/slack/slack_message.py +++ b/slack/slack_message.py @@ -19,7 +19,7 @@ from slack.python_compatibility import removeprefix, removesuffix from slack.shared import shared from slack.slack_user import SlackBot, SlackUser, format_bot_nick, nick_color from slack.task import gather -from slack.util import intersperse, with_color +from slack.util import intersperse, unhtmlescape, with_color if TYPE_CHECKING: from slack_api.slack_conversations_history import SlackMessage as SlackMessageDict @@ -42,10 +42,6 @@ if TYPE_CHECKING: from slack.slack_workspace import SlackWorkspace -def unhtmlescape(text: str) -> str: - return text.replace("<", "<").replace(">", ">").replace("&", "&") - - def format_date(timestamp: int, token_string: str, link: Optional[str] = None) -> str: ref_datetime = datetime.fromtimestamp(timestamp) link_suffix = f" ({link})" if link else "" diff --git a/slack/slack_thread.py b/slack/slack_thread.py index 79ef68c..8033ff1 100644 --- a/slack/slack_thread.py +++ b/slack/slack_thread.py @@ -10,6 +10,7 @@ from slack.task import gather if TYPE_CHECKING: from typing_extensions import Literal + from slack.slack_conversation import SlackConversation from slack.slack_workspace import SlackWorkspace @@ -23,6 +24,10 @@ class SlackThread(SlackBuffer): return self.parent.workspace @property + def conversation(self) -> SlackConversation: + return self.parent.conversation + + @property def context(self) -> Literal["conversation", "thread"]: return "thread" @@ -116,3 +121,6 @@ class SlackThread(SlackBuffer): await self._api.subscriptions_thread_mark( self.parent.conversation, self.parent.ts, last_read_line_ts ) + + async def post_message(self, text: str) -> None: + await self._api.chat_post_message(self.conversation, text, self.parent.ts) diff --git a/slack/slack_workspace.py b/slack/slack_workspace.py index 286f5c7..0f9ddfb 100644 --- a/slack/slack_workspace.py +++ b/slack/slack_workspace.py @@ -445,16 +445,9 @@ class SlackWorkspace: if self._ws is None: raise SlackError(self, "is_connected is True while _ws is None") - if isinstance(buffer, SlackConversation): - conversation_id = buffer.id - elif isinstance(buffer, SlackThread): - conversation_id = buffer.parent.conversation.id - else: - raise NotImplementedError(f"Unknown buffer type: {type(buffer)}") - msg = { "type": "user_typing", - "channel": conversation_id, + "channel": buffer.conversation.id, } if isinstance(buffer, SlackThread): msg["thread_ts"] = buffer.parent.ts diff --git a/slack/util.py b/slack/util.py index 41993c5..c988f1f 100644 --- a/slack/util.py +++ b/slack/util.py @@ -34,6 +34,17 @@ def with_color(color: Optional[str], string: str, reset_color: str = "reset"): return string +# Escape chars that have special meaning to Slack. Note that we do not +# (and should not) perform full HTML entity-encoding here. +# See https://api.slack.com/reference/surfaces/formatting#escaping for details. +def htmlescape(text: str) -> str: + return text.replace("&", "&").replace("<", "<").replace(">", ">") + + +def unhtmlescape(text: str) -> str: + return text.replace("<", "<").replace(">", ">").replace("&", "&") + + # From https://github.com/more-itertools/more-itertools/blob/v10.1.0/more_itertools/recipes.py#L93-L106 def take(n: int, iterable: Iterable[T]) -> List[T]: """Return first *n* items of the iterable as a list. |