diff options
author | Trygve Aaberge <trygveaa@gmail.com> | 2023-09-17 11:30:53 +0200 |
---|---|---|
committer | Trygve Aaberge <trygveaa@gmail.com> | 2024-02-18 11:32:54 +0100 |
commit | 994e2d246fc09ed942a871696ccae1bdc3002217 (patch) | |
tree | 2c1ac01192e50c6d1c717f8c78b70fd47585fcf9 /slack/slack_conversation.py | |
parent | 2a9cad66efec738e68230d335540e6c9e7d9c7e8 (diff) | |
download | wee-slack-994e2d246fc09ed942a871696ccae1bdc3002217.tar.gz |
Support thread buffers
Diffstat (limited to 'slack/slack_conversation.py')
-rw-r--r-- | slack/slack_conversation.py | 283 |
1 files changed, 51 insertions, 232 deletions
diff --git a/slack/slack_conversation.py b/slack/slack_conversation.py index 6a24616..4baa17c 100644 --- a/slack/slack_conversation.py +++ b/slack/slack_conversation.py @@ -1,17 +1,17 @@ from __future__ import annotations import hashlib -import time from collections import OrderedDict -from contextlib import contextmanager -from typing import TYPE_CHECKING, Dict, List, Mapping, NoReturn, Optional, Union +from typing import TYPE_CHECKING, Dict, List, Mapping, NoReturn, Optional, Tuple, Union import weechat +from slack.python_compatibility import removeprefix from slack.shared import shared +from slack.slack_buffer import SlackBuffer from slack.slack_message import SlackMessage, SlackTs +from slack.slack_thread import SlackThread from slack.task import gather, run_async -from slack.util import get_callback_name if TYPE_CHECKING: from slack_api.slack_conversations_info import SlackConversationsInfo @@ -24,129 +24,15 @@ if TYPE_CHECKING: ) from typing_extensions import Literal - from slack.slack_api import SlackApi from slack.slack_workspace import SlackWorkspace -def get_conversation_from_buffer_pointer( - buffer_pointer: str, -) -> Optional[SlackConversation]: - for workspace in shared.workspaces.values(): - for conversation in workspace.open_conversations.values(): - if conversation.buffer_pointer == buffer_pointer: - return conversation - return None - - def invalidate_nicklists(): for workspace in shared.workspaces.values(): for conversation in workspace.open_conversations.values(): conversation.nicklist_needs_refresh = True -def hdata_line_ts(line_pointer: str) -> Optional[SlackTs]: - data = weechat.hdata_pointer(weechat.hdata_get("line"), line_pointer, "data") - for i in range( - weechat.hdata_integer(weechat.hdata_get("line_data"), data, "tags_count") - ): - tag = weechat.hdata_string( - weechat.hdata_get("line_data"), data, f"{i}|tags_array" - ) - if tag.startswith("slack_ts_"): - return SlackTs(tag[9:]) - return None - - -def tags_set_notify_none(tags: List[str]) -> List[str]: - notify_tags = {"notify_highlight", "notify_message", "notify_private"} - tags = [tag for tag in tags if tag not in notify_tags] - tags += ["no_highlight", "notify_none"] - return tags - - -def modify_buffer_line(buffer_pointer: str, ts: SlackTs, new_text: str): - own_lines = weechat.hdata_pointer( - weechat.hdata_get("buffer"), buffer_pointer, "own_lines" - ) - line_pointer = weechat.hdata_pointer( - weechat.hdata_get("lines"), own_lines, "last_line" - ) - - # Find the last line with this ts - is_last_line = True - while line_pointer and hdata_line_ts(line_pointer) != ts: - is_last_line = False - line_pointer = weechat.hdata_move(weechat.hdata_get("line"), line_pointer, -1) - - if not line_pointer: - return - - if shared.weechat_version >= 0x04000000: - data = weechat.hdata_pointer(weechat.hdata_get("line"), line_pointer, "data") - weechat.hdata_update( - weechat.hdata_get("line_data"), data, {"message": new_text} - ) - return - - # Find all lines for the message - pointers: List[str] = [] - while line_pointer and hdata_line_ts(line_pointer) == ts: - pointers.append(line_pointer) - line_pointer = weechat.hdata_move(weechat.hdata_get("line"), line_pointer, -1) - pointers.reverse() - - if not pointers: - return - - if is_last_line: - lines = new_text.split("\n") - extra_lines_count = len(lines) - len(pointers) - if extra_lines_count > 0: - line_data = weechat.hdata_pointer( - weechat.hdata_get("line"), pointers[0], "data" - ) - tags_count = weechat.hdata_integer( - weechat.hdata_get("line_data"), line_data, "tags_count" - ) - tags = [ - weechat.hdata_string( - weechat.hdata_get("line_data"), line_data, f"{i}|tags_array" - ) - for i in range(tags_count) - ] - tags = tags_set_notify_none(tags) - tags_str = ",".join(tags) - last_read_line = weechat.hdata_pointer( - weechat.hdata_get("lines"), own_lines, "last_read_line" - ) - should_set_unread = last_read_line == pointers[-1] - - # Insert new lines to match the number of lines in the message - weechat.buffer_set(buffer_pointer, "print_hooks_enabled", "0") - for _ in range(extra_lines_count): - weechat.prnt_date_tags(buffer_pointer, ts.major, tags_str, " \t ") - pointers.append( - weechat.hdata_pointer( - weechat.hdata_get("lines"), own_lines, "last_line" - ) - ) - if should_set_unread: - weechat.buffer_set(buffer_pointer, "unread", "") - weechat.buffer_set(buffer_pointer, "print_hooks_enabled", "1") - else: - # Split the message into at most the number of existing lines as we can't insert new lines - lines = new_text.split("\n", len(pointers) - 1) - # Replace newlines to prevent garbled lines in bare display mode - lines = [line.replace("\n", " | ") for line in lines] - - # Extend lines in case the new message is shorter than the old as we can't delete lines - lines += [""] * (len(pointers) - len(lines)) - - for pointer, line in zip(pointers, lines): - data = weechat.hdata_pointer(weechat.hdata_get("line"), pointer, "data") - weechat.hdata_update(weechat.hdata_get("line_data"), data, {"message": line}) - - def sha1_hex(string: str) -> str: return str(hashlib.sha1(string.encode()).hexdigest()) @@ -198,6 +84,8 @@ class SlackConversationMessageHashes(Dict[SlackTs, str]): other_message = self._conversation.messages[ts_with_same_hash] run_async(self._conversation.rerender_message(other_message)) + if other_message.thread_buffer is not None: + run_async(other_message.thread_buffer.update_buffer_props()) for reply in other_message.replies.values(): run_async(self._conversation.rerender_message(reply)) @@ -205,49 +93,43 @@ class SlackConversationMessageHashes(Dict[SlackTs, str]): self._inverse_map[short_hash] = key return self[key] + def get_ts(self, ts_hash: str) -> Optional[SlackTs]: + hash_without_prefix = removeprefix(ts_hash, "$") + return self._inverse_map.get(hash_without_prefix) + -class SlackConversation: +class SlackConversation(SlackBuffer): def __init__( self, workspace: SlackWorkspace, info: SlackConversationsInfo, ): - self.workspace = workspace + super().__init__() + self._workspace = workspace self._info = info self._members: Optional[List[str]] = None self._messages: OrderedDict[SlackTs, SlackMessage] = OrderedDict() - self._typing_self_last_sent = time.time() - # TODO: buffer_pointer may be accessed by buffer_switch before it's initialized - self.buffer_pointer: str = "" - self.is_loading = False - self.history_filled = False - self.history_pending = False self.nicklist_needs_refresh = True self.message_hashes = SlackConversationMessageHashes(self) - self.completion_context: Literal[ - "NO_COMPLETION", - "PENDING_COMPLETION", - "ACTIVE_COMPLETION", - "IN_PROGRESS_COMPLETION", - ] = "NO_COMPLETION" - self.completion_values: List[str] = [] - self.completion_index = 0 - @classmethod async def create(cls, workspace: SlackWorkspace, conversation_id: str): info_response = await workspace.api.fetch_conversations_info(conversation_id) return cls(workspace, info_response["channel"]) @property - def _api(self) -> SlackApi: - return self.workspace.api - - @property def id(self) -> str: return self._info["id"] @property + def workspace(self) -> SlackWorkspace: + return self._workspace + + @property + def context(self) -> Literal["conversation", "thread"]: + return "conversation" + + @property def messages(self) -> Mapping[SlackTs, SlackMessage]: return self._messages @@ -262,6 +144,10 @@ class SlackConversation: else: return "channel" + @property + def buffer_type(self) -> Literal["private", "channel"]: + return "private" if self.type in ("im", "mpim") else "channel" + async def name(self) -> str: if self._info["is_im"] is True: im_user = await self.workspace.users[self._info["user"]] @@ -302,24 +188,6 @@ class SlackConversation: ) -> str: return f"{self.name_prefix(name_type)}{await self.name()}" - @contextmanager - def loading(self): - self.is_loading = True - weechat.bar_item_update("input_text") - try: - yield - finally: - self.is_loading = False - weechat.bar_item_update("input_text") - - @contextmanager - def completing(self): - self.completion_context = "IN_PROGRESS_COMPLETION" - try: - yield - finally: - self.completion_context = "ACTIVE_COMPLETION" - async def open_if_open(self): if "is_open" in self._info: if self._info["is_open"]: @@ -327,54 +195,36 @@ class SlackConversation: elif self._info.get("is_member"): await self.open_buffer() - async def open_buffer(self): - if self.buffer_pointer: - return + async def get_name_and_buffer_props(self) -> Tuple[str, Dict[str, str]]: + name_without_prefix = await self.name() + name = f"{self.name_prefix('full_name')}{name_without_prefix}" + short_name = self.name_prefix("short_name") + name_without_prefix - name = await self.name() - name_with_prefix_for_full_name = f"{self.name_prefix('full_name')}{name}" - full_name = f"{shared.SCRIPT_NAME}.{self.workspace.name}.{name_with_prefix_for_full_name}" - short_name = self.name_prefix("short_name") + name - - buffer_props = { + return name, { "short_name": short_name, "title": "topic", "input_multiline": "1", "nicklist": "0" if self.type == "im" else "1", "nicklist_display_groups": "0", - "localvar_set_type": ( - "private" if self.type in ("im", "mpim") else "channel" - ), + "localvar_set_type": self.buffer_type, "localvar_set_slack_type": self.type, "localvar_set_nick": self.workspace.my_user.nick(), - "localvar_set_channel": name_with_prefix_for_full_name, + "localvar_set_channel": name, "localvar_set_server": self.workspace.name, } - if shared.weechat_version >= 0x03050000: - self.buffer_pointer = weechat.buffer_new_props( - full_name, - buffer_props, - get_callback_name(self._buffer_input_cb), - "", - get_callback_name(self._buffer_close_cb), - "", - ) - else: - self.buffer_pointer = weechat.buffer_new( - full_name, - get_callback_name(self._buffer_input_cb), - "", - get_callback_name(self._buffer_close_cb), - "", - ) - for prop_name, value in buffer_props.items(): - weechat.buffer_set(self.buffer_pointer, prop_name, value) + async def buffer_switched_to(self): + await gather(self.nicklist_update(), self.fill_history()) + async def open_buffer(self, switch: bool = False): + await super().open_buffer(switch) self.workspace.open_conversations[self.id] = self - async def buffer_switched_to(self): - await gather(self.nicklist_update(), self.fill_history()) + async def rerender_message(self, message: SlackMessage): + await super().rerender_message(message) + parent_message = message.parent_message + if parent_message and parent_message.thread_buffer: + await parent_message.thread_buffer.rerender_message(message) async def load_members(self, load_all: bool = False): if self._members is None: @@ -439,7 +289,7 @@ class SlackConversation: sender_bot_ids = [m.sender_bot_id for m in messages if m.sender_bot_id] self.workspace.bots.initialize_items(sender_bot_ids) - await gather(*(message.render() for message in messages)) + await gather(*(message.render(self.context) for message in messages)) for message in messages: await self.print_message(message, backlog=True) @@ -492,6 +342,8 @@ class SlackConversation: parent_message = message.parent_message if parent_message: parent_message.replies[message.ts] = message + if parent_message.thread_buffer: + await parent_message.thread_buffer.print_message(message) if message.sender_user_id: # TODO: thread buffers @@ -502,15 +354,6 @@ class SlackConversation: f"{self.buffer_pointer};off;{user.nick()}", ) - async def rerender_message(self, message: SlackMessage): - modify_buffer_line( - self.buffer_pointer, message.ts, await message.render_message(rerender=True) - ) - - async def rerender_history(self): - for message in self._messages.values(): - await self.rerender_message(message) - async def change_message( self, data: Union[SlackMessageChanged, SlackMessageReplied] ): @@ -550,34 +393,10 @@ class SlackConversation: message.reaction_remove(reaction, user_id) await self.rerender_message(message) - async def typing_add_user(self, user_id: str, thread_ts: Optional[str]): - if not shared.config.look.typing_status_nicks: - return - - if not thread_ts: - user = await self.workspace.users[user_id] - weechat.hook_signal_send( - "typing_set_nick", - weechat.WEECHAT_HOOK_SIGNAL_STRING, - f"{self.buffer_pointer};typing;{user.nick()}", - ) - - def typing_update_self(self, typing_state: str): - now = time.time() - if now - 4 > self._typing_self_last_sent: - self._typing_self_last_sent = now - self.workspace.send_typing(self.id) - - async def print_message(self, message: SlackMessage, backlog: bool = False): - tags = await message.tags(backlog=backlog) - rendered = await message.render() - weechat.prnt_date_tags(self.buffer_pointer, message.ts.major, tags, rendered) - - def _buffer_input_cb(self, data: str, buffer: str, input_data: str) -> int: - weechat.prnt(buffer, "Text: %s" % input_data) - return weechat.WEECHAT_RC_OK - - def _buffer_close_cb(self, data: str, buffer: str) -> int: - self.buffer_pointer = "" - self.history_filled = False - return weechat.WEECHAT_RC_OK + async def open_thread(self, thread_hash: str, switch: bool = False): + thread_ts = self.message_hashes.get_ts(thread_hash) + if thread_ts: + thread_message = self.messages[thread_ts] + if thread_message.thread_buffer is None: + thread_message.thread_buffer = SlackThread(thread_message) + await thread_message.thread_buffer.open_buffer(switch) |