aboutsummaryrefslogtreecommitdiffstats
path: root/slack/slack_conversation.py
diff options
context:
space:
mode:
authorTrygve Aaberge <trygveaa@gmail.com>2023-09-17 11:30:53 +0200
committerTrygve Aaberge <trygveaa@gmail.com>2024-02-18 11:32:54 +0100
commit994e2d246fc09ed942a871696ccae1bdc3002217 (patch)
tree2c1ac01192e50c6d1c717f8c78b70fd47585fcf9 /slack/slack_conversation.py
parent2a9cad66efec738e68230d335540e6c9e7d9c7e8 (diff)
downloadwee-slack-994e2d246fc09ed942a871696ccae1bdc3002217.tar.gz
Support thread buffers
Diffstat (limited to 'slack/slack_conversation.py')
-rw-r--r--slack/slack_conversation.py283
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)